aboutsummaryrefslogtreecommitdiffstats
path: root/protocols
diff options
context:
space:
mode:
Diffstat (limited to 'protocols')
-rw-r--r--protocols/Makefile59
-rw-r--r--protocols/account.c459
-rw-r--r--protocols/account.h74
-rw-r--r--protocols/bee.c96
-rw-r--r--protocols/bee.h183
-rw-r--r--protocols/bee_chat.c242
-rw-r--r--protocols/bee_ft.c66
-rw-r--r--protocols/bee_user.c288
-rw-r--r--protocols/ft.h176
-rw-r--r--protocols/jabber/Makefile46
-rw-r--r--protocols/jabber/conference.c384
-rw-r--r--protocols/jabber/io.c549
-rw-r--r--protocols/jabber/iq.c870
-rw-r--r--protocols/jabber/jabber.c628
-rw-r--r--protocols/jabber/jabber.h330
-rw-r--r--protocols/jabber/jabber_util.c762
-rw-r--r--protocols/jabber/message.c149
-rw-r--r--protocols/jabber/presence.c258
-rw-r--r--protocols/jabber/s5bytestream.c1153
-rw-r--r--protocols/jabber/sasl.c348
-rw-r--r--protocols/jabber/si.c533
-rw-r--r--protocols/msn/Makefile46
-rw-r--r--protocols/msn/invitation.c622
-rw-r--r--protocols/msn/invitation.h82
-rw-r--r--protocols/msn/msn.c415
-rw-r--r--protocols/msn/msn.h266
-rw-r--r--protocols/msn/msn_util.c590
-rw-r--r--protocols/msn/ns.c886
-rw-r--r--protocols/msn/sb.c806
-rw-r--r--protocols/msn/soap.c1162
-rw-r--r--protocols/msn/soap.h378
-rw-r--r--protocols/msn/tables.c157
-rw-r--r--protocols/nogaim.c708
-rw-r--r--protocols/nogaim.h346
-rw-r--r--protocols/oscar/AUTHORS31
-rw-r--r--protocols/oscar/COPYING504
-rw-r--r--protocols/oscar/Makefile47
-rw-r--r--protocols/oscar/admin.c196
-rw-r--r--protocols/oscar/admin.h13
-rw-r--r--protocols/oscar/aim.h914
-rw-r--r--protocols/oscar/aim_internal.h214
-rw-r--r--protocols/oscar/auth.c538
-rw-r--r--protocols/oscar/bos.c161
-rw-r--r--protocols/oscar/bos.h14
-rw-r--r--protocols/oscar/buddylist.c150
-rw-r--r--protocols/oscar/buddylist.h23
-rw-r--r--protocols/oscar/chat.c702
-rw-r--r--protocols/oscar/chat.h17
-rw-r--r--protocols/oscar/chatnav.c421
-rw-r--r--protocols/oscar/chatnav.h14
-rw-r--r--protocols/oscar/conn.c684
-rw-r--r--protocols/oscar/icq.c441
-rw-r--r--protocols/oscar/icq.h98
-rw-r--r--protocols/oscar/im.c2141
-rw-r--r--protocols/oscar/im.h200
-rw-r--r--protocols/oscar/info.c726
-rw-r--r--protocols/oscar/info.h44
-rw-r--r--protocols/oscar/misc.c396
-rw-r--r--protocols/oscar/msgcookie.c164
-rw-r--r--protocols/oscar/oscar.c2646
-rw-r--r--protocols/oscar/oscar_util.c161
-rw-r--r--protocols/oscar/rxhandlers.c373
-rw-r--r--protocols/oscar/rxqueue.c500
-rw-r--r--protocols/oscar/search.c121
-rw-r--r--protocols/oscar/search.h6
-rw-r--r--protocols/oscar/service.c949
-rw-r--r--protocols/oscar/snac.c147
-rw-r--r--protocols/oscar/ssi.c1523
-rw-r--r--protocols/oscar/ssi.h78
-rw-r--r--protocols/oscar/stats.c38
-rw-r--r--protocols/oscar/tlv.c599
-rw-r--r--protocols/oscar/txqueue.c358
-rw-r--r--protocols/purple/Makefile47
-rw-r--r--protocols/purple/ft-direct.c239
-rw-r--r--protocols/purple/ft.c355
-rw-r--r--protocols/purple/purple.c1342
-rw-r--r--protocols/skype/.gitignore19
-rw-r--r--protocols/skype/.mailmap1
-rw-r--r--protocols/skype/HACKING26
-rw-r--r--protocols/skype/Makefile97
-rw-r--r--protocols/skype/NEWS131
-rw-r--r--protocols/skype/README488
-rw-r--r--protocols/skype/asciidoc.conf21
-rw-r--r--protocols/skype/client.sh1
-rw-r--r--protocols/skype/config.mak.in16
-rw-r--r--protocols/skype/configure.ac96
-rw-r--r--protocols/skype/skype.c1562
-rw-r--r--protocols/skype/skyped.cnf40
-rw-r--r--protocols/skype/skyped.conf.dist.in10
-rw-r--r--protocols/skype/skyped.py488
-rw-r--r--protocols/skype/skyped.txt52
-rw-r--r--protocols/skype/t/Makefile33
-rw-r--r--protocols/skype/t/bitlbee.conf0
-rwxr-xr-xprotocols/skype/t/irssi/livetest-irssi.sh109
-rw-r--r--protocols/skype/t/irssi/skype-call.test13
-rw-r--r--protocols/skype/t/irssi/skype-info.test12
-rw-r--r--protocols/skype/t/irssi/skype-login.test10
-rw-r--r--protocols/skype/t/irssi/skype-msg.test17
-rw-r--r--protocols/skype/t/irssi/trigger.pl1225
-rwxr-xr-xprotocols/skype/t/livetest-bitlbee.sh116
-rw-r--r--protocols/twitter/Makefile46
-rw-r--r--protocols/twitter/twitter.c633
-rw-r--r--protocols/twitter/twitter.h87
-rw-r--r--protocols/twitter/twitter_http.c142
-rw-r--r--protocols/twitter/twitter_http.h36
-rw-r--r--protocols/twitter/twitter_lib.c898
-rw-r--r--protocols/twitter/twitter_lib.h91
-rw-r--r--protocols/yahoo/Makefile47
-rw-r--r--protocols/yahoo/crypt.c203
-rw-r--r--protocols/yahoo/libyahoo2.c5437
-rw-r--r--protocols/yahoo/yahoo.c1023
-rw-r--r--protocols/yahoo/yahoo2.h247
-rw-r--r--protocols/yahoo/yahoo2_callbacks.h783
-rw-r--r--protocols/yahoo/yahoo2_types.h396
-rw-r--r--protocols/yahoo/yahoo_debug.h38
-rw-r--r--protocols/yahoo/yahoo_fn.c4622
-rw-r--r--protocols/yahoo/yahoo_fn.h32
-rw-r--r--protocols/yahoo/yahoo_httplib.c404
-rw-r--r--protocols/yahoo/yahoo_httplib.h48
-rw-r--r--protocols/yahoo/yahoo_list.h48
-rw-r--r--protocols/yahoo/yahoo_util.c107
-rw-r--r--protocols/yahoo/yahoo_util.h104
122 files changed, 52507 insertions, 0 deletions
diff --git a/protocols/Makefile b/protocols/Makefile
new file mode 100644
index 00000000..9e8d3fb9
--- /dev/null
+++ b/protocols/Makefile
@@ -0,0 +1,59 @@
+###########################
+## Makefile for BitlBee ##
+## ##
+## Copyright 2002 Lintux ##
+###########################
+
+### DEFINITIONS
+
+-include ../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/
+endif
+
+# [SH] Program variables
+objects = account.o bee.o bee_chat.o bee_ft.o bee_user.o nogaim.o
+
+
+# [SH] The next two lines should contain the directory name (in $(subdirs))
+# and the name of the object file, which should be linked into
+# protocols.o (in $(subdirobjs)). These need to be in order, i.e. the
+# first object file should be in the first directory.
+subdirs = $(PROTOCOLS)
+subdirobjs = $(PROTOOBJS)
+
+# Expansion of variables
+subdirobjs := $(join $(subdirs),$(addprefix /,$(subdirobjs)))
+LFLAGS += -r
+
+# [SH] Phony targets
+all: protocols.o
+check: all
+lcov: check
+gcov:
+ gcov *.c
+
+.PHONY: all clean distclean $(subdirs)
+
+clean: $(subdirs)
+ rm -f *.o $(OUTFILE) core
+
+distclean: clean $(subdirs)
+ rm -rf .depend
+
+$(subdirs):
+ @$(MAKE) -C $@ $(MAKECMDGOALS)
+
+### MAIN PROGRAM
+
+protocols.o: $(objects) $(subdirs)
+ @echo '*' Linking protocols.o
+ @$(LD) $(LFLAGS) $(objects) $(subdirobjs) -o protocols.o
+
+$(objects): ../Makefile.settings Makefile
+
+$(objects): %.o: $(SRCDIR)%.c
+ @echo '*' Compiling $<
+ @$(CC) -c $(CFLAGS) $< -o $@
+
+-include .depend/*.d
diff --git a/protocols/account.c b/protocols/account.c
new file mode 100644
index 00000000..12831531
--- /dev/null
+++ b/protocols/account.c
@@ -0,0 +1,459 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* Account management functions */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#define BITLBEE_CORE
+#include "bitlbee.h"
+#include "account.h"
+
+static char *set_eval_nick_source( set_t *set, char *value );
+
+account_t *account_add( bee_t *bee, struct prpl *prpl, char *user, char *pass )
+{
+ account_t *a;
+ set_t *s;
+ char tag[strlen(prpl->name)+10];
+
+ if( bee->accounts )
+ {
+ for( a = bee->accounts; a->next; a = a->next );
+ a = a->next = g_new0( account_t, 1 );
+ }
+ else
+ {
+ bee->accounts = a = g_new0 ( account_t, 1 );
+ }
+
+ a->prpl = prpl;
+ a->user = g_strdup( user );
+ a->pass = g_strdup( pass );
+ a->auto_connect = 1;
+ a->bee = bee;
+
+ s = set_add( &a->set, "auto_connect", "true", set_eval_account, a );
+ s->flags |= ACC_SET_NOSAVE;
+
+ s = set_add( &a->set, "auto_reconnect", "true", set_eval_bool, a );
+
+ s = set_add( &a->set, "nick_format", NULL, NULL, a );
+ s->flags |= SET_NULL_OK;
+
+ s = set_add( &a->set, "nick_source", "handle", set_eval_nick_source, a );
+ s->flags |= 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 | SET_PASSWORD;
+
+ s = set_add( &a->set, "tag", NULL, set_eval_account, a );
+ s->flags |= ACC_SET_NOSAVE;
+
+ s = set_add( &a->set, "username", NULL, set_eval_account, a );
+ s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY;
+ set_setstr( &a->set, "username", user );
+
+ /* Hardcode some more clever tag guesses. */
+ strcpy( tag, prpl->name );
+ if( strcmp( prpl->name, "oscar" ) == 0 )
+ {
+ if( isdigit( a->user[0] ) )
+ strcpy( tag, "icq" );
+ else
+ strcpy( tag, "aim" );
+ }
+ else if( strcmp( prpl->name, "jabber" ) == 0 )
+ {
+ if( strstr( a->user, "@gmail.com" ) ||
+ strstr( a->user, "@googlemail.com" ) )
+ strcpy( tag, "gtalk" );
+ else if( strstr( a->user, "@chat.facebook.com" ) )
+ strcpy( tag, "fb" );
+ }
+
+ if( account_by_tag( bee, tag ) )
+ {
+ char *numpos = tag + strlen( tag );
+ int i;
+
+ for( i = 2; i < 10000; i ++ )
+ {
+ sprintf( numpos, "%d", i );
+ if( !account_by_tag( bee, tag ) )
+ break;
+ }
+ }
+ set_setstr( &a->set, "tag", tag );
+
+ a->nicks = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, g_free );
+
+ /* This function adds some more settings (and might want to do more
+ things that have to be done now, although I can't think of anything. */
+ if( prpl->init )
+ prpl->init( a );
+
+ s = set_add( &a->set, "away", NULL, set_eval_account, a );
+ s->flags |= SET_NULL_OK;
+
+ if( a->flags & ACC_FLAG_STATUS_MESSAGE )
+ {
+ s = set_add( &a->set, "status", NULL, set_eval_account, a );
+ s->flags |= SET_NULL_OK;
+ }
+
+ return a;
+}
+
+char *set_eval_account( set_t *set, char *value )
+{
+ account_t *acc = set->data;
+
+ /* Double-check: We refuse to edit on-line accounts. */
+ if( set->flags & ACC_SET_OFFLINE_ONLY && acc->ic )
+ return SET_INVALID;
+
+ if( strcmp( set->key, "server" ) == 0 )
+ {
+ g_free( acc->server );
+ if( value && *value )
+ {
+ acc->server = g_strdup( value );
+ return value;
+ }
+ else
+ {
+ acc->server = g_strdup( set->def );
+ return g_strdup( set->def );
+ }
+ }
+ else if( strcmp( set->key, "username" ) == 0 )
+ {
+ g_free( acc->user );
+ acc->user = g_strdup( value );
+ return value;
+ }
+ else if( strcmp( set->key, "password" ) == 0 )
+ {
+ if( value )
+ {
+ g_free( acc->pass );
+ acc->pass = g_strdup( value );
+ return NULL; /* password shouldn't be visible in plaintext! */
+ }
+ else
+ {
+ /* NULL can (should) be stored in the set_t
+ variable, but is otherwise not correct. */
+ return SET_INVALID;
+ }
+ }
+ else if( strcmp( set->key, "tag" ) == 0 )
+ {
+ account_t *oa;
+
+ /* Enforce uniqueness. */
+ if( ( oa = account_by_tag( acc->bee, value ) ) && oa != acc )
+ return SET_INVALID;
+
+ g_free( acc->tag );
+ acc->tag = g_strdup( value );
+ return value;
+ }
+ else if( strcmp( set->key, "auto_connect" ) == 0 )
+ {
+ if( !is_bool( value ) )
+ return SET_INVALID;
+
+ acc->auto_connect = bool2int( value );
+ return value;
+ }
+ else if( strcmp( set->key, "away" ) == 0 ||
+ strcmp( set->key, "status" ) == 0 )
+ {
+ if( acc->ic && acc->ic->flags & OPT_LOGGED_IN )
+ {
+ /* If we're currently on-line, set the var now already
+ (bit of a hack) and send an update. */
+ g_free( set->value );
+ set->value = g_strdup( value );
+
+ imc_away_send_update( acc->ic );
+ }
+
+ return value;
+ }
+
+ return SET_INVALID;
+}
+
+/* For bw compatibility, have this write-only setting. */
+static char *set_eval_nick_source( set_t *set, char *value )
+{
+ account_t *a = set->data;
+
+ if( strcmp( value, "full_name" ) == 0 )
+ set_setstr( &a->set, "nick_format", "%full_name" );
+ else if( strcmp( value, "first_name" ) == 0 )
+ set_setstr( &a->set, "nick_format", "%first_name" );
+ else
+ set_setstr( &a->set, "nick_format", "%-@nick" );
+
+ return value;
+}
+
+account_t *account_get( bee_t *bee, const char *id )
+{
+ account_t *a, *ret = NULL;
+ char *handle, *s;
+ int nr;
+
+ /* Tags get priority above anything else. */
+ if( ( a = account_by_tag( bee, id ) ) )
+ return a;
+
+ /* This checks if the id string ends with (...) */
+ if( ( handle = strchr( id, '(' ) ) && ( s = strchr( handle, ')' ) ) && s[1] == 0 )
+ {
+ struct prpl *proto;
+
+ *s = *handle = 0;
+ handle ++;
+
+ if( ( proto = find_protocol( id ) ) )
+ {
+ for( a = bee->accounts; a; a = a->next )
+ if( a->prpl == proto &&
+ a->prpl->handle_cmp( handle, a->user ) == 0 )
+ ret = a;
+ }
+
+ /* Restore the string. */
+ handle --;
+ *handle = '(';
+ *s = ')';
+
+ if( ret )
+ return ret;
+ }
+
+ if( sscanf( id, "%d", &nr ) == 1 && nr < 1000 )
+ {
+ for( a = bee->accounts; a; a = a->next )
+ if( ( nr-- ) == 0 )
+ return( a );
+
+ return( NULL );
+ }
+
+ for( a = bee->accounts; a; a = a->next )
+ {
+ if( g_strcasecmp( id, a->prpl->name ) == 0 )
+ {
+ if( !ret )
+ ret = a;
+ else
+ return( NULL ); /* We don't want to match more than one... */
+ }
+ else if( strstr( a->user, id ) )
+ {
+ if( !ret )
+ ret = a;
+ else
+ return( NULL );
+ }
+ }
+
+ return( ret );
+}
+
+account_t *account_by_tag( bee_t *bee, const char *tag )
+{
+ account_t *a;
+
+ for( a = bee->accounts; a; a = a->next )
+ if( a->tag && g_strcasecmp( tag, a->tag ) == 0 )
+ return a;
+
+ return NULL;
+}
+
+void account_del( bee_t *bee, account_t *acc )
+{
+ account_t *a, *l = NULL;
+
+ if( acc->ic )
+ /* Caller should have checked, accounts still in use can't be deleted. */
+ return;
+
+ for( a = bee->accounts; a; a = (l=a)->next )
+ if( a == acc )
+ {
+ if( l )
+ l->next = a->next;
+ else
+ bee->accounts = a->next;
+
+ /** FIXME
+ for( c = bee->chatrooms; c; c = nc )
+ {
+ nc = c->next;
+ if( acc == c->acc )
+ chat_del( bee, c );
+ }
+ */
+
+ while( a->set )
+ set_del( &a->set, a->set->key );
+
+ g_hash_table_destroy( a->nicks );
+
+ g_free( a->tag );
+ g_free( a->user );
+ g_free( a->pass );
+ g_free( a->server );
+ if( a->reconnect ) /* This prevents any reconnect still queued to happen */
+ cancel_auto_reconnect( a );
+ g_free( a );
+
+ break;
+ }
+}
+
+static gboolean account_on_timeout( gpointer d, gint fd, b_input_condition cond );
+
+void account_on( bee_t *bee, account_t *a )
+{
+ if( a->ic )
+ {
+ /* Trying to enable an already-enabled account */
+ return;
+ }
+
+ cancel_auto_reconnect( a );
+
+ a->reconnect = 0;
+ a->prpl->login( a );
+
+ if( a->ic && !( a->ic->flags & OPT_SLOW_LOGIN ) )
+ a->ic->keepalive = b_timeout_add( 120000, account_on_timeout, a->ic );
+}
+
+void account_off( bee_t *bee, account_t *a )
+{
+ imc_logout( a->ic, FALSE );
+ a->ic = NULL;
+ if( a->reconnect )
+ {
+ /* Shouldn't happen */
+ cancel_auto_reconnect( a );
+ }
+}
+
+static gboolean account_on_timeout( gpointer d, gint fd, b_input_condition cond )
+{
+ struct im_connection *ic = d;
+
+ imcb_error( ic, "Connection timeout" );
+ imc_logout( ic, TRUE );
+
+ return FALSE;
+}
+
+struct account_reconnect_delay
+{
+ int start;
+ char op;
+ int step;
+ int max;
+};
+
+int account_reconnect_delay_parse( char *value, struct account_reconnect_delay *p )
+{
+ memset( p, 0, sizeof( *p ) );
+ /* A whole day seems like a sane "maximum maximum". */
+ p->max = 86400;
+
+ /* Format: /[0-9]+([*+][0-9]+(<[0-9+])?)?/ */
+ while( *value && isdigit( *value ) )
+ p->start = p->start * 10 + *value++ - '0';
+
+ /* Sure, call me evil for implementing my own fscanf here, but it's
+ dead simple and I immediately know where to continue parsing. */
+
+ if( *value == 0 )
+ /* If the string ends now, the delay is constant. */
+ return 1;
+ else if( *value != '+' && *value != '*' )
+ /* Otherwise allow either a + or a * */
+ return 0;
+
+ p->op = *value++;
+
+ /* + or * the delay by this number every time. */
+ while( *value && isdigit( *value ) )
+ p->step = p->step * 10 + *value++ - '0';
+
+ if( *value == 0 )
+ /* Use the default maximum (one day). */
+ return 1;
+ else if( *value != '<' )
+ return 0;
+
+ p->max = 0;
+ value ++;
+ while( *value && isdigit( *value ) )
+ p->max = p->max * 10 + *value++ - '0';
+
+ return p->max > 0;
+}
+
+char *set_eval_account_reconnect_delay( set_t *set, char *value )
+{
+ struct account_reconnect_delay p;
+
+ return account_reconnect_delay_parse( value, &p ) ? value : SET_INVALID;
+}
+
+int account_reconnect_delay( account_t *a )
+{
+ char *setting = set_getstr( &a->bee->set, "auto_reconnect_delay" );
+ struct account_reconnect_delay p;
+
+ if( account_reconnect_delay_parse( setting, &p ) )
+ {
+ if( a->auto_reconnect_delay == 0 )
+ a->auto_reconnect_delay = p.start;
+ else if( p.op == '+' )
+ a->auto_reconnect_delay += p.step;
+ else if( p.op == '*' )
+ a->auto_reconnect_delay *= p.step;
+
+ if( a->auto_reconnect_delay > p.max )
+ a->auto_reconnect_delay = p.max;
+ }
+ else
+ {
+ a->auto_reconnect_delay = 0;
+ }
+
+ return a->auto_reconnect_delay;
+}
diff --git a/protocols/account.h b/protocols/account.h
new file mode 100644
index 00000000..a39be2e2
--- /dev/null
+++ b/protocols/account.h
@@ -0,0 +1,74 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2004 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* Account management functions */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#ifndef _ACCOUNT_H
+#define _ACCOUNT_H
+
+typedef struct account
+{
+ struct prpl *prpl;
+ char *user;
+ char *pass;
+ char *server;
+ char *tag;
+
+ int auto_connect;
+ int auto_reconnect_delay;
+ int reconnect;
+ int flags;
+
+ set_t *set;
+ GHashTable *nicks;
+
+ struct bee *bee;
+ struct im_connection *ic;
+ struct account *next;
+} account_t;
+
+account_t *account_add( bee_t *bee, struct prpl *prpl, char *user, char *pass );
+account_t *account_get( bee_t *bee, const char *id );
+account_t *account_by_tag( bee_t *bee, const char *tag );
+void account_del( bee_t *bee, account_t *acc );
+void account_on( bee_t *bee, account_t *a );
+void account_off( bee_t *bee, account_t *a );
+
+char *set_eval_account( set_t *set, char *value );
+char *set_eval_account_reconnect_delay( set_t *set, char *value );
+int account_reconnect_delay( account_t *a );
+
+typedef enum
+{
+ ACC_SET_NOSAVE = 0x01, /* Don't save this setting (i.e. stored elsewhere). */
+ ACC_SET_OFFLINE_ONLY = 0x02, /* Allow changes only if the acct is offline. */
+ ACC_SET_ONLINE_ONLY = 0x04, /* Allow changes only if the acct is online. */
+} account_set_flag_t;
+
+typedef enum
+{
+ ACC_FLAG_AWAY_MESSAGE = 0x01, /* Supports away messages instead of just states. */
+ ACC_FLAG_STATUS_MESSAGE = 0x02, /* Supports status messages (without being away). */
+} account_flag_t;
+
+#endif
diff --git a/protocols/bee.c b/protocols/bee.c
new file mode 100644
index 00000000..5800233d
--- /dev/null
+++ b/protocols/bee.c
@@ -0,0 +1,96 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* Some IM-core stuff */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#define BITLBEE_CORE
+#include "bitlbee.h"
+
+static char *set_eval_away_status( set_t *set, char *value );
+
+bee_t *bee_new()
+{
+ bee_t *b = g_new0( bee_t, 1 );
+ set_t *s;
+
+ s = set_add( &b->set, "auto_connect", "true", set_eval_bool, b );
+ s = set_add( &b->set, "auto_reconnect", "true", set_eval_bool, b );
+ s = set_add( &b->set, "auto_reconnect_delay", "5*3<900", set_eval_account_reconnect_delay, b );
+ s = set_add( &b->set, "away", NULL, set_eval_away_status, b );
+ s->flags |= SET_NULL_OK | SET_HIDDEN;
+ s = set_add( &b->set, "debug", "false", set_eval_bool, b );
+ s = set_add( &b->set, "mobile_is_away", "false", set_eval_bool, b );
+ s = set_add( &b->set, "save_on_quit", "true", set_eval_bool, b );
+ s = set_add( &b->set, "status", NULL, set_eval_away_status, b );
+ s->flags |= SET_NULL_OK;
+ s = set_add( &b->set, "strip_html", "true", NULL, b );
+
+ b->user = g_malloc( 1 );
+
+ return b;
+}
+
+void bee_free( bee_t *b )
+{
+ while( b->accounts )
+ {
+ if( b->accounts->ic )
+ imc_logout( b->accounts->ic, FALSE );
+ else if( b->accounts->reconnect )
+ cancel_auto_reconnect( b->accounts );
+
+ if( b->accounts->ic == NULL )
+ account_del( b, b->accounts );
+ else
+ /* Nasty hack, but account_del() doesn't work in this
+ case and we don't want infinite loops, do we? ;-) */
+ b->accounts = b->accounts->next;
+ }
+
+ while( b->set )
+ set_del( &b->set, b->set->key );
+
+ bee_group_free( b );
+
+ g_free( b->user );
+ g_free( b );
+}
+
+static char *set_eval_away_status( set_t *set, char *value )
+{
+ bee_t *bee = set->data;
+ account_t *a;
+
+ g_free( set->value );
+ set->value = g_strdup( value );
+
+ for( a = bee->accounts; a; a = a->next )
+ {
+ struct im_connection *ic = a->ic;
+
+ if( ic && ic->flags & OPT_LOGGED_IN )
+ imc_away_send_update( ic );
+ }
+
+ return value;
+}
diff --git a/protocols/bee.h b/protocols/bee.h
new file mode 100644
index 00000000..49ea6fb5
--- /dev/null
+++ b/protocols/bee.h
@@ -0,0 +1,183 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* Stuff to handle, save and search buddies */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#ifndef __BEE_H__
+#define __BEE_H__
+
+struct bee_ui_funcs;
+struct groupchat;
+
+typedef struct bee
+{
+ /* Settings. See set.h for how these work. The UI can add its
+ own settings here. */
+ struct set *set;
+
+ GSList *users; /* struct bee_user */
+ GSList *groups; /* struct bee_group */
+ struct account *accounts; /* TODO(wilmer): Use GSList here too? */
+
+ /* Symbolic, to refer to the local user (who has no real bee_user
+ object). Not to be used by anything except so far imcb_chat_add/
+ remove_buddy(). */
+ struct bee_user *user;
+
+ /* Fill in the callbacks for events you care about. */
+ const struct bee_ui_funcs *ui;
+
+ /* And this one will be passed to every callback for any state the
+ UI may want to keep. */
+ void *ui_data;
+} bee_t;
+
+bee_t *bee_new();
+void bee_free( bee_t *b );
+
+/* TODO(wilmer): Kill at least the OPT_ flags that have an equivalent here. */
+typedef enum
+{
+ BEE_USER_ONLINE = 1, /* Compatibility with old OPT_LOGGED_IN flag */
+ BEE_USER_AWAY = 4, /* Compatibility with old OPT_AWAY flag */
+ BEE_USER_MOBILE = 8, /* Compatibility with old OPT_MOBILE flag */
+ BEE_USER_LOCAL = 256, /* Locally-added contacts (not in real contact list) */
+} bee_user_flags_t;
+
+typedef struct bee_user
+{
+ struct im_connection *ic;
+ char *handle;
+ char *fullname;
+ char *nick;
+ struct bee_group *group;
+
+ bee_user_flags_t flags;
+ char *status; /* NULL means available, anything else is an away state. */
+ char *status_msg; /* Status and/or away message. */
+
+ /* Set using imcb_buddy_times(). */
+ time_t login_time, idle_time;
+
+ bee_t *bee;
+ void *ui_data;
+ void *data; /* Can be used by the IM module. */
+} bee_user_t;
+
+/* This one's mostly used so save space and make it easier (cheaper) to
+ compare groups of contacts, etc. */
+typedef struct bee_group
+{
+ char *key; /* Lower case version of the name. */
+ char *name;
+} bee_group_t;
+
+typedef struct bee_ui_funcs
+{
+ void (*imc_connected)( struct im_connection *ic );
+ void (*imc_disconnected)( struct im_connection *ic );
+
+ gboolean (*user_new)( bee_t *bee, struct bee_user *bu );
+ gboolean (*user_free)( bee_t *bee, struct bee_user *bu );
+ /* Set the fullname first, then call this one to notify the UI. */
+ gboolean (*user_fullname)( bee_t *bee, bee_user_t *bu );
+ gboolean (*user_nick_hint)( bee_t *bee, bee_user_t *bu, const char *hint );
+ /* Notify the UI when an existing user is moved between groups. */
+ gboolean (*user_group)( bee_t *bee, bee_user_t *bu );
+ /* State info is already updated, old is provided in case the UI needs a diff. */
+ gboolean (*user_status)( bee_t *bee, struct bee_user *bu, struct bee_user *old );
+ /* On every incoming message. sent_at = 0 means unknown. */
+ gboolean (*user_msg)( bee_t *bee, bee_user_t *bu, const char *msg, time_t sent_at );
+ /* Flags currently defined (OPT_TYPING/THINKING) in nogaim.h. */
+ gboolean (*user_typing)( bee_t *bee, bee_user_t *bu, guint32 flags );
+ /* CTCP-like stuff (buddy action) response */
+ gboolean (*user_action_response)( bee_t *bee, bee_user_t *bu, const char *action, char * const args[], void *data );
+
+ /* Called at creation time. Don't show to the user until s/he is
+ added using chat_add_user(). UI state can be stored via c->data. */
+ gboolean (*chat_new)( bee_t *bee, struct groupchat *c );
+ gboolean (*chat_free)( bee_t *bee, struct groupchat *c );
+ /* System messages of any kind. */
+ gboolean (*chat_log)( bee_t *bee, struct groupchat *c, const char *text );
+ gboolean (*chat_msg)( bee_t *bee, struct groupchat *c, bee_user_t *bu, const char *msg, time_t sent_at );
+ gboolean (*chat_add_user)( bee_t *bee, struct groupchat *c, bee_user_t *bu );
+ gboolean (*chat_remove_user)( bee_t *bee, struct groupchat *c, bee_user_t *bu );
+ gboolean (*chat_topic)( bee_t *bee, struct groupchat *c, const char *new, bee_user_t *bu );
+ gboolean (*chat_name_hint)( bee_t *bee, struct groupchat *c, const char *name );
+ gboolean (*chat_invite)( bee_t *bee, bee_user_t *bu, const char *name, const char *msg );
+
+ struct file_transfer* (*ft_in_start)( bee_t *bee, bee_user_t *bu, const char *file_name, size_t file_size );
+ gboolean (*ft_out_start)( struct im_connection *ic, struct file_transfer *ft );
+ void (*ft_close)( struct im_connection *ic, struct file_transfer *ft );
+ void (*ft_finished)( struct im_connection *ic, struct file_transfer *ft );
+} bee_ui_funcs_t;
+
+
+/* bee.c */
+bee_t *bee_new();
+void bee_free( bee_t *b );
+
+/* bee_user.c */
+bee_user_t *bee_user_new( bee_t *bee, struct im_connection *ic, const char *handle, bee_user_flags_t flags );
+int bee_user_free( bee_t *bee, bee_user_t *bu );
+bee_user_t *bee_user_by_handle( bee_t *bee, struct im_connection *ic, const char *handle );
+int bee_user_msg( bee_t *bee, bee_user_t *bu, const char *msg, int flags );
+bee_group_t *bee_group_by_name( bee_t *bee, const char *name, gboolean creat );
+void bee_group_free( bee_t *bee );
+
+/* Callbacks from IM modules to core: */
+/* Buddy activity */
+/* To manipulate the status of a handle.
+ * - flags can be |='d with OPT_* constants. You will need at least:
+ * OPT_LOGGED_IN and OPT_AWAY.
+ * - 'state' and 'message' can be NULL */
+G_MODULE_EXPORT void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags, const char *state, const char *message );
+G_MODULE_EXPORT void imcb_buddy_status_msg( struct im_connection *ic, const char *handle, const char *message );
+G_MODULE_EXPORT void imcb_buddy_times( struct im_connection *ic, const char *handle, time_t login, time_t idle );
+/* Call when a handle says something. 'flags' and 'sent_at may be just 0. */
+G_MODULE_EXPORT void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, guint32 flags, time_t sent_at );
+
+/* bee_chat.c */
+/* These two functions are to create a group chat.
+ * - imcb_chat_new(): the 'handle' parameter identifies the chat, like the
+ * channel name on IRC.
+ * - After you have a groupchat pointer, you should add the handles, finally
+ * the user her/himself. At that point the group chat will be visible to the
+ * user, too. */
+G_MODULE_EXPORT struct groupchat *imcb_chat_new( struct im_connection *ic, const char *handle );
+G_MODULE_EXPORT void imcb_chat_name_hint( struct groupchat *c, const char *name );
+G_MODULE_EXPORT void imcb_chat_free( struct groupchat *c );
+/* To tell BitlBee 'who' said 'msg' in 'c'. 'flags' and 'sent_at' can be 0. */
+G_MODULE_EXPORT void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, guint32 flags, time_t sent_at );
+/* System messages specific to a groupchat, so they can be displayed in the right context. */
+G_MODULE_EXPORT void imcb_chat_log( struct groupchat *c, char *format, ... );
+/* To tell BitlBee 'who' changed the topic of 'c' to 'topic'. */
+G_MODULE_EXPORT void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at );
+G_MODULE_EXPORT void imcb_chat_add_buddy( struct groupchat *c, const char *handle );
+/* To remove a handle from a group chat. Reason can be NULL. */
+G_MODULE_EXPORT void imcb_chat_remove_buddy( struct groupchat *c, const char *handle, const char *reason );
+G_MODULE_EXPORT int bee_chat_msg( bee_t *bee, struct groupchat *c, const char *msg, int flags );
+G_MODULE_EXPORT struct groupchat *bee_chat_by_title( bee_t *bee, struct im_connection *ic, const char *title );
+G_MODULE_EXPORT void imcb_chat_invite( struct im_connection *ic, const char *name, const char *who, const char *msg );
+
+#endif /* __BEE_H__ */
diff --git a/protocols/bee_chat.c b/protocols/bee_chat.c
new file mode 100644
index 00000000..349e0547
--- /dev/null
+++ b/protocols/bee_chat.c
@@ -0,0 +1,242 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* Stuff to handle rooms */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#define BITLBEE_CORE
+#include "bitlbee.h"
+
+struct groupchat *imcb_chat_new( struct im_connection *ic, const char *handle )
+{
+ struct groupchat *c = g_new0( struct groupchat, 1 );
+ bee_t *bee = ic->bee;
+
+ /* This one just creates the conversation structure, user won't see
+ anything yet until s/he is joined to the conversation. (This
+ allows you to add other already present participants first.) */
+
+ ic->groupchats = g_slist_prepend( ic->groupchats, c );
+ c->ic = ic;
+ c->title = g_strdup( handle );
+ c->topic = g_strdup_printf( "BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", c->title );
+
+ if( set_getbool( &ic->bee->set, "debug" ) )
+ imcb_log( ic, "Creating new conversation: (id=%p,handle=%s)", c, handle );
+
+ if( bee->ui->chat_new )
+ bee->ui->chat_new( bee, c );
+
+ return c;
+}
+
+void imcb_chat_name_hint( struct groupchat *c, const char *name )
+{
+ bee_t *bee = c->ic->bee;
+
+ if( bee->ui->chat_name_hint )
+ bee->ui->chat_name_hint( bee, c, name );
+}
+
+void imcb_chat_free( struct groupchat *c )
+{
+ struct im_connection *ic = c->ic;
+ bee_t *bee = ic->bee;
+ GList *ir;
+
+ if( bee->ui->chat_free )
+ bee->ui->chat_free( bee, c );
+
+ if( set_getbool( &ic->bee->set, "debug" ) )
+ imcb_log( ic, "You were removed from conversation %p", c );
+
+ ic->groupchats = g_slist_remove( ic->groupchats, c );
+
+ for( ir = c->in_room; ir; ir = ir->next )
+ g_free( ir->data );
+ g_list_free( c->in_room );
+ g_free( c->title );
+ g_free( c->topic );
+ g_free( c );
+}
+
+void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, uint32_t flags, time_t sent_at )
+{
+ struct im_connection *ic = c->ic;
+ bee_t *bee = ic->bee;
+ bee_user_t *bu;
+ char *s;
+
+ /* Gaim sends own messages through this too. IRC doesn't want this, so kill them */
+ if( g_strcasecmp( who, ic->acc->user ) == 0 )
+ return;
+
+ bu = bee_user_by_handle( bee, ic, who );
+
+ s = set_getstr( &ic->bee->set, "strip_html" );
+ if( ( g_strcasecmp( s, "always" ) == 0 ) ||
+ ( ( ic->flags & OPT_DOES_HTML ) && s ) )
+ strip_html( msg );
+
+ if( bu && bee->ui->chat_msg )
+ bee->ui->chat_msg( bee, c, bu, msg, sent_at );
+ else
+ imcb_chat_log( c, "Message from unknown participant %s: %s", who, msg );
+}
+
+void imcb_chat_log( struct groupchat *c, char *format, ... )
+{
+ struct im_connection *ic = c->ic;
+ bee_t *bee = ic->bee;
+ va_list params;
+ char *text;
+
+ if( !bee->ui->chat_log )
+ return;
+
+ va_start( params, format );
+ text = g_strdup_vprintf( format, params );
+ va_end( params );
+
+ bee->ui->chat_log( bee, c, text );
+ g_free( text );
+}
+
+void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at )
+{
+ struct im_connection *ic = c->ic;
+ bee_t *bee = ic->bee;
+ bee_user_t *bu;
+
+ if( !bee->ui->chat_topic )
+ return;
+
+ if( who == NULL)
+ bu = NULL;
+ else if( g_strcasecmp( who, ic->acc->user ) == 0 )
+ bu = bee->user;
+ else
+ bu = bee_user_by_handle( bee, ic, who );
+
+ if( ( g_strcasecmp( set_getstr( &ic->bee->set, "strip_html" ), "always" ) == 0 ) ||
+ ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->bee->set, "strip_html" ) ) )
+ strip_html( topic );
+
+ bee->ui->chat_topic( bee, c, topic, bu );
+}
+
+void imcb_chat_add_buddy( struct groupchat *c, const char *handle )
+{
+ struct im_connection *ic = c->ic;
+ bee_t *bee = ic->bee;
+ bee_user_t *bu = bee_user_by_handle( bee, ic, handle );
+ gboolean me;
+
+ if( set_getbool( &c->ic->bee->set, "debug" ) )
+ imcb_log( c->ic, "User %s added to conversation %p", handle, c );
+
+ me = ic->acc->prpl->handle_cmp( handle, ic->acc->user ) == 0;
+
+ /* Most protocols allow people to join, even when they're not in
+ your contact list. Try to handle that here */
+ if( !me && !bu )
+ bu = bee_user_new( bee, ic, handle, BEE_USER_LOCAL );
+
+ /* Add the handle to the room userlist */
+ /* TODO: Use bu instead of a string */
+ c->in_room = g_list_append( c->in_room, g_strdup( handle ) );
+
+ if( bee->ui->chat_add_user )
+ bee->ui->chat_add_user( bee, c, me ? bee->user : bu );
+
+ if( me )
+ c->joined = 1;
+}
+
+void imcb_chat_remove_buddy( struct groupchat *c, const char *handle, const char *reason )
+{
+ struct im_connection *ic = c->ic;
+ bee_t *bee = ic->bee;
+ bee_user_t *bu = NULL;
+
+ if( set_getbool( &bee->set, "debug" ) )
+ imcb_log( ic, "User %s removed from conversation %p (%s)", handle, c, reason ? reason : "" );
+
+ /* It might be yourself! */
+ if( g_strcasecmp( handle, ic->acc->user ) == 0 )
+ {
+ if( c->joined == 0 )
+ return;
+
+ bu = bee->user;
+ c->joined = 0;
+ }
+ else
+ {
+ bu = bee_user_by_handle( bee, ic, handle );
+ }
+
+ if( bee->ui->chat_remove_user && bu )
+ bee->ui->chat_remove_user( bee, c, bu );
+}
+
+int bee_chat_msg( bee_t *bee, struct groupchat *c, const char *msg, int flags )
+{
+ struct im_connection *ic = c->ic;
+ char *buf = NULL;
+
+ if( ( ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<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;
+}
+
+void imcb_chat_invite( struct im_connection *ic, const char *name, const char *who, const char *msg )
+{
+ bee_user_t *bu = bee_user_by_handle( ic->bee, ic, who );
+
+ if( bu && ic->bee->ui->chat_invite )
+ ic->bee->ui->chat_invite( ic->bee, bu, name, msg );
+}
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..f0091799
--- /dev/null
+++ b/protocols/bee_user.c
@@ -0,0 +1,288 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* Stuff to handle, save and search buddies */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#define BITLBEE_CORE
+#include "bitlbee.h"
+
+bee_user_t *bee_user_new( bee_t *bee, struct im_connection *ic, const char *handle, bee_user_flags_t flags )
+{
+ bee_user_t *bu;
+
+ if( bee_user_by_handle( bee, ic, handle ) != NULL )
+ return NULL;
+
+ bu = g_new0( bee_user_t, 1 );
+ bu->bee = bee;
+ bu->ic = ic;
+ bu->flags = flags;
+ bu->handle = g_strdup( handle );
+ bee->users = g_slist_prepend( bee->users, bu );
+
+ if( bee->ui->user_new )
+ bee->ui->user_new( bee, bu );
+ if( ic->acc->prpl->buddy_data_add )
+ ic->acc->prpl->buddy_data_add( bu );
+
+ /* Offline by default. This will set the right flags. */
+ imcb_buddy_status( ic, handle, 0, NULL, NULL );
+
+ return bu;
+}
+
+int bee_user_free( bee_t *bee, bee_user_t *bu )
+{
+ if( !bu )
+ return 0;
+
+ if( bee->ui->user_free )
+ bee->ui->user_free( bee, bu );
+ if( bu->ic->acc->prpl->buddy_data_free )
+ bu->ic->acc->prpl->buddy_data_free( bu );
+
+ g_free( bu->handle );
+ g_free( bu->fullname );
+ g_free( bu->nick );
+ g_free( bu->status );
+ g_free( bu->status_msg );
+ g_free( bu );
+
+ bee->users = g_slist_remove( bee->users, bu );
+
+ return 1;
+}
+
+bee_user_t *bee_user_by_handle( bee_t *bee, struct im_connection *ic, const char *handle )
+{
+ GSList *l;
+
+ for( l = bee->users; l; l = l->next )
+ {
+ bee_user_t *bu = l->data;
+
+ if( bu->ic == ic && ic->acc->prpl->handle_cmp( bu->handle, handle ) == 0 )
+ return bu;
+ }
+
+ return NULL;
+}
+
+int bee_user_msg( bee_t *bee, bee_user_t *bu, const char *msg, int flags )
+{
+ char *buf = NULL;
+ int st;
+
+ if( ( bu->ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<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_msg = g_strdup( message );
+ if( state && *state )
+ bu->status = g_strdup( state );
+ else if( flags & OPT_AWAY )
+ bu->status = g_strdup( "Away" );
+ else
+ bu->status = NULL;
+
+ if( bu->status == NULL && ( flags & OPT_MOBILE ) &&
+ set_getbool( &bee->set, "mobile_is_away" ) )
+ {
+ bu->flags |= BEE_USER_AWAY;
+ bu->status = g_strdup( "Mobile" );
+ }
+
+ if( bee->ui->user_status )
+ bee->ui->user_status( bee, bu, old );
+
+ g_free( old->status_msg );
+ g_free( old->status );
+ g_free( old );
+}
+
+/* Same, but only change the away/status message, not any away/online state info. */
+void imcb_buddy_status_msg( struct im_connection *ic, const char *handle, const char *message )
+{
+ bee_t *bee = ic->bee;
+ bee_user_t *bu, *old;
+
+ if( !( bu = bee_user_by_handle( bee, ic, handle ) ) )
+ {
+ return;
+ }
+
+ old = g_memdup( bu, sizeof( bee_user_t ) );
+
+ bu->status_msg = message && *message ? g_strdup( message ) : NULL;
+
+ if( bee->ui->user_status )
+ bee->ui->user_status( bee, bu, old );
+
+ g_free( old->status_msg );
+ g_free( old );
+}
+
+void imcb_buddy_times( struct im_connection *ic, const char *handle, time_t login, time_t idle )
+{
+ bee_t *bee = ic->bee;
+ bee_user_t *bu;
+
+ if( !( bu = bee_user_by_handle( bee, ic, handle ) ) )
+ return;
+
+ bu->login_time = login;
+ bu->idle_time = idle;
+}
+
+void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, uint32_t flags, time_t sent_at )
+{
+ bee_t *bee = ic->bee;
+ bee_user_t *bu;
+
+ bu = bee_user_by_handle( bee, ic, handle );
+
+ if( !bu && !( ic->flags & OPT_LOGGING_OUT ) )
+ {
+ char *h = set_getstr( &bee->set, "handle_unknown" );
+
+ if( g_strcasecmp( h, "ignore" ) == 0 )
+ {
+ return;
+ }
+ else if( g_strncasecmp( h, "add", 3 ) == 0 )
+ {
+ bu = bee_user_new( bee, ic, handle, BEE_USER_LOCAL );
+ }
+ }
+
+ if( bee->ui->user_msg && bu )
+ bee->ui->user_msg( bee, bu, msg, sent_at );
+ else
+ imcb_log( ic, "Message from unknown handle %s:\n%s", handle, msg );
+}
+
+void imcb_buddy_typing( struct im_connection *ic, const char *handle, uint32_t flags )
+{
+ bee_user_t *bu;
+
+ if( ic->bee->ui->user_typing &&
+ ( bu = bee_user_by_handle( ic->bee, ic, handle ) ) )
+ {
+ ic->bee->ui->user_typing( ic->bee, bu, flags );
+ }
+}
+
+void imcb_buddy_action_response( bee_user_t *bu, const char *action, char * const args[], void *data )
+{
+ if( bu->bee->ui->user_action_response )
+ bu->bee->ui->user_action_response( bu->bee, bu, action, args, data );
+}
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
new file mode 100644
index 00000000..32946b18
--- /dev/null
+++ b/protocols/jabber/Makefile
@@ -0,0 +1,46 @@
+###########################
+## Makefile for BitlBee ##
+## ##
+## Copyright 2002 Lintux ##
+###########################
+
+### DEFINITIONS
+
+-include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/jabber/
+endif
+
+# [SH] Program variables
+objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o s5bytestream.o sasl.o si.o
+
+LFLAGS += -r
+
+# [SH] Phony targets
+all: jabber_mod.o
+check: all
+lcov: check
+gcov:
+ gcov *.c
+
+.PHONY: all clean distclean
+
+clean:
+ rm -f *.o core
+
+distclean: clean
+ rm -rf .depend
+
+### MAIN PROGRAM
+
+$(objects): ../../Makefile.settings Makefile
+
+$(objects): %.o: $(SRCDIR)%.c
+ @echo '*' Compiling $<
+ @$(CC) -c $(CFLAGS) $< -o $@
+
+jabber_mod.o: $(objects)
+ @echo '*' Linking jabber_mod.o
+ @$(LD) $(LFLAGS) $(objects) -o jabber_mod.o
+
+-include .depend/*.d
diff --git a/protocols/jabber/conference.c b/protocols/jabber/conference.c
new file mode 100644
index 00000000..0c2db0b3
--- /dev/null
+++ b/protocols/jabber/conference.c
@@ -0,0 +1,384 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Jabber module - Conference rooms *
+* *
+* Copyright 2007 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 "jabber.h"
+
+static xt_status jabber_chat_join_failed( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
+
+struct groupchat *jabber_chat_join( struct im_connection *ic, const char *room, const char *nick, const char *password )
+{
+ struct jabber_chat *jc;
+ struct xt_node *node;
+ struct groupchat *c;
+ char *roomjid;
+
+ roomjid = g_strdup_printf( "%s/%s", room, nick );
+ node = xt_new_node( "x", NULL, NULL );
+ xt_add_attr( node, "xmlns", XMLNS_MUC );
+ if( password )
+ xt_add_child( node, xt_new_node( "password", password, NULL ) );
+ node = jabber_make_packet( "presence", NULL, roomjid, node );
+ jabber_cache_add( ic, node, jabber_chat_join_failed );
+
+ if( !jabber_write_packet( ic, node ) )
+ {
+ g_free( roomjid );
+ return NULL;
+ }
+
+ jc = g_new0( struct jabber_chat, 1 );
+ jc->name = jabber_normalize( room );
+
+ if( ( jc->me = jabber_buddy_add( ic, roomjid ) ) == NULL )
+ {
+ g_free( roomjid );
+ g_free( jc->name );
+ g_free( jc );
+ return NULL;
+ }
+
+ /* roomjid isn't normalized yet, and we need an original version
+ of the nick to send a proper presence update. */
+ jc->my_full_jid = roomjid;
+
+ c = imcb_chat_new( ic, room );
+ c->data = jc;
+
+ return c;
+}
+
+static xt_status jabber_chat_join_failed( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+ struct jabber_error *err;
+ struct jabber_buddy *bud;
+ char *room;
+
+ room = xt_find_attr( orig, "to" );
+ bud = jabber_buddy_by_jid( ic, room, 0 );
+ err = jabber_error_parse( xt_find_node( node->children, "error" ), XMLNS_STANZA_ERROR );
+ if( err )
+ {
+ imcb_error( ic, "Error joining groupchat %s: %s%s%s", room, err->code,
+ err->text ? ": " : "", err->text ? err->text : "" );
+ jabber_error_free( err );
+ }
+ if( bud )
+ jabber_chat_free( jabber_chat_by_jid( ic, bud->bare_jid ) );
+
+ return XT_HANDLED;
+}
+
+struct groupchat *jabber_chat_by_jid( struct im_connection *ic, const char *name )
+{
+ char *normalized = jabber_normalize( name );
+ GSList *l;
+ struct groupchat *ret;
+ struct jabber_chat *jc;
+
+ for( l = ic->groupchats; l; l = l->next )
+ {
+ ret = l->data;
+ jc = ret->data;
+ if( strcmp( normalized, jc->name ) == 0 )
+ break;
+ }
+ g_free( normalized );
+
+ return l ? ret : NULL;
+}
+
+void jabber_chat_free( struct groupchat *c )
+{
+ struct jabber_chat *jc = c->data;
+
+ jabber_buddy_remove_bare( c->ic, jc->name );
+
+ g_free( jc->my_full_jid );
+ g_free( jc->name );
+ g_free( jc );
+
+ imcb_chat_free( c );
+}
+
+int jabber_chat_msg( struct groupchat *c, char *message, int flags )
+{
+ struct im_connection *ic = c->ic;
+ struct jabber_chat *jc = c->data;
+ struct xt_node *node;
+
+ jc->flags |= JCFLAG_MESSAGE_SENT;
+
+ node = xt_new_node( "body", message, NULL );
+ node = jabber_make_packet( "message", "groupchat", jc->name, node );
+
+ if( !jabber_write_packet( ic, node ) )
+ {
+ xt_free_node( node );
+ return 0;
+ }
+ xt_free_node( node );
+
+ return 1;
+}
+
+int jabber_chat_topic( struct groupchat *c, char *topic )
+{
+ struct im_connection *ic = c->ic;
+ struct jabber_chat *jc = c->data;
+ struct xt_node *node;
+
+ node = xt_new_node( "subject", topic, NULL );
+ node = jabber_make_packet( "message", "groupchat", jc->name, node );
+
+ if( !jabber_write_packet( ic, node ) )
+ {
+ xt_free_node( node );
+ return 0;
+ }
+ xt_free_node( node );
+
+ return 1;
+}
+
+int jabber_chat_leave( struct groupchat *c, const char *reason )
+{
+ struct im_connection *ic = c->ic;
+ struct jabber_chat *jc = c->data;
+ struct xt_node *node;
+
+ node = xt_new_node( "x", NULL, NULL );
+ xt_add_attr( node, "xmlns", XMLNS_MUC );
+ node = jabber_make_packet( "presence", "unavailable", jc->my_full_jid, node );
+
+ if( !jabber_write_packet( ic, node ) )
+ {
+ xt_free_node( node );
+ return 0;
+ }
+ xt_free_node( node );
+
+ return 1;
+}
+
+void jabber_chat_invite( struct groupchat *c, char *who, char *message )
+{
+ struct xt_node *node;
+ struct im_connection *ic = c->ic;
+ struct jabber_chat *jc = c->data;
+
+ node = xt_new_node( "reason", message, NULL );
+
+ node = xt_new_node( "invite", NULL, node );
+ xt_add_attr( node, "to", who );
+
+ node = xt_new_node( "x", NULL, node );
+ xt_add_attr( node, "xmlns", XMLNS_MUC_USER );
+
+ node = jabber_make_packet( "message", NULL, jc->name, node );
+
+ jabber_write_packet( ic, node );
+
+ xt_free_node( node );
+}
+
+/* Not really the same syntax as the normal pkt_ functions, but this isn't
+ called by the xmltree parser directly and this way I can add some extra
+ parameters so we won't have to repeat too many things done by the caller
+ already. */
+void jabber_chat_pkt_presence( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node )
+{
+ struct groupchat *chat;
+ struct xt_node *c;
+ char *type = xt_find_attr( node, "type" );
+ struct jabber_chat *jc;
+ char *s;
+
+ if( ( chat = jabber_chat_by_jid( ic, bud->bare_jid ) ) == NULL )
+ {
+ /* How could this happen?? We could do kill( self, 11 )
+ now or just wait for the OS to do it. :-) */
+ return;
+ }
+
+ jc = chat->data;
+
+ if( type == NULL && !( bud->flags & JBFLAG_IS_CHATROOM ) )
+ {
+ bud->flags |= JBFLAG_IS_CHATROOM;
+ /* If this one wasn't set yet, this buddy just joined the chat.
+ Slightly hackish way of finding out eh? ;-) */
+
+ /* This is pretty messy... Here it sets ext_jid to the real
+ JID of the participant. Works for non-anonymized channels.
+ Might break if someone joins a chat twice, though. */
+ for( c = node->children; ( c = xt_find_node( c, "x" ) ); c = c->next )
+ if( ( s = xt_find_attr( c, "xmlns" ) ) &&
+ ( strcmp( s, XMLNS_MUC_USER ) == 0 ) )
+ {
+ struct xt_node *item;
+
+ item = xt_find_node( c->children, "item" );
+ if( ( s = xt_find_attr( item, "jid" ) ) )
+ {
+ /* Yay, found what we need. :-) */
+ bud->ext_jid = jabber_normalize( s );
+ break;
+ }
+ }
+
+ /* Make up some other handle, if necessary. */
+ if( bud->ext_jid == NULL )
+ {
+ if( bud == jc->me )
+ {
+ bud->ext_jid = jabber_normalize( ic->acc->user );
+ }
+ else
+ {
+ int i;
+
+ /* Don't want the nick to be at the end, so let's
+ think of some slightly different notation to use
+ for anonymous groupchat participants in BitlBee. */
+ bud->ext_jid = g_strdup_printf( "%s=%s", bud->resource, bud->bare_jid );
+
+ /* And strip any unwanted characters. */
+ for( i = 0; bud->resource[i]; i ++ )
+ if( bud->ext_jid[i] == '=' || bud->ext_jid[i] == '@' )
+ bud->ext_jid[i] = '_';
+
+ /* Some program-specific restrictions. */
+ imcb_clean_handle( ic, bud->ext_jid );
+ }
+ bud->flags |= JBFLAG_IS_ANONYMOUS;
+ }
+
+ if( bud != jc->me && bud->flags & JBFLAG_IS_ANONYMOUS )
+ {
+ /* If JIDs are anonymized, add them to the local
+ list for the duration of this chat. */
+ imcb_add_buddy( ic, bud->ext_jid, NULL );
+ imcb_buddy_nick_hint( ic, bud->ext_jid, bud->resource );
+ }
+
+ s = strchr( bud->ext_jid, '/' );
+ if( s ) *s = 0; /* Should NEVER be NULL, but who knows... */
+ imcb_chat_add_buddy( chat, bud->ext_jid );
+ if( s ) *s = '/';
+ }
+ else if( type ) /* type can only be NULL or "unavailable" in this function */
+ {
+ if( ( bud->flags & JBFLAG_IS_CHATROOM ) && bud->ext_jid )
+ {
+ s = strchr( bud->ext_jid, '/' );
+ if( s ) *s = 0;
+ imcb_chat_remove_buddy( chat, bud->ext_jid, NULL );
+ if( bud != jc->me && bud->flags & JBFLAG_IS_ANONYMOUS )
+ imcb_remove_buddy( ic, bud->ext_jid, NULL );
+ if( s ) *s = '/';
+ }
+
+ if( bud == jc->me )
+ jabber_chat_free( chat );
+ }
+}
+
+void jabber_chat_pkt_message( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node )
+{
+ struct xt_node *subject = xt_find_node( node->children, "subject" );
+ struct xt_node *body = xt_find_node( node->children, "body" );
+ struct groupchat *chat = bud ? jabber_chat_by_jid( ic, bud->bare_jid ) : NULL;
+ struct jabber_chat *jc = chat ? chat->data : NULL;
+ char *s;
+
+ if( subject && chat )
+ {
+ s = bud ? strchr( bud->ext_jid, '/' ) : NULL;
+ if( s ) *s = 0;
+ imcb_chat_topic( chat, bud ? bud->ext_jid : NULL, subject->text_len > 0 ?
+ subject->text : NULL, jabber_get_timestamp( node ) );
+ if( s ) *s = '/';
+ }
+
+ if( bud == NULL || ( jc && ~jc->flags & JCFLAG_MESSAGE_SENT && bud == jc->me ) )
+ {
+ char *nick;
+
+ if( body == NULL || body->text_len == 0 )
+ /* Meh. Empty messages aren't very interesting, no matter
+ how much some servers love to send them. */
+ return;
+
+ s = xt_find_attr( node, "from" ); /* pkt_message() already NULL-checked this one. */
+ nick = strchr( s, '/' );
+ if( nick )
+ {
+ /* If this message included a resource/nick we don't know,
+ we might still know the groupchat itself. */
+ *nick = 0;
+ chat = jabber_chat_by_jid( ic, s );
+ *nick = '/';
+
+ nick ++;
+ }
+ else
+ {
+ /* message.c uses the EXACT_JID option, so bud should
+ always be NULL here for bare JIDs. */
+ chat = jabber_chat_by_jid( ic, s );
+ }
+
+ if( nick == NULL )
+ {
+ /* This is fine, the groupchat itself isn't in jd->buddies. */
+ if( chat )
+ imcb_chat_log( chat, "From conference server: %s", body->text );
+ else
+ imcb_log( ic, "System message from unknown groupchat %s: %s", s, body->text );
+ }
+ else
+ {
+ /* This can happen too, at least when receiving a backlog when
+ just joining a channel. */
+ if( chat )
+ imcb_chat_log( chat, "Message from unknown participant %s: %s", nick, body->text );
+ else
+ imcb_log( ic, "Groupchat message from unknown JID %s: %s", s, body->text );
+ }
+
+ return;
+ }
+ else if( chat == NULL )
+ {
+ /* How could this happen?? We could do kill( self, 11 )
+ now or just wait for the OS to do it. :-) */
+ return;
+ }
+ if( body && body->text_len > 0 )
+ {
+ s = strchr( bud->ext_jid, '/' );
+ if( s ) *s = 0;
+ imcb_chat_msg( chat, bud->ext_jid, body->text, 0, jabber_get_timestamp( node ) );
+ if( s ) *s = '/';
+ }
+}
diff --git a/protocols/jabber/io.c b/protocols/jabber/io.c
new file mode 100644
index 00000000..ef7d5c13
--- /dev/null
+++ b/protocols/jabber/io.c
@@ -0,0 +1,549 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Jabber module - I/O stuff (plain, SSL), queues, etc *
+* *
+* Copyright 2006 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 "jabber.h"
+#include "ssl_client.h"
+
+static gboolean jabber_write_callback( gpointer data, gint fd, b_input_condition cond );
+static gboolean jabber_write_queue( struct im_connection *ic );
+
+int jabber_write_packet( struct im_connection *ic, struct xt_node *node )
+{
+ char *buf;
+ int st;
+
+ buf = xt_to_string( node );
+ st = jabber_write( ic, buf, strlen( buf ) );
+ g_free( buf );
+
+ return st;
+}
+
+int jabber_write( struct im_connection *ic, char *buf, int len )
+{
+ struct jabber_data *jd = ic->proto_data;
+ gboolean ret;
+
+ if( jd->flags & JFLAG_XMLCONSOLE && !( ic->flags & OPT_LOGGING_OUT ) )
+ {
+ char *msg, *s;
+
+ msg = g_strdup_printf( "TX: %s", buf );
+ /* Don't include auth info in XML logs. */
+ if( strncmp( msg, "TX: <auth ", 10 ) == 0 && ( s = strchr( msg, '>' ) ) )
+ {
+ s++;
+ while( *s && *s != '<' )
+ *(s++) = '*';
+ }
+ imcb_buddy_msg( ic, JABBER_XMLCONSOLE_HANDLE, msg, 0, 0 );
+ g_free( msg );
+ }
+
+ if( jd->tx_len == 0 )
+ {
+ /* If the queue is empty, allocate a new buffer. */
+ jd->tx_len = len;
+ jd->txq = g_memdup( buf, len );
+
+ /* Try if we can write it immediately so we don't have to do
+ it via the event handler. If not, add the handler. (In
+ most cases it probably won't be necessary.) */
+ if( ( ret = jabber_write_queue( ic ) ) && jd->tx_len > 0 )
+ jd->w_inpa = b_input_add( jd->fd, B_EV_IO_WRITE, jabber_write_callback, ic );
+ }
+ else
+ {
+ /* Just add it to the buffer if it's already filled. The
+ event handler is already set. */
+ jd->txq = g_renew( char, jd->txq, jd->tx_len + len );
+ memcpy( jd->txq + jd->tx_len, buf, len );
+ jd->tx_len += len;
+
+ /* The return value for write() doesn't necessarily mean
+ that everything got sent, it mainly means that the
+ connection (officially) still exists and can still
+ be accessed without hitting SIGSEGV. IOW: */
+ ret = TRUE;
+ }
+
+ return ret;
+}
+
+/* Splitting up in two separate functions: One to use as a callback and one
+ to use in the function above to escape from having to wait for the event
+ handler to call us, if possible.
+
+ Two different functions are necessary because of the return values: The
+ callback should only return TRUE if the write was successful AND if the
+ buffer is not empty yet (ie. if the handler has to be called again when
+ the socket is ready for more data). */
+static gboolean jabber_write_callback( gpointer data, gint fd, b_input_condition cond )
+{
+ struct jabber_data *jd = ((struct im_connection *)data)->proto_data;
+
+ return jd->fd != -1 &&
+ jabber_write_queue( data ) &&
+ jd->tx_len > 0;
+}
+
+static gboolean jabber_write_queue( struct im_connection *ic )
+{
+ struct jabber_data *jd = ic->proto_data;
+ int st;
+
+ if( jd->ssl )
+ st = ssl_write( jd->ssl, jd->txq, jd->tx_len );
+ else
+ st = write( jd->fd, jd->txq, jd->tx_len );
+
+ if( st == jd->tx_len )
+ {
+ /* We wrote everything, clear the buffer. */
+ g_free( jd->txq );
+ jd->txq = NULL;
+ jd->tx_len = 0;
+
+ return TRUE;
+ }
+ else if( st == 0 || ( st < 0 && !ssl_sockerr_again( jd->ssl ) ) )
+ {
+ /* Set fd to -1 to make sure we won't write to it anymore. */
+ closesocket( jd->fd ); /* Shouldn't be necessary after errors? */
+ jd->fd = -1;
+
+ imcb_error( ic, "Short write() to server" );
+ imc_logout( ic, TRUE );
+ return FALSE;
+ }
+ else if( st > 0 )
+ {
+ char *s;
+
+ s = g_memdup( jd->txq + st, jd->tx_len - st );
+ jd->tx_len -= st;
+ g_free( jd->txq );
+ jd->txq = s;
+
+ return TRUE;
+ }
+ else
+ {
+ /* Just in case we had EINPROGRESS/EAGAIN: */
+
+ return TRUE;
+ }
+}
+
+static gboolean jabber_read_callback( gpointer data, gint fd, b_input_condition cond )
+{
+ struct im_connection *ic = data;
+ struct jabber_data *jd = ic->proto_data;
+ char buf[512];
+ int st;
+
+ if( jd->fd == -1 )
+ return FALSE;
+
+ if( jd->ssl )
+ st = ssl_read( jd->ssl, buf, sizeof( buf ) );
+ else
+ st = read( jd->fd, buf, sizeof( buf ) );
+
+ if( st > 0 )
+ {
+ /* Parse. */
+ if( xt_feed( jd->xt, buf, st ) < 0 )
+ {
+ imcb_error( ic, "XML stream error" );
+ imc_logout( ic, TRUE );
+ return FALSE;
+ }
+
+ /* Execute all handlers. */
+ if( !xt_handle( jd->xt, NULL, 1 ) )
+ {
+ /* Don't do anything, the handlers should have
+ aborted the connection already. */
+ return FALSE;
+ }
+
+ if( jd->flags & JFLAG_STREAM_RESTART )
+ {
+ jd->flags &= ~JFLAG_STREAM_RESTART;
+ jabber_start_stream( ic );
+ }
+
+ /* Garbage collection. */
+ xt_cleanup( jd->xt, NULL, 1 );
+
+ /* This is a bit hackish, unfortunately. Although xmltree
+ has nifty event handler stuff, it only calls handlers
+ when nodes are complete. Since the server should only
+ send an opening <stream:stream> tag, we have to check
+ this by hand. :-( */
+ if( !( jd->flags & JFLAG_STREAM_STARTED ) && jd->xt && jd->xt->root )
+ {
+ if( g_strcasecmp( jd->xt->root->name, "stream:stream" ) == 0 )
+ {
+ jd->flags |= JFLAG_STREAM_STARTED;
+
+ /* If there's no version attribute, assume
+ this is an old server that can't do SASL
+ authentication. */
+ if( !sasl_supported( ic ) )
+ {
+ /* If there's no version= tag, we suppose
+ this server does NOT implement: XMPP 1.0,
+ SASL and TLS. */
+ if( set_getbool( &ic->acc->set, "tls" ) )
+ {
+ imcb_error( ic, "TLS is turned on for this "
+ "account, but is not supported by this server" );
+ imc_logout( ic, FALSE );
+ return FALSE;
+ }
+ else
+ {
+ return jabber_init_iq_auth( ic );
+ }
+ }
+ }
+ else
+ {
+ imcb_error( ic, "XML stream error" );
+ imc_logout( ic, TRUE );
+ return FALSE;
+ }
+ }
+ }
+ else if( st == 0 || ( st < 0 && !ssl_sockerr_again( jd->ssl ) ) )
+ {
+ closesocket( jd->fd );
+ jd->fd = -1;
+
+ imcb_error( ic, "Error while reading from server" );
+ imc_logout( ic, TRUE );
+ return FALSE;
+ }
+
+ if( ssl_pending( jd->ssl ) )
+ /* OpenSSL empties the TCP buffers completely but may keep some
+ data in its internap buffers. select() won't see that, but
+ ssl_pending() does. */
+ return jabber_read_callback( data, fd, cond );
+ else
+ return TRUE;
+}
+
+gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition cond )
+{
+ struct im_connection *ic = data;
+
+ if( g_slist_find( jabber_connections, ic ) == NULL )
+ return FALSE;
+
+ if( source == -1 )
+ {
+ imcb_error( ic, "Could not connect to server" );
+ imc_logout( ic, TRUE );
+ return FALSE;
+ }
+
+ imcb_log( ic, "Connected to server, logging in" );
+
+ return jabber_start_stream( ic );
+}
+
+gboolean jabber_connected_ssl( gpointer data, void *source, b_input_condition cond )
+{
+ struct im_connection *ic = data;
+ struct jabber_data *jd;
+
+ if( g_slist_find( jabber_connections, ic ) == NULL )
+ return FALSE;
+
+ jd = ic->proto_data;
+
+ if( source == NULL )
+ {
+ /* The SSL connection will be cleaned up by the SSL lib
+ already, set it to NULL here to prevent a double cleanup: */
+ jd->ssl = NULL;
+
+ imcb_error( ic, "Could not connect to server" );
+ imc_logout( ic, TRUE );
+ return FALSE;
+ }
+
+ imcb_log( ic, "Connected to server, logging in" );
+
+ return jabber_start_stream( ic );
+}
+
+static xt_status jabber_end_of_stream( struct xt_node *node, gpointer data )
+{
+ imc_logout( data, TRUE );
+ return XT_ABORT;
+}
+
+static xt_status jabber_pkt_features( struct xt_node *node, gpointer data )
+{
+ struct im_connection *ic = data;
+ struct jabber_data *jd = ic->proto_data;
+ struct xt_node *c, *reply;
+ int trytls;
+
+ trytls = g_strcasecmp( set_getstr( &ic->acc->set, "tls" ), "try" ) == 0;
+ c = xt_find_node( node->children, "starttls" );
+ if( c && !jd->ssl )
+ {
+ /* If the server advertises the STARTTLS feature and if we're
+ not in a secure connection already: */
+
+ c = xt_find_node( c->children, "required" );
+
+ if( c && ( !trytls && !set_getbool( &ic->acc->set, "tls" ) ) )
+ {
+ imcb_error( ic, "Server requires TLS connections, but TLS is turned off for this account" );
+ imc_logout( ic, FALSE );
+
+ return XT_ABORT;
+ }
+
+ /* Only run this if the tls setting is set to true or try: */
+ if( ( trytls || set_getbool( &ic->acc->set, "tls" ) ) )
+ {
+ reply = xt_new_node( "starttls", NULL, NULL );
+ xt_add_attr( reply, "xmlns", XMLNS_TLS );
+ if( !jabber_write_packet( ic, reply ) )
+ {
+ xt_free_node( reply );
+ return XT_ABORT;
+ }
+ xt_free_node( reply );
+
+ return XT_HANDLED;
+ }
+ }
+ else if( !c && !jd->ssl )
+ {
+ /* If the server does not advertise the STARTTLS feature and
+ we're not in a secure connection already: (Servers have a
+ habit of not advertising <starttls/> anymore when already
+ using SSL/TLS. */
+
+ if( !trytls && set_getbool( &ic->acc->set, "tls" ) )
+ {
+ imcb_error( ic, "TLS is turned on for this account, but is not supported by this server" );
+ imc_logout( ic, FALSE );
+
+ return XT_ABORT;
+ }
+ }
+
+ /* This one used to be in jabber_handlers[], but it has to be done
+ from here to make sure the TLS session will be initialized
+ properly before we attempt SASL authentication. */
+ if( ( c = xt_find_node( node->children, "mechanisms" ) ) )
+ {
+ if( sasl_pkt_mechanisms( c, data ) == XT_ABORT )
+ return XT_ABORT;
+ }
+ /* If the server *SEEMS* to support SASL authentication but doesn't
+ support it after all, we should try to do authentication the
+ other way. jabber.com doesn't seem to do SASL while it pretends
+ to be XMPP 1.0 compliant! */
+ else if( !( jd->flags & JFLAG_AUTHENTICATED ) && sasl_supported( ic ) )
+ {
+ if( !jabber_init_iq_auth( ic ) )
+ return XT_ABORT;
+ }
+
+ if( ( c = xt_find_node( node->children, "bind" ) ) )
+ jd->flags |= JFLAG_WANT_BIND;
+
+ if( ( c = xt_find_node( node->children, "session" ) ) )
+ jd->flags |= JFLAG_WANT_SESSION;
+
+ if( jd->flags & JFLAG_AUTHENTICATED )
+ return jabber_pkt_bind_sess( ic, NULL, NULL );
+
+ return XT_HANDLED;
+}
+
+static xt_status jabber_pkt_proceed_tls( struct xt_node *node, gpointer data )
+{
+ struct im_connection *ic = data;
+ struct jabber_data *jd = ic->proto_data;
+ char *xmlns;
+
+ xmlns = xt_find_attr( node, "xmlns" );
+
+ /* Just ignore it when it doesn't seem to be TLS-related (is that at
+ all possible??). */
+ if( !xmlns || strcmp( xmlns, XMLNS_TLS ) != 0 )
+ return XT_HANDLED;
+
+ /* We don't want event handlers to touch our TLS session while it's
+ still initializing! */
+ b_event_remove( jd->r_inpa );
+ if( jd->tx_len > 0 )
+ {
+ /* Actually the write queue should be empty here, but just
+ to be sure... */
+ b_event_remove( jd->w_inpa );
+ g_free( jd->txq );
+ jd->txq = NULL;
+ jd->tx_len = 0;
+ }
+ jd->w_inpa = jd->r_inpa = 0;
+
+ imcb_log( ic, "Converting stream to TLS" );
+
+ jd->flags |= JFLAG_STARTTLS_DONE;
+ jd->ssl = ssl_starttls( jd->fd, jabber_connected_ssl, ic );
+
+ return XT_HANDLED;
+}
+
+static xt_status jabber_pkt_stream_error( struct xt_node *node, gpointer data )
+{
+ struct im_connection *ic = data;
+ int allow_reconnect = TRUE;
+ struct jabber_error *err;
+
+ err = jabber_error_parse( node, XMLNS_STREAM_ERROR );
+
+ /* Tssk... */
+ if( err->code == NULL )
+ {
+ imcb_error( ic, "Unknown stream error reported by server" );
+ imc_logout( ic, allow_reconnect );
+ jabber_error_free( err );
+ return XT_ABORT;
+ }
+
+ /* We know that this is a fatal error. If it's a "conflict" error, we
+ should turn off auto-reconnect to make sure we won't get some nasty
+ infinite loop! */
+ if( strcmp( err->code, "conflict" ) == 0 )
+ {
+ imcb_error( ic, "Account and resource used from a different location" );
+ allow_reconnect = FALSE;
+ }
+ else
+ {
+ imcb_error( ic, "Stream error: %s%s%s", err->code, err->text ? ": " : "",
+ err->text ? err->text : "" );
+ }
+
+ jabber_error_free( err );
+ imc_logout( ic, allow_reconnect );
+
+ return XT_ABORT;
+}
+
+static xt_status jabber_xmlconsole( struct xt_node *node, gpointer data )
+{
+ struct im_connection *ic = data;
+ struct jabber_data *jd = ic->proto_data;
+
+ if( jd->flags & JFLAG_XMLCONSOLE )
+ {
+ char *msg, *pkt;
+
+ pkt = xt_to_string( node );
+ msg = g_strdup_printf( "RX: %s", pkt );
+ imcb_buddy_msg( ic, JABBER_XMLCONSOLE_HANDLE, msg, 0, 0 );
+ g_free( msg );
+ g_free( pkt );
+ }
+
+ return XT_NEXT;
+}
+
+static const struct xt_handler_entry jabber_handlers[] = {
+ { NULL, "stream:stream", jabber_xmlconsole },
+ { "stream:stream", "<root>", jabber_end_of_stream },
+ { "message", "stream:stream", jabber_pkt_message },
+ { "presence", "stream:stream", jabber_pkt_presence },
+ { "iq", "stream:stream", jabber_pkt_iq },
+ { "stream:features", "stream:stream", jabber_pkt_features },
+ { "stream:error", "stream:stream", jabber_pkt_stream_error },
+ { "proceed", "stream:stream", jabber_pkt_proceed_tls },
+ { "challenge", "stream:stream", sasl_pkt_challenge },
+ { "success", "stream:stream", sasl_pkt_result },
+ { "failure", "stream:stream", sasl_pkt_result },
+ { NULL, NULL, NULL }
+};
+
+gboolean jabber_start_stream( struct im_connection *ic )
+{
+ struct jabber_data *jd = ic->proto_data;
+ int st;
+ char *greet;
+
+ /* We'll start our stream now, so prepare everything to receive one
+ from the server too. */
+ xt_free( jd->xt ); /* In case we're RE-starting. */
+ jd->xt = xt_new( jabber_handlers, ic );
+
+ if( jd->r_inpa <= 0 )
+ jd->r_inpa = b_input_add( jd->fd, B_EV_IO_READ, jabber_read_callback, ic );
+
+ greet = g_strdup_printf( "%s<stream:stream to=\"%s\" xmlns=\"jabber:client\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" version=\"1.0\">",
+ ( jd->flags & JFLAG_STARTTLS_DONE ) ? "" : "<?xml version='1.0' ?>",
+ jd->server );
+
+ st = jabber_write( ic, greet, strlen( greet ) );
+
+ g_free( greet );
+
+ return st;
+}
+
+void jabber_end_stream( struct im_connection *ic )
+{
+ struct jabber_data *jd = ic->proto_data;
+
+ /* Let's only do this if the queue is currently empty, otherwise it'd
+ take too long anyway. */
+ if( jd->tx_len == 0 )
+ {
+ char eos[] = "</stream:stream>";
+ struct xt_node *node;
+ int st = 1;
+
+ if( ic->flags & OPT_LOGGED_IN )
+ {
+ node = jabber_make_packet( "presence", "unavailable", NULL, NULL );
+ st = jabber_write_packet( ic, node );
+ xt_free_node( node );
+ }
+
+ if( st )
+ jabber_write( ic, eos, strlen( eos ) );
+ }
+}
diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c
new file mode 100644
index 00000000..0c5671d0
--- /dev/null
+++ b/protocols/jabber/iq.c
@@ -0,0 +1,870 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Jabber module - IQ packets *
+* *
+* Copyright 2006 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 "jabber.h"
+#include "sha1.h"
+
+static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
+static xt_status jabber_iq_display_vcard( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
+
+xt_status jabber_pkt_iq( struct xt_node *node, gpointer data )
+{
+ struct im_connection *ic = data;
+ struct xt_node *c, *reply = NULL;
+ char *type, *s;
+ int st, pack = 1;
+
+ type = xt_find_attr( node, "type" );
+
+ if( !type )
+ {
+ imcb_error( ic, "Received IQ packet without type." );
+ imc_logout( ic, TRUE );
+ return XT_ABORT;
+ }
+
+ if( strcmp( type, "result" ) == 0 || strcmp( type, "error" ) == 0 )
+ {
+ return jabber_cache_handle_packet( ic, node );
+ }
+ else if( strcmp( type, "get" ) == 0 )
+ {
+ if( !( ( c = xt_find_node( node->children, "query" ) ) ||
+ ( c = xt_find_node( node->children, "ping" ) ) ||
+ ( c = xt_find_node( node->children, "time" ) ) ) ||
+ !( s = xt_find_attr( c, "xmlns" ) ) )
+ {
+ /* Sigh. Who decided to suddenly invent new elements
+ instead of just sticking with <query/>? */
+ return XT_HANDLED;
+ }
+
+ reply = xt_new_node( "query", NULL, NULL );
+ xt_add_attr( reply, "xmlns", s );
+
+ /* Of course this is a very essential query to support. ;-) */
+ if( strcmp( s, XMLNS_VERSION ) == 0 )
+ {
+ xt_add_child( reply, xt_new_node( "name", set_getstr( &ic->acc->set, "user_agent" ), NULL ) );
+ xt_add_child( reply, xt_new_node( "version", BITLBEE_VERSION, NULL ) );
+ xt_add_child( reply, xt_new_node( "os", ARCH, NULL ) );
+ }
+ else if( strcmp( s, XMLNS_TIME_OLD ) == 0 )
+ {
+ time_t time_ep;
+ char buf[1024];
+
+ buf[sizeof(buf)-1] = 0;
+ time_ep = time( NULL );
+
+ strftime( buf, sizeof( buf ) - 1, "%Y%m%dT%H:%M:%S", gmtime( &time_ep ) );
+ xt_add_child( reply, xt_new_node( "utc", buf, NULL ) );
+
+ strftime( buf, sizeof( buf ) - 1, "%Z", localtime( &time_ep ) );
+ xt_add_child( reply, xt_new_node( "tz", buf, NULL ) );
+ }
+ else if( strcmp( s, XMLNS_TIME ) == 0 )
+ {
+ time_t time_ep;
+ char buf[1024];
+
+ buf[sizeof(buf)-1] = 0;
+ time_ep = time( NULL );
+
+ xt_free_node( reply );
+ reply = xt_new_node( "time", NULL, NULL );
+ xt_add_attr( reply, "xmlns", XMLNS_TIME );
+
+ strftime( buf, sizeof( buf ) - 1, "%Y%m%dT%H:%M:%SZ", gmtime( &time_ep ) );
+ xt_add_child( reply, xt_new_node( "utc", buf, NULL ) );
+
+ strftime( buf, sizeof( buf ) - 1, "%z", localtime( &time_ep ) );
+ if( strlen( buf ) >= 5 )
+ {
+ buf[6] = '\0';
+ buf[5] = buf[4];
+ buf[4] = buf[3];
+ buf[3] = ':';
+ }
+ xt_add_child( reply, xt_new_node( "tzo", buf, NULL ) );
+ }
+ else if( strcmp( s, XMLNS_PING ) == 0 )
+ {
+ xt_free_node( reply );
+ reply = jabber_make_packet( "iq", "result", xt_find_attr( node, "from" ), NULL );
+ if( ( s = xt_find_attr( node, "id" ) ) )
+ xt_add_attr( reply, "id", s );
+ pack = 0;
+ }
+ else if( strcmp( s, XMLNS_DISCO_INFO ) == 0 )
+ {
+ const char *features[] = { XMLNS_DISCO_INFO,
+ XMLNS_VERSION,
+ XMLNS_TIME_OLD,
+ XMLNS_TIME,
+ XMLNS_CHATSTATES,
+ XMLNS_MUC,
+ XMLNS_PING,
+ XMLNS_SI,
+ XMLNS_BYTESTREAMS,
+ XMLNS_FILETRANSFER,
+ NULL };
+ const char **f;
+
+ c = xt_new_node( "identity", NULL, NULL );
+ xt_add_attr( c, "category", "client" );
+ xt_add_attr( c, "type", "pc" );
+ xt_add_attr( c, "name", set_getstr( &ic->acc->set, "user_agent" ) );
+ xt_add_child( reply, c );
+
+ for( f = features; *f; f ++ )
+ {
+ c = xt_new_node( "feature", NULL, NULL );
+ xt_add_attr( c, "var", *f );
+ xt_add_child( reply, c );
+ }
+ }
+ else
+ {
+ xt_free_node( reply );
+ reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL );
+ pack = 0;
+ }
+ }
+ else if( strcmp( type, "set" ) == 0 )
+ {
+ if( ( c = xt_find_node( node->children, "si" ) ) &&
+ ( s = xt_find_attr( c, "xmlns" ) ) &&
+ ( strcmp( s, XMLNS_SI ) == 0 ) )
+ {
+ return jabber_si_handle_request( ic, node, c );
+ }
+ else if( !( c = xt_find_node( node->children, "query" ) ) ||
+ !( s = xt_find_attr( c, "xmlns" ) ) )
+ {
+ return XT_HANDLED;
+ }
+ else if( strcmp( s, XMLNS_ROSTER ) == 0 )
+ {
+ /* This is a roster push. XMPP servers send this when someone
+ was added to (or removed from) the buddy list. AFAIK they're
+ sent even if we added this buddy in our own session. */
+ int bare_len = strlen( ic->acc->user );
+
+ if( ( s = xt_find_attr( node, "from" ) ) == NULL ||
+ ( strncmp( s, ic->acc->user, bare_len ) == 0 &&
+ ( s[bare_len] == 0 || s[bare_len] == '/' ) ) )
+ {
+ jabber_parse_roster( ic, node, NULL );
+
+ /* Should we generate a reply here? Don't think it's
+ very important... */
+ }
+ else
+ {
+ imcb_log( ic, "Warning: %s tried to fake a roster push!", s ? s : "(unknown)" );
+
+ xt_free_node( reply );
+ reply = jabber_make_error_packet( node, "not-allowed", "cancel", NULL );
+ pack = 0;
+ }
+ }
+ else if( strcmp( s, XMLNS_BYTESTREAMS ) == 0 )
+ {
+ /* Bytestream Request (stage 2 of file transfer) */
+ return jabber_bs_recv_request( ic, node, c );
+ }
+ else
+ {
+ xt_free_node( reply );
+ reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL );
+ pack = 0;
+ }
+ }
+
+ /* If we recognized the xmlns and managed to generate a reply,
+ finish and send it. */
+ if( reply )
+ {
+ /* Normally we still have to pack it into an iq-result
+ packet, but for errors, for example, we don't. */
+ if( pack )
+ {
+ reply = jabber_make_packet( "iq", "result", xt_find_attr( node, "from" ), reply );
+ if( ( s = xt_find_attr( node, "id" ) ) )
+ xt_add_attr( reply, "id", s );
+ }
+
+ st = jabber_write_packet( ic, reply );
+ xt_free_node( reply );
+ if( !st )
+ return XT_ABORT;
+ }
+
+ return XT_HANDLED;
+}
+
+static xt_status jabber_do_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
+static xt_status jabber_finish_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
+
+int jabber_init_iq_auth( struct im_connection *ic )
+{
+ struct jabber_data *jd = ic->proto_data;
+ struct xt_node *node;
+ int st;
+
+ node = xt_new_node( "query", NULL, xt_new_node( "username", jd->username, NULL ) );
+ xt_add_attr( node, "xmlns", XMLNS_AUTH );
+ node = jabber_make_packet( "iq", "get", NULL, node );
+
+ jabber_cache_add( ic, node, jabber_do_iq_auth );
+ st = jabber_write_packet( ic, node );
+
+ return st;
+}
+
+static xt_status jabber_do_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+ struct jabber_data *jd = ic->proto_data;
+ struct xt_node *reply, *query;
+ xt_status st;
+ char *s;
+
+ if( !( query = xt_find_node( node->children, "query" ) ) )
+ {
+ imcb_log( ic, "Warning: Received incomplete IQ packet while authenticating" );
+ imc_logout( ic, FALSE );
+ return XT_HANDLED;
+ }
+
+ /* Time to authenticate ourselves! */
+ reply = xt_new_node( "query", NULL, NULL );
+ xt_add_attr( reply, "xmlns", XMLNS_AUTH );
+ xt_add_child( reply, xt_new_node( "username", jd->username, NULL ) );
+ xt_add_child( reply, xt_new_node( "resource", set_getstr( &ic->acc->set, "resource" ), NULL ) );
+
+ if( xt_find_node( query->children, "digest" ) && ( s = xt_find_attr( jd->xt->root, "id" ) ) )
+ {
+ /* We can do digest authentication, it seems, and of
+ course we prefer that. */
+ sha1_state_t sha;
+ char hash_hex[41];
+ unsigned char hash[20];
+ int i;
+
+ sha1_init( &sha );
+ sha1_append( &sha, (unsigned char*) s, strlen( s ) );
+ sha1_append( &sha, (unsigned char*) ic->acc->pass, strlen( ic->acc->pass ) );
+ sha1_finish( &sha, hash );
+
+ for( i = 0; i < 20; i ++ )
+ sprintf( hash_hex + i * 2, "%02x", hash[i] );
+
+ xt_add_child( reply, xt_new_node( "digest", hash_hex, NULL ) );
+ }
+ else if( xt_find_node( query->children, "password" ) )
+ {
+ /* We'll have to stick with plaintext. Let's hope we're using SSL/TLS... */
+ xt_add_child( reply, xt_new_node( "password", ic->acc->pass, NULL ) );
+ }
+ else
+ {
+ xt_free_node( reply );
+
+ imcb_error( ic, "Can't find suitable authentication method" );
+ imc_logout( ic, FALSE );
+ return XT_ABORT;
+ }
+
+ reply = jabber_make_packet( "iq", "set", NULL, reply );
+ jabber_cache_add( ic, reply, jabber_finish_iq_auth );
+ st = jabber_write_packet( ic, reply );
+
+ return st ? XT_HANDLED : XT_ABORT;
+}
+
+static xt_status jabber_finish_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+ struct jabber_data *jd = ic->proto_data;
+ char *type;
+
+ if( !( type = xt_find_attr( node, "type" ) ) )
+ {
+ imcb_log( ic, "Warning: Received incomplete IQ packet while authenticating" );
+ imc_logout( ic, FALSE );
+ return XT_HANDLED;
+ }
+
+ if( strcmp( type, "error" ) == 0 )
+ {
+ imcb_error( ic, "Authentication failure" );
+ imc_logout( ic, FALSE );
+ return XT_ABORT;
+ }
+ else if( strcmp( type, "result" ) == 0 )
+ {
+ /* This happens when we just successfully authenticated the
+ old (non-SASL) way. */
+ jd->flags |= JFLAG_AUTHENTICATED;
+ if( !jabber_get_roster( ic ) )
+ return XT_ABORT;
+ }
+
+ return XT_HANDLED;
+}
+
+xt_status jabber_pkt_bind_sess( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+ struct jabber_data *jd = ic->proto_data;
+ struct xt_node *c, *reply = NULL;
+ char *s;
+
+ if( node && ( c = xt_find_node( node->children, "bind" ) ) )
+ {
+ c = xt_find_node( c->children, "jid" );
+ if( c && c->text_len && ( s = strchr( c->text, '/' ) ) &&
+ strcmp( s + 1, set_getstr( &ic->acc->set, "resource" ) ) != 0 )
+ imcb_log( ic, "Server changed session resource string to `%s'", s + 1 );
+ }
+
+ if( jd->flags & JFLAG_WANT_BIND )
+ {
+ reply = xt_new_node( "bind", NULL, xt_new_node( "resource", set_getstr( &ic->acc->set, "resource" ), NULL ) );
+ xt_add_attr( reply, "xmlns", XMLNS_BIND );
+ jd->flags &= ~JFLAG_WANT_BIND;
+ }
+ else if( jd->flags & JFLAG_WANT_SESSION )
+ {
+ reply = xt_new_node( "session", NULL, NULL );
+ xt_add_attr( reply, "xmlns", XMLNS_SESSION );
+ jd->flags &= ~JFLAG_WANT_SESSION;
+ }
+
+ if( reply != NULL )
+ {
+ reply = jabber_make_packet( "iq", "set", NULL, reply );
+ jabber_cache_add( ic, reply, jabber_pkt_bind_sess );
+
+ if( !jabber_write_packet( ic, reply ) )
+ return XT_ABORT;
+ }
+ else if( ( jd->flags & ( JFLAG_WANT_BIND | JFLAG_WANT_SESSION ) ) == 0 )
+ {
+ if( !jabber_get_roster( ic ) )
+ return XT_ABORT;
+ }
+
+ return XT_HANDLED;
+}
+
+int jabber_get_roster( struct im_connection *ic )
+{
+ struct xt_node *node;
+ int st;
+
+ imcb_log( ic, "Authenticated, requesting buddy list" );
+
+ node = xt_new_node( "query", NULL, NULL );
+ xt_add_attr( node, "xmlns", XMLNS_ROSTER );
+ node = jabber_make_packet( "iq", "get", NULL, node );
+
+ jabber_cache_add( ic, node, jabber_parse_roster );
+ st = jabber_write_packet( ic, node );
+
+ return st;
+}
+
+static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+ struct xt_node *query, *c;
+ int initial = ( orig != NULL );
+
+ if( !( query = xt_find_node( node->children, "query" ) ) )
+ {
+ imcb_log( ic, "Warning: Received NULL roster packet" );
+ return XT_HANDLED;
+ }
+
+ c = query->children;
+ while( ( c = xt_find_node( c, "item" ) ) )
+ {
+ struct xt_node *group = xt_find_node( c->children, "group" );
+ char *jid = xt_find_attr( c, "jid" );
+ char *name = xt_find_attr( c, "name" );
+ char *sub = xt_find_attr( c, "subscription" );
+
+ if( jid && sub )
+ {
+ if( ( strcmp( sub, "both" ) == 0 || strcmp( sub, "to" ) == 0 ) )
+ {
+ imcb_add_buddy( ic, jid, ( group && group->text_len ) ?
+ group->text : NULL );
+
+ if( name )
+ imcb_rename_buddy( ic, jid, name );
+ }
+ else if( strcmp( sub, "remove" ) == 0 )
+ {
+ jabber_buddy_remove_bare( ic, jid );
+ imcb_remove_buddy( ic, jid, NULL );
+ }
+ }
+
+ c = c->next;
+ }
+
+ if( initial )
+ imcb_connected( ic );
+
+ return XT_HANDLED;
+}
+
+int jabber_get_vcard( struct im_connection *ic, char *bare_jid )
+{
+ struct xt_node *node;
+
+ if( strchr( bare_jid, '/' ) )
+ return 1; /* This was an error, but return 0 should only be done if the connection died... */
+
+ node = xt_new_node( "vCard", NULL, NULL );
+ xt_add_attr( node, "xmlns", XMLNS_VCARD );
+ node = jabber_make_packet( "iq", "get", bare_jid, node );
+
+ jabber_cache_add( ic, node, jabber_iq_display_vcard );
+ return jabber_write_packet( ic, node );
+}
+
+static xt_status jabber_iq_display_vcard( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+ struct xt_node *vc, *c, *sc; /* subchild, ic is already in use ;-) */
+ GString *reply;
+ char *s;
+
+ if( ( s = xt_find_attr( node, "type" ) ) == NULL ||
+ strcmp( s, "result" ) != 0 ||
+ ( vc = xt_find_node( node->children, "vCard" ) ) == NULL )
+ {
+ s = xt_find_attr( orig, "to" ); /* If this returns NULL something's wrong.. */
+ imcb_log( ic, "Could not retrieve vCard of %s", s ? s : "(NULL)" );
+ return XT_HANDLED;
+ }
+
+ s = xt_find_attr( orig, "to" );
+ reply = g_string_new( "vCard information for " );
+ reply = g_string_append( reply, s ? s : "(NULL)" );
+ reply = g_string_append( reply, ":\n" );
+
+ /* I hate this format, I really do... */
+
+ if( ( c = xt_find_node( vc->children, "FN" ) ) && c->text_len )
+ g_string_append_printf( reply, "Name: %s\n", c->text );
+
+ if( ( c = xt_find_node( vc->children, "N" ) ) && c->children )
+ {
+ reply = g_string_append( reply, "Full name:" );
+
+ if( ( sc = xt_find_node( c->children, "PREFIX" ) ) && sc->text_len )
+ g_string_append_printf( reply, " %s", sc->text );
+ if( ( sc = xt_find_node( c->children, "GIVEN" ) ) && sc->text_len )
+ g_string_append_printf( reply, " %s", sc->text );
+ if( ( sc = xt_find_node( c->children, "MIDDLE" ) ) && sc->text_len )
+ g_string_append_printf( reply, " %s", sc->text );
+ if( ( sc = xt_find_node( c->children, "FAMILY" ) ) && sc->text_len )
+ g_string_append_printf( reply, " %s", sc->text );
+ if( ( sc = xt_find_node( c->children, "SUFFIX" ) ) && sc->text_len )
+ g_string_append_printf( reply, " %s", sc->text );
+
+ reply = g_string_append_c( reply, '\n' );
+ }
+
+ if( ( c = xt_find_node( vc->children, "NICKNAME" ) ) && c->text_len )
+ g_string_append_printf( reply, "Nickname: %s\n", c->text );
+
+ if( ( c = xt_find_node( vc->children, "BDAY" ) ) && c->text_len )
+ g_string_append_printf( reply, "Date of birth: %s\n", c->text );
+
+ /* Slightly alternative use of for... ;-) */
+ for( c = vc->children; ( c = xt_find_node( c, "EMAIL" ) ); c = c->next )
+ {
+ if( ( sc = xt_find_node( c->children, "USERID" ) ) == NULL || sc->text_len == 0 )
+ continue;
+
+ if( xt_find_node( c->children, "HOME" ) )
+ s = "Home";
+ else if( xt_find_node( c->children, "WORK" ) )
+ s = "Work";
+ else
+ s = "Misc.";
+
+ g_string_append_printf( reply, "%s e-mail address: %s\n", s, sc->text );
+ }
+
+ if( ( c = xt_find_node( vc->children, "URL" ) ) && c->text_len )
+ g_string_append_printf( reply, "Homepage: %s\n", c->text );
+
+ /* Slightly alternative use of for... ;-) */
+ for( c = vc->children; ( c = xt_find_node( c, "ADR" ) ); c = c->next )
+ {
+ if( xt_find_node( c->children, "HOME" ) )
+ s = "Home";
+ else if( xt_find_node( c->children, "WORK" ) )
+ s = "Work";
+ else
+ s = "Misc.";
+
+ g_string_append_printf( reply, "%s address: ", s );
+
+ if( ( sc = xt_find_node( c->children, "STREET" ) ) && sc->text_len )
+ g_string_append_printf( reply, "%s ", sc->text );
+ if( ( sc = xt_find_node( c->children, "EXTADR" ) ) && sc->text_len )
+ g_string_append_printf( reply, "%s, ", sc->text );
+ if( ( sc = xt_find_node( c->children, "PCODE" ) ) && sc->text_len )
+ g_string_append_printf( reply, "%s, ", sc->text );
+ if( ( sc = xt_find_node( c->children, "LOCALITY" ) ) && sc->text_len )
+ g_string_append_printf( reply, "%s, ", sc->text );
+ if( ( sc = xt_find_node( c->children, "REGION" ) ) && sc->text_len )
+ g_string_append_printf( reply, "%s, ", sc->text );
+ if( ( sc = xt_find_node( c->children, "CTRY" ) ) && sc->text_len )
+ g_string_append_printf( reply, "%s", sc->text );
+
+ if( reply->str[reply->len-2] == ',' )
+ reply = g_string_truncate( reply, reply->len-2 );
+
+ reply = g_string_append_c( reply, '\n' );
+ }
+
+ for( c = vc->children; ( c = xt_find_node( c, "TEL" ) ); c = c->next )
+ {
+ if( ( sc = xt_find_node( c->children, "NUMBER" ) ) == NULL || sc->text_len == 0 )
+ continue;
+
+ if( xt_find_node( c->children, "HOME" ) )
+ s = "Home";
+ else if( xt_find_node( c->children, "WORK" ) )
+ s = "Work";
+ else
+ s = "Misc.";
+
+ g_string_append_printf( reply, "%s phone number: %s\n", s, sc->text );
+ }
+
+ if( ( c = xt_find_node( vc->children, "DESC" ) ) && c->text_len )
+ g_string_append_printf( reply, "Other information:\n%s", c->text );
+
+ /* *sigh* */
+
+ imcb_log( ic, "%s", reply->str );
+ g_string_free( reply, TRUE );
+
+ return XT_HANDLED;
+}
+
+static xt_status jabber_add_to_roster_callback( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
+
+int jabber_add_to_roster( struct im_connection *ic, const char *handle, const char *name, const char *group )
+{
+ struct xt_node *node;
+ int st;
+
+ /* Build the item entry */
+ node = xt_new_node( "item", NULL, NULL );
+ xt_add_attr( node, "jid", handle );
+ if( name )
+ xt_add_attr( node, "name", name );
+ if( group )
+ xt_add_child( node, xt_new_node( "group", group, NULL ) );
+
+ /* And pack it into a roster-add packet */
+ node = xt_new_node( "query", NULL, node );
+ xt_add_attr( node, "xmlns", XMLNS_ROSTER );
+ node = jabber_make_packet( "iq", "set", NULL, node );
+ jabber_cache_add( ic, node, jabber_add_to_roster_callback );
+
+ st = jabber_write_packet( ic, node );
+
+ return st;
+}
+
+static xt_status jabber_add_to_roster_callback( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+ char *s, *jid = NULL;
+ struct xt_node *c;
+
+ if( ( c = xt_find_node( orig->children, "query" ) ) &&
+ ( c = xt_find_node( c->children, "item" ) ) &&
+ ( jid = xt_find_attr( c, "jid" ) ) &&
+ ( s = xt_find_attr( node, "type" ) ) &&
+ strcmp( s, "result" ) == 0 )
+ {
+ if( bee_user_by_handle( ic->bee, ic, jid ) == NULL )
+ imcb_add_buddy( ic, jid, NULL );
+ }
+ else
+ {
+ imcb_log( ic, "Error while adding `%s' to your contact list.",
+ jid ? jid : "(unknown handle)" );
+ }
+
+ return XT_HANDLED;
+}
+
+int jabber_remove_from_roster( struct im_connection *ic, char *handle )
+{
+ struct xt_node *node;
+ int st;
+
+ /* Build the item entry */
+ node = xt_new_node( "item", NULL, NULL );
+ xt_add_attr( node, "jid", handle );
+ xt_add_attr( node, "subscription", "remove" );
+
+ /* And pack it into a roster-add packet */
+ node = xt_new_node( "query", NULL, node );
+ xt_add_attr( node, "xmlns", XMLNS_ROSTER );
+ node = jabber_make_packet( "iq", "set", NULL, node );
+
+ st = jabber_write_packet( ic, node );
+
+ xt_free_node( node );
+ return st;
+}
+
+xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
+
+xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid )
+{
+ struct xt_node *node, *query;
+ struct jabber_buddy *bud;
+
+ if( ( bud = jabber_buddy_by_jid( ic, bare_jid , 0 ) ) == NULL )
+ {
+ /* Who cares about the unknown... */
+ imcb_log( ic, "Couldn't find buddy: %s", bare_jid);
+ return XT_HANDLED;
+ }
+
+ if( bud->features ) /* been here already */
+ return XT_HANDLED;
+
+ node = xt_new_node( "query", NULL, NULL );
+ xt_add_attr( node, "xmlns", XMLNS_DISCO_INFO );
+
+ if( !( query = jabber_make_packet( "iq", "get", bare_jid, node ) ) )
+ {
+ imcb_log( ic, "WARNING: Couldn't generate feature query" );
+ xt_free_node( node );
+ return XT_HANDLED;
+ }
+
+ jabber_cache_add( ic, query, jabber_iq_parse_features );
+
+ return jabber_write_packet( ic, query ) ? XT_HANDLED : XT_ABORT;
+}
+
+xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+ struct xt_node *c;
+ struct jabber_buddy *bud;
+ char *feature, *xmlns, *from;
+
+ if( !( from = xt_find_attr( node, "from" ) ) ||
+ !( c = xt_find_node( node->children, "query" ) ) ||
+ !( xmlns = xt_find_attr( c, "xmlns" ) ) ||
+ !( strcmp( xmlns, XMLNS_DISCO_INFO ) == 0 ) )
+ {
+ imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" );
+ return XT_HANDLED;
+ }
+ if( ( bud = jabber_buddy_by_jid( ic, from, 0 ) ) == NULL )
+ {
+ /* Who cares about the unknown... */
+ imcb_log( ic, "Couldn't find buddy: %s", from );
+ return XT_HANDLED;
+ }
+
+ c = c->children;
+ while( ( c = xt_find_node( c, "feature" ) ) )
+ {
+ feature = xt_find_attr( c, "var" );
+ if( feature )
+ bud->features = g_slist_append( bud->features, g_strdup( feature ) );
+ c = c->next;
+ }
+
+ return XT_HANDLED;
+}
+
+xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
+
+xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns )
+{
+ struct xt_node *node, *query;
+ struct jabber_data *jd = ic->proto_data;
+
+ node = xt_new_node( "query", NULL, NULL );
+ xt_add_attr( node, "xmlns", xmlns );
+
+ if( !( query = jabber_make_packet( "iq", "get", jid, node ) ) )
+ {
+ imcb_log( ic, "WARNING: Couldn't generate server query" );
+ xt_free_node( node );
+ }
+
+ jd->have_streamhosts--;
+ jabber_cache_add( ic, query, jabber_iq_parse_server_features );
+
+ return jabber_write_packet( ic, query ) ? XT_HANDLED : XT_ABORT;
+}
+
+/*
+ * Query the server for "items", query each "item" for identities, query each "item" that's a proxy for it's bytestream info
+ */
+xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+ struct xt_node *c;
+ struct jabber_data *jd = ic->proto_data;
+ char *xmlns, *from;
+
+ if( !( c = xt_find_node( node->children, "query" ) ) ||
+ !( from = xt_find_attr( node, "from" ) ) ||
+ !( xmlns = xt_find_attr( c, "xmlns" ) ) )
+ {
+ imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" );
+ return XT_HANDLED;
+ }
+
+ jd->have_streamhosts++;
+
+ if( strcmp( xmlns, XMLNS_DISCO_ITEMS ) == 0 )
+ {
+ char *itemjid;
+
+ /* answer from server */
+
+ c = c->children;
+ while( ( c = xt_find_node( c, "item" ) ) )
+ {
+ itemjid = xt_find_attr( c, "jid" );
+
+ if( itemjid )
+ jabber_iq_query_server( ic, itemjid, XMLNS_DISCO_INFO );
+
+ c = c->next;
+ }
+ }
+ else if( strcmp( xmlns, XMLNS_DISCO_INFO ) == 0 )
+ {
+ char *category, *type;
+
+ /* answer from potential proxy */
+
+ c = c->children;
+ while( ( c = xt_find_node( c, "identity" ) ) )
+ {
+ category = xt_find_attr( c, "category" );
+ type = xt_find_attr( c, "type" );
+
+ if( type && ( strcmp( type, "bytestreams" ) == 0 ) &&
+ category && ( strcmp( category, "proxy" ) == 0 ) )
+ jabber_iq_query_server( ic, from, XMLNS_BYTESTREAMS );
+
+ c = c->next;
+ }
+ }
+ else if( strcmp( xmlns, XMLNS_BYTESTREAMS ) == 0 )
+ {
+ char *host, *jid, *port_s;
+ int port;
+
+ /* answer from proxy */
+
+ if( ( c = xt_find_node( c->children, "streamhost" ) ) &&
+ ( host = xt_find_attr( c, "host" ) ) &&
+ ( port_s = xt_find_attr( c, "port" ) ) &&
+ ( sscanf( port_s, "%d", &port ) == 1 ) &&
+ ( jid = xt_find_attr( c, "jid" ) ) )
+ {
+ jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 );
+
+ sh->jid = g_strdup( jid );
+ sh->host = g_strdup( host );
+ g_snprintf( sh->port, sizeof( sh->port ), "%u", port );
+
+ imcb_log( ic, "Proxy found: jid %s host %s port %u", jid, host, port );
+ jd->streamhosts = g_slist_append( jd->streamhosts, sh );
+ }
+ }
+
+ if( jd->have_streamhosts == 0 )
+ jd->have_streamhosts++;
+
+ return XT_HANDLED;
+}
+
+static xt_status jabber_iq_version_response( struct im_connection *ic,
+ struct xt_node *node, struct xt_node *orig );
+
+void jabber_iq_version_send( struct im_connection *ic, struct jabber_buddy *bud, void *data )
+{
+ struct xt_node *node, *query;
+
+ node = xt_new_node( "query", NULL, NULL );
+ xt_add_attr( node, "xmlns", XMLNS_VERSION );
+ query = jabber_make_packet( "iq", "get", bud->full_jid, node );
+ jabber_cache_add( ic, query, jabber_iq_version_response );
+
+ jabber_write_packet( ic, query );
+}
+
+static xt_status jabber_iq_version_response( struct im_connection *ic,
+ struct xt_node *node, struct xt_node *orig )
+{
+ struct xt_node *query;
+ GString *rets;
+ char *s;
+ char *ret[2] = {};
+ bee_user_t *bu;
+ struct jabber_buddy *bud = NULL;
+
+ if( ( s = xt_find_attr( node, "from" ) ) &&
+ ( bud = jabber_buddy_by_jid( ic, s, 0 ) ) &&
+ ( query = xt_find_node( node->children, "query" ) ) &&
+ ( bu = bee_user_by_handle( ic->bee, ic, bud->bare_jid ) ) )
+ {
+ rets = g_string_new( "Resource " );
+ g_string_append( rets, bud->resource );
+ }
+ else
+ return XT_HANDLED;
+
+ for( query = query->children; query; query = query->next )
+ if( query->text_len > 0 )
+ g_string_append_printf( rets, " %s: %s,", query->name, query->text );
+
+ g_string_truncate( rets, rets->len - 1 );
+ ret[0] = rets->str;
+ imcb_buddy_action_response( bu, "VERSION", ret, NULL );
+ g_string_free( rets, TRUE );
+
+ return XT_HANDLED;
+}
diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c
new file mode 100644
index 00000000..802158c1
--- /dev/null
+++ b/protocols/jabber/jabber.c
@@ -0,0 +1,628 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Jabber module - Main file *
+* *
+* Copyright 2006 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 <glib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdio.h>
+
+#include "ssl_client.h"
+#include "xmltree.h"
+#include "bitlbee.h"
+#include "jabber.h"
+#include "md5.h"
+
+GSList *jabber_connections;
+
+/* First enty is the default */
+static const int jabber_port_list[] = {
+ 5222,
+ 5223,
+ 5220,
+ 5221,
+ 5224,
+ 5225,
+ 5226,
+ 5227,
+ 5228,
+ 5229,
+ 80,
+ 443,
+ 0
+};
+
+static void jabber_init( account_t *acc )
+{
+ set_t *s;
+ char str[16];
+
+ s = set_add( &acc->set, "activity_timeout", "600", set_eval_int, acc );
+
+ g_snprintf( str, sizeof( str ), "%d", jabber_port_list[0] );
+ s = set_add( &acc->set, "port", str, set_eval_int, acc );
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add( &acc->set, "priority", "0", set_eval_priority, acc );
+
+ s = set_add( &acc->set, "proxy", "<local>;<auto>", NULL, acc );
+
+ s = set_add( &acc->set, "resource", "BitlBee", NULL, acc );
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add( &acc->set, "resource_select", "activity", NULL, acc );
+
+ s = set_add( &acc->set, "server", NULL, set_eval_account, acc );
+ s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY | SET_NULL_OK;
+
+ s = set_add( &acc->set, "ssl", "false", set_eval_bool, acc );
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add( &acc->set, "tls", "try", set_eval_tls, acc );
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add( &acc->set, "user_agent", "BitlBee", NULL, acc );
+
+ s = set_add( &acc->set, "xmlconsole", "false", set_eval_bool, acc );
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE;
+}
+
+static void jabber_generate_id_hash( struct jabber_data *jd );
+
+static void jabber_login( account_t *acc )
+{
+ struct im_connection *ic = imcb_new( acc );
+ struct jabber_data *jd = g_new0( struct jabber_data, 1 );
+ struct ns_srv_reply **srvl = NULL, *srv = NULL;
+ char *connect_to, *s;
+ int i;
+
+ /* For now this is needed in the _connected() handlers if using
+ GLib event handling, to make sure we're not handling events
+ on dead connections. */
+ jabber_connections = g_slist_prepend( jabber_connections, ic );
+
+ jd->ic = ic;
+ ic->proto_data = jd;
+
+ jd->username = g_strdup( acc->user );
+ jd->server = strchr( jd->username, '@' );
+
+ jd->fd = jd->r_inpa = jd->w_inpa = -1;
+
+ if( jd->server == NULL )
+ {
+ imcb_error( ic, "Incomplete account name (format it like <username@jabberserver.name>)" );
+ imc_logout( ic, FALSE );
+ return;
+ }
+
+ /* So don't think of free()ing jd->server.. :-) */
+ *jd->server = 0;
+ jd->server ++;
+
+ if( ( s = strchr( jd->server, '/' ) ) )
+ {
+ *s = 0;
+ set_setstr( &acc->set, "resource", s + 1 );
+
+ /* Also remove the /resource from the original variable so we
+ won't have to do this again every time. */
+ s = strchr( acc->user, '/' );
+ *s = 0;
+ }
+
+ /* This code isn't really pretty. Backwards compatibility never is... */
+ s = acc->server;
+ while( s )
+ {
+ static int had_port = 0;
+
+ if( strncmp( s, "ssl", 3 ) == 0 )
+ {
+ set_setstr( &acc->set, "ssl", "true" );
+
+ /* Flush this part so that (if this was the first
+ part of the server string) acc->server gets
+ flushed. We don't want to have to do this another
+ time. :-) */
+ *s = 0;
+ s ++;
+
+ /* Only set this if the user didn't specify a custom
+ port number already... */
+ if( !had_port )
+ set_setint( &acc->set, "port", 5223 );
+ }
+ else if( isdigit( *s ) )
+ {
+ int i;
+
+ /* The first character is a digit. It could be an
+ IP address though. Only accept this as a port#
+ if there are only digits. */
+ for( i = 0; isdigit( s[i] ); i ++ );
+
+ /* If the first non-digit character is a colon or
+ the end of the string, save the port number
+ where it should be. */
+ if( s[i] == ':' || s[i] == 0 )
+ {
+ sscanf( s, "%d", &i );
+ set_setint( &acc->set, "port", i );
+
+ /* See above. */
+ *s = 0;
+ s ++;
+ }
+
+ had_port = 1;
+ }
+
+ s = strchr( s, ':' );
+ if( s )
+ {
+ *s = 0;
+ s ++;
+ }
+ }
+
+ jd->node_cache = g_hash_table_new_full( g_str_hash, g_str_equal, NULL, jabber_cache_entry_free );
+ jd->buddies = g_hash_table_new( g_str_hash, g_str_equal );
+
+ /* Figure out the hostname to connect to. */
+ if( acc->server && *acc->server )
+ connect_to = acc->server;
+ else if( ( srvl = srv_lookup( "xmpp-client", "tcp", jd->server ) ) ||
+ ( srvl = srv_lookup( "jabber-client", "tcp", jd->server ) ) )
+ {
+ /* Find the lowest-priority one. These usually come
+ back in random/shuffled order. Not looking at
+ weights etc for now. */
+ srv = *srvl;
+ for( i = 1; srvl[i]; i ++ )
+ if( srvl[i]->prio < srv->prio )
+ srv = srvl[i];
+
+ connect_to = srv->name;
+ }
+ else
+ connect_to = jd->server;
+
+ imcb_log( ic, "Connecting" );
+
+ for( i = 0; jabber_port_list[i] > 0; i ++ )
+ if( set_getint( &acc->set, "port" ) == jabber_port_list[i] )
+ break;
+
+ if( jabber_port_list[i] == 0 )
+ {
+ imcb_log( ic, "Illegal port number" );
+ imc_logout( ic, FALSE );
+ return;
+ }
+
+ /* For non-SSL connections we can try to use the port # from the SRV
+ reply, but let's not do that when using SSL, SSL usually runs on
+ non-standard ports... */
+ if( set_getbool( &acc->set, "ssl" ) )
+ {
+ jd->ssl = ssl_connect( connect_to, set_getint( &acc->set, "port" ), jabber_connected_ssl, ic );
+ jd->fd = jd->ssl ? ssl_getfd( jd->ssl ) : -1;
+ }
+ else
+ {
+ jd->fd = proxy_connect( connect_to, srv ? srv->port : set_getint( &acc->set, "port" ), jabber_connected_plain, ic );
+ }
+ srv_free( srvl );
+
+ if( jd->fd == -1 )
+ {
+ imcb_error( ic, "Could not connect to server" );
+ imc_logout( ic, TRUE );
+
+ return;
+ }
+
+ if( set_getbool( &acc->set, "xmlconsole" ) )
+ {
+ jd->flags |= JFLAG_XMLCONSOLE;
+ /* Shouldn't really do this at this stage already, maybe. But
+ I think this shouldn't break anything. */
+ imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
+ }
+
+ jabber_generate_id_hash( jd );
+}
+
+/* This generates an unfinished md5_state_t variable. Every time we generate
+ an ID, we finish the state by adding a sequence number and take the hash. */
+static void jabber_generate_id_hash( struct jabber_data *jd )
+{
+ md5_byte_t binbuf[4];
+ char *s;
+
+ md5_init( &jd->cached_id_prefix );
+ md5_append( &jd->cached_id_prefix, (unsigned char *) jd->username, strlen( jd->username ) );
+ md5_append( &jd->cached_id_prefix, (unsigned char *) jd->server, strlen( jd->server ) );
+ s = set_getstr( &jd->ic->acc->set, "resource" );
+ md5_append( &jd->cached_id_prefix, (unsigned char *) s, strlen( s ) );
+ random_bytes( binbuf, 4 );
+ md5_append( &jd->cached_id_prefix, binbuf, 4 );
+}
+
+static void jabber_logout( struct im_connection *ic )
+{
+ struct jabber_data *jd = ic->proto_data;
+
+ while( jd->filetransfers )
+ imcb_file_canceled( ic, ( ( struct jabber_transfer *) jd->filetransfers->data )->ft, "Logging out" );
+
+ while( jd->streamhosts )
+ {
+ jabber_streamhost_t *sh = jd->streamhosts->data;
+ jd->streamhosts = g_slist_remove( jd->streamhosts, sh );
+ g_free( sh->jid );
+ g_free( sh->host );
+ g_free( sh );
+ }
+
+ if( jd->fd >= 0 )
+ jabber_end_stream( ic );
+
+ while( ic->groupchats )
+ jabber_chat_free( ic->groupchats->data );
+
+ if( jd->r_inpa >= 0 )
+ b_event_remove( jd->r_inpa );
+ if( jd->w_inpa >= 0 )
+ b_event_remove( jd->w_inpa );
+
+ if( jd->ssl )
+ ssl_disconnect( jd->ssl );
+ if( jd->fd >= 0 )
+ closesocket( jd->fd );
+
+ if( jd->tx_len )
+ g_free( jd->txq );
+
+ if( jd->node_cache )
+ g_hash_table_destroy( jd->node_cache );
+
+ jabber_buddy_remove_all( ic );
+
+ xt_free( jd->xt );
+
+ g_free( jd->away_message );
+ g_free( jd->username );
+ g_free( jd );
+
+ jabber_connections = g_slist_remove( jabber_connections, ic );
+}
+
+static int jabber_buddy_msg( struct im_connection *ic, char *who, char *message, int flags )
+{
+ struct jabber_data *jd = ic->proto_data;
+ struct jabber_buddy *bud;
+ struct xt_node *node;
+ char *s;
+ int st;
+
+ if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
+ return jabber_write( ic, message, strlen( message ) );
+
+ if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) )
+ bud = jabber_buddy_by_ext_jid( ic, who, 0 );
+ else
+ bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_BARE_OK );
+
+ node = xt_new_node( "body", message, NULL );
+ node = jabber_make_packet( "message", "chat", bud ? bud->full_jid : who, node );
+
+ if( bud && ( jd->flags & JFLAG_WANT_TYPING ) &&
+ ( ( bud->flags & JBFLAG_DOES_XEP85 ) ||
+ !( bud->flags & JBFLAG_PROBED_XEP85 ) ) )
+ {
+ struct xt_node *act;
+
+ /* If the user likes typing notification and if we don't know
+ (and didn't probe before) if this resource supports XEP85,
+ include a probe in this packet now. Also, if we know this
+ buddy does support XEP85, we have to send this <active/>
+ tag to tell that the user stopped typing (well, that's what
+ we guess when s/he pressed Enter...). */
+ act = xt_new_node( "active", NULL, NULL );
+ xt_add_attr( act, "xmlns", XMLNS_CHATSTATES );
+ xt_add_child( node, act );
+
+ /* Just make sure we do this only once. */
+ bud->flags |= JBFLAG_PROBED_XEP85;
+ }
+
+ st = jabber_write_packet( ic, node );
+ xt_free_node( node );
+
+ return st;
+}
+
+static GList *jabber_away_states( struct im_connection *ic )
+{
+ static GList *l = NULL;
+ int i;
+
+ if( l == NULL )
+ for( i = 0; jabber_away_state_list[i].full_name; i ++ )
+ l = g_list_append( l, (void*) jabber_away_state_list[i].full_name );
+
+ return l;
+}
+
+static void jabber_get_info( struct im_connection *ic, char *who )
+{
+ struct jabber_buddy *bud;
+
+ bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_FIRST );
+
+ while( bud )
+ {
+ imcb_log( ic, "Buddy %s (%d) information:", bud->full_jid, bud->priority );
+ if( bud->away_state )
+ imcb_log( ic, "Away state: %s", bud->away_state->full_name );
+ imcb_log( ic, "Status message: %s", bud->away_message ? bud->away_message : "(none)" );
+
+ bud = bud->next;
+ }
+
+ jabber_get_vcard( ic, bud ? bud->full_jid : who );
+}
+
+static void jabber_set_away( struct im_connection *ic, char *state_txt, char *message )
+{
+ struct jabber_data *jd = ic->proto_data;
+
+ /* state_txt == NULL -> Not away.
+ Unknown state -> fall back to the first defined away state. */
+ if( state_txt == NULL )
+ jd->away_state = NULL;
+ else if( ( jd->away_state = jabber_away_state_by_name( state_txt ) ) == NULL )
+ jd->away_state = jabber_away_state_list;
+
+ g_free( jd->away_message );
+ jd->away_message = ( message && *message ) ? g_strdup( message ) : NULL;
+
+ presence_send_update( ic );
+}
+
+static void jabber_add_buddy( struct im_connection *ic, char *who, char *group )
+{
+ struct jabber_data *jd = ic->proto_data;
+
+ if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
+ {
+ jd->flags |= JFLAG_XMLCONSOLE;
+ imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
+ return;
+ }
+
+ if( jabber_add_to_roster( ic, who, NULL, group ) )
+ presence_send_request( ic, who, "subscribe" );
+}
+
+static void jabber_remove_buddy( struct im_connection *ic, char *who, char *group )
+{
+ struct jabber_data *jd = ic->proto_data;
+
+ if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
+ {
+ jd->flags &= ~JFLAG_XMLCONSOLE;
+ /* Not necessary for now. And for now the code isn't too
+ happy if the buddy is completely gone right after calling
+ this function already.
+ imcb_remove_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
+ */
+ return;
+ }
+
+ /* We should always do this part. Clean up our administration a little bit. */
+ jabber_buddy_remove_bare( ic, who );
+
+ if( jabber_remove_from_roster( ic, who ) )
+ presence_send_request( ic, who, "unsubscribe" );
+}
+
+static struct groupchat *jabber_chat_join_( struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets )
+{
+ if( strchr( room, '@' ) == NULL )
+ imcb_error( ic, "Invalid room name: %s", room );
+ else if( jabber_chat_by_jid( ic, room ) )
+ imcb_error( ic, "Already present in chat `%s'", room );
+ else
+ return jabber_chat_join( ic, room, nick, set_getstr( sets, "password" ) );
+
+ return NULL;
+}
+
+static void jabber_chat_msg_( struct groupchat *c, char *message, int flags )
+{
+ if( c && message )
+ jabber_chat_msg( c, message, flags );
+}
+
+static void jabber_chat_topic_( struct groupchat *c, char *topic )
+{
+ if( c && topic )
+ jabber_chat_topic( c, topic );
+}
+
+static void jabber_chat_leave_( struct groupchat *c )
+{
+ if( c )
+ jabber_chat_leave( c, NULL );
+}
+
+static void jabber_chat_invite_( struct groupchat *c, char *who, char *msg )
+{
+ struct jabber_chat *jc = c->data;
+ gchar *msg_alt = NULL;
+
+ if( msg == NULL )
+ msg_alt = g_strdup_printf( "%s invited you to %s", c->ic->acc->user, jc->name );
+
+ if( c && who )
+ jabber_chat_invite( c, who, msg ? msg : msg_alt );
+
+ g_free( msg_alt );
+}
+
+static void jabber_keepalive( struct im_connection *ic )
+{
+ /* Just any whitespace character is enough as a keepalive for XMPP sessions. */
+ if( !jabber_write( ic, "\n", 1 ) )
+ return;
+
+ /* This runs the garbage collection every minute, which means every packet
+ is in the cache for about a minute (which should be enough AFAIK). */
+ jabber_cache_clean( ic );
+}
+
+static int jabber_send_typing( struct im_connection *ic, char *who, int typing )
+{
+ struct jabber_data *jd = ic->proto_data;
+ struct jabber_buddy *bud;
+
+ /* Enable typing notification related code from now. */
+ jd->flags |= JFLAG_WANT_TYPING;
+
+ if( ( bud = jabber_buddy_by_jid( ic, who, 0 ) ) == NULL )
+ {
+ /* Sending typing notifications to unknown buddies is
+ unsupported for now. Shouldn't be a problem, I think. */
+ return 0;
+ }
+
+ if( bud->flags & JBFLAG_DOES_XEP85 )
+ {
+ /* We're only allowed to send this stuff if we know the other
+ side supports it. */
+
+ struct xt_node *node;
+ char *type;
+ int st;
+
+ if( typing & OPT_TYPING )
+ type = "composing";
+ else if( typing & OPT_THINKING )
+ type = "paused";
+ else
+ type = "active";
+
+ node = xt_new_node( type, NULL, NULL );
+ xt_add_attr( node, "xmlns", XMLNS_CHATSTATES );
+ node = jabber_make_packet( "message", "chat", bud->full_jid, node );
+
+ st = jabber_write_packet( ic, node );
+ xt_free_node( node );
+
+ return st;
+ }
+
+ return 1;
+}
+
+void jabber_chat_add_settings( account_t *acc, set_t **head )
+{
+ /* Meh. Stupid room passwords. Not trying to obfuscate/hide
+ them from the user for now. */
+ set_add( head, "password", NULL, NULL, NULL );
+}
+
+void jabber_chat_free_settings( account_t *acc, set_t **head )
+{
+ set_del( head, "password" );
+}
+
+GList *jabber_buddy_action_list( bee_user_t *bu )
+{
+ static GList *ret = NULL;
+
+ if( ret == NULL )
+ {
+ static const struct buddy_action ba[2] = {
+ { "VERSION", "Get client (version) information" },
+ };
+
+ ret = g_list_prepend( ret, (void*) ba + 0 );
+ }
+
+ return ret;
+}
+
+void *jabber_buddy_action( struct bee_user *bu, const char *action, char * const args[], void *data )
+{
+ if( g_strcasecmp( action, "VERSION" ) == 0 )
+ {
+ struct jabber_buddy *bud;
+
+ if( ( bud = jabber_buddy_by_ext_jid( bu->ic, bu->handle, 0 ) ) == NULL )
+ bud = jabber_buddy_by_jid( bu->ic, bu->handle, GET_BUDDY_FIRST );
+ for( ; bud; bud = bud->next )
+ jabber_iq_version_send( bu->ic, bud, data );
+ }
+
+ return NULL;
+}
+
+void jabber_initmodule()
+{
+ struct prpl *ret = g_new0( struct prpl, 1 );
+
+ ret->name = "jabber";
+ ret->mms = 0; /* no limit */
+ ret->login = jabber_login;
+ ret->init = jabber_init;
+ ret->logout = jabber_logout;
+ ret->buddy_msg = jabber_buddy_msg;
+ ret->away_states = jabber_away_states;
+ ret->set_away = jabber_set_away;
+// ret->set_info = jabber_set_info;
+ ret->get_info = jabber_get_info;
+ ret->add_buddy = jabber_add_buddy;
+ ret->remove_buddy = jabber_remove_buddy;
+ ret->chat_msg = jabber_chat_msg_;
+ ret->chat_topic = jabber_chat_topic_;
+ ret->chat_invite = jabber_chat_invite_;
+ ret->chat_leave = jabber_chat_leave_;
+ ret->chat_join = jabber_chat_join_;
+ ret->chat_add_settings = jabber_chat_add_settings;
+ ret->chat_free_settings = jabber_chat_free_settings;
+ ret->keepalive = jabber_keepalive;
+ ret->send_typing = jabber_send_typing;
+ ret->handle_cmp = g_strcasecmp;
+ ret->transfer_request = jabber_si_transfer_request;
+ ret->buddy_action_list = jabber_buddy_action_list;
+ ret->buddy_action = jabber_buddy_action;
+
+ register_protocol( ret );
+}
diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h
new file mode 100644
index 00000000..adf9a291
--- /dev/null
+++ b/protocols/jabber/jabber.h
@@ -0,0 +1,330 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Jabber module - Main file *
+* *
+* Copyright 2006 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. *
+* *
+\***************************************************************************/
+
+#ifndef _JABBER_H
+#define _JABBER_H
+
+#include <glib.h>
+
+#include "bitlbee.h"
+#include "md5.h"
+#include "xmltree.h"
+
+extern GSList *jabber_connections;
+
+typedef enum
+{
+ JFLAG_STREAM_STARTED = 1, /* Set when we detected the beginning of the stream
+ and want to do auth. */
+ JFLAG_AUTHENTICATED = 2, /* Set when we're successfully authenticatd. */
+ JFLAG_STREAM_RESTART = 4, /* Set when we want to restart the stream (after
+ SASL or TLS). */
+ JFLAG_WANT_SESSION = 8, /* Set if the server wants a <session/> tag
+ before we continue. */
+ JFLAG_WANT_BIND = 16, /* ... for <bind> tag. */
+ JFLAG_WANT_TYPING = 32, /* Set if we ever sent a typing notification, this
+ activates all XEP-85 related code. */
+ JFLAG_XMLCONSOLE = 64, /* If the user added an xmlconsole buddy. */
+ JFLAG_STARTTLS_DONE = 128, /* If a plaintext session was converted to TLS. */
+} jabber_flags_t;
+
+typedef enum
+{
+ JBFLAG_PROBED_XEP85 = 1, /* Set this when we sent our probe packet to make
+ sure it gets sent only once. */
+ JBFLAG_DOES_XEP85 = 2, /* Set this when the resource seems to support
+ XEP85 (typing notification shite). */
+ JBFLAG_IS_CHATROOM = 4, /* It's convenient to use this JID thingy for
+ groupchat state info too. */
+ JBFLAG_IS_ANONYMOUS = 8, /* For anonymous chatrooms, when we don't have
+ have a real JID. */
+ JBFLAG_HIDE_SUBJECT = 16, /* Hide the subject field since we probably
+ showed it already. */
+} jabber_buddy_flags_t;
+
+/* Stores a streamhost's (a.k.a. proxy) data */
+typedef struct
+{
+ char *jid;
+ char *host;
+ char port[6];
+} jabber_streamhost_t;
+
+typedef enum
+{
+ JCFLAG_MESSAGE_SENT = 1, /* Set this after sending the first message, so
+ we can detect echoes/backlogs. */
+} jabber_chat_flags_t;
+
+struct jabber_data
+{
+ struct im_connection *ic;
+
+ int fd;
+ void *ssl;
+ char *txq;
+ int tx_len;
+ int r_inpa, w_inpa;
+
+ struct xt_parser *xt;
+ jabber_flags_t flags;
+
+ char *username; /* USERNAME@server */
+ char *server; /* username@SERVER -=> server/domain, not hostname */
+
+ /* After changing one of these two (or the priority setting), call
+ presence_send_update() to inform the server about the changes. */
+ const struct jabber_away_state *away_state;
+ char *away_message;
+
+ md5_state_t cached_id_prefix;
+ GHashTable *node_cache;
+ GHashTable *buddies;
+
+ GSList *filetransfers;
+ GSList *streamhosts;
+ int have_streamhosts;
+};
+
+struct jabber_away_state
+{
+ char code[5];
+ char *full_name;
+};
+
+typedef xt_status (*jabber_cache_event) ( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
+
+struct jabber_cache_entry
+{
+ time_t saved_at;
+ struct xt_node *node;
+ jabber_cache_event func;
+};
+
+/* Somewhat messy data structure: We have a hash table with the bare JID as
+ the key and the head of a struct jabber_buddy list as the value. The head
+ is always a bare JID. If the JID has other resources (often the case,
+ except for some transports that don't support multiple resources), those
+ follow. In that case, the bare JID at the beginning doesn't actually
+ refer to a real session and should only be used for operations that
+ support incomplete JIDs. */
+struct jabber_buddy
+{
+ char *bare_jid;
+ char *full_jid;
+ char *resource;
+
+ char *ext_jid; /* The JID to use in BitlBee. The real JID if possible, */
+ /* otherwise something similar to the conference JID. */
+
+ int priority;
+ struct jabber_away_state *away_state;
+ char *away_message;
+ GSList *features;
+
+ time_t last_msg;
+ jabber_buddy_flags_t flags;
+
+ struct jabber_buddy *next;
+};
+
+struct jabber_chat
+{
+ int flags;
+ char *name;
+ char *my_full_jid; /* Separate copy because of case sensitivity. */
+ struct jabber_buddy *me;
+};
+
+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
+ first one should be used, but when storing a packet in the cache, a
+ "special" kind of ID is assigned to make it easier later to figure out
+ if we have to do call an event handler for the response packet. Also
+ we'll append a hash to make sure we won't trigger on cached packets from
+ other BitlBee users. :-) */
+#define JABBER_PACKET_ID "BeeP"
+#define JABBER_CACHED_ID "BeeC"
+
+/* The number of seconds to keep cached packets before garbage collecting
+ them. This gc is done on every keepalive (every minute). */
+#define JABBER_CACHE_MAX_AGE 600
+
+/* RFC 392[01] stuff */
+#define XMLNS_TLS "urn:ietf:params:xml:ns:xmpp-tls"
+#define XMLNS_SASL "urn:ietf:params:xml:ns:xmpp-sasl"
+#define XMLNS_BIND "urn:ietf:params:xml:ns:xmpp-bind"
+#define XMLNS_SESSION "urn:ietf:params:xml:ns:xmpp-session"
+#define XMLNS_STANZA_ERROR "urn:ietf:params:xml:ns:xmpp-stanzas"
+#define XMLNS_STREAM_ERROR "urn:ietf:params:xml:ns:xmpp-streams"
+#define XMLNS_ROSTER "jabber:iq:roster"
+
+/* Some supported extensions/legacy stuff */
+#define XMLNS_AUTH "jabber:iq:auth" /* XEP-0078 */
+#define XMLNS_VERSION "jabber:iq:version" /* XEP-0092 */
+#define XMLNS_TIME_OLD "jabber:iq:time" /* XEP-0090 */
+#define XMLNS_TIME "urn:xmpp:time" /* XEP-0202 */
+#define XMLNS_PING "urn:xmpp:ping" /* XEP-0199 */
+#define XMLNS_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 );
+int jabber_init_iq_auth( struct im_connection *ic );
+xt_status jabber_pkt_bind_sess( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
+int jabber_get_roster( struct im_connection *ic );
+int jabber_get_vcard( struct im_connection *ic, char *bare_jid );
+int jabber_add_to_roster( struct im_connection *ic, const char *handle, const char *name, const char *group );
+int jabber_remove_from_roster( struct im_connection *ic, char *handle );
+xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid );
+xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns );
+void jabber_iq_version_send( struct im_connection *ic, struct jabber_buddy *bud, void *data );
+
+/* si.c */
+int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode );
+void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who );
+void jabber_si_free_transfer( file_transfer_t *ft);
+
+/* s5bytestream.c */
+int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode);
+gboolean jabber_bs_send_start( struct jabber_transfer *tf );
+gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int len );
+
+/* message.c */
+xt_status jabber_pkt_message( struct xt_node *node, gpointer data );
+
+/* presence.c */
+xt_status jabber_pkt_presence( struct xt_node *node, gpointer data );
+int presence_send_update( struct im_connection *ic );
+int presence_send_request( struct im_connection *ic, char *handle, char *request );
+
+/* jabber_util.c */
+char *set_eval_priority( set_t *set, char *value );
+char *set_eval_tls( set_t *set, char *value );
+struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_node *children );
+struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code );
+void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func );
+struct xt_node *jabber_cache_get( struct im_connection *ic, char *id );
+void jabber_cache_entry_free( gpointer entry );
+void jabber_cache_clean( struct im_connection *ic );
+xt_status jabber_cache_handle_packet( struct im_connection *ic, struct xt_node *node );
+const struct jabber_away_state *jabber_away_state_by_code( char *code );
+const struct jabber_away_state *jabber_away_state_by_name( char *name );
+void jabber_buddy_ask( struct im_connection *ic, char *handle );
+char *jabber_normalize( const char *orig );
+
+typedef enum
+{
+ GET_BUDDY_CREAT = 1, /* Try to create it, if necessary. */
+ GET_BUDDY_EXACT = 2, /* Get an exact match (only makes sense with bare JIDs). */
+ GET_BUDDY_FIRST = 4, /* No selection, simply get the first resource for this JID. */
+ GET_BUDDY_BARE = 8, /* Get the bare version of the JID (possibly inexistent). */
+ GET_BUDDY_BARE_OK = 16, /* Allow returning a bare JID if that seems better. */
+} get_buddy_flags_t;
+
+struct jabber_error
+{
+ char *code, *text, *type;
+};
+
+struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid );
+struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid, get_buddy_flags_t flags );
+struct jabber_buddy *jabber_buddy_by_ext_jid( struct im_connection *ic, char *jid, get_buddy_flags_t flags );
+int jabber_buddy_remove( struct im_connection *ic, char *full_jid );
+int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid );
+void jabber_buddy_remove_all( struct im_connection *ic );
+time_t jabber_get_timestamp( struct xt_node *xt );
+struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns );
+void jabber_error_free( struct jabber_error *err );
+
+extern const struct jabber_away_state jabber_away_state_list[];
+
+/* io.c */
+int jabber_write_packet( struct im_connection *ic, struct xt_node *node );
+int jabber_write( struct im_connection *ic, char *buf, int len );
+gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition cond );
+gboolean jabber_connected_ssl( gpointer data, void *source, b_input_condition cond );
+gboolean jabber_start_stream( struct im_connection *ic );
+void jabber_end_stream( struct im_connection *ic );
+
+/* sasl.c */
+xt_status sasl_pkt_mechanisms( struct xt_node *node, gpointer data );
+xt_status sasl_pkt_challenge( struct xt_node *node, gpointer data );
+xt_status sasl_pkt_result( struct xt_node *node, gpointer data );
+gboolean sasl_supported( struct im_connection *ic );
+
+/* conference.c */
+struct groupchat *jabber_chat_join( struct im_connection *ic, const char *room, const char *nick, const char *password );
+struct groupchat *jabber_chat_by_jid( struct im_connection *ic, const char *name );
+void jabber_chat_free( struct groupchat *c );
+int jabber_chat_msg( struct groupchat *ic, char *message, int flags );
+int jabber_chat_topic( struct groupchat *c, char *topic );
+int jabber_chat_leave( struct groupchat *c, const char *reason );
+void jabber_chat_pkt_presence( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node );
+void jabber_chat_pkt_message( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node );
+void jabber_chat_invite( struct groupchat *c, char *who, char *message );
+
+#endif
diff --git a/protocols/jabber/jabber_util.c b/protocols/jabber/jabber_util.c
new file mode 100644
index 00000000..e6b13659
--- /dev/null
+++ b/protocols/jabber/jabber_util.c
@@ -0,0 +1,762 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Jabber module - Misc. stuff *
+* *
+* Copyright 2006-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 "jabber.h"
+#include "md5.h"
+#include "base64.h"
+
+static unsigned int next_id = 1;
+
+char *set_eval_priority( set_t *set, char *value )
+{
+ account_t *acc = set->data;
+ int i;
+
+ if( sscanf( value, "%d", &i ) == 1 )
+ {
+ /* Priority is a signed 8-bit integer, according to RFC 3921. */
+ if( i < -128 || i > 127 )
+ return SET_INVALID;
+ }
+ else
+ return SET_INVALID;
+
+ /* Only run this stuff if the account is online ATM,
+ and if the setting seems to be acceptable. */
+ if( acc->ic )
+ {
+ /* Although set_eval functions usually are very nice and
+ convenient, they have one disadvantage: If I would just
+ call p_s_u() now to send the new prio setting, it would
+ send the old setting because the set->value gets changed
+ after the (this) eval returns a non-NULL value.
+
+ So now I can choose between implementing post-set
+ functions next to evals, or just do this little hack: */
+
+ g_free( set->value );
+ set->value = g_strdup( value );
+
+ /* (Yes, sorry, I prefer the hack. :-P) */
+
+ presence_send_update( acc->ic );
+ }
+
+ return value;
+}
+
+char *set_eval_tls( set_t *set, char *value )
+{
+ if( g_strcasecmp( value, "try" ) == 0 )
+ return value;
+ else
+ return set_eval_bool( set, value );
+}
+
+struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_node *children )
+{
+ struct xt_node *node;
+
+ node = xt_new_node( name, NULL, children );
+
+ if( type )
+ xt_add_attr( node, "type", type );
+ if( to )
+ xt_add_attr( node, "to", to );
+
+ /* IQ packets should always have an ID, so let's generate one. It
+ might get overwritten by jabber_cache_add() if this packet has
+ to be saved until we receive a response. Cached packets get
+ slightly different IDs so we can recognize them. */
+ if( strcmp( name, "iq" ) == 0 )
+ {
+ char *id = g_strdup_printf( "%s%05x", JABBER_PACKET_ID, ( next_id++ ) & 0xfffff );
+ xt_add_attr( node, "id", id );
+ g_free( id );
+ }
+
+ return node;
+}
+
+struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code )
+{
+ struct xt_node *node, *c;
+ char *to;
+
+ /* Create the "defined-condition" tag. */
+ c = xt_new_node( err_cond, NULL, NULL );
+ xt_add_attr( c, "xmlns", XMLNS_STANZA_ERROR );
+
+ /* Put it in an <error> tag. */
+ c = xt_new_node( "error", NULL, c );
+ xt_add_attr( c, "type", err_type );
+
+ /* Add the error code, if present */
+ if (err_code)
+ xt_add_attr( c, "code", err_code );
+
+ /* To make the actual error packet, we copy the original packet and
+ add our <error>/type="error" tag. Including the original packet
+ is recommended, so let's just do it. */
+ node = xt_dup( orig );
+ xt_add_child( node, c );
+ xt_add_attr( node, "type", "error" );
+
+ /* Return to sender. */
+ if( ( to = xt_find_attr( node, "from" ) ) )
+ {
+ xt_add_attr( node, "to", to );
+ xt_remove_attr( node, "from" );
+ }
+
+ return node;
+}
+
+/* Cache a node/packet for later use. Mainly useful for IQ packets if you need
+ them when you receive the response. Use this BEFORE sending the packet so
+ it'll get a new id= tag, and do NOT free() the packet after sending it! */
+void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func )
+{
+ struct jabber_data *jd = ic->proto_data;
+ struct jabber_cache_entry *entry = g_new0( struct jabber_cache_entry, 1 );
+ md5_state_t id_hash;
+ md5_byte_t id_sum[16];
+ char *id, *asc_hash;
+
+ next_id ++;
+
+ id_hash = jd->cached_id_prefix;
+ md5_append( &id_hash, (md5_byte_t*) &next_id, sizeof( next_id ) );
+ md5_finish( &id_hash, id_sum );
+ asc_hash = base64_encode( id_sum, 12 );
+
+ id = g_strdup_printf( "%s%s", JABBER_CACHED_ID, asc_hash );
+ xt_add_attr( node, "id", id );
+ g_free( id );
+ g_free( asc_hash );
+
+ entry->node = node;
+ entry->func = func;
+ entry->saved_at = time( NULL );
+ g_hash_table_insert( jd->node_cache, xt_find_attr( node, "id" ), entry );
+}
+
+void jabber_cache_entry_free( gpointer data )
+{
+ struct jabber_cache_entry *entry = data;
+
+ xt_free_node( entry->node );
+ g_free( entry );
+}
+
+gboolean jabber_cache_clean_entry( gpointer key, gpointer entry, gpointer nullpointer );
+
+/* This one should be called from time to time (from keepalive, in this case)
+ to make sure things don't stay in the node cache forever. By marking nodes
+ during the first run and deleting marked nodes during a next run, every
+ node should be available in the cache for at least a minute (assuming the
+ function is indeed called every minute). */
+void jabber_cache_clean( struct im_connection *ic )
+{
+ struct jabber_data *jd = ic->proto_data;
+ time_t threshold = time( NULL ) - JABBER_CACHE_MAX_AGE;
+
+ g_hash_table_foreach_remove( jd->node_cache, jabber_cache_clean_entry, &threshold );
+}
+
+gboolean jabber_cache_clean_entry( gpointer key, gpointer entry_, gpointer threshold_ )
+{
+ struct jabber_cache_entry *entry = entry_;
+ time_t *threshold = threshold_;
+
+ return entry->saved_at < *threshold;
+}
+
+xt_status jabber_cache_handle_packet( struct im_connection *ic, struct xt_node *node )
+{
+ struct jabber_data *jd = ic->proto_data;
+ struct jabber_cache_entry *entry;
+ char *s;
+
+ if( ( s = xt_find_attr( node, "id" ) ) == NULL ||
+ strncmp( s, JABBER_CACHED_ID, strlen( JABBER_CACHED_ID ) ) != 0 )
+ {
+ /* Silently ignore it, without an ID (or a non-cache
+ ID) we don't know how to handle the packet and we
+ probably don't have to. */
+ return XT_HANDLED;
+ }
+
+ entry = g_hash_table_lookup( jd->node_cache, s );
+
+ if( entry == NULL )
+ {
+ /*
+ There's no longer an easy way to see if we generated this
+ one or someone else, and there's a ten-minute timeout anyway,
+ so meh.
+
+ imcb_log( ic, "Warning: Received %s-%s packet with unknown/expired ID %s!",
+ node->name, xt_find_attr( node, "type" ) ? : "(no type)", s );
+ */
+ }
+ else if( entry->func )
+ {
+ return entry->func( ic, node, entry->node );
+ }
+
+ return XT_HANDLED;
+}
+
+const struct jabber_away_state jabber_away_state_list[] =
+{
+ { "away", "Away" },
+ { "chat", "Free for Chat" }, /* WTF actually uses this? */
+ { "dnd", "Do not Disturb" },
+ { "xa", "Extended Away" },
+ { "", NULL }
+};
+
+const struct jabber_away_state *jabber_away_state_by_code( char *code )
+{
+ int i;
+
+ if( code == NULL )
+ return NULL;
+
+ for( i = 0; jabber_away_state_list[i].full_name; i ++ )
+ if( g_strcasecmp( jabber_away_state_list[i].code, code ) == 0 )
+ return jabber_away_state_list + i;
+
+ return NULL;
+}
+
+const struct jabber_away_state *jabber_away_state_by_name( char *name )
+{
+ int i;
+
+ if( name == NULL )
+ return NULL;
+
+ for( i = 0; jabber_away_state_list[i].full_name; i ++ )
+ if( g_strcasecmp( jabber_away_state_list[i].full_name, name ) == 0 )
+ return jabber_away_state_list + i;
+
+ return NULL;
+}
+
+struct jabber_buddy_ask_data
+{
+ struct im_connection *ic;
+ char *handle;
+ char *realname;
+};
+
+static void jabber_buddy_ask_yes( void *data )
+{
+ struct jabber_buddy_ask_data *bla = data;
+
+ presence_send_request( bla->ic, bla->handle, "subscribed" );
+
+ imcb_ask_add( bla->ic, bla->handle, NULL );
+
+ g_free( bla->handle );
+ g_free( bla );
+}
+
+static void jabber_buddy_ask_no( void *data )
+{
+ struct jabber_buddy_ask_data *bla = data;
+
+ presence_send_request( bla->ic, bla->handle, "subscribed" );
+
+ g_free( bla->handle );
+ g_free( bla );
+}
+
+void jabber_buddy_ask( struct im_connection *ic, char *handle )
+{
+ struct jabber_buddy_ask_data *bla = g_new0( struct jabber_buddy_ask_data, 1 );
+ char *buf;
+
+ bla->ic = ic;
+ bla->handle = g_strdup( handle );
+
+ buf = g_strdup_printf( "The user %s wants to add you to his/her buddy list.", handle );
+ imcb_ask( ic, buf, bla, jabber_buddy_ask_yes, jabber_buddy_ask_no );
+ g_free( buf );
+}
+
+/* Returns a new string. Don't leak it! */
+char *jabber_normalize( const char *orig )
+{
+ int len, i;
+ char *new;
+
+ len = strlen( orig );
+ new = g_new( char, len + 1 );
+
+ /* So it turns out the /resource part is case sensitive. Yeah, and
+ it's Unicode but feck Unicode. :-P So stop once we see a slash. */
+ for( i = 0; i < len && orig[i] != '/' ; i ++ )
+ new[i] = tolower( orig[i] );
+ for( ; orig[i]; i ++ )
+ new[i] = orig[i];
+
+ new[i] = 0;
+ return new;
+}
+
+/* Adds a buddy/resource to our list. Returns NULL if full_jid is not really a
+ FULL jid or if we already have this buddy/resource. XXX: No, great, actually
+ buddies from transports don't (usually) have resources. So we'll really have
+ to deal with that properly. Set their ->resource property to NULL. Do *NOT*
+ allow to mix this stuff, though... */
+struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid_ )
+{
+ struct jabber_data *jd = ic->proto_data;
+ struct jabber_buddy *bud, *new, *bi;
+ char *s, *full_jid;
+
+ full_jid = jabber_normalize( full_jid_ );
+
+ if( ( s = strchr( full_jid, '/' ) ) )
+ *s = 0;
+
+ new = g_new0( struct jabber_buddy, 1 );
+
+ if( ( bud = g_hash_table_lookup( jd->buddies, full_jid ) ) )
+ {
+ /* The first entry is always a bare JID. If there are more, we
+ should ignore the first one here. */
+ if( bud->next )
+ bud = bud->next;
+
+ /* If this is a transport buddy or whatever, it can't have more
+ than one instance, so this is always wrong: */
+ if( s == NULL || bud->resource == NULL )
+ {
+ if( s ) *s = '/';
+ g_free( new );
+ g_free( full_jid );
+ return NULL;
+ }
+
+ new->bare_jid = bud->bare_jid;
+
+ /* We already have another resource for this buddy, add the
+ new one to the list. */
+ for( bi = bud; bi; bi = bi->next )
+ {
+ /* Check for dupes. */
+ if( strcmp( bi->resource, s + 1 ) == 0 )
+ {
+ *s = '/';
+ g_free( new );
+ g_free( full_jid );
+ return NULL;
+ }
+ /* Append the new item to the list. */
+ else if( bi->next == NULL )
+ {
+ bi->next = new;
+ break;
+ }
+ }
+ }
+ else
+ {
+ new->full_jid = new->bare_jid = g_strdup( full_jid );
+ g_hash_table_insert( jd->buddies, new->bare_jid, new );
+
+ if( s )
+ {
+ new->next = g_new0( struct jabber_buddy, 1 );
+ new->next->bare_jid = new->bare_jid;
+ new = new->next;
+ }
+ }
+
+ if( s )
+ {
+ *s = '/';
+ new->full_jid = full_jid;
+ new->resource = strchr( new->full_jid, '/' ) + 1;
+ }
+ else
+ {
+ /* Let's waste some more bytes of RAM instead of to make
+ memory management a total disaster here. And it saves
+ me one g_free() call in this function. :-P */
+ new->full_jid = full_jid;
+ }
+
+ return new;
+}
+
+/* Finds a buddy from our structures. Can find both full- and bare JIDs. When
+ asked for a bare JID, it uses the "resource_select" setting to see which
+ resource to pick. */
+struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_, get_buddy_flags_t flags )
+{
+ struct jabber_data *jd = ic->proto_data;
+ struct jabber_buddy *bud, *head;
+ char *s, *jid;
+
+ jid = jabber_normalize( jid_ );
+
+ if( ( s = strchr( jid, '/' ) ) )
+ {
+ int bare_exists = 0;
+
+ *s = 0;
+ if( ( bud = g_hash_table_lookup( jd->buddies, jid ) ) )
+ {
+ bare_exists = 1;
+
+ if( bud->next )
+ bud = bud->next;
+
+ /* Just return the first one for this bare JID. */
+ if( flags & GET_BUDDY_FIRST )
+ {
+ *s = '/';
+ g_free( jid );
+ return bud;
+ }
+
+ /* Is this one of those no-resource buddies? */
+ if( bud->resource == NULL )
+ {
+ *s = '/';
+ g_free( jid );
+ return NULL;
+ }
+
+ /* See if there's an exact match. */
+ for( ; bud; bud = bud->next )
+ if( strcmp( bud->resource, s + 1 ) == 0 )
+ break;
+ }
+
+ if( bud == NULL && ( flags & GET_BUDDY_CREAT ) &&
+ ( bare_exists || bee_user_by_handle( ic->bee, ic, jid ) ) )
+ {
+ *s = '/';
+ bud = jabber_buddy_add( ic, jid );
+ }
+
+ g_free( jid );
+ return bud;
+ }
+ else
+ {
+ struct jabber_buddy *best_prio, *best_time;
+ char *set;
+
+ head = g_hash_table_lookup( jd->buddies, jid );
+ bud = ( head && head->next ) ? head->next : head;
+
+ g_free( jid );
+
+ if( bud == NULL )
+ /* No match. Create it now? */
+ return ( ( flags & GET_BUDDY_CREAT ) &&
+ bee_user_by_handle( ic->bee, ic, jid_ ) ) ?
+ jabber_buddy_add( ic, jid_ ) : NULL;
+ else if( bud->resource && ( flags & GET_BUDDY_EXACT ) )
+ /* We want an exact match, so in thise case there shouldn't be a /resource. */
+ return NULL;
+ else if( bud->resource == NULL || bud->next == NULL )
+ /* No need for selection if there's only one option. */
+ return bud;
+ else if( flags & GET_BUDDY_FIRST )
+ /* Looks like the caller doesn't care about details. */
+ return bud;
+ else if( flags & GET_BUDDY_BARE )
+ return head;
+
+ best_prio = best_time = bud;
+ for( ; bud; bud = bud->next )
+ {
+ if( bud->priority > best_prio->priority )
+ best_prio = bud;
+ if( bud->last_msg > best_time->last_msg )
+ best_time = bud;
+ }
+
+ if( ( set = set_getstr( &ic->acc->set, "resource_select" ) ) == NULL )
+ return NULL;
+ else if( strcmp( set, "priority" ) == 0 )
+ return best_prio;
+ else if( flags & GET_BUDDY_BARE_OK ) /* && strcmp( set, "activity" ) == 0 */
+ {
+ if( best_time->last_msg + set_getint( &ic->acc->set, "activity_timeout" ) >= time( NULL ) )
+ return best_time;
+ else
+ return head;
+ }
+ else
+ return best_time;
+ }
+}
+
+/* I'm keeping a separate ext_jid attribute to save a JID that makes sense
+ to export to BitlBee. This is mainly for groupchats right now. It's
+ a bit of a hack, but I just think having the user nickname in the hostname
+ part of the hostmask doesn't look nice on IRC. Normally you can convert
+ a normal JID to ext_jid by swapping the part before and after the / and
+ replacing the / with a =. But there should be some stripping (@s are
+ allowed in Jabber nicks...). */
+struct jabber_buddy *jabber_buddy_by_ext_jid( struct im_connection *ic, char *jid_, get_buddy_flags_t flags )
+{
+ struct jabber_buddy *bud;
+ char *s, *jid;
+
+ jid = jabber_normalize( jid_ );
+
+ if( ( s = strchr( jid, '=' ) ) == NULL )
+ return NULL;
+
+ for( bud = jabber_buddy_by_jid( ic, s + 1, GET_BUDDY_FIRST ); bud; bud = bud->next )
+ {
+ /* Hmmm, could happen if not all people in the chat are anonymized? */
+ if( bud->ext_jid == NULL )
+ continue;
+
+ if( strcmp( bud->ext_jid, jid ) == 0 )
+ break;
+ }
+
+ g_free( jid );
+
+ return bud;
+}
+
+/* Remove one specific full JID from our list. Use this when a buddy goes
+ off-line (because (s)he can still be online from a different location.
+ XXX: See above, we should accept bare JIDs too... */
+int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )
+{
+ struct jabber_data *jd = ic->proto_data;
+ struct jabber_buddy *bud, *prev = NULL, *bi;
+ char *s, *full_jid;
+
+ full_jid = jabber_normalize( full_jid_ );
+
+ if( ( s = strchr( full_jid, '/' ) ) )
+ *s = 0;
+
+ if( ( bud = g_hash_table_lookup( jd->buddies, full_jid ) ) )
+ {
+ if( bud->next )
+ bud = (prev=bud)->next;
+
+ /* If there's only one item in the list (and if the resource
+ matches), removing it is simple. (And the hash reference
+ should be removed too!) */
+ if( bud->next == NULL &&
+ ( ( s == NULL && bud->resource == NULL ) ||
+ ( bud->resource && s && strcmp( bud->resource, s + 1 ) == 0 ) ) )
+ {
+ int st = jabber_buddy_remove_bare( ic, full_jid );
+ g_free( full_jid );
+ return st;
+ }
+ else if( s == NULL || bud->resource == NULL )
+ {
+ /* Tried to remove a bare JID while this JID does seem
+ to have resources... (Or the opposite.) *sigh* */
+ g_free( full_jid );
+ return 0;
+ }
+ else
+ {
+ for( bi = bud; bi; bi = (prev=bi)->next )
+ if( strcmp( bi->resource, s + 1 ) == 0 )
+ break;
+
+ g_free( full_jid );
+
+ if( bi )
+ {
+ if( prev )
+ prev->next = bi->next;
+ else
+ /* Don't think this should ever happen anymore. */
+ g_hash_table_replace( jd->buddies, bi->bare_jid, bi->next );
+
+ g_free( bi->ext_jid );
+ g_free( bi->full_jid );
+ g_free( bi->away_message );
+ g_free( bi );
+
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+ else
+ {
+ g_free( full_jid );
+ return 0;
+ }
+}
+
+/* Remove a buddy completely; removes all resources that belong to the
+ specified bare JID. Use this when removing someone from the contact
+ list, for example. */
+int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid )
+{
+ struct jabber_data *jd = ic->proto_data;
+ struct jabber_buddy *bud, *next;
+
+ if( strchr( bare_jid, '/' ) )
+ return 0;
+
+ if( ( bud = jabber_buddy_by_jid( ic, bare_jid, GET_BUDDY_FIRST ) ) )
+ {
+ /* Most important: Remove the hash reference. We don't know
+ this buddy anymore. */
+ g_hash_table_remove( jd->buddies, bud->bare_jid );
+ g_free( bud->bare_jid );
+
+ /* Deallocate the linked list of resources. */
+ while( bud )
+ {
+ /* ext_jid && anonymous means that this buddy is
+ specific to one groupchat (the one we're
+ currently cleaning up) so it can be deleted
+ completely. */
+ if( bud->ext_jid && bud->flags & JBFLAG_IS_ANONYMOUS )
+ imcb_remove_buddy( ic, bud->ext_jid, NULL );
+
+ next = bud->next;
+ g_free( bud->ext_jid );
+ g_free( bud->full_jid );
+ g_free( bud->away_message );
+ g_free( bud );
+ bud = next;
+ }
+
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+static gboolean jabber_buddy_remove_all_cb( gpointer key, gpointer value, gpointer data )
+{
+ struct jabber_buddy *bud, *next;
+
+ bud = value;
+ while( bud )
+ {
+ next = bud->next;
+ g_free( bud->ext_jid );
+ g_free( bud->full_jid );
+ g_free( bud->away_message );
+ g_free( bud );
+ bud = next;
+ }
+
+ return TRUE;
+}
+
+void jabber_buddy_remove_all( struct im_connection *ic )
+{
+ struct jabber_data *jd = ic->proto_data;
+
+ g_hash_table_foreach_remove( jd->buddies, jabber_buddy_remove_all_cb, NULL );
+ g_hash_table_destroy( jd->buddies );
+}
+
+time_t jabber_get_timestamp( struct xt_node *xt )
+{
+ struct xt_node *c;
+ char *s = NULL;
+ struct tm tp;
+
+ for( c = xt->children; ( c = xt_find_node( c, "x" ) ); c = c->next )
+ {
+ if( ( s = xt_find_attr( c, "xmlns" ) ) && strcmp( s, XMLNS_DELAY ) == 0 )
+ break;
+ }
+
+ if( !c || !( s = xt_find_attr( c, "stamp" ) ) )
+ return 0;
+
+ memset( &tp, 0, sizeof( tp ) );
+ if( sscanf( s, "%4d%2d%2dT%2d:%2d:%2d", &tp.tm_year, &tp.tm_mon, &tp.tm_mday,
+ &tp.tm_hour, &tp.tm_min, &tp.tm_sec ) != 6 )
+ return 0;
+
+ tp.tm_year -= 1900;
+ tp.tm_mon --;
+
+ return mktime_utc( &tp );
+}
+
+struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns )
+{
+ struct jabber_error *err;
+ struct xt_node *c;
+ char *s;
+
+ if( node == NULL )
+ return NULL;
+
+ err = g_new0( struct jabber_error, 1 );
+ err->type = xt_find_attr( node, "type" );
+
+ for( c = node->children; c; c = c->next )
+ {
+ if( !( s = xt_find_attr( c, "xmlns" ) ) ||
+ strcmp( s, xmlns ) != 0 )
+ continue;
+
+ if( strcmp( c->name, "text" ) != 0 )
+ {
+ err->code = c->name;
+ }
+ /* Only use the text if it doesn't have an xml:lang attribute,
+ if it's empty or if it's set to something English. */
+ else if( !( s = xt_find_attr( c, "xml:lang" ) ) ||
+ !*s || strncmp( s, "en", 2 ) == 0 )
+ {
+ err->text = c->text;
+ }
+ }
+
+ return err;
+}
+
+void jabber_error_free( struct jabber_error *err )
+{
+ g_free( err );
+}
diff --git a/protocols/jabber/message.c b/protocols/jabber/message.c
new file mode 100644
index 00000000..85c71c9d
--- /dev/null
+++ b/protocols/jabber/message.c
@@ -0,0 +1,149 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Jabber module - Handling of message(s) (tags), etc *
+* *
+* Copyright 2006 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 "jabber.h"
+
+xt_status jabber_pkt_message( struct xt_node *node, gpointer data )
+{
+ struct im_connection *ic = data;
+ char *from = xt_find_attr( node, "from" );
+ char *type = xt_find_attr( node, "type" );
+ struct xt_node *body = xt_find_node( node->children, "body" ), *c;
+ struct jabber_buddy *bud = NULL;
+ char *s, *room = NULL, *reason = NULL;
+
+ if( !from )
+ return XT_HANDLED; /* Consider this packet corrupted. */
+
+ bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT );
+
+ if( type && strcmp( type, "error" ) == 0 )
+ {
+ /* Handle type=error packet. */
+ }
+ else if( type && from && strcmp( type, "groupchat" ) == 0 )
+ {
+ jabber_chat_pkt_message( ic, bud, node );
+ }
+ else /* "chat", "normal", "headline", no-type or whatever. Should all be pretty similar. */
+ {
+ GString *fullmsg = g_string_new( "" );
+
+ for( c = node->children; ( c = xt_find_node( c, "x" ) ); c = c->next )
+ {
+ char *ns = xt_find_attr( c, "xmlns" );
+ struct xt_node *inv;
+
+ if( ns && strcmp( ns, XMLNS_MUC_USER ) == 0 &&
+ ( inv = xt_find_node( c->children, "invite" ) ) )
+ {
+ /* This is an invitation. Set some vars which
+ will be passed to imcb_chat_invite() below. */
+ room = from;
+ if( ( from = xt_find_attr( inv, "from" ) ) == NULL )
+ from = room;
+ if( ( inv = xt_find_node( inv->children, "reason" ) ) && inv->text_len > 0 )
+ reason = inv->text;
+ }
+ }
+
+ if( ( s = strchr( from, '/' ) ) )
+ {
+ if( bud )
+ {
+ bud->last_msg = time( NULL );
+ from = bud->ext_jid ? bud->ext_jid : bud->bare_jid;
+ }
+ else
+ *s = 0; /* We need to generate a bare JID now. */
+ }
+
+ if( type && strcmp( type, "headline" ) == 0 )
+ {
+ if( ( c = xt_find_node( node->children, "subject" ) ) && c->text_len > 0 )
+ g_string_append_printf( fullmsg, "Headline: %s\n", c->text );
+
+ /* <x xmlns="jabber:x:oob"><url>http://....</url></x> can contain a URL, it seems. */
+ for( c = node->children; c; c = c->next )
+ {
+ struct xt_node *url;
+
+ if( ( url = xt_find_node( c->children, "url" ) ) && url->text_len > 0 )
+ g_string_append_printf( fullmsg, "URL: %s\n", url->text );
+ }
+ }
+ else if( ( c = xt_find_node( node->children, "subject" ) ) && c->text_len > 0 &&
+ ( !bud || !( bud->flags & JBFLAG_HIDE_SUBJECT ) ) )
+ {
+ g_string_append_printf( fullmsg, "<< \002BitlBee\002 - Message with subject: %s >>\n", c->text );
+ if( bud )
+ bud->flags |= JBFLAG_HIDE_SUBJECT;
+ }
+ else if( bud && !c )
+ {
+ /* Yeah, possibly we're hiding changes to this field now. But nobody uses
+ this for anything useful anyway, except GMail when people reply to an
+ e-mail via chat, repeating the same subject all the time. I don't want
+ to have to remember full subject strings for everyone. */
+ bud->flags &= ~JBFLAG_HIDE_SUBJECT;
+ }
+
+ if( body && body->text_len > 0 ) /* Could be just a typing notification. */
+ fullmsg = g_string_append( fullmsg, body->text );
+
+ if( fullmsg->len > 0 )
+ imcb_buddy_msg( ic, from, fullmsg->str,
+ 0, jabber_get_timestamp( node ) );
+ if( room )
+ imcb_chat_invite( ic, room, from, reason );
+
+ g_string_free( fullmsg, TRUE );
+
+ /* Handling of incoming typing notifications. */
+ if( bud == NULL )
+ {
+ /* Can't handle these for unknown buddies. */
+ }
+ else if( xt_find_node( node->children, "composing" ) )
+ {
+ bud->flags |= JBFLAG_DOES_XEP85;
+ imcb_buddy_typing( ic, from, OPT_TYPING );
+ }
+ /* No need to send a "stopped typing" signal when there's a message. */
+ else if( xt_find_node( node->children, "active" ) && ( body == NULL ) )
+ {
+ bud->flags |= JBFLAG_DOES_XEP85;
+ imcb_buddy_typing( ic, from, 0 );
+ }
+ else if( xt_find_node( node->children, "paused" ) )
+ {
+ bud->flags |= JBFLAG_DOES_XEP85;
+ imcb_buddy_typing( ic, from, OPT_THINKING );
+ }
+
+ if( s )
+ *s = '/'; /* And convert it back to a full JID. */
+ }
+
+ return XT_HANDLED;
+}
diff --git a/protocols/jabber/presence.c b/protocols/jabber/presence.c
new file mode 100644
index 00000000..2875d23e
--- /dev/null
+++ b/protocols/jabber/presence.c
@@ -0,0 +1,258 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Jabber module - Handling of presence (tags), etc *
+* *
+* Copyright 2006 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 "jabber.h"
+
+xt_status jabber_pkt_presence( struct xt_node *node, gpointer data )
+{
+ struct im_connection *ic = data;
+ char *from = xt_find_attr( node, "from" );
+ char *type = xt_find_attr( node, "type" ); /* NULL should mean the person is online. */
+ struct xt_node *c, *cap;
+ struct jabber_buddy *bud, *send_presence = NULL;
+ int is_chat = 0;
+ char *s;
+
+ if( !from )
+ return XT_HANDLED;
+
+ if( ( s = strchr( from, '/' ) ) )
+ {
+ *s = 0;
+ if( jabber_chat_by_jid( ic, from ) )
+ is_chat = 1;
+ *s = '/';
+ }
+
+ if( type == NULL )
+ {
+ if( !( bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT | GET_BUDDY_CREAT ) ) )
+ {
+ /*
+ imcb_log( ic, "Warning: Could not handle presence information from JID: %s", from );
+ */
+ return XT_HANDLED;
+ }
+
+ g_free( bud->away_message );
+ if( ( c = xt_find_node( node->children, "status" ) ) && c->text_len > 0 )
+ bud->away_message = g_strdup( c->text );
+ else
+ bud->away_message = NULL;
+
+ if( ( c = xt_find_node( node->children, "show" ) ) && c->text_len > 0 )
+ {
+ bud->away_state = (void*) jabber_away_state_by_code( c->text );
+ }
+ else
+ {
+ bud->away_state = NULL;
+ }
+
+ if( ( c = xt_find_node( node->children, "priority" ) ) && c->text_len > 0 )
+ bud->priority = atoi( c->text );
+ else
+ bud->priority = 0;
+
+ if( bud && ( cap = xt_find_node( node->children, "c" ) ) &&
+ ( s = xt_find_attr( cap, "xmlns" ) ) && strcmp( s, XMLNS_CAPS ) == 0 )
+ {
+ /* This <presence> stanza includes an XEP-0115
+ capabilities part. Not too interesting, but we can
+ see if it has an ext= attribute. */
+ s = xt_find_attr( cap, "ext" );
+ if( s && ( strstr( s, "cstates" ) || strstr( s, "chatstate" ) ) )
+ bud->flags |= JBFLAG_DOES_XEP85;
+
+ /* This field can contain more information like xhtml
+ support, but we don't support that ourselves.
+ Officially the ext= tag was deprecated, but enough
+ clients do send it.
+
+ (I'm aware that this is not the right way to use
+ this field.) See for an explanation of ext=:
+ http://www.xmpp.org/extensions/attic/xep-0115-1.3.html*/
+ }
+
+ if( is_chat )
+ jabber_chat_pkt_presence( ic, bud, node );
+ else
+ send_presence = jabber_buddy_by_jid( ic, bud->bare_jid, 0 );
+ }
+ else if( strcmp( type, "unavailable" ) == 0 )
+ {
+ if( ( bud = jabber_buddy_by_jid( ic, from, 0 ) ) == NULL )
+ {
+ /*
+ imcb_log( ic, "Warning: Received presence information from unknown JID: %s", from );
+ */
+ return XT_HANDLED;
+ }
+
+ /* Handle this before we delete the JID. */
+ if( is_chat )
+ {
+ jabber_chat_pkt_presence( ic, bud, node );
+ }
+
+ if( strchr( from, '/' ) == NULL )
+ /* Sometimes servers send a type="unavailable" from a
+ bare JID, which should mean that suddenly all
+ resources for this JID disappeared. */
+ jabber_buddy_remove_bare( ic, from );
+ else
+ jabber_buddy_remove( ic, from );
+
+ if( is_chat )
+ {
+ /* Nothing else to do for now? */
+ }
+ else if( ( s = strchr( from, '/' ) ) )
+ {
+ *s = 0;
+
+ /* If another resource is still available, send its presence
+ information. */
+ if( ( send_presence = jabber_buddy_by_jid( ic, from, 0 ) ) == NULL )
+ {
+ /* Otherwise, count him/her as offline now. */
+ imcb_buddy_status( ic, from, 0, NULL, NULL );
+ }
+
+ *s = '/';
+ }
+ else
+ {
+ imcb_buddy_status( ic, from, 0, NULL, NULL );
+ }
+ }
+ else if( strcmp( type, "subscribe" ) == 0 )
+ {
+ jabber_buddy_ask( ic, from );
+ }
+ else if( strcmp( type, "subscribed" ) == 0 )
+ {
+ /* Not sure about this one, actually... */
+ imcb_log( ic, "%s just accepted your authorization request", from );
+ }
+ else if( strcmp( type, "unsubscribe" ) == 0 || strcmp( type, "unsubscribed" ) == 0 )
+ {
+ /* Do nothing here. Plenty of control freaks or over-curious
+ souls get excited when they can see who still has them in
+ their buddy list and who finally removed them. Somehow I
+ got the impression that those are the people who get
+ removed from many buddy lists for "some" reason...
+
+ If you're one of those people, this is your chance to write
+ your first line of code in C... */
+ }
+ else if( strcmp( type, "error" ) == 0 )
+ {
+ return jabber_cache_handle_packet( ic, node );
+
+ /*
+ struct jabber_error *err;
+ if( ( c = xt_find_node( node->children, "error" ) ) )
+ {
+ err = jabber_error_parse( c, XMLNS_STANZA_ERROR );
+ imcb_error( ic, "Stanza (%s) error: %s%s%s", node->name,
+ err->code, err->text ? ": " : "",
+ err->text ? err->text : "" );
+ jabber_error_free( err );
+ } */
+ }
+
+ if( send_presence )
+ {
+ int is_away = 0;
+
+ if( send_presence->away_state &&
+ strcmp( send_presence->away_state->code, "chat" ) != 0 )
+ is_away = OPT_AWAY;
+
+ imcb_buddy_status( ic, send_presence->bare_jid, OPT_LOGGED_IN | is_away,
+ is_away ? send_presence->away_state->full_name : NULL,
+ send_presence->away_message );
+ }
+
+ return XT_HANDLED;
+}
+
+/* Whenever presence information is updated, call this function to inform the
+ server. */
+int presence_send_update( struct im_connection *ic )
+{
+ struct jabber_data *jd = ic->proto_data;
+ struct xt_node *node, *cap;
+ GSList *l;
+ int st;
+
+ node = jabber_make_packet( "presence", NULL, NULL, NULL );
+ xt_add_child( node, xt_new_node( "priority", set_getstr( &ic->acc->set, "priority" ), NULL ) );
+ if( jd->away_state )
+ xt_add_child( node, xt_new_node( "show", jd->away_state->code, NULL ) );
+ if( jd->away_message )
+ xt_add_child( node, xt_new_node( "status", jd->away_message, NULL ) );
+
+ /* This makes the packet slightly bigger, but clients interested in
+ capabilities can now cache the discovery info. This reduces the
+ usual post-login iq-flood. See XEP-0115. At least libpurple and
+ Trillian seem to do this right. */
+ cap = xt_new_node( "c", NULL, NULL );
+ xt_add_attr( cap, "xmlns", XMLNS_CAPS );
+ xt_add_attr( cap, "node", "http://bitlbee.org/xmpp/caps" );
+ xt_add_attr( cap, "ver", BITLBEE_VERSION ); /* The XEP wants this hashed, but nobody's doing that. */
+ xt_add_child( node, cap );
+
+ st = jabber_write_packet( ic, node );
+
+ /* Have to send this update to all groupchats too, the server won't
+ do this automatically. */
+ for( l = ic->groupchats; l && st; l = l->next )
+ {
+ struct groupchat *c = l->data;
+ struct jabber_chat *jc = c->data;
+
+ xt_add_attr( node, "to", jc->my_full_jid );
+ st = jabber_write_packet( ic, node );
+ }
+
+ xt_free_node( node );
+ return st;
+}
+
+/* Send a subscribe/unsubscribe request to a buddy. */
+int presence_send_request( struct im_connection *ic, char *handle, char *request )
+{
+ struct xt_node *node;
+ int st;
+
+ node = jabber_make_packet( "presence", NULL, NULL, NULL );
+ xt_add_attr( node, "to", handle );
+ xt_add_attr( node, "type", request );
+
+ st = jabber_write_packet( ic, node );
+
+ xt_free_node( node );
+ return st;
+}
diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c
new file mode 100644
index 00000000..3304d99e
--- /dev/null
+++ b/protocols/jabber/s5bytestream.c
@@ -0,0 +1,1153 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Jabber module - SOCKS5 Bytestreams ( XEP-0065 ) *
+* *
+* Copyright 2007 Uli Meis <a.sporto+bee@gmail.com> *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+* *
+* This program is distributed in the hope that it will be useful, *
+* but WITHOUT ANY WARRANTY; without even the implied warranty of *
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+* GNU General Public License for more details. *
+* *
+* You should have received a copy of the GNU General Public License along *
+* with this program; if not, write to the Free Software Foundation, Inc., *
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
+* *
+\***************************************************************************/
+
+#include "jabber.h"
+#include "sha1.h"
+#include "lib/ftutil.h"
+#include <poll.h>
+
+struct bs_transfer {
+
+ struct jabber_transfer *tf;
+
+ jabber_streamhost_t *sh;
+ GSList *streamhosts;
+
+ enum
+ {
+ BS_PHASE_CONNECT,
+ BS_PHASE_CONNECTED,
+ BS_PHASE_REQUEST,
+ BS_PHASE_REPLY
+ } phase;
+
+ /* SHA1( SID + Initiator JID + Target JID) */
+ char *pseudoadr;
+
+ gint connect_timeout;
+
+ char peek_buf[64];
+ int peek_buf_len;
+};
+
+struct socks5_message
+{
+ unsigned char ver;
+ union
+ {
+ unsigned char cmd;
+ unsigned char rep;
+ } cmdrep;
+ unsigned char rsv;
+ unsigned char atyp;
+ unsigned char addrlen;
+ unsigned char address[40];
+ in_port_t port;
+} __attribute__ ((packed));
+
+char *socks5_reply_code[] = {
+ "succeeded",
+ "general SOCKS server failure",
+ "connection not allowed by ruleset",
+ "Network unreachable",
+ "Host unreachable",
+ "Connection refused",
+ "TTL expired",
+ "Command not supported",
+ "Address type not supported",
+ "unassigned"};
+
+/* connect() timeout in seconds. */
+#define JABBER_BS_CONTIMEOUT 15
+/* listen timeout */
+#define JABBER_BS_LISTEN_TIMEOUT 90
+
+/* very useful */
+#define ASSERTSOCKOP(op, msg) \
+ if( (op) == -1 ) \
+ return jabber_bs_abort( bt , msg ": %s", strerror( errno ) );
+
+gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... );
+void jabber_bs_canceled( file_transfer_t *ft , char *reason );
+void jabber_bs_free_transfer( file_transfer_t *ft );
+gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond );
+gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents );
+gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen );
+
+void jabber_bs_recv_answer_request( struct bs_transfer *bt );
+gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond );
+gboolean jabber_bs_recv_write_request( file_transfer_t *ft );
+gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition cond );
+gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error );
+int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode );
+
+gboolean jabber_bs_send_handshake_abort( struct bs_transfer *bt, char *error );
+gboolean jabber_bs_send_request( struct jabber_transfer *tf, GSList *streamhosts );
+gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition cond );
+static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
+void jabber_bs_send_activate( struct bs_transfer *bt );
+
+/*
+ * Frees a bs_transfer struct and calls the SI free function
+ */
+void jabber_bs_free_transfer( file_transfer_t *ft) {
+ struct jabber_transfer *tf = ft->data;
+ struct bs_transfer *bt = tf->streamhandle;
+ jabber_streamhost_t *sh;
+
+ if ( bt->connect_timeout )
+ {
+ b_event_remove( bt->connect_timeout );
+ bt->connect_timeout = 0;
+ }
+
+ if ( tf->watch_in )
+ b_event_remove( tf->watch_in );
+
+ if( tf->watch_out )
+ b_event_remove( tf->watch_out );
+
+ g_free( bt->pseudoadr );
+
+ while( bt->streamhosts )
+ {
+ sh = bt->streamhosts->data;
+ bt->streamhosts = g_slist_remove( bt->streamhosts, sh );
+ g_free( sh->jid );
+ g_free( sh->host );
+ g_free( sh );
+ }
+
+ g_free( bt );
+
+ jabber_si_free_transfer( ft );
+}
+
+/*
+ * Checks if buflen data is available on the socket and
+ * writes it to buffer if that's the case.
+ */
+gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen )
+{
+ int ret;
+ int fd = bt->tf->fd;
+
+ if( buflen > sizeof( bt->peek_buf ) )
+ return jabber_bs_abort( bt, "BUG: %d > sizeof(peek_buf)", buflen );
+
+ ASSERTSOCKOP( ret = recv( fd, bt->peek_buf + bt->peek_buf_len,
+ buflen - bt->peek_buf_len, 0 ), "recv() on SOCKS5 connection" );
+
+ if( ret == 0 )
+ return jabber_bs_abort( bt, "Remote end closed connection" );
+
+ bt->peek_buf_len += ret;
+ memcpy( buffer, bt->peek_buf, bt->peek_buf_len );
+
+ if( bt->peek_buf_len == buflen )
+ {
+ /* If we have everything the caller wanted, reset the peek buffer. */
+ bt->peek_buf_len = 0;
+ return buflen;
+ }
+ else
+ return bt->peek_buf_len;
+}
+
+
+/*
+ * This function is scheduled in bs_handshake via b_timeout_add after a (non-blocking) connect().
+ */
+gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond )
+{
+ struct bs_transfer *bt = data;
+
+ bt->connect_timeout = 0;
+
+ jabber_bs_abort( bt, "no connection after %d seconds", bt->tf->ft->sending ? JABBER_BS_LISTEN_TIMEOUT : JABBER_BS_CONTIMEOUT );
+
+ return FALSE;
+}
+
+/*
+ * Polls the socket, checks for errors and removes a connect timer
+ * if there is one.
+ */
+gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents )
+{
+ struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR };
+
+ if ( bt->connect_timeout )
+ {
+ b_event_remove( bt->connect_timeout );
+ bt->connect_timeout = 0;
+ }
+
+ ASSERTSOCKOP( poll( &pfd, 1, 0 ), "poll()" )
+
+ if( pfd.revents & POLLERR )
+ {
+ int sockerror;
+ socklen_t errlen = sizeof( sockerror );
+
+ if ( getsockopt( fd, SOL_SOCKET, SO_ERROR, &sockerror, &errlen ) )
+ return jabber_bs_abort( bt, "getsockopt() failed, unknown socket error during SOCKS5 handshake (weird!)" );
+
+ if ( bt->phase == BS_PHASE_CONNECTED )
+ return jabber_bs_abort( bt, "connect failed: %s", strerror( sockerror ) );
+
+ return jabber_bs_abort( bt, "Socket error during SOCKS5 handshake(weird!): %s", strerror( sockerror ) );
+ }
+
+ if( pfd.revents & POLLHUP )
+ return jabber_bs_abort( bt, "Remote end closed connection" );
+
+ *revents = pfd.revents;
+
+ return TRUE;
+}
+
+/*
+ * Used for receive and send path.
+ */
+gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... )
+{
+ va_list params;
+ va_start( params, format );
+ char error[128];
+
+ if( vsnprintf( error, 128, format, params ) < 0 )
+ sprintf( error, "internal error parsing error string (BUG)" );
+ va_end( params );
+ if( bt->tf->ft->sending )
+ return jabber_bs_send_handshake_abort( bt, error );
+ else
+ return jabber_bs_recv_handshake_abort( bt, error );
+}
+
+/* Bad luck */
+void jabber_bs_canceled( file_transfer_t *ft , char *reason )
+{
+ struct jabber_transfer *tf = ft->data;
+
+ imcb_log( tf->ic, "File transfer aborted: %s", reason );
+}
+
+/*
+ * Parses an incoming bytestream request and calls jabber_bs_handshake on success.
+ */
+int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode)
+{
+ char *sid, *ini_jid, *tgt_jid, *mode, *iq_id;
+ struct jabber_data *jd = ic->proto_data;
+ struct jabber_transfer *tf = NULL;
+ GSList *tflist;
+ struct bs_transfer *bt;
+ GSList *shlist=NULL;
+ struct xt_node *shnode;
+
+ sha1_state_t sha;
+ char hash_hex[41];
+ unsigned char hash[20];
+ int i;
+
+ if( !(iq_id = xt_find_attr( node, "id" ) ) ||
+ !(ini_jid = xt_find_attr( node, "from" ) ) ||
+ !(tgt_jid = xt_find_attr( node, "to" ) ) ||
+ !(sid = xt_find_attr( qnode, "sid" ) ) )
+ {
+ imcb_log( ic, "WARNING: Received incomplete SI bytestream request");
+ return XT_HANDLED;
+ }
+
+ if( ( mode = xt_find_attr( qnode, "mode" ) ) &&
+ ( strcmp( mode, "tcp" ) != 0 ) )
+ {
+ imcb_log( ic, "WARNING: Received SI Request for unsupported bytestream mode %s", xt_find_attr( qnode, "mode" ) );
+ return XT_HANDLED;
+ }
+
+ shnode = qnode->children;
+ while( ( shnode = xt_find_node( shnode, "streamhost" ) ) )
+ {
+ char *jid, *host, *port_s;
+ int port;
+ if( ( jid = xt_find_attr( shnode, "jid" ) ) &&
+ ( host = xt_find_attr( shnode, "host" ) ) &&
+ ( port_s = xt_find_attr( shnode, "port" ) ) &&
+ ( sscanf( port_s, "%d", &port ) == 1 ) )
+ {
+ jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 );
+ sh->jid = g_strdup(jid);
+ sh->host = g_strdup(host);
+ sprintf( sh->port, "%u", port );
+ shlist = g_slist_append( shlist, sh );
+ }
+ shnode = shnode->next;
+ }
+
+ if( !shlist )
+ {
+ imcb_log( ic, "WARNING: Received incomplete SI bytestream request, no parseable streamhost entries");
+ return XT_HANDLED;
+ }
+
+ /* Let's see if we can find out what this bytestream should be for... */
+
+ for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) )
+ {
+ struct jabber_transfer *tft = tflist->data;
+ if( ( strcmp( tft->sid, sid ) == 0 ) &&
+ ( strcmp( tft->ini_jid, ini_jid ) == 0 ) &&
+ ( strcmp( tft->tgt_jid, tgt_jid ) == 0 ) )
+ {
+ tf = tft;
+ break;
+ }
+ }
+
+ if (!tf)
+ {
+ imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid );
+ return XT_HANDLED;
+ }
+
+ /* iq_id and canceled can be reused since SI is done */
+ g_free( tf->iq_id );
+ tf->iq_id = g_strdup( iq_id );
+
+ tf->ft->canceled = jabber_bs_canceled;
+
+ /* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */
+ sha1_init( &sha );
+ sha1_append( &sha, (unsigned char*) sid, strlen( sid ) );
+ sha1_append( &sha, (unsigned char*) ini_jid, strlen( ini_jid ) );
+ sha1_append( &sha, (unsigned char*) tgt_jid, strlen( tgt_jid ) );
+ sha1_finish( &sha, hash );
+
+ for( i = 0; i < 20; i ++ )
+ sprintf( hash_hex + i * 2, "%02x", hash[i] );
+
+ bt = g_new0( struct bs_transfer, 1 );
+ bt->tf = tf;
+ bt->streamhosts = shlist;
+ bt->sh = shlist->data;
+ bt->phase = BS_PHASE_CONNECT;
+ bt->pseudoadr = g_strdup( hash_hex );
+ tf->streamhandle = bt;
+ tf->ft->free = jabber_bs_free_transfer;
+
+ jabber_bs_recv_handshake( bt, -1, 0 );
+
+ return XT_HANDLED;
+}
+
+/*
+ * This is what a protocol handshake can look like in cooperative multitasking :)
+ * Might be confusing at first because it's called from different places and is recursing.
+ * (places being the event thread, bs_request, bs_handshake_abort, and itself)
+ *
+ * All in all, it turned out quite nice :)
+ */
+gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition cond )
+{
+
+ struct bs_transfer *bt = data;
+ short revents;
+ int gret;
+
+ if ( ( fd != -1 ) && !jabber_bs_poll( bt, fd, &revents ) )
+ return FALSE;
+
+ switch( bt->phase )
+ {
+ case BS_PHASE_CONNECT:
+ {
+ struct addrinfo hints, *rp;
+
+ memset( &hints, 0, sizeof( struct addrinfo ) );
+ hints.ai_socktype = SOCK_STREAM;
+
+ if ( ( gret = getaddrinfo( bt->sh->host, bt->sh->port, &hints, &rp ) ) != 0 )
+ return jabber_bs_abort( bt, "getaddrinfo() failed: %s", gai_strerror( gret ) );
+
+ ASSERTSOCKOP( bt->tf->fd = fd = socket( rp->ai_family, rp->ai_socktype, 0 ), "Opening socket" );
+
+ sock_make_nonblocking( fd );
+
+ imcb_log( bt->tf->ic, "File %s: Connecting to streamhost %s:%s", bt->tf->ft->file_name, bt->sh->host, bt->sh->port );
+
+ if( ( connect( fd, rp->ai_addr, rp->ai_addrlen ) == -1 ) &&
+ ( errno != EINPROGRESS ) )
+ return jabber_bs_abort( bt , "connect() failed: %s", strerror( errno ) );
+
+ freeaddrinfo( rp );
+
+ bt->phase = BS_PHASE_CONNECTED;
+
+ bt->tf->watch_out = b_input_add( fd, B_EV_IO_WRITE, jabber_bs_recv_handshake, bt );
+
+ /* since it takes forever(3mins?) till connect() fails on itself we schedule a timeout */
+ bt->connect_timeout = b_timeout_add( JABBER_BS_CONTIMEOUT * 1000, jabber_bs_connect_timeout, bt );
+
+ bt->tf->watch_in = 0;
+ return FALSE;
+ }
+ case BS_PHASE_CONNECTED:
+ {
+ struct {
+ unsigned char ver;
+ unsigned char nmethods;
+ unsigned char method;
+ } socks5_hello = {
+ .ver = 5,
+ .nmethods = 1,
+ .method = 0x00 /* no auth */
+ /* one could also implement username/password. If you know
+ * a jabber client or proxy that actually does it, tell me.
+ */
+ };
+
+ ASSERTSOCKOP( send( fd, &socks5_hello, sizeof( socks5_hello ) , 0 ), "Sending auth request" );
+
+ bt->phase = BS_PHASE_REQUEST;
+
+ bt->tf->watch_in = b_input_add( fd, B_EV_IO_READ, jabber_bs_recv_handshake, bt );
+
+ bt->tf->watch_out = 0;
+ return FALSE;
+ }
+ case BS_PHASE_REQUEST:
+ {
+ struct socks5_message socks5_connect =
+ {
+ .ver = 5,
+ .cmdrep.cmd = 0x01,
+ .rsv = 0,
+ .atyp = 0x03,
+ .addrlen = strlen( bt->pseudoadr ),
+ .port = 0
+ };
+ int ret;
+ char buf[2];
+
+ /* If someone's trying to be funny and sends only one byte at a time we'll fail :) */
+ ASSERTSOCKOP( ret = recv( fd, buf, 2, 0 ) , "Receiving auth reply" );
+
+ if( !( ret == 2 ) ||
+ !( buf[0] == 5 ) ||
+ !( buf[1] == 0 ) )
+ return jabber_bs_abort( bt, "Auth not accepted by streamhost (reply: len=%d, ver=%d, status=%d)",
+ ret, buf[0], buf[1] );
+
+ /* copy hash into connect message */
+ memcpy( socks5_connect.address, bt->pseudoadr, socks5_connect.addrlen );
+
+ ASSERTSOCKOP( send( fd, &socks5_connect, sizeof( struct socks5_message ), 0 ) , "Sending SOCKS5 Connect" );
+
+ bt->phase = BS_PHASE_REPLY;
+
+ return TRUE;
+ }
+ case BS_PHASE_REPLY:
+ {
+ struct socks5_message socks5_reply;
+ int ret;
+
+ if ( !( ret = jabber_bs_peek( bt, &socks5_reply, sizeof( struct socks5_message ) ) ) )
+ return FALSE;
+
+ if ( ret < 5 ) /* header up to address length */
+ return TRUE;
+ else if( ret < sizeof( struct socks5_message ) )
+ {
+ /* Either a buggy proxy or just one that doesnt regard
+ * the SHOULD in XEP-0065 saying the reply SHOULD
+ * contain the address. We'll take it, so make sure the
+ * next jabber_bs_peek starts with an empty buffer. */
+ bt->peek_buf_len = 0;
+ }
+
+ if( !( socks5_reply.ver == 5 ) ||
+ !( socks5_reply.cmdrep.rep == 0 ) ) {
+ char errstr[128] = "";
+ if( ( socks5_reply.ver == 5 ) && ( socks5_reply.cmdrep.rep <
+ ( sizeof( socks5_reply_code ) / sizeof( socks5_reply_code[0] ) ) ) ) {
+ sprintf( errstr, "with \"%s\" ", socks5_reply_code[ socks5_reply.cmdrep.rep ] );
+ }
+ return jabber_bs_abort( bt, "SOCKS5 CONNECT failed %s(reply: ver=%d, rep=%d, atyp=%d, addrlen=%d)",
+ errstr,
+ socks5_reply.ver,
+ socks5_reply.cmdrep.rep,
+ socks5_reply.atyp,
+ socks5_reply.addrlen);
+ }
+
+ /* usually a proxy sends back the 40 bytes address but I encountered at least one (of jabber.cz)
+ * that sends atyp=0 addrlen=0 and only 6 bytes (one less than one would expect).
+ * Therefore I removed the wait for more bytes. Since we don't care about what else the proxy
+ * is sending, it shouldnt matter */
+
+ if( bt->tf->ft->sending )
+ jabber_bs_send_activate( bt );
+ else
+ jabber_bs_recv_answer_request( bt );
+
+ return FALSE;
+ }
+ default:
+ /* BUG */
+ imcb_log( bt->tf->ic, "BUG in file transfer code: undefined handshake phase" );
+
+ bt->tf->watch_in = 0;
+ return FALSE;
+ }
+}
+
+/*
+ * If the handshake failed we can try the next streamhost, if there is one.
+ * An intelligent sender would probably specify himself as the first streamhost and
+ * a proxy as the second (Kopete and PSI are examples here). That way, a (potentially)
+ * slow proxy is only used if neccessary. This of course also means, that the timeout
+ * per streamhost should be kept short. If one or two firewalled adresses are specified,
+ * they have to timeout first before a proxy is tried.
+ */
+gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error )
+{
+ struct jabber_transfer *tf = bt->tf;
+ struct xt_node *reply, *iqnode;
+ GSList *shlist;
+
+ imcb_log( tf->ic, "Transferring file %s: connection to streamhost %s:%s failed (%s)",
+ tf->ft->file_name,
+ bt->sh->host,
+ bt->sh->port,
+ error );
+
+ /* Alright, this streamhost failed, let's try the next... */
+ bt->phase = BS_PHASE_CONNECT;
+ shlist = g_slist_find( bt->streamhosts, bt->sh );
+ if( shlist && shlist->next )
+ {
+ bt->sh = shlist->next->data;
+ return jabber_bs_recv_handshake( bt, -1, 0 );
+ }
+
+
+ /* out of stream hosts */
+
+ iqnode = jabber_make_packet( "iq", "result", tf->ini_jid, NULL );
+ reply = jabber_make_error_packet( iqnode, "item-not-found", "cancel" , "404" );
+ xt_free_node( iqnode );
+
+ xt_add_attr( reply, "id", tf->iq_id );
+
+ if( !jabber_write_packet( tf->ic, reply ) )
+ imcb_log( tf->ic, "WARNING: Error transmitting bytestream response" );
+ xt_free_node( reply );
+
+ imcb_file_canceled( tf->ic, tf->ft, "couldn't connect to any streamhosts" );
+
+ /* MUST always return FALSE! */
+ return FALSE;
+}
+
+/*
+ * After the SOCKS5 handshake succeeds we need to inform the initiator which streamhost we chose.
+ * If he is the streamhost himself, he might already know that. However, if it's a proxy,
+ * the initiator will have to make a connection himself.
+ */
+void jabber_bs_recv_answer_request( struct bs_transfer *bt )
+{
+ struct jabber_transfer *tf = bt->tf;
+ struct xt_node *reply;
+
+ imcb_log( tf->ic, "File %s: established SOCKS5 connection to %s:%s",
+ tf->ft->file_name,
+ bt->sh->host,
+ bt->sh->port );
+
+ tf->ft->data = tf;
+ tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_recv_read, bt );
+ tf->ft->write_request = jabber_bs_recv_write_request;
+
+ reply = xt_new_node( "streamhost-used", NULL, NULL );
+ xt_add_attr( reply, "jid", bt->sh->jid );
+
+ reply = xt_new_node( "query", NULL, reply );
+ xt_add_attr( reply, "xmlns", XMLNS_BYTESTREAMS );
+
+ reply = jabber_make_packet( "iq", "result", tf->ini_jid, reply );
+
+ xt_add_attr( reply, "id", tf->iq_id );
+
+ if( !jabber_write_packet( tf->ic, reply ) )
+ imcb_file_canceled( tf->ic, tf->ft, "Error transmitting bytestream response" );
+ xt_free_node( reply );
+}
+
+/*
+ * This function is called from write_request directly. If no data is available, it will install itself
+ * as a watcher for input on fd and once that happens, deliver the data and unschedule itself again.
+ */
+gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond )
+{
+ int ret;
+ struct bs_transfer *bt = data;
+ struct jabber_transfer *tf = bt->tf;
+
+ if( fd != -1 ) /* called via event thread */
+ {
+ tf->watch_in = 0;
+ ASSERTSOCKOP( ret = recv( fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) , "Receiving" );
+ }
+ else
+ {
+ /* called directly. There might not be any data available. */
+ if( ( ( ret = recv( tf->fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) ) == -1 ) &&
+ ( errno != EAGAIN ) )
+ return jabber_bs_abort( bt, "Receiving: %s", strerror( errno ) );
+
+ if( ( ret == -1 ) && ( errno == EAGAIN ) )
+ {
+ tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_recv_read, bt );
+ return FALSE;
+ }
+ }
+
+ /* shouldn't happen since we know the file size */
+ if( ret == 0 )
+ return jabber_bs_abort( bt, "Remote end closed connection" );
+
+ tf->bytesread += ret;
+
+ if( tf->bytesread >= tf->ft->file_size )
+ imcb_file_finished( tf->ic, tf->ft );
+
+ tf->ft->write( tf->ft, tf->ft->buffer, ret );
+
+ return FALSE;
+}
+
+/*
+ * imc callback that is invoked when it is ready to receive some data.
+ */
+gboolean jabber_bs_recv_write_request( file_transfer_t *ft )
+{
+ struct jabber_transfer *tf = ft->data;
+
+ if( tf->watch_in )
+ {
+ imcb_file_canceled( tf->ic, ft, "BUG in jabber file transfer: write_request called when already watching for input" );
+ return FALSE;
+ }
+
+ jabber_bs_recv_read( tf->streamhandle, -1 , 0 );
+
+ return TRUE;
+}
+
+/*
+ * Issues a write_request to imc.
+ * */
+gboolean jabber_bs_send_can_write( gpointer data, gint fd, b_input_condition cond )
+{
+ struct bs_transfer *bt = data;
+
+ bt->tf->watch_out = 0;
+
+ bt->tf->ft->write_request( bt->tf->ft );
+
+ return FALSE;
+}
+
+/*
+ * This should only be called if we can write, so just do it.
+ * Add a write watch so we can write more during the next cycle (if possible).
+ */
+gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int len )
+{
+ struct jabber_transfer *tf = ft->data;
+ struct bs_transfer *bt = tf->streamhandle;
+ int ret;
+
+ if( tf->watch_out )
+ return jabber_bs_abort( bt, "BUG: write() called while watching " );
+
+ /* TODO: catch broken pipe */
+ ASSERTSOCKOP( ret = send( tf->fd, buffer, len, 0 ), "Sending" );
+
+ tf->byteswritten += ret;
+
+ /* TODO: this should really not be fatal */
+ if( ret < len )
+ return jabber_bs_abort( bt, "send() sent %d instead of %d (send buffer too big!)", ret, len );
+
+ if( tf->byteswritten >= ft->file_size )
+ imcb_file_finished( tf->ic, ft );
+ else
+ bt->tf->watch_out = b_input_add( tf->fd, B_EV_IO_WRITE, jabber_bs_send_can_write, bt );
+
+ return TRUE;
+}
+
+/*
+ * Handles the reply by the receiver containing the used streamhost.
+ */
+static xt_status jabber_bs_send_handle_reply(struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) {
+ struct jabber_transfer *tf = NULL;
+ struct jabber_data *jd = ic->proto_data;
+ struct bs_transfer *bt;
+ GSList *tflist;
+ struct xt_node *c;
+ char *sid, *jid;
+
+ if( !( c = xt_find_node( node->children, "query" ) ) ||
+ !( c = xt_find_node( c->children, "streamhost-used" ) ) ||
+ !( jid = xt_find_attr( c, "jid" ) ) )
+
+ {
+ imcb_log( ic, "WARNING: Received incomplete bytestream reply" );
+ return XT_HANDLED;
+ }
+
+ if( !( c = xt_find_node( orig->children, "query" ) ) ||
+ !( sid = xt_find_attr( c, "sid" ) ) )
+ {
+ imcb_log( ic, "WARNING: Error parsing request corresponding to the incoming bytestream reply" );
+ return XT_HANDLED;
+ }
+
+ /* Let's see if we can find out what this bytestream should be for... */
+
+ for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) )
+ {
+ struct jabber_transfer *tft = tflist->data;
+ if( ( strcmp( tft->sid, sid ) == 0 ) )
+ {
+ tf = tft;
+ break;
+ }
+ }
+
+ if( !tf )
+ {
+ imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply to unknown request" );
+ return XT_HANDLED;
+ }
+
+ bt = tf->streamhandle;
+
+ tf->accepted = TRUE;
+
+ if( strcmp( jid, tf->ini_jid ) == 0 )
+ {
+ /* we're streamhost and target */
+ if( bt->phase == BS_PHASE_REPLY )
+ {
+ /* handshake went through, let's start transferring */
+ tf->ft->write_request( tf->ft );
+ }
+ } else
+ {
+ /* using a proxy, abort listen */
+
+ if( tf->watch_in )
+ {
+ b_event_remove( tf->watch_in );
+ tf->watch_in = 0;
+ }
+
+ if( tf->fd != -1 ) {
+ closesocket( tf->fd );
+ tf->fd = -1;
+ }
+
+ if ( bt->connect_timeout )
+ {
+ b_event_remove( bt->connect_timeout );
+ bt->connect_timeout = 0;
+ }
+
+ GSList *shlist;
+ for( shlist = jd->streamhosts ; shlist ; shlist = g_slist_next( shlist ) )
+ {
+ jabber_streamhost_t *sh = shlist->data;
+ if( strcmp( sh->jid, jid ) == 0 )
+ {
+ bt->sh = sh;
+ jabber_bs_recv_handshake( bt, -1, 0 );
+ return XT_HANDLED;
+ }
+ }
+
+ imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply with unknown streamhost %s", jid );
+ }
+
+ return XT_HANDLED;
+}
+
+/*
+ * Tell the proxy to activate the stream. Looks like this:
+ *
+ * <iq type=set>
+ * <query xmlns=bs sid=sid>
+ * <activate>tgt_jid</activate>
+ * </query>
+ * </iq>
+ */
+void jabber_bs_send_activate( struct bs_transfer *bt )
+{
+ struct xt_node *node;
+
+ node = xt_new_node( "activate", bt->tf->tgt_jid, NULL );
+ node = xt_new_node( "query", NULL, node );
+ xt_add_attr( node, "xmlns", XMLNS_BYTESTREAMS );
+ xt_add_attr( node, "sid", bt->tf->sid );
+ node = jabber_make_packet( "iq", "set", bt->sh->jid, node );
+
+ jabber_cache_add( bt->tf->ic, node, jabber_bs_send_handle_activate );
+
+ jabber_write_packet( bt->tf->ic, node );
+}
+
+/*
+ * The proxy has activated the bytestream.
+ * We can finally start pushing some data out.
+ */
+static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+ char *sid;
+ GSList *tflist;
+ struct jabber_transfer *tf = NULL;
+ struct xt_node *query;
+ struct jabber_data *jd = ic->proto_data;
+
+ query = xt_find_node( orig->children, "query" );
+ sid = xt_find_attr( query, "sid" );
+
+ for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) )
+ {
+ struct jabber_transfer *tft = tflist->data;
+ if( ( strcmp( tft->sid, sid ) == 0 ) )
+ {
+ tf = tft;
+ break;
+ }
+ }
+
+ if( !tf )
+ {
+ imcb_log( ic, "WARNING: Received SOCKS5 bytestream activation for unknown stream" );
+ return XT_HANDLED;
+ }
+
+ imcb_log( tf->ic, "File %s: SOCKS5 handshake and activation successful! Transfer about to start...", tf->ft->file_name );
+
+ /* handshake went through, let's start transferring */
+ tf->ft->write_request( tf->ft );
+
+ return XT_HANDLED;
+}
+
+jabber_streamhost_t *jabber_si_parse_proxy( struct im_connection *ic, char *proxy )
+{
+ char *host, *port, *jid;
+ jabber_streamhost_t *sh;
+
+ if( ( ( host = strchr( proxy, ',' ) ) == 0 ) ||
+ ( ( port = strchr( host+1, ',' ) ) == 0 ) ) {
+ imcb_log( ic, "Error parsing proxy setting: \"%s\" (ignored)", proxy );
+ return NULL;
+ }
+
+ jid = proxy;
+ *host++ = '\0';
+ *port++ = '\0';
+
+ sh = g_new0( jabber_streamhost_t, 1 );
+ sh->jid = g_strdup( jid );
+ sh->host = g_strdup( host );
+ strcpy( sh->port, port );
+
+ return sh;
+}
+
+void jabber_si_set_proxies( struct bs_transfer *bt )
+{
+ struct jabber_transfer *tf = bt->tf;
+ struct jabber_data *jd = tf->ic->proto_data;
+ char *proxysetting = g_strdup ( set_getstr( &tf->ic->acc->set, "proxy" ) );
+ char *proxy, *next, *errmsg = NULL;
+ char port[6];
+ char host[HOST_NAME_MAX+1];
+ jabber_streamhost_t *sh, *sh2;
+ GSList *streamhosts = jd->streamhosts;
+
+ proxy = proxysetting;
+ while ( proxy && ( *proxy!='\0' ) ) {
+ if( ( next = strchr( proxy, ';' ) ) )
+ *next++ = '\0';
+
+ if( strcmp( proxy, "<local>" ) == 0 ) {
+ if( ( tf->fd = ft_listen( &tf->saddr, host, port, jd->fd, 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/sasl.c b/protocols/jabber/sasl.c
new file mode 100644
index 00000000..53248ef3
--- /dev/null
+++ b/protocols/jabber/sasl.c
@@ -0,0 +1,348 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Jabber module - SASL authentication *
+* *
+* Copyright 2006 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 <ctype.h>
+
+#include "jabber.h"
+#include "base64.h"
+
+xt_status sasl_pkt_mechanisms( struct xt_node *node, gpointer data )
+{
+ struct im_connection *ic = data;
+ struct jabber_data *jd = ic->proto_data;
+ struct xt_node *c, *reply;
+ char *s;
+ int sup_plain = 0, sup_digest = 0;
+
+ if( !sasl_supported( ic ) )
+ {
+ /* Should abort this now, since we should already be doing
+ IQ authentication. Strange things happen when you try
+ to do both... */
+ imcb_log( ic, "XMPP 1.0 non-compliant server seems to support SASL, please report this as a BitlBee bug!" );
+ return XT_HANDLED;
+ }
+
+ s = xt_find_attr( node, "xmlns" );
+ if( !s || strcmp( s, XMLNS_SASL ) != 0 )
+ {
+ imcb_log( ic, "Stream error while authenticating" );
+ imc_logout( ic, FALSE );
+ return XT_ABORT;
+ }
+
+ c = node->children;
+ while( ( c = xt_find_node( c, "mechanism" ) ) )
+ {
+ if( c->text && g_strcasecmp( c->text, "PLAIN" ) == 0 )
+ sup_plain = 1;
+ if( c->text && g_strcasecmp( c->text, "DIGEST-MD5" ) == 0 )
+ sup_digest = 1;
+
+ c = c->next;
+ }
+
+ if( !sup_plain && !sup_digest )
+ {
+ imcb_error( ic, "No known SASL authentication schemes supported" );
+ imc_logout( ic, FALSE );
+ return XT_ABORT;
+ }
+
+ reply = xt_new_node( "auth", NULL, NULL );
+ xt_add_attr( reply, "xmlns", XMLNS_SASL );
+
+ if( sup_digest )
+ {
+ xt_add_attr( reply, "mechanism", "DIGEST-MD5" );
+
+ /* The rest will be done later, when we receive a <challenge/>. */
+ }
+ else if( sup_plain )
+ {
+ int len;
+
+ xt_add_attr( reply, "mechanism", "PLAIN" );
+
+ /* With SASL PLAIN in XMPP, the text should be b64(\0user\0pass) */
+ len = strlen( jd->username ) + strlen( ic->acc->pass ) + 2;
+ s = g_malloc( len + 1 );
+ s[0] = 0;
+ strcpy( s + 1, jd->username );
+ strcpy( s + 2 + strlen( jd->username ), ic->acc->pass );
+ reply->text = base64_encode( (unsigned char *)s, len );
+ reply->text_len = strlen( reply->text );
+ g_free( s );
+ }
+
+ if( !jabber_write_packet( ic, reply ) )
+ {
+ xt_free_node( reply );
+ return XT_ABORT;
+ }
+ xt_free_node( reply );
+
+ /* To prevent classic authentication from happening. */
+ jd->flags |= JFLAG_STREAM_STARTED;
+
+ return XT_HANDLED;
+}
+
+/* Non-static function, but not mentioned in jabber.h because it's for internal
+ use, just that the unittest should be able to reach it... */
+char *sasl_get_part( char *data, char *field )
+{
+ int i, len;
+
+ len = strlen( field );
+
+ while( isspace( *data ) || *data == ',' )
+ data ++;
+
+ if( g_strncasecmp( data, field, len ) == 0 && data[len] == '=' )
+ {
+ i = strlen( field ) + 1;
+ }
+ else
+ {
+ for( i = 0; data[i]; i ++ )
+ {
+ /* If we have a ", skip until it's closed again. */
+ if( data[i] == '"' )
+ {
+ i ++;
+ while( data[i] != '"' || data[i-1] == '\\' )
+ i ++;
+ }
+
+ /* If we got a comma, we got a new field. Check it,
+ find the next key after it. */
+ if( data[i] == ',' )
+ {
+ while( isspace( data[i] ) || data[i] == ',' )
+ i ++;
+
+ if( g_strncasecmp( data + i, field, len ) == 0 &&
+ data[i+len] == '=' )
+ {
+ i += len + 1;
+ break;
+ }
+ }
+ }
+ }
+
+ if( data[i] == '"' )
+ {
+ int j;
+ char *ret;
+
+ i ++;
+ len = 0;
+ while( data[i+len] != '"' || data[i+len-1] == '\\' )
+ len ++;
+
+ ret = g_strndup( data + i, len );
+ for( i = j = 0; ret[i]; i ++ )
+ {
+ if( ret[i] == '\\' )
+ {
+ ret[j++] = ret[++i];
+ }
+ else
+ {
+ ret[j++] = ret[i];
+ }
+ }
+ ret[j] = 0;
+
+ return ret;
+ }
+ else if( data[i] )
+ {
+ len = 0;
+ while( data[i+len] && data[i+len] != ',' )
+ len ++;
+
+ return g_strndup( data + i, len );
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+xt_status sasl_pkt_challenge( struct xt_node *node, gpointer data )
+{
+ struct im_connection *ic = data;
+ struct jabber_data *jd = ic->proto_data;
+ struct xt_node *reply = NULL;
+ char *nonce = NULL, *realm = NULL, *cnonce = NULL;
+ unsigned char cnonce_bin[30];
+ char *digest_uri = NULL;
+ char *dec = NULL;
+ char *s = NULL;
+ xt_status ret = XT_ABORT;
+
+ if( node->text_len == 0 )
+ goto error;
+
+ dec = frombase64( node->text );
+
+ if( !( s = sasl_get_part( dec, "rspauth" ) ) )
+ {
+ /* See RFC 2831 for for information. */
+ md5_state_t A1, A2, H;
+ md5_byte_t A1r[16], A2r[16], Hr[16];
+ char A1h[33], A2h[33], Hh[33];
+ int i;
+
+ nonce = sasl_get_part( dec, "nonce" );
+ realm = sasl_get_part( dec, "realm" );
+
+ if( !nonce )
+ goto error;
+
+ /* Jabber.Org considers the realm part optional and doesn't
+ specify one. Oh well, actually they're right, but still,
+ don't know if this is right... */
+ if( !realm )
+ realm = g_strdup( jd->server );
+
+ random_bytes( cnonce_bin, sizeof( cnonce_bin ) );
+ cnonce = base64_encode( cnonce_bin, sizeof( cnonce_bin ) );
+ digest_uri = g_strdup_printf( "%s/%s", "xmpp", jd->server );
+
+ /* Generate the MD5 hash of username:realm:password,
+ I decided to call it H. */
+ md5_init( &H );
+ s = g_strdup_printf( "%s:%s:%s", jd->username, realm, ic->acc->pass );
+ md5_append( &H, (unsigned char *) s, strlen( s ) );
+ g_free( s );
+ md5_finish( &H, Hr );
+
+ /* Now generate the hex. MD5 hash of H:nonce:cnonce, called A1. */
+ md5_init( &A1 );
+ s = g_strdup_printf( ":%s:%s", nonce, cnonce );
+ md5_append( &A1, Hr, 16 );
+ md5_append( &A1, (unsigned char *) s, strlen( s ) );
+ g_free( s );
+ md5_finish( &A1, A1r );
+ for( i = 0; i < 16; i ++ )
+ sprintf( A1h + i * 2, "%02x", A1r[i] );
+
+ /* A2... */
+ md5_init( &A2 );
+ s = g_strdup_printf( "%s:%s", "AUTHENTICATE", digest_uri );
+ md5_append( &A2, (unsigned char *) s, strlen( s ) );
+ g_free( s );
+ md5_finish( &A2, A2r );
+ for( i = 0; i < 16; i ++ )
+ sprintf( A2h + i * 2, "%02x", A2r[i] );
+
+ /* Final result: A1:nonce:00000001:cnonce:auth:A2. Let's reuse H for it. */
+ md5_init( &H );
+ s = g_strdup_printf( "%s:%s:%s:%s:%s:%s", A1h, nonce, "00000001", cnonce, "auth", A2h );
+ md5_append( &H, (unsigned char *) s, strlen( s ) );
+ g_free( s );
+ md5_finish( &H, Hr );
+ for( i = 0; i < 16; i ++ )
+ sprintf( Hh + i * 2, "%02x", Hr[i] );
+
+ /* Now build the SASL response string: */
+ g_free( dec );
+ dec = g_strdup_printf( "username=\"%s\",realm=\"%s\",nonce=\"%s\",cnonce=\"%s\","
+ "nc=%08x,qop=auth,digest-uri=\"%s\",response=%s,charset=%s",
+ jd->username, realm, nonce, cnonce, 1, digest_uri, Hh, "utf-8" );
+ s = tobase64( dec );
+ }
+ else
+ {
+ /* We found rspauth, but don't really care... */
+ g_free( s );
+ s = NULL;
+ }
+
+ reply = xt_new_node( "response", s, NULL );
+ xt_add_attr( reply, "xmlns", XMLNS_SASL );
+
+ if( !jabber_write_packet( ic, reply ) )
+ goto silent_error;
+
+ ret = XT_HANDLED;
+ goto silent_error;
+
+error:
+ imcb_error( ic, "Incorrect SASL challenge received" );
+ imc_logout( ic, FALSE );
+
+silent_error:
+ g_free( digest_uri );
+ g_free( cnonce );
+ g_free( nonce );
+ g_free( realm );
+ g_free( dec );
+ g_free( s );
+ xt_free_node( reply );
+
+ return ret;
+}
+
+xt_status sasl_pkt_result( struct xt_node *node, gpointer data )
+{
+ struct im_connection *ic = data;
+ struct jabber_data *jd = ic->proto_data;
+ char *s;
+
+ s = xt_find_attr( node, "xmlns" );
+ if( !s || strcmp( s, XMLNS_SASL ) != 0 )
+ {
+ imcb_log( ic, "Stream error while authenticating" );
+ imc_logout( ic, FALSE );
+ return XT_ABORT;
+ }
+
+ if( strcmp( node->name, "success" ) == 0 )
+ {
+ imcb_log( ic, "Authentication finished" );
+ jd->flags |= JFLAG_AUTHENTICATED | JFLAG_STREAM_RESTART;
+ }
+ else if( strcmp( node->name, "failure" ) == 0 )
+ {
+ imcb_error( ic, "Authentication failure" );
+ imc_logout( ic, FALSE );
+ return XT_ABORT;
+ }
+
+ return XT_HANDLED;
+}
+
+/* This one is needed to judge if we'll do authentication using IQ or SASL.
+ It's done by checking if the <stream:stream> from the server has a
+ version attribute. I don't know if this is the right way though... */
+gboolean sasl_supported( struct im_connection *ic )
+{
+ struct jabber_data *jd = ic->proto_data;
+
+ return ( jd->xt && jd->xt->root && xt_find_attr( jd->xt->root, "version" ) ) != 0;
+}
diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c
new file mode 100644
index 00000000..4b0e57c4
--- /dev/null
+++ b/protocols/jabber/si.c
@@ -0,0 +1,533 @@
+/***************************************************************************\
+* *
+* 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;
+ }
+ else
+ {
+ c = c->next;
+ }
+
+ if ( !requestok )
+ imcb_log( ic, "WARNING: Unsupported file transfer request from %s", ini_jid);
+ }
+
+ if( requestok )
+ {
+ /* Figure out who the transfer should come frome... */
+
+ ext_jid = ini_jid;
+ if( ( s = strchr( ini_jid, '/' ) ) )
+ {
+ if( ( bud = jabber_buddy_by_jid( ic, ini_jid, GET_BUDDY_EXACT ) ) )
+ {
+ bud->last_msg = time( NULL );
+ ext_jid = bud->ext_jid ? : bud->bare_jid;
+ }
+ else
+ *s = 0; /* We need to generate a bare JID now. */
+ }
+
+ if( !( ft = imcb_file_send_start( ic, ext_jid, name, size ) ) )
+ {
+ imcb_log( ic, "WARNING: Error handling transfer request from %s", ini_jid);
+ requestok = FALSE;
+ }
+
+ *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 = NULL, *tgt_jid, *iq_id, *cmp;
+ GSList *tflist;
+ struct jabber_transfer *tf=NULL;
+ struct jabber_data *jd = ic->proto_data;
+
+ if( !( tgt_jid = xt_find_attr( node, "from" ) ) ||
+ !( ini_jid = xt_find_attr( node, "to" ) ) )
+ {
+ imcb_log( ic, "Invalid SI response from=%s to=%s", tgt_jid, ini_jid );
+ return XT_HANDLED;
+ }
+
+ /* All this means we expect something like this: ( I think )
+ * <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
new file mode 100644
index 00000000..8845d41b
--- /dev/null
+++ b/protocols/msn/Makefile
@@ -0,0 +1,46 @@
+###########################
+## Makefile for BitlBee ##
+## ##
+## Copyright 2002 Lintux ##
+###########################
+
+### DEFINITIONS
+
+-include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/msn/
+endif
+
+# [SH] Program variables
+objects = msn.o msn_util.o ns.o sb.o soap.o tables.o
+
+LFLAGS += -r
+
+# [SH] Phony targets
+all: msn_mod.o
+check: all
+lcov: check
+gcov:
+ gcov *.c
+
+.PHONY: all clean distclean
+
+clean:
+ rm -f *.o core
+
+distclean: clean
+ rm -rf .depend
+
+### MAIN PROGRAM
+
+$(objects): ../../Makefile.settings Makefile
+
+$(objects): %.o: $(SRCDIR)%.c
+ @echo '*' Compiling $<
+ @$(CC) -c $(CFLAGS) $< -o $@
+
+msn_mod.o: $(objects)
+ @echo '*' Linking msn_mod.o
+ @$(LD) $(LFLAGS) $(objects) -o msn_mod.o
+
+-include .depend/*.d
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
new file mode 100644
index 00000000..03f28422
--- /dev/null
+++ b/protocols/msn/msn.c
@@ -0,0 +1,415 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* MSN module - Main file; functions to be called from BitlBee */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "nogaim.h"
+#include "soap.h"
+#include "msn.h"
+
+int msn_chat_id;
+GSList *msn_connections;
+GSList *msn_switchboards;
+
+static char *set_eval_display_name( set_t *set, char *value );
+
+static void msn_init( account_t *acc )
+{
+ set_t *s;
+
+ s = set_add( &acc->set, "display_name", NULL, set_eval_display_name, acc );
+ s->flags |= ACC_SET_NOSAVE | ACC_SET_ONLINE_ONLY;
+
+ set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc );
+ set_add( &acc->set, "switchboard_keepalives", "false", set_eval_bool, acc );
+
+ acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE;
+}
+
+static void msn_login( account_t *acc )
+{
+ struct im_connection *ic = imcb_new( acc );
+ struct msn_data *md = g_new0( struct msn_data, 1 );
+
+ ic->proto_data = md;
+
+ if( strchr( acc->user, '@' ) == NULL )
+ {
+ imcb_error( ic, "Invalid account name" );
+ imc_logout( ic, FALSE );
+ return;
+ }
+
+ md->ic = ic;
+ md->away_state = msn_away_state_list;
+ md->domaintree = g_tree_new( msn_domaintree_cmp );
+ md->ns->fd = -1;
+
+ msn_connections = g_slist_prepend( msn_connections, ic );
+
+ imcb_log( ic, "Connecting" );
+ msn_ns_connect( ic, md->ns, MSN_NS_HOST, MSN_NS_PORT );
+}
+
+static void msn_logout( struct im_connection *ic )
+{
+ struct msn_data *md = ic->proto_data;
+ GSList *l;
+ int i;
+
+ if( md )
+ {
+ /** Disabling MSN ft support for now.
+ while( md->filetransfers ) {
+ imcb_file_canceled( md->filetransfers->data, "Closing connection" );
+ }
+ */
+
+ msn_ns_close( md->ns );
+
+ while( md->switchboards )
+ msn_sb_destroy( md->switchboards->data );
+
+ msn_msgq_purge( ic, &md->msgq );
+ msn_soapq_flush( ic, FALSE );
+
+ for( i = 0; i < sizeof( md->tokens ) / sizeof( md->tokens[0] ); i ++ )
+ g_free( md->tokens[i] );
+ g_free( md->lock_key );
+ g_free( md->pp_policy );
+
+ while( md->groups )
+ {
+ struct msn_group *mg = md->groups->data;
+ g_free( mg->id );
+ g_free( mg->name );
+ g_free( mg );
+ md->groups = g_slist_remove( md->groups, mg );
+ }
+
+ g_free( md->profile_rid );
+
+ if( md->domaintree )
+ g_tree_destroy( md->domaintree );
+ md->domaintree = NULL;
+
+ while( md->grpq )
+ {
+ struct msn_groupadd *ga = md->grpq->data;
+ g_free( ga->group );
+ g_free( ga->who );
+ g_free( ga );
+ md->grpq = g_slist_remove( md->grpq, ga );
+ }
+
+ g_free( md );
+ }
+
+ for( l = ic->permit; l; l = l->next )
+ g_free( l->data );
+ g_slist_free( ic->permit );
+
+ for( l = ic->deny; l; l = l->next )
+ g_free( l->data );
+ g_slist_free( ic->deny );
+
+ msn_connections = g_slist_remove( msn_connections, ic );
+}
+
+static int msn_buddy_msg( struct im_connection *ic, char *who, char *message, int away )
+{
+ struct msn_switchboard *sb;
+
+#ifdef DEBUG
+ if( strcmp( who, "raw" ) == 0 )
+ {
+ msn_ns_write( ic, -1, "%s\r\n", message );
+ }
+ else
+#endif
+ if( ( sb = msn_sb_by_handle( ic, who ) ) )
+ {
+ return( msn_sb_sendmessage( sb, message ) );
+ }
+ else
+ {
+ struct msn_message *m;
+
+ /* Create a message. We have to arrange a usable switchboard, and send the message later. */
+ m = g_new0( struct msn_message, 1 );
+ m->who = g_strdup( who );
+ m->text = g_strdup( message );
+
+ return msn_sb_write_msg( ic, m );
+ }
+
+ return( 0 );
+}
+
+static GList *msn_away_states( struct im_connection *ic )
+{
+ static GList *l = NULL;
+ int i;
+
+ if( l == NULL )
+ for( i = 0; *msn_away_state_list[i].code; i ++ )
+ if( *msn_away_state_list[i].name )
+ l = g_list_append( l, (void*) msn_away_state_list[i].name );
+
+ return l;
+}
+
+static void msn_set_away( struct im_connection *ic, char *state, char *message )
+{
+ char *uux;
+ struct msn_data *md = ic->proto_data;
+
+ if( state == NULL )
+ md->away_state = msn_away_state_list;
+ else if( ( md->away_state = msn_away_state_by_name( state ) ) == NULL )
+ md->away_state = msn_away_state_list + 1;
+
+ if( !msn_ns_write( ic, -1, "CHG %d %s\r\n", ++md->trId, md->away_state->code ) )
+ return;
+
+ uux = g_markup_printf_escaped( "<Data><PSM>%s</PSM><CurrentMedia></CurrentMedia>"
+ "</Data>", message ? message : "" );
+ msn_ns_write( ic, -1, "UUX %d %zd\r\n%s", ++md->trId, strlen( uux ), uux );
+ g_free( uux );
+}
+
+static void msn_get_info(struct im_connection *ic, char *who)
+{
+ /* Just make an URL and let the user fetch the info */
+ imcb_log( ic, "%s\n%s: %s%s", _("User Info"), _("For now, fetch yourself"), PROFILE_URL, who );
+}
+
+static void msn_add_buddy( struct im_connection *ic, char *who, char *group )
+{
+ struct bee_user *bu = bee_user_by_handle( ic->bee, ic, who );
+
+ msn_buddy_list_add( ic, MSN_BUDDY_FL, who, who, group );
+ if( bu && bu->group )
+ msn_buddy_list_remove( ic, MSN_BUDDY_FL, who, bu->group->name );
+}
+
+static void msn_remove_buddy( struct im_connection *ic, char *who, char *group )
+{
+ msn_buddy_list_remove( ic, MSN_BUDDY_FL, who, NULL );
+}
+
+static void msn_chat_msg( struct groupchat *c, char *message, int flags )
+{
+ struct msn_switchboard *sb = msn_sb_by_chat( c );
+
+ if( sb )
+ msn_sb_sendmessage( sb, message );
+ /* FIXME: Error handling (although this can't happen unless something's
+ already severely broken) disappeared here! */
+}
+
+static void msn_chat_invite( struct groupchat *c, char *who, char *message )
+{
+ struct msn_switchboard *sb = msn_sb_by_chat( c );
+ char buf[1024];
+
+ if( sb )
+ {
+ g_snprintf( buf, sizeof( buf ), "CAL %d %s\r\n", ++sb->trId, who );
+ msn_sb_write( sb, buf, strlen( buf ) );
+ }
+}
+
+static void msn_chat_leave( struct groupchat *c )
+{
+ struct msn_switchboard *sb = msn_sb_by_chat( c );
+
+ if( sb )
+ msn_sb_write( sb, "OUT\r\n", 5 );
+}
+
+static struct groupchat *msn_chat_with( struct im_connection *ic, char *who )
+{
+ struct msn_switchboard *sb;
+ struct groupchat *c = imcb_chat_new( ic, who );
+
+ if( ( sb = msn_sb_by_handle( ic, who ) ) )
+ {
+ debug( "Converting existing switchboard to %s to a groupchat", who );
+ return msn_sb_to_chat( sb );
+ }
+ else
+ {
+ struct msn_message *m;
+
+ /* Create a magic message. This is quite hackish, but who cares? :-P */
+ m = g_new0( struct msn_message, 1 );
+ m->who = g_strdup( who );
+ m->text = g_strdup( GROUPCHAT_SWITCHBOARD_MESSAGE );
+
+ msn_sb_write_msg( ic, m );
+
+ return c;
+ }
+}
+
+static void msn_keepalive( struct im_connection *ic )
+{
+ msn_ns_write( ic, -1, "PNG\r\n" );
+}
+
+static void msn_add_permit( struct im_connection *ic, char *who )
+{
+ msn_buddy_list_add( ic, MSN_BUDDY_AL, who, who, NULL );
+}
+
+static void msn_rem_permit( struct im_connection *ic, char *who )
+{
+ msn_buddy_list_remove( ic, MSN_BUDDY_AL, who, NULL );
+}
+
+static void msn_add_deny( struct im_connection *ic, char *who )
+{
+ struct msn_switchboard *sb;
+
+ msn_buddy_list_add( ic, MSN_BUDDY_BL, who, who, NULL );
+
+ /* If there's still a conversation with this person, close it. */
+ if( ( sb = msn_sb_by_handle( ic, who ) ) )
+ {
+ msn_sb_destroy( sb );
+ }
+}
+
+static void msn_rem_deny( struct im_connection *ic, char *who )
+{
+ msn_buddy_list_remove( ic, MSN_BUDDY_BL, who, NULL );
+}
+
+static int msn_send_typing( struct im_connection *ic, char *who, int typing )
+{
+ struct bee_user *bu = bee_user_by_handle( ic->bee, ic, who );
+
+ if( !( bu->flags & BEE_USER_ONLINE ) )
+ return 0;
+ else if( typing & OPT_TYPING )
+ return( msn_buddy_msg( ic, who, TYPING_NOTIFICATION_MESSAGE, 0 ) );
+ else
+ return 1;
+}
+
+static char *set_eval_display_name( set_t *set, char *value )
+{
+ account_t *acc = set->data;
+ struct im_connection *ic = acc->ic;
+ struct msn_data *md = ic->proto_data;
+
+ if( md->flags & MSN_EMAIL_UNVERIFIED )
+ imcb_log( ic, "Warning: Your e-mail address is unverified. MSN doesn't allow "
+ "changing your display name until your e-mail address is verified." );
+
+ if( md->flags & MSN_GOT_PROFILE_DN )
+ msn_soap_profile_set_dn( ic, value );
+ else
+ msn_soap_addressbook_set_display_name( ic, value );
+
+ return msn_ns_set_display_name( ic, value ) ? value : NULL;
+}
+
+static void msn_buddy_data_add( bee_user_t *bu )
+{
+ struct msn_data *md = bu->ic->proto_data;
+ bu->data = g_new0( struct msn_buddy_data, 1 );
+ g_tree_insert( md->domaintree, bu->handle, bu );
+}
+
+static void msn_buddy_data_free( bee_user_t *bu )
+{
+ struct msn_data *md = bu->ic->proto_data;
+ struct msn_buddy_data *bd = bu->data;
+
+ g_free( bd->cid );
+ g_free( bd );
+
+ g_tree_remove( md->domaintree, bu->handle );
+}
+
+GList *msn_buddy_action_list( bee_user_t *bu )
+{
+ static GList *ret = NULL;
+
+ if( ret == NULL )
+ {
+ static const struct buddy_action ba[2] = {
+ { "NUDGE", "Draw attention" },
+ };
+
+ ret = g_list_prepend( ret, (void*) ba + 0 );
+ }
+
+ return ret;
+}
+
+void *msn_buddy_action( struct bee_user *bu, const char *action, char * const args[], void *data )
+{
+ if( g_strcasecmp( action, "NUDGE" ) == 0 )
+ msn_buddy_msg( bu->ic, bu->handle, NUDGE_MESSAGE, 0 );
+
+ return NULL;
+}
+
+void msn_initmodule()
+{
+ struct prpl *ret = g_new0(struct prpl, 1);
+
+ ret->name = "msn";
+ ret->mms = 1409; /* this guess taken from libotr UPGRADING file */
+ ret->login = msn_login;
+ ret->init = msn_init;
+ ret->logout = msn_logout;
+ ret->buddy_msg = msn_buddy_msg;
+ ret->away_states = msn_away_states;
+ ret->set_away = msn_set_away;
+ ret->get_info = msn_get_info;
+ ret->add_buddy = msn_add_buddy;
+ ret->remove_buddy = msn_remove_buddy;
+ ret->chat_msg = msn_chat_msg;
+ ret->chat_invite = msn_chat_invite;
+ ret->chat_leave = msn_chat_leave;
+ ret->chat_with = msn_chat_with;
+ ret->keepalive = msn_keepalive;
+ ret->add_permit = msn_add_permit;
+ ret->rem_permit = msn_rem_permit;
+ ret->add_deny = msn_add_deny;
+ ret->rem_deny = msn_rem_deny;
+ ret->send_typing = msn_send_typing;
+ ret->handle_cmp = g_strcasecmp;
+ ret->buddy_data_add = msn_buddy_data_add;
+ ret->buddy_data_free = msn_buddy_data_free;
+ ret->buddy_action_list = msn_buddy_action_list;
+ ret->buddy_action = msn_buddy_action;
+
+ //ret->transfer_request = msn_ftp_transfer_request;
+
+ register_protocol(ret);
+}
diff --git a/protocols/msn/msn.h b/protocols/msn/msn.h
new file mode 100644
index 00000000..da9430ab
--- /dev/null
+++ b/protocols/msn/msn.h
@@ -0,0 +1,266 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* MSN module */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#ifndef _MSN_H
+#define _MSN_H
+
+/* Some hackish magicstrings to make special-purpose messages/switchboards.
+ */
+#define TYPING_NOTIFICATION_MESSAGE "\r\r\rBEWARE, ME R TYPINK MESSAGE!!!!\r\r\r"
+#define NUDGE_MESSAGE "\r\r\rSHAKE THAT THING\r\r\r"
+#define GROUPCHAT_SWITCHBOARD_MESSAGE "\r\r\rME WANT TALK TO MANY PEOPLE\r\r\r"
+#define SB_KEEPALIVE_MESSAGE "\r\r\rDONT HANG UP ON ME!\r\r\r"
+
+#ifdef DEBUG_MSN
+#define debug( text... ) imcb_log( ic, text );
+#else
+#define debug( text... )
+#endif
+
+/* This should be MSN Messenger 7.0.0813
+#define MSNP11_PROD_KEY "CFHUR$52U_{VIX5T"
+#define MSNP11_PROD_ID "PROD0101{0RM?UBW"
+*/
+
+#define MSN_NS_HOST "messenger.hotmail.com"
+#define MSN_NS_PORT 1863
+
+/* Some other version.
+#define MSNP11_PROD_KEY "O4BG@C7BWLYQX?5G"
+#define MSNP11_PROD_ID "PROD01065C%ZFN6F"
+*/
+
+#define MSNP11_PROD_KEY "ILTXC!4IXB5FB*PX"
+#define MSNP11_PROD_ID "PROD0119GSJUC$18"
+#define MSNP_VER "MSNP15"
+#define MSNP_BUILD "8.5.1288"
+
+#define MSN_SB_NEW -24062002
+
+#define MSN_MESSAGE_HEADERS "MIME-Version: 1.0\r\n" \
+ "Content-Type: text/plain; charset=UTF-8\r\n" \
+ "User-Agent: BitlBee " BITLBEE_VERSION "\r\n" \
+ "X-MMS-IM-Format: FN=MS%20Shell%20Dlg; EF=; CO=0; CS=0; PF=0\r\n" \
+ "\r\n"
+
+#define MSN_TYPING_HEADERS "MIME-Version: 1.0\r\n" \
+ "Content-Type: text/x-msmsgscontrol\r\n" \
+ "TypingUser: %s\r\n" \
+ "\r\n\r\n"
+
+#define MSN_NUDGE_HEADERS "MIME-Version: 1.0\r\n" \
+ "Content-Type: text/x-msnmsgr-datacast\r\n" \
+ "\r\n" \
+ "ID: 1\r\n" \
+ "\r\n"
+
+#define MSN_SB_KEEPALIVE_HEADERS "MIME-Version: 1.0\r\n" \
+ "Content-Type: text/x-ping\r\n" \
+ "\r\n\r\n"
+
+#define PROFILE_URL "http://members.msn.com/"
+
+typedef enum
+{
+ MSN_GOT_PROFILE = 1,
+ MSN_GOT_PROFILE_DN = 2,
+ MSN_DONE_ADL = 4,
+ MSN_REAUTHING = 8,
+ MSN_EMAIL_UNVERIFIED = 16,
+} msn_flags_t;
+
+struct msn_handler_data
+{
+ int fd, inpa;
+ int rxlen;
+ char *rxq;
+
+ int msglen;
+ char *cmd_text;
+
+ /* Either ic or sb */
+ gpointer data;
+
+ int (*exec_command) ( struct msn_handler_data *handler, char **cmd, int count );
+ int (*exec_message) ( struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int count );
+};
+
+struct msn_data
+{
+ struct im_connection *ic;
+
+ struct msn_handler_data ns[1];
+ msn_flags_t flags;
+
+ int trId;
+ char *tokens[4];
+ char *lock_key, *pp_policy;
+
+ GSList *msgq, *grpq, *soapq;
+ GSList *switchboards;
+ int sb_failures;
+ time_t first_sb_failure;
+
+ const struct msn_away_state *away_state;
+ GSList *groups;
+ char *profile_rid;
+
+ /* Mostly used for sending the ADL command; since MSNP13 the client
+ is responsible for downloading the contact list and then sending
+ it to the MSNP server. */
+ GTree *domaintree;
+ int adl_todo;
+};
+
+struct msn_switchboard
+{
+ struct im_connection *ic;
+
+ /* The following two are also in the handler. TODO: Clean up. */
+ int fd;
+ gint inp;
+ struct msn_handler_data *handler;
+ gint keepalive;
+
+ int trId;
+ int ready;
+
+ int session;
+ char *key;
+
+ GSList *msgq;
+ char *who;
+ struct groupchat *chat;
+};
+
+struct msn_away_state
+{
+ char code[4];
+ char name[16];
+};
+
+struct msn_status_code
+{
+ int number;
+ char *text;
+ int flags;
+};
+
+struct msn_message
+{
+ char *who;
+ char *text;
+};
+
+struct msn_groupadd
+{
+ char *who;
+ char *group;
+};
+
+typedef enum
+{
+ MSN_BUDDY_FL = 1, /* Warning: FL,AL,BL *must* be 1,2,4. */
+ MSN_BUDDY_AL = 2,
+ MSN_BUDDY_BL = 4,
+ MSN_BUDDY_RL = 8,
+ MSN_BUDDY_PL = 16,
+ MSN_BUDDY_ADL_SYNCED = 256,
+} msn_buddy_flags_t;
+
+struct msn_buddy_data
+{
+ char *cid;
+ msn_buddy_flags_t flags;
+};
+
+struct msn_group
+{
+ char *name;
+ char *id;
+};
+
+/* Bitfield values for msn_status_code.flags */
+#define STATUS_FATAL 1
+#define STATUS_SB_FATAL 2
+#define STATUS_SB_IM_SPARE 4 /* Make one-to-one conversation switchboard available again, invite failed. */
+#define STATUS_SB_CHAT_SPARE 8 /* Same, but also for groupchats (not used yet). */
+
+extern int msn_chat_id;
+extern const struct msn_away_state msn_away_state_list[];
+extern const struct msn_status_code msn_status_code_list[];
+
+/* Keep a list of all the active connections. We need these lists because
+ "connected" callbacks might be called when the connection they belong too
+ is down already (for example, when an impatient user disabled the
+ connection), the callback should check whether it's still listed here
+ before doing *anything* else. */
+extern GSList *msn_connections;
+extern GSList *msn_switchboards;
+
+/* ns.c */
+int msn_ns_write( struct im_connection *ic, int fd, const char *fmt, ... );
+gboolean msn_ns_connect( struct im_connection *ic, struct msn_handler_data *handler, const char *host, int port );
+void msn_ns_close( struct msn_handler_data *handler );
+void msn_auth_got_passport_token( struct im_connection *ic, const char *token, const char *error );
+void msn_auth_got_contact_list( struct im_connection *ic );
+int msn_ns_finish_login( struct im_connection *ic );
+
+/* msn_util.c */
+int msn_logged_in( struct im_connection *ic );
+int msn_buddy_list_add( struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *realname_, const char *group );
+int msn_buddy_list_remove( struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *group );
+void msn_buddy_ask( bee_user_t *bu );
+char *msn_findheader( char *text, char *header, int len );
+char **msn_linesplit( char *line );
+int msn_handler( struct msn_handler_data *h );
+void msn_msgq_purge( struct im_connection *ic, GSList **list );
+char *msn_p11_challenge( char *challenge );
+gint msn_domaintree_cmp( gconstpointer a_, gconstpointer b_ );
+struct msn_group *msn_group_by_name( struct im_connection *ic, const char *name );
+struct msn_group *msn_group_by_id( struct im_connection *ic, const char *id );
+int msn_ns_set_display_name( struct im_connection *ic, const char *value );
+
+/* tables.c */
+const struct msn_away_state *msn_away_state_by_number( int number );
+const struct msn_away_state *msn_away_state_by_code( char *code );
+const struct msn_away_state *msn_away_state_by_name( char *name );
+const struct msn_status_code *msn_status_by_number( int number );
+
+/* sb.c */
+int msn_sb_write( struct msn_switchboard *sb, const char *fmt, ... );
+struct msn_switchboard *msn_sb_create( struct im_connection *ic, char *host, int port, char *key, int session );
+struct msn_switchboard *msn_sb_by_handle( struct im_connection *ic, char *handle );
+struct msn_switchboard *msn_sb_by_chat( struct groupchat *c );
+struct msn_switchboard *msn_sb_spare( struct im_connection *ic );
+int msn_sb_sendmessage( struct msn_switchboard *sb, char *text );
+struct groupchat *msn_sb_to_chat( struct msn_switchboard *sb );
+void msn_sb_destroy( struct msn_switchboard *sb );
+gboolean msn_sb_connected( gpointer data, gint source, b_input_condition cond );
+int msn_sb_write_msg( struct im_connection *ic, struct msn_message *m );
+void msn_sb_start_keepalives( struct msn_switchboard *sb, gboolean initial );
+void msn_sb_stop_keepalives( struct msn_switchboard *sb );
+
+#endif //_MSN_H
diff --git a/protocols/msn/msn_util.c b/protocols/msn/msn_util.c
new file mode 100644
index 00000000..7fa68915
--- /dev/null
+++ b/protocols/msn/msn_util.c
@@ -0,0 +1,590 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* MSN module - Miscellaneous utilities */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "nogaim.h"
+#include "msn.h"
+#include "md5.h"
+#include "soap.h"
+#include <ctype.h>
+
+int msn_logged_in( struct im_connection *ic )
+{
+ imcb_connected( ic );
+
+ return( 0 );
+}
+
+static char *adlrml_entry( const char *handle_, msn_buddy_flags_t list )
+{
+ char *domain, handle[strlen(handle_)+1];
+
+ strcpy( handle, handle_ );
+ if( ( domain = strchr( handle, '@' ) ) )
+ *(domain++) = '\0';
+ else
+ return NULL;
+
+ return g_markup_printf_escaped( "<ml><d n=\"%s\"><c n=\"%s\" l=\"%d\" t=\"1\"/></d></ml>",
+ domain, handle, list );
+}
+
+int msn_buddy_list_add( struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *realname, const char *group )
+{
+ struct msn_data *md = ic->proto_data;
+ char groupid[8];
+ bee_user_t *bu;
+ struct msn_buddy_data *bd;
+ char *adl;
+
+ *groupid = '\0';
+#if 0
+ if( group )
+ {
+ int i;
+ for( i = 0; i < md->groupcount; i ++ )
+ if( g_strcasecmp( md->grouplist[i], group ) == 0 )
+ {
+ g_snprintf( groupid, sizeof( groupid ), " %d", i );
+ break;
+ }
+
+ if( *groupid == '\0' )
+ {
+ /* Have to create this group, it doesn't exist yet. */
+ struct msn_groupadd *ga;
+ GSList *l;
+
+ for( l = md->grpq; l; l = l->next )
+ {
+ ga = l->data;
+ if( g_strcasecmp( ga->group, group ) == 0 )
+ break;
+ }
+
+ ga = g_new0( struct msn_groupadd, 1 );
+ ga->who = g_strdup( who );
+ ga->group = g_strdup( group );
+ md->grpq = g_slist_prepend( md->grpq, ga );
+
+ if( l == NULL )
+ {
+ char groupname[strlen(group)+1];
+ strcpy( groupname, group );
+ http_encode( groupname );
+ g_snprintf( buf, sizeof( buf ), "ADG %d %s %d\r\n", ++md->trId, groupname, 0 );
+ return msn_write( ic, buf, strlen( buf ) );
+ }
+ else
+ {
+ /* This can happen if the user's doing lots of adds to a
+ new group at once; we're still waiting for the server
+ to confirm group creation. */
+ return 1;
+ }
+ }
+ }
+#endif
+
+ if( !( ( bu = bee_user_by_handle( ic->bee, ic, who ) ) ||
+ ( bu = bee_user_new( ic->bee, ic, who, 0 ) ) ) ||
+ !( bd = bu->data ) || bd->flags & list )
+ return 1;
+
+ bd->flags |= list;
+
+ if( list == MSN_BUDDY_FL )
+ msn_soap_ab_contact_add( ic, bu );
+ else
+ msn_soap_memlist_edit( ic, who, TRUE, list );
+
+ if( ( adl = adlrml_entry( who, list ) ) )
+ {
+ int st = msn_ns_write( ic, -1, "ADL %d %zd\r\n%s",
+ ++md->trId, strlen( adl ), adl );
+ g_free( adl );
+
+ return st;
+ }
+
+ return 1;
+}
+
+int msn_buddy_list_remove( struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *group )
+{
+ struct msn_data *md = ic->proto_data;
+ char groupid[8];
+ bee_user_t *bu;
+ struct msn_buddy_data *bd;
+ char *adl;
+
+ *groupid = '\0';
+#if 0
+ if( group )
+ {
+ int i;
+ for( i = 0; i < md->groupcount; i ++ )
+ if( g_strcasecmp( md->grouplist[i], group ) == 0 )
+ {
+ g_snprintf( groupid, sizeof( groupid ), " %d", i );
+ break;
+ }
+ }
+#endif
+
+ if( !( bu = bee_user_by_handle( ic->bee, ic, who ) ) ||
+ !( bd = bu->data ) || !( bd->flags & list ) )
+ return 1;
+
+ bd->flags &= ~list;
+
+ if( list == MSN_BUDDY_FL )
+ msn_soap_ab_contact_del( ic, bu );
+ else
+ msn_soap_memlist_edit( ic, who, FALSE, list );
+
+ if( ( adl = adlrml_entry( who, list ) ) )
+ {
+ int st = msn_ns_write( ic, -1, "RML %d %zd\r\n%s",
+ ++md->trId, strlen( adl ), adl );
+ g_free( adl );
+
+ return st;
+ }
+
+ return 1;
+}
+
+struct msn_buddy_ask_data
+{
+ struct im_connection *ic;
+ char *handle;
+ char *realname;
+};
+
+static void msn_buddy_ask_yes( void *data )
+{
+ struct msn_buddy_ask_data *bla = data;
+
+ msn_buddy_list_add( bla->ic, MSN_BUDDY_AL, bla->handle, bla->realname, NULL );
+
+ imcb_ask_add( bla->ic, bla->handle, NULL );
+
+ g_free( bla->handle );
+ g_free( bla->realname );
+ g_free( bla );
+}
+
+static void msn_buddy_ask_no( void *data )
+{
+ struct msn_buddy_ask_data *bla = data;
+
+ msn_buddy_list_add( bla->ic, MSN_BUDDY_BL, bla->handle, bla->realname, NULL );
+
+ g_free( bla->handle );
+ g_free( bla->realname );
+ g_free( bla );
+}
+
+void msn_buddy_ask( bee_user_t *bu )
+{
+ struct msn_buddy_ask_data *bla;
+ struct msn_buddy_data *bd = bu->data;
+ char buf[1024];
+
+ if( ( bd->flags & 30 ) != 8 && ( bd->flags & 30 ) != 16 )
+ return;
+
+ bla = g_new0( struct msn_buddy_ask_data, 1 );
+ bla->ic = bu->ic;
+ bla->handle = g_strdup( bu->handle );
+ bla->realname = g_strdup( bu->fullname );
+
+ g_snprintf( buf, sizeof( buf ),
+ "The user %s (%s) wants to add you to his/her buddy list.",
+ bu->handle, bu->fullname );
+ imcb_ask( bu->ic, buf, bla, msn_buddy_ask_yes, msn_buddy_ask_no );
+}
+
+char *msn_findheader( char *text, char *header, int len )
+{
+ int hlen = strlen( header ), i;
+ char *ret;
+
+ if( len == 0 )
+ len = strlen( text );
+
+ i = 0;
+ while( ( i + hlen ) < len )
+ {
+ /* Maybe this is a bit over-commented, but I just hate this part... */
+ if( g_strncasecmp( text + i, header, hlen ) == 0 )
+ {
+ /* Skip to the (probable) end of the header */
+ i += hlen;
+
+ /* Find the first non-[: \t] character */
+ while( i < len && ( text[i] == ':' || text[i] == ' ' || text[i] == '\t' ) ) i ++;
+
+ /* Make sure we're still inside the string */
+ if( i >= len ) return( NULL );
+
+ /* Save the position */
+ ret = text + i;
+
+ /* Search for the end of this line */
+ while( i < len && text[i] != '\r' && text[i] != '\n' ) i ++;
+
+ /* Make sure we're still inside the string */
+ if( i >= len ) return( NULL );
+
+ /* Copy the found data */
+ return( g_strndup( ret, text + i - ret ) );
+ }
+
+ /* This wasn't the header we were looking for, skip to the next line. */
+ while( i < len && ( text[i] != '\r' && text[i] != '\n' ) ) i ++;
+ while( i < len && ( text[i] == '\r' || text[i] == '\n' ) ) i ++;
+
+ /* End of headers? */
+ if( ( i >= 4 && strncmp( text + i - 4, "\r\n\r\n", 4 ) == 0 ) ||
+ ( i >= 2 && ( strncmp( text + i - 2, "\n\n", 2 ) == 0 ||
+ strncmp( text + i - 2, "\r\r", 2 ) == 0 ) ) )
+ {
+ break;
+ }
+ }
+
+ return( NULL );
+}
+
+/* *NOT* thread-safe, but that's not a problem for now... */
+char **msn_linesplit( char *line )
+{
+ static char **ret = NULL;
+ static int size = 3;
+ int i, n = 0;
+
+ if( ret == NULL )
+ ret = g_new0( char*, size );
+
+ for( i = 0; line[i] && line[i] == ' '; i ++ );
+ if( line[i] )
+ {
+ ret[n++] = line + i;
+ for( i ++; line[i]; i ++ )
+ {
+ if( line[i] == ' ' )
+ line[i] = 0;
+ else if( line[i] != ' ' && !line[i-1] )
+ ret[n++] = line + i;
+
+ if( n >= size )
+ ret = g_renew( char*, ret, size += 2 );
+ }
+ }
+ ret[n] = NULL;
+
+ return( ret );
+}
+
+/* This one handles input from a MSN Messenger server. Both the NS and SB servers usually give
+ commands, but sometimes they give additional data (payload). This function tries to handle
+ this all in a nice way and send all data to the right places. */
+
+/* Return values: -1: Read error, abort connection.
+ 0: Command reported error; Abort *immediately*. (The connection does not exist anymore)
+ 1: OK */
+
+int msn_handler( struct msn_handler_data *h )
+{
+ int st;
+
+ h->rxq = g_renew( char, h->rxq, h->rxlen + 1024 );
+ st = read( h->fd, h->rxq + h->rxlen, 1024 );
+ h->rxlen += st;
+
+ if( st <= 0 )
+ return( -1 );
+
+ if( getenv( "BITLBEE_DEBUG" ) )
+ {
+ write( 2, "->C:", 4 );
+ write( 2, h->rxq + h->rxlen - st, st );
+ }
+
+ while( st )
+ {
+ int i;
+
+ if( h->msglen == 0 )
+ {
+ for( i = 0; i < h->rxlen; i ++ )
+ {
+ if( h->rxq[i] == '\r' || h->rxq[i] == '\n' )
+ {
+ char *cmd_text, **cmd;
+ int count;
+
+ cmd_text = g_strndup( h->rxq, i );
+ cmd = msn_linesplit( cmd_text );
+ for( count = 0; cmd[count]; count ++ );
+ st = h->exec_command( h, cmd, count );
+ g_free( cmd_text );
+
+ /* If the connection broke, don't continue. We don't even exist anymore. */
+ if( !st )
+ return( 0 );
+
+ if( h->msglen )
+ h->cmd_text = g_strndup( h->rxq, i );
+
+ /* Skip to the next non-emptyline */
+ while( i < h->rxlen && ( h->rxq[i] == '\r' || h->rxq[i] == '\n' ) ) i ++;
+
+ break;
+ }
+ }
+
+ /* If we reached the end of the buffer, there's still an incomplete command there.
+ Return and wait for more data. */
+ if( i == h->rxlen && h->rxq[i-1] != '\r' && h->rxq[i-1] != '\n' )
+ break;
+ }
+ else
+ {
+ char *msg, **cmd;
+ int count;
+
+ /* Do we have the complete message already? */
+ if( h->msglen > h->rxlen )
+ break;
+
+ msg = g_strndup( h->rxq, h->msglen );
+ cmd = msn_linesplit( h->cmd_text );
+ for( count = 0; cmd[count]; count ++ );
+
+ st = h->exec_message( h, msg, h->msglen, cmd, count );
+ g_free( msg );
+ g_free( h->cmd_text );
+ h->cmd_text = NULL;
+
+ if( !st )
+ return( 0 );
+
+ i = h->msglen;
+ h->msglen = 0;
+ }
+
+ /* More data after this block? */
+ if( i < h->rxlen )
+ {
+ char *tmp;
+
+ tmp = g_memdup( h->rxq + i, h->rxlen - i );
+ g_free( h->rxq );
+ h->rxq = tmp;
+ h->rxlen -= i;
+ i = 0;
+ }
+ else
+ /* If not, reset the rx queue and get lost. */
+ {
+ g_free( h->rxq );
+ h->rxq = g_new0( char, 1 );
+ h->rxlen = 0;
+ return( 1 );
+ }
+ }
+
+ return( 1 );
+}
+
+void msn_msgq_purge( struct im_connection *ic, GSList **list )
+{
+ struct msn_message *m;
+ GString *ret;
+ GSList *l;
+ int n = 0;
+
+ l = *list;
+ if( l == NULL )
+ return;
+
+ m = l->data;
+ ret = g_string_sized_new( 1024 );
+ g_string_printf( ret, "Warning: Cleaning up MSN (switchboard) connection with unsent "
+ "messages to %s:", m->who ? m->who : "unknown recipient" );
+
+ while( l )
+ {
+ m = l->data;
+
+ if( strncmp( m->text, "\r\r\r", 3 ) != 0 )
+ {
+ g_string_append_printf( ret, "\n%s", m->text );
+ n ++;
+ }
+
+ g_free( m->who );
+ g_free( m->text );
+ g_free( m );
+
+ l = l->next;
+ }
+ g_slist_free( *list );
+ *list = NULL;
+
+ if( n > 0 )
+ imcb_log( ic, "%s", ret->str );
+ g_string_free( ret, TRUE );
+}
+
+/* Copied and heavily modified from http://tmsnc.sourceforge.net/chl.c */
+char *msn_p11_challenge( char *challenge )
+{
+ char *output, buf[256];
+ md5_state_t md5c;
+ unsigned char md5Hash[16], *newHash;
+ unsigned int *md5Parts, *chlStringParts, newHashParts[5];
+ long long nHigh = 0, nLow = 0;
+ int i, n;
+
+ /* Create the MD5 hash */
+ md5_init(&md5c);
+ md5_append(&md5c, (unsigned char*) challenge, strlen(challenge));
+ md5_append(&md5c, (unsigned char*) MSNP11_PROD_KEY, strlen(MSNP11_PROD_KEY));
+ md5_finish(&md5c, md5Hash);
+
+ /* Split it into four integers */
+ md5Parts = (unsigned int *)md5Hash;
+ for (i = 0; i < 4; i ++)
+ {
+ md5Parts[i] = GUINT32_TO_LE(md5Parts[i]);
+
+ /* & each integer with 0x7FFFFFFF */
+ /* and save one unmodified array for later */
+ newHashParts[i] = md5Parts[i];
+ md5Parts[i] &= 0x7FFFFFFF;
+ }
+
+ /* make a new string and pad with '0' */
+ n = g_snprintf(buf, sizeof(buf)-5, "%s%s00000000", challenge, MSNP11_PROD_ID);
+ /* truncate at an 8-byte boundary */
+ buf[n&=~7] = '\0';
+
+ /* split into integers */
+ chlStringParts = (unsigned int *)buf;
+
+ /* this is magic */
+ for (i = 0; i < (n / 4) - 1; i += 2)
+ {
+ long long temp;
+
+ chlStringParts[i] = GUINT32_TO_LE(chlStringParts[i]);
+ chlStringParts[i+1] = GUINT32_TO_LE(chlStringParts[i+1]);
+
+ temp = (md5Parts[0] * (((0x0E79A9C1 * (long long)chlStringParts[i]) % 0x7FFFFFFF)+nHigh) + md5Parts[1])%0x7FFFFFFF;
+ nHigh = (md5Parts[2] * (((long long)chlStringParts[i+1]+temp) % 0x7FFFFFFF) + md5Parts[3]) % 0x7FFFFFFF;
+ nLow = nLow + nHigh + temp;
+ }
+ nHigh = (nHigh+md5Parts[1]) % 0x7FFFFFFF;
+ nLow = (nLow+md5Parts[3]) % 0x7FFFFFFF;
+
+ newHashParts[0] ^= nHigh;
+ newHashParts[1] ^= nLow;
+ newHashParts[2] ^= nHigh;
+ newHashParts[3] ^= nLow;
+
+ /* swap more bytes if big endian */
+ for (i = 0; i < 4; i ++)
+ newHashParts[i] = GUINT32_TO_LE(newHashParts[i]);
+
+ /* make a string of the parts */
+ newHash = (unsigned char *)newHashParts;
+
+ /* convert to hexadecimal */
+ output = g_new(char, 33);
+ for (i = 0; i < 16; i ++)
+ sprintf(output + i * 2, "%02x", newHash[i]);
+
+ return output;
+}
+
+gint msn_domaintree_cmp( gconstpointer a_, gconstpointer b_ )
+{
+ const char *a = a_, *b = b_;
+ gint ret;
+
+ if( !( a = strchr( a, '@' ) ) || !( b = strchr( b, '@' ) ) ||
+ ( ret = strcmp( a, b ) ) == 0 )
+ ret = strcmp( a_, b_ );
+
+ return ret;
+}
+
+struct msn_group *msn_group_by_name( struct im_connection *ic, const char *name )
+{
+ struct msn_data *md = ic->proto_data;
+ GSList *l;
+
+ for( l = md->groups; l; l = l->next )
+ {
+ struct msn_group *mg = l->data;
+
+ if( g_strcasecmp( mg->name, name ) == 0 )
+ return mg;
+ }
+
+ return NULL;
+}
+
+struct msn_group *msn_group_by_id( struct im_connection *ic, const char *id )
+{
+ struct msn_data *md = ic->proto_data;
+ GSList *l;
+
+ for( l = md->groups; l; l = l->next )
+ {
+ struct msn_group *mg = l->data;
+
+ if( g_strcasecmp( mg->id, id ) == 0 )
+ return mg;
+ }
+
+ return NULL;
+}
+
+int msn_ns_set_display_name( struct im_connection *ic, const char *value )
+{
+ struct msn_data *md = ic->proto_data;
+ char fn[strlen(value)*3+1];
+
+ strcpy( fn, value );
+ http_encode( fn );
+
+ /* Note: We don't actually know if the server accepted the new name,
+ and won't give proper feedback yet if it doesn't. */
+ return msn_ns_write( ic, -1, "PRP %d MFN %s\r\n", ++md->trId, fn );
+}
diff --git a/protocols/msn/ns.c b/protocols/msn/ns.c
new file mode 100644
index 00000000..604e2f4e
--- /dev/null
+++ b/protocols/msn/ns.c
@@ -0,0 +1,886 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* MSN module - Notification server callbacks */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <ctype.h>
+#include "nogaim.h"
+#include "msn.h"
+#include "md5.h"
+#include "soap.h"
+#include "xmltree.h"
+
+static gboolean msn_ns_connected( gpointer data, gint source, b_input_condition cond );
+static gboolean msn_ns_callback( gpointer data, gint source, b_input_condition cond );
+static int msn_ns_command( struct msn_handler_data *handler, char **cmd, int num_parts );
+static int msn_ns_message( struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts );
+
+static void msn_ns_send_adl_start( struct im_connection *ic );
+static void msn_ns_send_adl( struct im_connection *ic );
+
+int msn_ns_write( struct im_connection *ic, int fd, const char *fmt, ... )
+{
+ struct msn_data *md = ic->proto_data;
+ va_list params;
+ char *out;
+ size_t len;
+ int st;
+
+ va_start( params, fmt );
+ out = g_strdup_vprintf( fmt, params );
+ va_end( params );
+
+ if( fd < 0 )
+ fd = md->ns->fd;
+
+ if( getenv( "BITLBEE_DEBUG" ) )
+ fprintf( stderr, "->NS%d:%s", fd, out );
+
+ len = strlen( out );
+ st = write( fd, out, len );
+ g_free( out );
+ if( st != len )
+ {
+ imcb_error( ic, "Short write() to main server" );
+ imc_logout( ic, TRUE );
+ return 0;
+ }
+
+ return 1;
+}
+
+gboolean msn_ns_connect( struct im_connection *ic, struct msn_handler_data *handler, const char *host, int port )
+{
+ if( handler->fd >= 0 )
+ closesocket( handler->fd );
+
+ handler->exec_command = msn_ns_command;
+ handler->exec_message = msn_ns_message;
+ handler->data = ic;
+ handler->fd = proxy_connect( host, port, msn_ns_connected, handler );
+ if( handler->fd < 0 )
+ {
+ imcb_error( ic, "Could not connect to server" );
+ imc_logout( ic, TRUE );
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean msn_ns_connected( gpointer data, gint source, b_input_condition cond )
+{
+ struct msn_handler_data *handler = data;
+ struct im_connection *ic = handler->data;
+ struct msn_data *md;
+
+ if( !g_slist_find( msn_connections, ic ) )
+ return FALSE;
+
+ md = ic->proto_data;
+
+ if( source == -1 )
+ {
+ imcb_error( ic, "Could not connect to server" );
+ imc_logout( ic, TRUE );
+ return FALSE;
+ }
+
+ g_free( handler->rxq );
+ handler->rxlen = 0;
+ handler->rxq = g_new0( char, 1 );
+
+ if( msn_ns_write( ic, source, "VER %d %s CVR0\r\n", ++md->trId, MSNP_VER ) )
+ {
+ handler->inpa = b_input_add( handler->fd, B_EV_IO_READ, msn_ns_callback, handler );
+ imcb_log( ic, "Connected to server, waiting for reply" );
+ }
+
+ return FALSE;
+}
+
+void msn_ns_close( struct msn_handler_data *handler )
+{
+ if( handler->fd >= 0 )
+ {
+ closesocket( handler->fd );
+ b_event_remove( handler->inpa );
+ }
+
+ handler->fd = handler->inpa = -1;
+ g_free( handler->rxq );
+ g_free( handler->cmd_text );
+
+ handler->rxlen = 0;
+ handler->rxq = NULL;
+ handler->cmd_text = NULL;
+}
+
+static gboolean msn_ns_callback( gpointer data, gint source, b_input_condition cond )
+{
+ struct msn_handler_data *handler = data;
+ struct im_connection *ic = handler->data;
+
+ if( msn_handler( handler ) == -1 ) /* Don't do this on ret == 0, it's already done then. */
+ {
+ imcb_error( ic, "Error while reading from server" );
+ imc_logout( ic, TRUE );
+
+ return FALSE;
+ }
+ else
+ return TRUE;
+}
+
+static int msn_ns_command( struct msn_handler_data *handler, char **cmd, int num_parts )
+{
+ struct im_connection *ic = handler->data;
+ struct msn_data *md = ic->proto_data;
+
+ if( num_parts == 0 )
+ {
+ /* Hrrm... Empty command...? Ignore? */
+ return( 1 );
+ }
+
+ if( strcmp( cmd[0], "VER" ) == 0 )
+ {
+ if( cmd[2] && strncmp( cmd[2], MSNP_VER, 5 ) != 0 )
+ {
+ imcb_error( ic, "Unsupported protocol" );
+ imc_logout( ic, FALSE );
+ return( 0 );
+ }
+
+ return( msn_ns_write( ic, handler->fd, "CVR %d 0x0409 mac 10.2.0 ppc macmsgs 3.5.1 macmsgs %s\r\n",
+ ++md->trId, ic->acc->user ) );
+ }
+ else if( strcmp( cmd[0], "CVR" ) == 0 )
+ {
+ /* We don't give a damn about the information we just received */
+ return msn_ns_write( ic, handler->fd, "USR %d SSO I %s\r\n", ++md->trId, ic->acc->user );
+ }
+ else if( strcmp( cmd[0], "XFR" ) == 0 )
+ {
+ char *server;
+ int port;
+
+ if( num_parts >= 6 && strcmp( cmd[2], "NS" ) == 0 )
+ {
+ b_event_remove( handler->inpa );
+ handler->inpa = -1;
+
+ server = strchr( cmd[3], ':' );
+ if( !server )
+ {
+ imcb_error( ic, "Syntax error" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+ *server = 0;
+ port = atoi( server + 1 );
+ server = cmd[3];
+
+ imcb_log( ic, "Transferring to other server" );
+ return msn_ns_connect( ic, handler, server, port );
+ }
+ else if( num_parts >= 6 && strcmp( cmd[2], "SB" ) == 0 )
+ {
+ struct msn_switchboard *sb;
+
+ server = strchr( cmd[3], ':' );
+ if( !server )
+ {
+ imcb_error( ic, "Syntax error" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+ *server = 0;
+ port = atoi( server + 1 );
+ server = cmd[3];
+
+ if( strcmp( cmd[4], "CKI" ) != 0 )
+ {
+ imcb_error( ic, "Unknown authentication method for switchboard" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+
+ debug( "Connecting to a new switchboard with key %s", cmd[5] );
+
+ if( ( sb = msn_sb_create( ic, server, port, cmd[5], MSN_SB_NEW ) ) == NULL )
+ {
+ /* Although this isn't strictly fatal for the NS connection, it's
+ definitely something serious (we ran out of file descriptors?). */
+ imcb_error( ic, "Could not create new switchboard" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+
+ if( md->msgq )
+ {
+ struct msn_message *m = md->msgq->data;
+ GSList *l;
+
+ sb->who = g_strdup( m->who );
+
+ /* Move all the messages to the first user in the message
+ queue to the switchboard message queue. */
+ l = md->msgq;
+ while( l )
+ {
+ m = l->data;
+ l = l->next;
+ if( strcmp( m->who, sb->who ) == 0 )
+ {
+ sb->msgq = g_slist_append( sb->msgq, m );
+ md->msgq = g_slist_remove( md->msgq, m );
+ }
+ }
+ }
+ }
+ else
+ {
+ imcb_error( ic, "Syntax error" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+ }
+ else if( strcmp( cmd[0], "USR" ) == 0 )
+ {
+ if( num_parts >= 6 && strcmp( cmd[2], "SSO" ) == 0 &&
+ strcmp( cmd[3], "S" ) == 0 )
+ {
+ g_free( md->pp_policy );
+ md->pp_policy = g_strdup( cmd[4] );
+ msn_soap_passport_sso_request( ic, cmd[5] );
+ }
+ else if( strcmp( cmd[2], "OK" ) == 0 )
+ {
+ /* If the number after the handle is 0, the e-mail
+ address is unverified, which means we can't change
+ the display name. */
+ if( cmd[4][0] == '0' )
+ md->flags |= MSN_EMAIL_UNVERIFIED;
+
+ imcb_log( ic, "Authenticated, getting buddy list" );
+ msn_soap_memlist_request( ic );
+ }
+ else
+ {
+ imcb_error( ic, "Unknown authentication type" );
+ imc_logout( ic, FALSE );
+ return( 0 );
+ }
+ }
+ else if( strcmp( cmd[0], "MSG" ) == 0 )
+ {
+ if( num_parts < 4 )
+ {
+ imcb_error( ic, "Syntax error" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+
+ handler->msglen = atoi( cmd[3] );
+
+ if( handler->msglen <= 0 )
+ {
+ imcb_error( ic, "Syntax error" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+ }
+ else if( strcmp( cmd[0], "BLP" ) == 0 )
+ {
+ msn_ns_send_adl_start( ic );
+ return msn_ns_finish_login( ic );
+ }
+ else if( strcmp( cmd[0], "ADL" ) == 0 )
+ {
+ if( num_parts >= 3 && strcmp( cmd[2], "OK" ) == 0 )
+ {
+ msn_ns_send_adl( ic );
+ return msn_ns_finish_login( ic );
+ }
+ else if( num_parts >= 3 )
+ {
+ handler->msglen = atoi( cmd[2] );
+ }
+ }
+ else if( strcmp( cmd[0], "PRP" ) == 0 )
+ {
+ imcb_connected( ic );
+ }
+ else if( strcmp( cmd[0], "CHL" ) == 0 )
+ {
+ char *resp;
+ int st;
+
+ if( num_parts < 3 )
+ {
+ imcb_error( ic, "Syntax error" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+
+ resp = msn_p11_challenge( cmd[2] );
+
+ st = msn_ns_write( ic, -1, "QRY %d %s %zd\r\n%s",
+ ++md->trId, MSNP11_PROD_ID,
+ strlen( resp ), resp );
+ g_free( resp );
+ return st;
+ }
+ else if( strcmp( cmd[0], "ILN" ) == 0 )
+ {
+ const struct msn_away_state *st;
+
+ if( num_parts < 6 )
+ {
+ imcb_error( ic, "Syntax error" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+
+ http_decode( cmd[5] );
+ imcb_rename_buddy( ic, cmd[3], cmd[5] );
+
+ st = msn_away_state_by_code( cmd[2] );
+ if( !st )
+ {
+ /* FIXME: Warn/Bomb about unknown away state? */
+ st = msn_away_state_list + 1;
+ }
+
+ imcb_buddy_status( ic, cmd[3], OPT_LOGGED_IN |
+ ( st != msn_away_state_list ? OPT_AWAY : 0 ),
+ st->name, NULL );
+ }
+ else if( strcmp( cmd[0], "FLN" ) == 0 )
+ {
+ if( cmd[1] == NULL )
+ return 1;
+
+ imcb_buddy_status( ic, cmd[1], 0, NULL, NULL );
+
+ msn_sb_start_keepalives( msn_sb_by_handle( ic, cmd[1] ), TRUE );
+ }
+ else if( strcmp( cmd[0], "NLN" ) == 0 )
+ {
+ const struct msn_away_state *st;
+ int cap;
+
+ if( num_parts < 6 )
+ {
+ imcb_error( ic, "Syntax error" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+
+ http_decode( cmd[4] );
+ cap = atoi( cmd[5] );
+ imcb_rename_buddy( ic, cmd[2], cmd[4] );
+
+ st = msn_away_state_by_code( cmd[1] );
+ if( !st )
+ {
+ /* FIXME: Warn/Bomb about unknown away state? */
+ st = msn_away_state_list + 1;
+ }
+
+ imcb_buddy_status( ic, cmd[2], OPT_LOGGED_IN |
+ ( st != msn_away_state_list ? OPT_AWAY : 0 ) |
+ ( cap & 1 ? OPT_MOBILE : 0 ),
+ st->name, NULL );
+
+ msn_sb_stop_keepalives( msn_sb_by_handle( ic, cmd[2] ) );
+ }
+ else if( strcmp( cmd[0], "RNG" ) == 0 )
+ {
+ struct msn_switchboard *sb;
+ char *server;
+ int session, port;
+
+ if( num_parts < 7 )
+ {
+ imcb_error( ic, "Syntax error" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+
+ session = atoi( cmd[1] );
+
+ server = strchr( cmd[2], ':' );
+ if( !server )
+ {
+ imcb_error( ic, "Syntax error" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+ *server = 0;
+ port = atoi( server + 1 );
+ server = cmd[2];
+
+ if( strcmp( cmd[3], "CKI" ) != 0 )
+ {
+ imcb_error( ic, "Unknown authentication method for switchboard" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+
+ debug( "Got a call from %s (session %d). Key = %s", cmd[5], session, cmd[4] );
+
+ if( ( sb = msn_sb_create( ic, server, port, cmd[4], session ) ) == NULL )
+ {
+ /* Although this isn't strictly fatal for the NS connection, it's
+ definitely something serious (we ran out of file descriptors?). */
+ imcb_error( ic, "Could not create new switchboard" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+ else
+ {
+ sb->who = g_strdup( cmd[5] );
+ }
+ }
+ else if( strcmp( cmd[0], "OUT" ) == 0 )
+ {
+ int allow_reconnect = TRUE;
+
+ if( cmd[1] && strcmp( cmd[1], "OTH" ) == 0 )
+ {
+ imcb_error( ic, "Someone else logged in with your account" );
+ allow_reconnect = FALSE;
+ }
+ else if( cmd[1] && strcmp( cmd[1], "SSD" ) == 0 )
+ {
+ imcb_error( ic, "Terminating session because of server shutdown" );
+ }
+ else
+ {
+ imcb_error( ic, "Session terminated by remote server (reason unknown)" );
+ }
+
+ imc_logout( ic, allow_reconnect );
+ return( 0 );
+ }
+ else if( strcmp( cmd[0], "IPG" ) == 0 )
+ {
+ imcb_error( ic, "Received IPG command, we don't handle them yet." );
+
+ handler->msglen = atoi( cmd[1] );
+
+ if( handler->msglen <= 0 )
+ {
+ imcb_error( ic, "Syntax error" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+ }
+#if 0
+ else if( strcmp( cmd[0], "ADG" ) == 0 )
+ {
+ char *group = g_strdup( cmd[3] );
+ int groupnum, i;
+ GSList *l, *next;
+
+ http_decode( group );
+ if( sscanf( cmd[4], "%d", &groupnum ) == 1 )
+ {
+ if( groupnum >= md->groupcount )
+ {
+ md->grouplist = g_renew( char *, md->grouplist, groupnum + 1 );
+ for( i = md->groupcount; i <= groupnum; i ++ )
+ md->grouplist[i] = NULL;
+ md->groupcount = groupnum + 1;
+ }
+ g_free( md->grouplist[groupnum] );
+ md->grouplist[groupnum] = group;
+ }
+ else
+ {
+ /* Shouldn't happen, but if it does, give up on the group. */
+ g_free( group );
+ imcb_error( ic, "Syntax error" );
+ imc_logout( ic, TRUE );
+ return 0;
+ }
+
+ for( l = md->grpq; l; l = next )
+ {
+ struct msn_groupadd *ga = l->data;
+ next = l->next;
+ if( g_strcasecmp( ga->group, group ) == 0 )
+ {
+ if( !msn_buddy_list_add( ic, "FL", ga->who, ga->who, group ) )
+ return 0;
+
+ g_free( ga->group );
+ g_free( ga->who );
+ g_free( ga );
+ md->grpq = g_slist_remove( md->grpq, ga );
+ }
+ }
+ }
+#endif
+ else if( strcmp( cmd[0], "GCF" ) == 0 )
+ {
+ /* Coming up is cmd[2] bytes of stuff we're supposed to
+ censore. Meh. */
+ handler->msglen = atoi( cmd[2] );
+ }
+ else if( strcmp( cmd[0], "UBX" ) == 0 )
+ {
+ /* Status message. */
+ if( num_parts >= 4 )
+ handler->msglen = atoi( cmd[3] );
+ }
+ else if( strcmp( cmd[0], "NOT" ) == 0 )
+ {
+ /* Some kind of notification, poorly documented but
+ apparently used to announce address book changes. */
+ if( num_parts >= 2 )
+ handler->msglen = atoi( cmd[1] );
+ }
+ else if( isdigit( cmd[0][0] ) )
+ {
+ int num = atoi( cmd[0] );
+ const struct msn_status_code *err = msn_status_by_number( num );
+
+ imcb_error( ic, "Error reported by MSN server: %s", err->text );
+
+ if( err->flags & STATUS_FATAL )
+ {
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+
+ /* Oh yes, errors can have payloads too now. Discard them for now. */
+ if( num_parts >= 3 )
+ handler->msglen = atoi( cmd[2] );
+ }
+ else
+ {
+ /* debug( "Received unknown command from main server: %s", cmd[0] ); */
+ }
+
+ return( 1 );
+}
+
+static int msn_ns_message( struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts )
+{
+ struct im_connection *ic = handler->data;
+ char *body;
+ int blen = 0;
+
+ if( !num_parts )
+ return( 1 );
+
+ if( ( body = strstr( msg, "\r\n\r\n" ) ) )
+ {
+ body += 4;
+ blen = msglen - ( body - msg );
+ }
+
+ if( strcmp( cmd[0], "MSG" ) == 0 )
+ {
+ if( g_strcasecmp( cmd[1], "Hotmail" ) == 0 )
+ {
+ char *ct = msn_findheader( msg, "Content-Type:", msglen );
+
+ if( !ct )
+ return( 1 );
+
+ if( g_strncasecmp( ct, "application/x-msmsgssystemmessage", 33 ) == 0 )
+ {
+ char *mtype;
+ char *arg1;
+
+ if( !body )
+ return( 1 );
+
+ mtype = msn_findheader( body, "Type:", blen );
+ arg1 = msn_findheader( body, "Arg1:", blen );
+
+ if( mtype && strcmp( mtype, "1" ) == 0 )
+ {
+ if( arg1 )
+ imcb_log( ic, "The server is going down for maintenance in %s minutes.", arg1 );
+ }
+
+ g_free( arg1 );
+ g_free( mtype );
+ }
+ else if( g_strncasecmp( ct, "text/x-msmsgsprofile", 20 ) == 0 )
+ {
+ /* We don't care about this profile for now... */
+ }
+ else if( g_strncasecmp( ct, "text/x-msmsgsinitialemailnotification", 37 ) == 0 )
+ {
+ if( set_getbool( &ic->acc->set, "mail_notifications" ) )
+ {
+ char *inbox = msn_findheader( body, "Inbox-Unread:", blen );
+ char *folders = msn_findheader( body, "Folders-Unread:", blen );
+
+ if( inbox && folders )
+ imcb_log( ic, "INBOX contains %s new messages, plus %s messages in other folders.", inbox, folders );
+
+ g_free( inbox );
+ g_free( folders );
+ }
+ }
+ else if( g_strncasecmp( ct, "text/x-msmsgsemailnotification", 30 ) == 0 )
+ {
+ if( set_getbool( &ic->acc->set, "mail_notifications" ) )
+ {
+ char *from = msn_findheader( body, "From-Addr:", blen );
+ char *fromname = msn_findheader( body, "From:", blen );
+
+ if( from && fromname )
+ imcb_log( ic, "Received an e-mail message from %s <%s>.", fromname, from );
+
+ g_free( from );
+ g_free( fromname );
+ }
+ }
+ else if( g_strncasecmp( ct, "text/x-msmsgsactivemailnotification", 35 ) == 0 )
+ {
+ /* Sorry, but this one really is *USELESS* */
+ }
+ else
+ {
+ debug( "Can't handle %s packet from notification server", ct );
+ }
+
+ g_free( ct );
+ }
+ }
+ else if( strcmp( cmd[0], "UBX" ) == 0 )
+ {
+ struct xt_node *ubx, *psm;
+ char *psm_text = NULL;
+
+ ubx = xt_from_string( msg );
+ if( ubx && strcmp( ubx->name, "Data" ) == 0 &&
+ ( psm = xt_find_node( ubx->children, "PSM" ) ) )
+ psm_text = psm->text;
+
+ imcb_buddy_status_msg( ic, cmd[1], psm_text );
+ xt_free_node( ubx );
+ }
+ else if( strcmp( cmd[0], "ADL" ) == 0 )
+ {
+ struct xt_node *adl, *d, *c;
+
+ if( !( adl = xt_from_string( msg ) ) )
+ return 1;
+
+ for( d = adl->children; d; d = d->next )
+ {
+ char *dn;
+ if( strcmp( d->name, "d" ) != 0 ||
+ ( dn = xt_find_attr( d, "n" ) ) == NULL )
+ continue;
+ for( c = d->children; c; c = c->next )
+ {
+ bee_user_t *bu;
+ struct msn_buddy_data *bd;
+ char *cn, *handle, *f, *l;
+ int flags;
+
+ if( strcmp( c->name, "c" ) != 0 ||
+ ( l = xt_find_attr( c, "l" ) ) == NULL ||
+ ( cn = xt_find_attr( c, "n" ) ) == NULL )
+ continue;
+
+ handle = g_strdup_printf( "%s@%s", cn, dn );
+ if( !( ( bu = bee_user_by_handle( ic->bee, ic, handle ) ) ||
+ ( bu = bee_user_new( ic->bee, ic, handle, 0 ) ) ) )
+ {
+ g_free( handle );
+ continue;
+ }
+ g_free( handle );
+ bd = bu->data;
+
+ if( ( f = xt_find_attr( c, "f" ) ) )
+ {
+ http_decode( f );
+ imcb_rename_buddy( ic, bu->handle, f );
+ }
+
+ flags = atoi( l ) & 15;
+ if( bd->flags != flags )
+ {
+ bd->flags = flags;
+ msn_buddy_ask( bu );
+ }
+ }
+ }
+ }
+
+ return( 1 );
+}
+
+void msn_auth_got_passport_token( struct im_connection *ic, const char *token, const char *error )
+{
+ struct msn_data *md;
+
+ /* Dead connection? */
+ if( g_slist_find( msn_connections, ic ) == NULL )
+ return;
+
+ md = ic->proto_data;
+
+ if( token )
+ {
+ msn_ns_write( ic, -1, "USR %d SSO S %s %s\r\n", ++md->trId, md->tokens[0], token );
+ }
+ else
+ {
+ imcb_error( ic, "Error during Passport authentication: %s", error );
+ imc_logout( ic, TRUE );
+ }
+}
+
+void msn_auth_got_contact_list( struct im_connection *ic )
+{
+ struct msn_data *md;
+
+ /* Dead connection? */
+ if( g_slist_find( msn_connections, ic ) == NULL )
+ return;
+
+ md = ic->proto_data;
+ msn_ns_write( ic, -1, "BLP %d %s\r\n", ++md->trId, "BL" );
+}
+
+static gboolean msn_ns_send_adl_1( gpointer key, gpointer value, gpointer data )
+{
+ struct xt_node *adl = data, *d, *c;
+ struct bee_user *bu = value;
+ struct msn_buddy_data *bd = bu->data;
+ struct msn_data *md = bu->ic->proto_data;
+ char handle[strlen(bu->handle)];
+ char *domain;
+ char l[4];
+
+ if( ( bd->flags & 7 ) == 0 || ( bd->flags & MSN_BUDDY_ADL_SYNCED ) )
+ return FALSE;
+
+ strcpy( handle, bu->handle );
+ if( ( domain = strchr( handle, '@' ) ) == NULL ) /* WTF */
+ return FALSE;
+ *domain = '\0';
+ domain ++;
+
+ if( ( d = adl->children ) == NULL ||
+ g_strcasecmp( xt_find_attr( d, "n" ), domain ) != 0 )
+ {
+ d = xt_new_node( "d", NULL, NULL );
+ xt_add_attr( d, "n", domain );
+ xt_insert_child( adl, d );
+ }
+
+ g_snprintf( l, sizeof( l ), "%d", bd->flags & 7 );
+ c = xt_new_node( "c", NULL, NULL );
+ xt_add_attr( c, "n", handle );
+ xt_add_attr( c, "l", l );
+ xt_add_attr( c, "t", "1" ); /* 1 means normal, 4 means mobile? */
+ xt_insert_child( d, c );
+
+ /* Do this in batches of 100. */
+ bd->flags |= MSN_BUDDY_ADL_SYNCED;
+ return (--md->adl_todo % 140) == 0;
+}
+
+static void msn_ns_send_adl( struct im_connection *ic )
+{
+ struct xt_node *adl;
+ struct msn_data *md = ic->proto_data;
+ char *adls;
+
+ adl = xt_new_node( "ml", NULL, NULL );
+ xt_add_attr( adl, "l", "1" );
+ g_tree_foreach( md->domaintree, msn_ns_send_adl_1, adl );
+ if( adl->children == NULL )
+ {
+ /* This tells the caller that we're done now. */
+ md->adl_todo = -1;
+ xt_free_node( adl );
+ return;
+ }
+
+ adls = xt_to_string( adl );
+ xt_free_node( adl );
+ msn_ns_write( ic, -1, "ADL %d %zd\r\n%s", ++md->trId, strlen( adls ), adls );
+ g_free( adls );
+}
+
+static void msn_ns_send_adl_start( struct im_connection *ic )
+{
+ struct msn_data *md;
+ GSList *l;
+
+ /* Dead connection? */
+ if( g_slist_find( msn_connections, ic ) == NULL )
+ return;
+
+ md = ic->proto_data;
+ md->adl_todo = 0;
+ for( l = ic->bee->users; l; l = l->next )
+ {
+ bee_user_t *bu = l->data;
+ struct msn_buddy_data *bd = bu->data;
+
+ if( bu->ic != ic || ( bd->flags & 7 ) == 0 )
+ continue;
+
+ bd->flags &= ~MSN_BUDDY_ADL_SYNCED;
+ md->adl_todo++;
+ }
+
+ msn_ns_send_adl( ic );
+}
+
+int msn_ns_finish_login( struct im_connection *ic )
+{
+ struct msn_data *md = ic->proto_data;
+
+ if( ic->flags & OPT_LOGGED_IN )
+ return 1;
+
+ if( md->adl_todo < 0 )
+ md->flags |= MSN_DONE_ADL;
+
+ if( ( md->flags & MSN_DONE_ADL ) && ( md->flags & MSN_GOT_PROFILE ) )
+ {
+ if( md->flags & MSN_EMAIL_UNVERIFIED )
+ imcb_connected( ic );
+ else
+ return msn_ns_set_display_name( ic, set_getstr( &ic->acc->set, "display_name" ) );
+ }
+
+ return 1;
+}
diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c
new file mode 100644
index 00000000..37ac2889
--- /dev/null
+++ b/protocols/msn/sb.c
@@ -0,0 +1,806 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* MSN module - Switchboard server callbacks and utilities */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <ctype.h>
+#include "nogaim.h"
+#include "msn.h"
+#include "md5.h"
+#include "soap.h"
+#include "invitation.h"
+
+static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition cond );
+static int msn_sb_command( struct msn_handler_data *handler, char **cmd, int num_parts );
+static int msn_sb_message( struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts );
+
+int msn_sb_write( struct msn_switchboard *sb, const char *fmt, ... )
+{
+ va_list params;
+ char *out;
+ size_t len;
+ int st;
+
+ va_start( params, fmt );
+ out = g_strdup_vprintf( fmt, params );
+ va_end( params );
+
+ if( getenv( "BITLBEE_DEBUG" ) )
+ fprintf( stderr, "->SB%d:%s", sb->fd, out );
+
+ len = strlen( out );
+ st = write( sb->fd, out, len );
+ g_free( out );
+ if( st != len )
+ {
+ msn_sb_destroy( sb );
+ return 0;
+ }
+
+ return 1;
+}
+
+int msn_sb_write_msg( struct im_connection *ic, struct msn_message *m )
+{
+ struct msn_data *md = ic->proto_data;
+ struct msn_switchboard *sb;
+
+ /* FIXME: *CHECK* the reliability of using spare sb's! */
+ if( ( sb = msn_sb_spare( ic ) ) )
+ {
+ debug( "Trying to use a spare switchboard to message %s", m->who );
+
+ sb->who = g_strdup( m->who );
+ if( msn_sb_write( sb, "CAL %d %s\r\n", ++sb->trId, m->who ) )
+ {
+ /* He/She should join the switchboard soon, let's queue the message. */
+ sb->msgq = g_slist_append( sb->msgq, m );
+ return( 1 );
+ }
+ }
+
+ debug( "Creating a new switchboard to message %s", m->who );
+
+ /* If we reach this line, there was no spare switchboard, so let's make one. */
+ if( !msn_ns_write( ic, -1, "XFR %d SB\r\n", ++md->trId ) )
+ {
+ g_free( m->who );
+ g_free( m->text );
+ g_free( m );
+
+ return( 0 );
+ }
+
+ /* And queue the message to md. We'll pick it up when the switchboard comes up. */
+ md->msgq = g_slist_append( md->msgq, m );
+
+ /* FIXME: If the switchboard creation fails, the message will not be sent. */
+
+ return( 1 );
+}
+
+struct msn_switchboard *msn_sb_create( struct im_connection *ic, char *host, int port, char *key, int session )
+{
+ struct msn_data *md = ic->proto_data;
+ struct msn_switchboard *sb = g_new0( struct msn_switchboard, 1 );
+
+ sb->fd = proxy_connect( host, port, msn_sb_connected, sb );
+ if( sb->fd < 0 )
+ {
+ g_free( sb );
+ return( NULL );
+ }
+
+ sb->ic = ic;
+ sb->key = g_strdup( key );
+ sb->session = session;
+
+ msn_switchboards = g_slist_append( msn_switchboards, sb );
+ md->switchboards = g_slist_append( md->switchboards, sb );
+
+ return( sb );
+}
+
+struct msn_switchboard *msn_sb_by_handle( struct im_connection *ic, char *handle )
+{
+ struct msn_data *md = ic->proto_data;
+ struct msn_switchboard *sb;
+ GSList *l;
+
+ for( l = md->switchboards; l; l = l->next )
+ {
+ sb = l->data;
+ if( sb->who && strcmp( sb->who, handle ) == 0 )
+ return( sb );
+ }
+
+ return( NULL );
+}
+
+struct msn_switchboard *msn_sb_by_chat( struct groupchat *c )
+{
+ struct msn_data *md = c->ic->proto_data;
+ struct msn_switchboard *sb;
+ GSList *l;
+
+ for( l = md->switchboards; l; l = l->next )
+ {
+ sb = l->data;
+ if( sb->chat == c )
+ return( sb );
+ }
+
+ return( NULL );
+}
+
+struct msn_switchboard *msn_sb_spare( struct im_connection *ic )
+{
+ struct msn_data *md = ic->proto_data;
+ struct msn_switchboard *sb;
+ GSList *l;
+
+ for( l = md->switchboards; l; l = l->next )
+ {
+ sb = l->data;
+ if( !sb->who && !sb->chat )
+ return( sb );
+ }
+
+ return( NULL );
+}
+
+int msn_sb_sendmessage( struct msn_switchboard *sb, char *text )
+{
+ if( sb->ready )
+ {
+ char *buf;
+ int i, j;
+
+ /* Build the message. Convert LF to CR-LF for normal messages. */
+ if( strcmp( text, TYPING_NOTIFICATION_MESSAGE ) == 0 )
+ {
+ i = strlen( MSN_TYPING_HEADERS ) + strlen( sb->ic->acc->user );
+ buf = g_new0( char, i );
+ i = g_snprintf( buf, i, MSN_TYPING_HEADERS, sb->ic->acc->user );
+ }
+ else if( strcmp( text, NUDGE_MESSAGE ) == 0 )
+ {
+ buf = g_strdup( MSN_NUDGE_HEADERS );
+ i = strlen( buf );
+ }
+ else if( strcmp( text, SB_KEEPALIVE_MESSAGE ) == 0 )
+ {
+ buf = g_strdup( MSN_SB_KEEPALIVE_HEADERS );
+ i = strlen( buf );
+ }
+ else if( strncmp( text, MSN_INVITE_HEADERS, sizeof( MSN_INVITE_HEADERS ) - 1 ) == 0 )
+ {
+ buf = g_strdup( text );
+ i = strlen( buf );
+ }
+ else
+ {
+ buf = g_new0( char, sizeof( MSN_MESSAGE_HEADERS ) + strlen( text ) * 2 + 1 );
+ i = strlen( MSN_MESSAGE_HEADERS );
+
+ strcpy( buf, MSN_MESSAGE_HEADERS );
+ for( j = 0; text[j]; j ++ )
+ {
+ if( text[j] == '\n' )
+ buf[i++] = '\r';
+
+ buf[i++] = text[j];
+ }
+ }
+
+ /* Build the final packet (MSG command + the message). */
+ if( msn_sb_write( sb, "MSG %d N %d\r\n%s", ++sb->trId, i, buf ) )
+ {
+ g_free( buf );
+ return 1;
+ }
+ else
+ {
+ g_free( buf );
+ return 0;
+ }
+ }
+ else if( sb->who )
+ {
+ struct msn_message *m = g_new0( struct msn_message, 1 );
+
+ m->who = g_strdup( "" );
+ m->text = g_strdup( text );
+ sb->msgq = g_slist_append( sb->msgq, m );
+
+ return( 1 );
+ }
+ else
+ {
+ return( 0 );
+ }
+}
+
+struct groupchat *msn_sb_to_chat( struct msn_switchboard *sb )
+{
+ struct im_connection *ic = sb->ic;
+ struct groupchat *c = NULL;
+ char buf[1024];
+
+ /* Create the groupchat structure. */
+ g_snprintf( buf, sizeof( buf ), "MSN groupchat session %d", sb->session );
+ if( sb->who )
+ c = bee_chat_by_title( ic->bee, ic, sb->who );
+ if( c && !msn_sb_by_chat( c ) )
+ sb->chat = c;
+ else
+ sb->chat = imcb_chat_new( ic, buf );
+
+ /* Populate the channel. */
+ if( sb->who ) imcb_chat_add_buddy( sb->chat, sb->who );
+ imcb_chat_add_buddy( sb->chat, ic->acc->user );
+
+ /* And make sure the switchboard doesn't look like a regular chat anymore. */
+ if( sb->who )
+ {
+ g_free( sb->who );
+ sb->who = NULL;
+ }
+
+ return sb->chat;
+}
+
+void msn_sb_destroy( struct msn_switchboard *sb )
+{
+ struct im_connection *ic = sb->ic;
+ struct msn_data *md = ic->proto_data;
+
+ debug( "Destroying switchboard: %s", sb->who ? sb->who : sb->key ? sb->key : "" );
+
+ msn_msgq_purge( ic, &sb->msgq );
+ msn_sb_stop_keepalives( sb );
+
+ if( sb->key ) g_free( sb->key );
+ if( sb->who ) g_free( sb->who );
+
+ if( sb->chat )
+ {
+ imcb_chat_free( sb->chat );
+ }
+
+ if( sb->handler )
+ {
+ if( sb->handler->rxq ) g_free( sb->handler->rxq );
+ if( sb->handler->cmd_text ) g_free( sb->handler->cmd_text );
+ g_free( sb->handler );
+ }
+
+ if( sb->inp ) b_event_remove( sb->inp );
+ closesocket( sb->fd );
+
+ msn_switchboards = g_slist_remove( msn_switchboards, sb );
+ md->switchboards = g_slist_remove( md->switchboards, sb );
+ g_free( sb );
+}
+
+gboolean msn_sb_connected( gpointer data, gint source, b_input_condition cond )
+{
+ struct msn_switchboard *sb = data;
+ struct im_connection *ic;
+ struct msn_data *md;
+ char buf[1024];
+
+ /* Are we still alive? */
+ if( !g_slist_find( msn_switchboards, sb ) )
+ return FALSE;
+
+ ic = sb->ic;
+ md = ic->proto_data;
+
+ if( source != sb->fd )
+ {
+ debug( "Error %d while connecting to switchboard server", 1 );
+ msn_sb_destroy( sb );
+ return FALSE;
+ }
+
+ /* Prepare the callback */
+ sb->handler = g_new0( struct msn_handler_data, 1 );
+ sb->handler->fd = sb->fd;
+ sb->handler->rxq = g_new0( char, 1 );
+ sb->handler->data = sb;
+ sb->handler->exec_command = msn_sb_command;
+ sb->handler->exec_message = msn_sb_message;
+
+ if( sb->session == MSN_SB_NEW )
+ g_snprintf( buf, sizeof( buf ), "USR %d %s %s\r\n", ++sb->trId, ic->acc->user, sb->key );
+ else
+ g_snprintf( buf, sizeof( buf ), "ANS %d %s %s %d\r\n", ++sb->trId, ic->acc->user, sb->key, sb->session );
+
+ if( msn_sb_write( sb, "%s", buf ) )
+ sb->inp = b_input_add( sb->fd, B_EV_IO_READ, msn_sb_callback, sb );
+ else
+ debug( "Error %d while connecting to switchboard server", 2 );
+
+ return FALSE;
+}
+
+static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition cond )
+{
+ struct msn_switchboard *sb = data;
+ struct im_connection *ic = sb->ic;
+ struct msn_data *md = ic->proto_data;
+
+ if( msn_handler( sb->handler ) != -1 )
+ return TRUE;
+
+ if( sb->msgq != NULL )
+ {
+ time_t now = time( NULL );
+
+ if( now - md->first_sb_failure > 600 )
+ {
+ /* It's not really the first one, but the start of this "series".
+ With this, the warning below will be shown only if this happens
+ at least three times in ten minutes. This algorithm isn't
+ perfect, but for this purpose it will do. */
+ md->first_sb_failure = now;
+ md->sb_failures = 0;
+ }
+
+ debug( "Error: Switchboard died" );
+ if( ++ md->sb_failures >= 3 )
+ imcb_log( ic, "Warning: Many switchboard failures on MSN connection. "
+ "There might be problems delivering your messages." );
+
+ if( md->msgq == NULL )
+ {
+ md->msgq = sb->msgq;
+ }
+ else
+ {
+ GSList *l;
+
+ for( l = md->msgq; l->next; l = l->next );
+ l->next = sb->msgq;
+ }
+ sb->msgq = NULL;
+
+ debug( "Moved queued messages back to the main queue, "
+ "creating a new switchboard to retry." );
+ if( !msn_ns_write( ic, -1, "XFR %d SB\r\n", ++md->trId ) )
+ return FALSE;
+ }
+
+ msn_sb_destroy( sb );
+ return FALSE;
+}
+
+static int msn_sb_command( struct msn_handler_data *handler, char **cmd, int num_parts )
+{
+ struct msn_switchboard *sb = handler->data;
+ struct im_connection *ic = sb->ic;
+
+ if( !num_parts )
+ {
+ /* Hrrm... Empty command...? Ignore? */
+ return( 1 );
+ }
+
+ if( strcmp( cmd[0], "XFR" ) == 0 )
+ {
+ imcb_error( ic, "Received an XFR from a switchboard server, unable to comply! This is likely to be a bug, please report it!" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+ else if( strcmp( cmd[0], "USR" ) == 0 )
+ {
+ if( num_parts < 5 )
+ {
+ msn_sb_destroy( sb );
+ return( 0 );
+ }
+
+ if( strcmp( cmd[2], "OK" ) != 0 )
+ {
+ msn_sb_destroy( sb );
+ return( 0 );
+ }
+
+ if( sb->who )
+ return msn_sb_write( sb, "CAL %d %s\r\n", ++sb->trId, sb->who );
+ else
+ debug( "Just created a switchboard, but I don't know what to do with it." );
+ }
+ else if( strcmp( cmd[0], "IRO" ) == 0 )
+ {
+ int num, tot;
+
+ if( num_parts < 6 )
+ {
+ msn_sb_destroy( sb );
+ return( 0 );
+ }
+
+ num = atoi( cmd[2] );
+ tot = atoi( cmd[3] );
+
+ if( tot <= 0 )
+ {
+ msn_sb_destroy( sb );
+ return( 0 );
+ }
+ else if( tot > 1 )
+ {
+ char buf[1024];
+
+ if( num == 1 )
+ {
+ g_snprintf( buf, sizeof( buf ), "MSN groupchat session %d", sb->session );
+ sb->chat = imcb_chat_new( ic, buf );
+
+ g_free( sb->who );
+ sb->who = NULL;
+ }
+
+ imcb_chat_add_buddy( sb->chat, cmd[4] );
+
+ if( num == tot )
+ {
+ imcb_chat_add_buddy( sb->chat, ic->acc->user );
+ }
+ }
+ }
+ else if( strcmp( cmd[0], "ANS" ) == 0 )
+ {
+ if( num_parts < 3 )
+ {
+ msn_sb_destroy( sb );
+ return( 0 );
+ }
+
+ if( strcmp( cmd[2], "OK" ) != 0 )
+ {
+ debug( "Switchboard server sent a negative ANS reply" );
+ msn_sb_destroy( sb );
+ return( 0 );
+ }
+
+ sb->ready = 1;
+
+ msn_sb_start_keepalives( sb, FALSE );
+ }
+ else if( strcmp( cmd[0], "CAL" ) == 0 )
+ {
+ if( num_parts < 4 || !isdigit( cmd[3][0] ) )
+ {
+ msn_sb_destroy( sb );
+ return( 0 );
+ }
+
+ sb->session = atoi( cmd[3] );
+ }
+ else if( strcmp( cmd[0], "JOI" ) == 0 )
+ {
+ if( num_parts < 3 )
+ {
+ msn_sb_destroy( sb );
+ return( 0 );
+ }
+
+ if( sb->who && g_strcasecmp( cmd[1], sb->who ) == 0 )
+ {
+ /* The user we wanted to talk to is finally there, let's send the queued messages then. */
+ struct msn_message *m;
+ GSList *l;
+ int st = 1;
+
+ debug( "%s arrived in the switchboard session, now sending queued message(s)", cmd[1] );
+
+ /* Without this, sendmessage() will put everything back on the queue... */
+ sb->ready = 1;
+
+ while( ( l = sb->msgq ) )
+ {
+ m = l->data;
+ if( st )
+ {
+ /* This hack is meant to convert a regular new chat into a groupchat */
+ if( strcmp( m->text, GROUPCHAT_SWITCHBOARD_MESSAGE ) == 0 )
+ msn_sb_to_chat( sb );
+ else
+ st = msn_sb_sendmessage( sb, m->text );
+ }
+ g_free( m->text );
+ g_free( m->who );
+ g_free( m );
+
+ sb->msgq = g_slist_remove( sb->msgq, m );
+ }
+
+ msn_sb_start_keepalives( sb, FALSE );
+
+ return( st );
+ }
+ else if( sb->who )
+ {
+ debug( "Converting chat with %s to a groupchat because %s joined the session.", sb->who, cmd[1] );
+
+ /* This SB is a one-to-one chat right now, but someone else is joining. */
+ msn_sb_to_chat( sb );
+
+ imcb_chat_add_buddy( sb->chat, cmd[1] );
+ }
+ else if( sb->chat )
+ {
+ imcb_chat_add_buddy( sb->chat, cmd[1] );
+ sb->ready = 1;
+ }
+ else
+ {
+ /* PANIC! */
+ }
+ }
+ else if( strcmp( cmd[0], "MSG" ) == 0 )
+ {
+ if( num_parts < 4 )
+ {
+ msn_sb_destroy( sb );
+ return( 0 );
+ }
+
+ sb->handler->msglen = atoi( cmd[3] );
+
+ if( sb->handler->msglen <= 0 )
+ {
+ debug( "Received a corrupted message on the switchboard, the switchboard will be closed" );
+ msn_sb_destroy( sb );
+ return( 0 );
+ }
+ }
+ else if( strcmp( cmd[0], "NAK" ) == 0 )
+ {
+ if( sb->who )
+ {
+ imcb_log( ic, "The MSN servers could not deliver one of your messages to %s.", sb->who );
+ }
+ else
+ {
+ imcb_log( ic, "The MSN servers could not deliver one of your groupchat messages to all participants." );
+ }
+ }
+ else if( strcmp( cmd[0], "BYE" ) == 0 )
+ {
+ if( num_parts < 2 )
+ {
+ msn_sb_destroy( sb );
+ return( 0 );
+ }
+
+ /* if( cmd[2] && *cmd[2] == '1' ) -=> Chat is being cleaned up because of idleness */
+
+ if( sb->who )
+ {
+ msn_sb_stop_keepalives( sb );
+
+ /* This is a single-person chat, and the other person is leaving. */
+ g_free( sb->who );
+ sb->who = NULL;
+ sb->ready = 0;
+
+ debug( "Person %s left the one-to-one switchboard connection. Keeping it around as a spare...", cmd[1] );
+
+ /* We could clean up the switchboard now, but keeping it around
+ as a spare for a next conversation sounds more sane to me.
+ The server will clean it up when it's idle for too long. */
+ }
+ else if( sb->chat )
+ {
+ imcb_chat_remove_buddy( sb->chat, cmd[1], "" );
+ }
+ else
+ {
+ /* PANIC! */
+ }
+ }
+ else if( isdigit( cmd[0][0] ) )
+ {
+ int num = atoi( cmd[0] );
+ const struct msn_status_code *err = msn_status_by_number( num );
+
+ /* If the person is offline, send an offline message instead,
+ and don't report an error. */
+ if( num == 217 )
+ msn_soap_oim_send_queue( ic, &sb->msgq );
+ else
+ imcb_error( ic, "Error reported by switchboard server: %s", err->text );
+
+ if( err->flags & STATUS_SB_FATAL )
+ {
+ msn_sb_destroy( sb );
+ return 0;
+ }
+ else if( err->flags & STATUS_FATAL )
+ {
+ imc_logout( ic, TRUE );
+ return 0;
+ }
+ else if( err->flags & STATUS_SB_IM_SPARE )
+ {
+ if( sb->who )
+ {
+ /* Apparently some invitation failed. We might want to use this
+ board later, so keep it as a spare. */
+ g_free( sb->who );
+ sb->who = NULL;
+
+ /* Also clear the msgq, otherwise someone else might get them. */
+ msn_msgq_purge( ic, &sb->msgq );
+ }
+
+ /* Do NOT return 0 here, we want to keep this sb. */
+ }
+ }
+ else
+ {
+ /* debug( "Received unknown command from switchboard server: %s", cmd[0] ); */
+ }
+
+ return( 1 );
+}
+
+static int msn_sb_message( struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts )
+{
+ struct msn_switchboard *sb = handler->data;
+ struct im_connection *ic = sb->ic;
+ char *body;
+ int blen = 0;
+
+ if( !num_parts )
+ return( 1 );
+
+ if( ( body = strstr( msg, "\r\n\r\n" ) ) )
+ {
+ body += 4;
+ blen = msglen - ( body - msg );
+ }
+
+ if( strcmp( cmd[0], "MSG" ) == 0 )
+ {
+ char *ct = msn_findheader( msg, "Content-Type:", msglen );
+
+ if( !ct )
+ return( 1 );
+
+ if( g_strncasecmp( ct, "text/plain", 10 ) == 0 )
+ {
+ g_free( ct );
+
+ if( !body )
+ return( 1 );
+
+ if( sb->who )
+ {
+ imcb_buddy_msg( ic, cmd[1], body, 0, 0 );
+ }
+ else if( sb->chat )
+ {
+ imcb_chat_msg( sb->chat, cmd[1], body, 0, 0 );
+ }
+ else
+ {
+ /* PANIC! */
+ }
+ }
+#if 0
+ // Disable MSN ft support for now.
+ else if( g_strncasecmp( ct, "text/x-msmsgsinvite", 19 ) == 0 )
+ {
+ char *command = msn_findheader( body, "Invitation-Command:", blen );
+ char *cookie = msn_findheader( body, "Invitation-Cookie:", blen );
+ unsigned int icookie;
+
+ g_free( ct );
+
+ /* Every invite should have both a Command and Cookie header */
+ if( !command || !cookie ) {
+ g_free( command );
+ g_free( cookie );
+ imcb_log( ic, "Warning: No command or cookie from %s", sb->who );
+ return 1;
+ }
+
+ icookie = strtoul( cookie, NULL, 10 );
+ g_free( cookie );
+
+ if( g_strncasecmp( command, "INVITE", 6 ) == 0 ) {
+ msn_invitation_invite( sb, cmd[1], icookie, body, blen );
+ } else if( g_strncasecmp( command, "ACCEPT", 6 ) == 0 ) {
+ msn_invitation_accept( sb, cmd[1], icookie, body, blen );
+ } else if( g_strncasecmp( command, "CANCEL", 6 ) == 0 ) {
+ msn_invitation_cancel( sb, cmd[1], icookie, body, blen );
+ } else {
+ imcb_log( ic, "Warning: Received invalid invitation with "
+ "command %s from %s", command, sb->who );
+ }
+
+ g_free( command );
+ }
+#endif
+ else if( g_strncasecmp( ct, "application/x-msnmsgrp2p", 24 ) == 0 )
+ {
+ /* Not currently implemented. Don't warn about it since
+ this seems to be used for avatars now. */
+ g_free( ct );
+ }
+ else if( g_strncasecmp( ct, "text/x-msmsgscontrol", 20 ) == 0 )
+ {
+ char *who = msn_findheader( msg, "TypingUser:", msglen );
+
+ if( who )
+ {
+ imcb_buddy_typing( ic, who, OPT_TYPING );
+ g_free( who );
+ }
+
+ g_free( ct );
+ }
+ else
+ {
+ g_free( ct );
+ }
+ }
+
+ return( 1 );
+}
+
+static gboolean msn_sb_keepalive( gpointer data, gint source, b_input_condition cond )
+{
+ struct msn_switchboard *sb = data;
+ return sb->ready && msn_sb_sendmessage( sb, SB_KEEPALIVE_MESSAGE );
+}
+
+void msn_sb_start_keepalives( struct msn_switchboard *sb, gboolean initial )
+{
+ bee_user_t *bu;
+
+ if( sb && sb->who && sb->keepalive == 0 &&
+ ( bu = bee_user_by_handle( sb->ic->bee, sb->ic, sb->who ) ) &&
+ !( bu->flags & BEE_USER_ONLINE ) &&
+ set_getbool( &sb->ic->acc->set, "switchboard_keepalives" ) )
+ {
+ if( initial )
+ msn_sb_keepalive( sb, 0, 0 );
+
+ sb->keepalive = b_timeout_add( 20000, msn_sb_keepalive, sb );
+ }
+}
+
+void msn_sb_stop_keepalives( struct msn_switchboard *sb )
+{
+ if( sb && sb->keepalive > 0 )
+ {
+ b_event_remove( sb->keepalive );
+ sb->keepalive = 0;
+ }
+}
diff --git a/protocols/msn/soap.c b/protocols/msn/soap.c
new file mode 100644
index 00000000..dac46a75
--- /dev/null
+++ b/protocols/msn/soap.c
@@ -0,0 +1,1162 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* MSN module - All the SOAPy XML stuff.
+ Some manager at Microsoft apparently thought MSNP wasn't XMLy enough so
+ someone stepped up and changed that. This is the result. Kilobytes and
+ more kilobytes of XML vomit to transfer tiny bits of informaiton. */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "http_client.h"
+#include "soap.h"
+#include "msn.h"
+#include "bitlbee.h"
+#include "url.h"
+#include "misc.h"
+#include "sha1.h"
+#include "base64.h"
+#include "xmltree.h"
+#include <ctype.h>
+#include <errno.h>
+
+/* This file tries to make SOAP stuff pretty simple to do by letting you just
+ provide a function to build a request, a few functions to parse various
+ parts of the response, and a function to run when the full response was
+ received and parsed. See the various examples below. */
+
+typedef enum
+{
+ MSN_SOAP_OK,
+ MSN_SOAP_RETRY,
+ MSN_SOAP_REAUTH,
+ MSN_SOAP_ABORT,
+} msn_soap_result_t;
+
+struct msn_soap_req_data;
+typedef int (*msn_soap_func) ( struct msn_soap_req_data * );
+
+struct msn_soap_req_data
+{
+ void *data;
+ struct im_connection *ic;
+ int ttl;
+
+ char *url, *action, *payload;
+ struct http_request *http_req;
+
+ const struct xt_handler_entry *xml_parser;
+ msn_soap_func build_request, handle_response, free_data;
+};
+
+static int msn_soap_send_request( struct msn_soap_req_data *req );
+static void msn_soap_free( struct msn_soap_req_data *soap_req );
+static void msn_soap_debug_print( const char *headers, const char *payload );
+
+static int msn_soap_start( struct im_connection *ic,
+ void *data,
+ msn_soap_func build_request,
+ const struct xt_handler_entry *xml_parser,
+ msn_soap_func handle_response,
+ msn_soap_func free_data )
+{
+ struct msn_soap_req_data *req = g_new0( struct msn_soap_req_data, 1 );
+
+ req->ic = ic;
+ req->data = data;
+ req->xml_parser = xml_parser;
+ req->build_request = build_request;
+ req->handle_response = handle_response;
+ req->free_data = free_data;
+ req->ttl = 3;
+
+ return msn_soap_send_request( req );
+}
+
+static void msn_soap_handle_response( struct http_request *http_req );
+
+static int msn_soap_send_request( struct msn_soap_req_data *soap_req )
+{
+ char *http_req;
+ char *soap_action = NULL;
+ url_t url;
+
+ soap_req->build_request( soap_req );
+
+ if( soap_req->action )
+ soap_action = g_strdup_printf( "SOAPAction: \"%s\"\r\n", soap_req->action );
+
+ url_set( &url, soap_req->url );
+ http_req = g_strdup_printf( SOAP_HTTP_REQUEST, url.file, url.host,
+ soap_action ? soap_action : "",
+ strlen( soap_req->payload ), soap_req->payload );
+
+ msn_soap_debug_print( http_req, soap_req->payload );
+
+ soap_req->http_req = http_dorequest( url.host, url.port, url.proto == PROTO_HTTPS,
+ http_req, msn_soap_handle_response, soap_req );
+
+ g_free( http_req );
+ g_free( soap_action );
+
+ return soap_req->http_req != NULL;
+}
+
+static void msn_soap_handle_response( struct http_request *http_req )
+{
+ struct msn_soap_req_data *soap_req = http_req->data;
+ int st;
+
+ if( g_slist_find( msn_connections, soap_req->ic ) == NULL )
+ {
+ msn_soap_free( soap_req );
+ return;
+ }
+
+ msn_soap_debug_print( http_req->reply_headers, http_req->reply_body );
+
+ if( http_req->body_size > 0 )
+ {
+ struct xt_parser *parser;
+ struct xt_node *err;
+
+ parser = xt_new( soap_req->xml_parser, soap_req );
+ xt_feed( parser, http_req->reply_body, http_req->body_size );
+ if( http_req->status_code == 500 &&
+ ( err = xt_find_path( parser->root, "soap:Body/soap:Fault/detail/errorcode" ) ) &&
+ err->text_len > 0 )
+ {
+ if( strcmp( err->text, "PassportAuthFail" ) == 0 )
+ {
+ xt_free( parser );
+ st = MSN_SOAP_REAUTH;
+ goto fail;
+ }
+ /* TODO: Handle/report other errors. */
+ }
+
+ xt_handle( parser, NULL, -1 );
+ xt_free( parser );
+ }
+
+ st = soap_req->handle_response( soap_req );
+
+fail:
+ g_free( soap_req->url );
+ g_free( soap_req->action );
+ g_free( soap_req->payload );
+ soap_req->url = soap_req->action = soap_req->payload = NULL;
+
+ if( st == MSN_SOAP_RETRY && --soap_req->ttl )
+ {
+ msn_soap_send_request( soap_req );
+ }
+ else if( st == MSN_SOAP_REAUTH )
+ {
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ if( !( md->flags & MSN_REAUTHING ) )
+ {
+ /* Nonce shouldn't actually be touched for re-auths. */
+ msn_soap_passport_sso_request( soap_req->ic, "blaataap" );
+ md->flags |= MSN_REAUTHING;
+ }
+ md->soapq = g_slist_append( md->soapq, soap_req );
+ }
+ else
+ {
+ soap_req->free_data( soap_req );
+ g_free( soap_req );
+ }
+}
+
+static char *msn_soap_abservice_build( const char *body_fmt, const char *scenario, const char *ticket, ... )
+{
+ va_list params;
+ char *ret, *format, *body;
+
+ format = g_markup_printf_escaped( SOAP_ABSERVICE_PAYLOAD, scenario, ticket );
+
+ va_start( params, ticket );
+ body = g_strdup_vprintf( body_fmt, params );
+ va_end( params );
+
+ ret = g_strdup_printf( format, body );
+ g_free( body );
+ g_free( format );
+
+ return ret;
+}
+
+static void msn_soap_debug_print( const char *headers, const char *payload )
+{
+ char *s;
+ int st;
+
+ if( !getenv( "BITLBEE_DEBUG" ) )
+ return;
+
+ if( ( s = strstr( headers, "\r\n\r\n" ) ) )
+ st = write( 1, s, s - headers + 4 );
+ else
+ st = write( 1, headers, strlen( headers ) );
+
+#ifdef DEBUG
+ {
+ struct xt_node *xt = xt_from_string( payload );
+ if( xt )
+ xt_print( xt );
+ xt_free_node( xt );
+ }
+#endif
+}
+
+int msn_soapq_flush( struct im_connection *ic, gboolean resend )
+{
+ struct msn_data *md = ic->proto_data;
+
+ while( md->soapq )
+ {
+ if( resend )
+ msn_soap_send_request( (struct msn_soap_req_data*) md->soapq->data );
+ else
+ msn_soap_free( (struct msn_soap_req_data*) md->soapq->data );
+ md->soapq = g_slist_remove( md->soapq, md->soapq->data );
+ }
+
+ return MSN_SOAP_OK;
+}
+
+static void msn_soap_free( struct msn_soap_req_data *soap_req )
+{
+ soap_req->free_data( soap_req );
+ g_free( soap_req->url );
+ g_free( soap_req->action );
+ g_free( soap_req->payload );
+ g_free( soap_req );
+}
+
+
+/* passport_sso: Authentication MSNP15+ */
+
+struct msn_soap_passport_sso_data
+{
+ char *nonce;
+ char *secret;
+ char *error;
+ char *redirect;
+};
+
+static int msn_soap_passport_sso_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_soap_passport_sso_data *sd = soap_req->data;
+ struct im_connection *ic = soap_req->ic;
+ struct msn_data *md = ic->proto_data;
+ char pass[MAX_PASSPORT_PWLEN+1];
+
+ if( sd->redirect )
+ {
+ soap_req->url = sd->redirect;
+ sd->redirect = NULL;
+ }
+ /* MS changed this URL and broke the old MSN-specific one. The generic
+ one works, forwarding us to a msn.com URL that works. Takes an extra
+ second, but that's better than not being able to log in at all. :-/
+ else if( g_str_has_suffix( ic->acc->user, "@msn.com" ) )
+ soap_req->url = g_strdup( SOAP_PASSPORT_SSO_URL_MSN );
+ */
+ else
+ soap_req->url = g_strdup( SOAP_PASSPORT_SSO_URL );
+
+ strncpy( pass, ic->acc->pass, MAX_PASSPORT_PWLEN );
+ pass[MAX_PASSPORT_PWLEN] = '\0';
+ soap_req->payload = g_markup_printf_escaped( SOAP_PASSPORT_SSO_PAYLOAD,
+ ic->acc->user, pass, md->pp_policy );
+
+ return MSN_SOAP_OK;
+}
+
+static xt_status msn_soap_passport_sso_token( struct xt_node *node, gpointer data )
+{
+ struct msn_soap_req_data *soap_req = data;
+ struct msn_soap_passport_sso_data *sd = soap_req->data;
+ struct msn_data *md = soap_req->ic->proto_data;
+ struct xt_node *p;
+ char *id;
+
+ if( ( id = xt_find_attr( node, "Id" ) ) == NULL )
+ return XT_HANDLED;
+ id += strlen( id ) - 1;
+ if( *id == '1' &&
+ ( p = xt_find_path( node, "../../wst:RequestedProofToken/wst:BinarySecret" ) ) &&
+ p->text )
+ sd->secret = g_strdup( p->text );
+
+ *id -= '1';
+ if( *id >= 0 && *id < sizeof( md->tokens ) / sizeof( md->tokens[0] ) )
+ {
+ g_free( md->tokens[(int)*id] );
+ md->tokens[(int)*id] = g_strdup( node->text );
+ }
+
+ return XT_HANDLED;
+}
+
+static xt_status msn_soap_passport_failure( struct xt_node *node, gpointer data )
+{
+ struct msn_soap_req_data *soap_req = data;
+ struct msn_soap_passport_sso_data *sd = soap_req->data;
+ struct xt_node *code = xt_find_node( node->children, "faultcode" );
+ struct xt_node *string = xt_find_node( node->children, "faultstring" );
+ struct xt_node *url;
+
+ if( code == NULL || code->text_len == 0 )
+ sd->error = g_strdup( "Unknown error" );
+ else if( strcmp( code->text, "psf:Redirect" ) == 0 &&
+ ( url = xt_find_node( node->children, "psf:redirectUrl" ) ) &&
+ url->text_len > 0 )
+ sd->redirect = g_strdup( url->text );
+ else
+ sd->error = g_strdup_printf( "%s (%s)", code->text, string && string->text_len ?
+ string->text : "no description available" );
+
+ return XT_HANDLED;
+}
+
+static const struct xt_handler_entry msn_soap_passport_sso_parser[] = {
+ { "wsse:BinarySecurityToken", "wst:RequestedSecurityToken", msn_soap_passport_sso_token },
+ { "S:Fault", "S:Envelope", msn_soap_passport_failure },
+ { NULL, NULL, NULL }
+};
+
+static char *msn_key_fuckery( char *key, int key_len, char *type )
+{
+ unsigned char hash1[20+strlen(type)+1];
+ unsigned char hash2[20];
+ char *ret;
+
+ sha1_hmac( key, key_len, type, 0, hash1 );
+ strcpy( (char*) hash1 + 20, type );
+ sha1_hmac( key, key_len, (char*) hash1, sizeof( hash1 ) - 1, hash2 );
+
+ /* This is okay as hash1 is read completely before it's overwritten. */
+ sha1_hmac( key, key_len, (char*) hash1, 20, hash1 );
+ sha1_hmac( key, key_len, (char*) hash1, sizeof( hash1 ) - 1, hash1 );
+
+ ret = g_malloc( 24 );
+ memcpy( ret, hash2, 20 );
+ memcpy( ret + 20, hash1, 4 );
+ return ret;
+}
+
+static int msn_soap_passport_sso_handle_response( struct msn_soap_req_data *soap_req )
+{
+ struct msn_soap_passport_sso_data *sd = soap_req->data;
+ struct im_connection *ic = soap_req->ic;
+ struct msn_data *md = ic->proto_data;
+ char *key1, *key2, *key3, *blurb64;
+ int key1_len;
+ unsigned char *padnonce, *des3res;
+ struct
+ {
+ unsigned int uStructHeaderSize; // 28. Does not count data
+ unsigned int uCryptMode; // CRYPT_MODE_CBC (1)
+ unsigned int uCipherType; // TripleDES (0x6603)
+ unsigned int uHashType; // SHA1 (0x8004)
+ unsigned int uIVLen; // 8
+ unsigned int uHashLen; // 20
+ unsigned int uCipherLen; // 72
+ unsigned char iv[8];
+ unsigned char hash[20];
+ unsigned char cipherbytes[72];
+ } blurb = {
+ GUINT32_TO_LE( 28 ),
+ GUINT32_TO_LE( 1 ),
+ GUINT32_TO_LE( 0x6603 ),
+ GUINT32_TO_LE( 0x8004 ),
+ GUINT32_TO_LE( 8 ),
+ GUINT32_TO_LE( 20 ),
+ GUINT32_TO_LE( 72 ),
+ };
+
+ if( sd->redirect )
+ return MSN_SOAP_RETRY;
+
+ if( md->soapq )
+ {
+ md->flags &= ~MSN_REAUTHING;
+ return msn_soapq_flush( ic, TRUE );
+ }
+
+ if( sd->secret == NULL )
+ {
+ msn_auth_got_passport_token( ic, NULL, sd->error );
+ return MSN_SOAP_OK;
+ }
+
+ key1_len = base64_decode( sd->secret, (unsigned char**) &key1 );
+
+ key2 = msn_key_fuckery( key1, key1_len, "WS-SecureConversationSESSION KEY HASH" );
+ key3 = msn_key_fuckery( key1, key1_len, "WS-SecureConversationSESSION KEY ENCRYPTION" );
+
+ sha1_hmac( key2, 24, sd->nonce, 0, blurb.hash );
+ padnonce = g_malloc( strlen( sd->nonce ) + 8 );
+ strcpy( (char*) padnonce, sd->nonce );
+ memset( padnonce + strlen( sd->nonce ), 8, 8 );
+
+ random_bytes( blurb.iv, 8 );
+
+ ssl_des3_encrypt( (unsigned char*) key3, 24, padnonce, strlen( sd->nonce ) + 8, blurb.iv, &des3res );
+ memcpy( blurb.cipherbytes, des3res, 72 );
+
+ blurb64 = base64_encode( (unsigned char*) &blurb, sizeof( blurb ) );
+ msn_auth_got_passport_token( ic, blurb64, NULL );
+
+ g_free( padnonce );
+ g_free( blurb64 );
+ g_free( des3res );
+ g_free( key1 );
+ g_free( key2 );
+ g_free( key3 );
+
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_passport_sso_free_data( struct msn_soap_req_data *soap_req )
+{
+ struct msn_soap_passport_sso_data *sd = soap_req->data;
+
+ g_free( sd->nonce );
+ g_free( sd->secret );
+ g_free( sd->error );
+ g_free( sd->redirect );
+ g_free( sd );
+
+ return MSN_SOAP_OK;
+}
+
+int msn_soap_passport_sso_request( struct im_connection *ic, const char *nonce )
+{
+ struct msn_soap_passport_sso_data *sd = g_new0( struct msn_soap_passport_sso_data, 1 );
+
+ sd->nonce = g_strdup( nonce );
+
+ return msn_soap_start( ic, sd, msn_soap_passport_sso_build_request,
+ msn_soap_passport_sso_parser,
+ msn_soap_passport_sso_handle_response,
+ msn_soap_passport_sso_free_data );
+}
+
+
+/* oim_send: Sending offline messages */
+
+struct msn_soap_oim_send_data
+{
+ char *to;
+ char *msg;
+ int number;
+ msn_soap_result_t need_retry;
+};
+
+static int msn_soap_oim_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_soap_oim_send_data *oim = soap_req->data;
+ struct im_connection *ic = soap_req->ic;
+ struct msn_data *md = ic->proto_data;
+ char *display_name_b64;
+
+ display_name_b64 = tobase64( set_getstr( &ic->acc->set, "display_name" ) );
+
+ soap_req->url = g_strdup( SOAP_OIM_SEND_URL );
+ soap_req->action = g_strdup( SOAP_OIM_SEND_ACTION );
+ soap_req->payload = g_markup_printf_escaped( SOAP_OIM_SEND_PAYLOAD,
+ ic->acc->user, display_name_b64, MSNP_VER, MSNP_BUILD,
+ oim->to, md->tokens[2],
+ MSNP11_PROD_ID, md->lock_key ? md->lock_key : "",
+ oim->number, oim->number, oim->msg );
+
+ g_free( display_name_b64 );
+ oim->need_retry = MSN_SOAP_OK;
+
+ return MSN_SOAP_OK;
+}
+
+static xt_status msn_soap_oim_reauth( struct xt_node *node, gpointer data )
+{
+ struct msn_soap_req_data *soap_req = data;
+ struct msn_soap_oim_send_data *oim = soap_req->data;
+ struct im_connection *ic = soap_req->ic;
+ struct msn_data *md = ic->proto_data;
+ struct xt_node *c;
+
+ if( ( c = xt_find_node( node->children, "LockKeyChallenge" ) ) && c->text_len > 0 )
+ {
+ g_free( md->lock_key );
+ md->lock_key = msn_p11_challenge( c->text );
+ oim->need_retry = MSN_SOAP_RETRY;
+ }
+ if( xt_find_node( node->children, "RequiredAuthPolicy" ) )
+ {
+ oim->need_retry = MSN_SOAP_REAUTH;
+ }
+
+ return XT_HANDLED;
+}
+
+static const struct xt_handler_entry msn_soap_oim_send_parser[] = {
+ { "detail", "soap:Fault", msn_soap_oim_reauth },
+ { NULL, NULL, NULL }
+};
+
+static int msn_soap_oim_handle_response( struct msn_soap_req_data *soap_req )
+{
+ struct msn_soap_oim_send_data *oim = soap_req->data;
+
+ if( soap_req->http_req->status_code == 500 && oim->need_retry && soap_req->ttl > 0 )
+ {
+ return oim->need_retry;
+ }
+ else if( soap_req->http_req->status_code == 200 )
+ {
+ /* Noise..
+ imcb_log( soap_req->ic, "Offline message successfully delivered to %s", oim->to );
+ */
+ return MSN_SOAP_OK;
+ }
+ else
+ {
+ char *dec = frombase64( oim->msg );
+ imcb_log( soap_req->ic, "Failed to deliver offline message to %s:\n%s", oim->to, dec );
+ g_free( dec );
+ return MSN_SOAP_ABORT;
+ }
+}
+
+static int msn_soap_oim_free_data( struct msn_soap_req_data *soap_req )
+{
+ struct msn_soap_oim_send_data *oim = soap_req->data;
+
+ g_free( oim->to );
+ g_free( oim->msg );
+ g_free( oim );
+
+ return MSN_SOAP_OK;
+}
+
+int msn_soap_oim_send( struct im_connection *ic, const char *to, const char *msg )
+{
+ struct msn_soap_oim_send_data *data;
+
+ /* Don't send any of the special messages since they creep people out. :-) */
+ if( strncmp( msg, "\r\r", 2 ) == 0 )
+ return 0;
+
+ data = g_new0( struct msn_soap_oim_send_data, 1 );
+ data->to = g_strdup( to );
+ data->msg = tobase64( msg );
+ data->number = 1;
+
+ return msn_soap_start( ic, data, msn_soap_oim_build_request,
+ msn_soap_oim_send_parser,
+ msn_soap_oim_handle_response,
+ msn_soap_oim_free_data );
+}
+
+int msn_soap_oim_send_queue( struct im_connection *ic, GSList **msgq )
+{
+ GSList *l;
+ char *n = NULL;
+
+ for( l = *msgq; l; l = l->next )
+ {
+ struct msn_message *m = l->data;
+
+ if( n == NULL )
+ n = m->who;
+ if( strcmp( n, m->who ) == 0 )
+ msn_soap_oim_send( ic, m->who, m->text );
+ }
+
+ while( *msgq != NULL )
+ {
+ struct msn_message *m = (*msgq)->data;
+
+ g_free( m->who );
+ g_free( m->text );
+ g_free( m );
+
+ *msgq = g_slist_remove( *msgq, m );
+ }
+
+ return 1;
+}
+
+
+/* memlist: Fetching the membership list (NOT address book) */
+
+static int msn_soap_memlist_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ soap_req->url = g_strdup( SOAP_MEMLIST_URL );
+ soap_req->action = g_strdup( SOAP_MEMLIST_ACTION );
+ soap_req->payload = msn_soap_abservice_build( SOAP_MEMLIST_PAYLOAD, "Initial", md->tokens[1] );
+
+ return 1;
+}
+
+static xt_status msn_soap_memlist_member( struct xt_node *node, gpointer data )
+{
+ bee_user_t *bu;
+ struct msn_buddy_data *bd;
+ struct xt_node *p;
+ char *role = NULL, *handle = NULL;
+ struct msn_soap_req_data *soap_req = data;
+ struct im_connection *ic = soap_req->ic;
+
+ if( ( p = xt_find_path( node, "../../MemberRole" ) ) )
+ role = p->text;
+
+ if( ( p = xt_find_node( node->children, "PassportName" ) ) )
+ handle = p->text;
+
+ if( !role || !handle ||
+ !( ( bu = bee_user_by_handle( ic->bee, ic, handle ) ) ||
+ ( bu = bee_user_new( ic->bee, ic, handle, 0 ) ) ) )
+ return XT_HANDLED;
+
+ bd = bu->data;
+ if( strcmp( role, "Allow" ) == 0 )
+ {
+ bd->flags |= MSN_BUDDY_AL;
+ ic->permit = g_slist_prepend( ic->permit, g_strdup( handle ) );
+ }
+ else if( strcmp( role, "Block" ) == 0 )
+ {
+ bd->flags |= MSN_BUDDY_BL;
+ ic->deny = g_slist_prepend( ic->deny, g_strdup( handle ) );
+ }
+ else if( strcmp( role, "Reverse" ) == 0 )
+ bd->flags |= MSN_BUDDY_RL;
+ else if( strcmp( role, "Pending" ) == 0 )
+ bd->flags |= MSN_BUDDY_PL;
+
+ if( getenv( "BITLBEE_DEBUG" ) )
+ printf( "%p %s %d\n", bu, handle, bd->flags );
+
+ return XT_HANDLED;
+}
+
+static const struct xt_handler_entry msn_soap_memlist_parser[] = {
+ { "Member", "Members", msn_soap_memlist_member },
+ { NULL, NULL, NULL }
+};
+
+static int msn_soap_memlist_handle_response( struct msn_soap_req_data *soap_req )
+{
+ msn_soap_addressbook_request( soap_req->ic );
+
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_memlist_free_data( struct msn_soap_req_data *soap_req )
+{
+ return 0;
+}
+
+int msn_soap_memlist_request( struct im_connection *ic )
+{
+ return msn_soap_start( ic, NULL, msn_soap_memlist_build_request,
+ msn_soap_memlist_parser,
+ msn_soap_memlist_handle_response,
+ msn_soap_memlist_free_data );
+}
+
+/* Variant: Adding/Removing people */
+struct msn_soap_memlist_edit_data
+{
+ char *handle;
+ gboolean add;
+ msn_buddy_flags_t list;
+};
+
+static int msn_soap_memlist_edit_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+ struct msn_soap_memlist_edit_data *med = soap_req->data;
+ char *add, *scenario, *list;
+
+ soap_req->url = g_strdup( SOAP_MEMLIST_URL );
+ if( med->add )
+ {
+ soap_req->action = g_strdup( SOAP_MEMLIST_ADD_ACTION );
+ add = "Add";
+ }
+ else
+ {
+ soap_req->action = g_strdup( SOAP_MEMLIST_DEL_ACTION );
+ add = "Delete";
+ }
+ switch( med->list )
+ {
+ case MSN_BUDDY_AL:
+ scenario = "BlockUnblock";
+ list = "Allow";
+ break;
+ case MSN_BUDDY_BL:
+ scenario = "BlockUnblock";
+ list = "Block";
+ break;
+ case MSN_BUDDY_RL:
+ scenario = "Timer";
+ list = "Reverse";
+ break;
+ case MSN_BUDDY_PL:
+ default:
+ scenario = "Timer";
+ list = "Pending";
+ break;
+ }
+ soap_req->payload = msn_soap_abservice_build( SOAP_MEMLIST_EDIT_PAYLOAD,
+ scenario, md->tokens[1], add, list, med->handle, add );
+
+ return 1;
+}
+
+static int msn_soap_memlist_edit_handle_response( struct msn_soap_req_data *soap_req )
+{
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_memlist_edit_free_data( struct msn_soap_req_data *soap_req )
+{
+ struct msn_soap_memlist_edit_data *med = soap_req->data;
+
+ g_free( med->handle );
+ g_free( med );
+
+ return 0;
+}
+
+int msn_soap_memlist_edit( struct im_connection *ic, const char *handle, gboolean add, int list )
+{
+ struct msn_soap_memlist_edit_data *med;
+
+ med = g_new0( struct msn_soap_memlist_edit_data, 1 );
+ med->handle = g_strdup( handle );
+ med->add = add;
+ med->list = list;
+
+ return msn_soap_start( ic, med, msn_soap_memlist_edit_build_request,
+ NULL,
+ msn_soap_memlist_edit_handle_response,
+ msn_soap_memlist_edit_free_data );
+}
+
+
+/* addressbook: Fetching the membership list (NOT address book) */
+
+static int msn_soap_addressbook_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ soap_req->url = g_strdup( SOAP_ADDRESSBOOK_URL );
+ soap_req->action = g_strdup( SOAP_ADDRESSBOOK_ACTION );
+ soap_req->payload = msn_soap_abservice_build( SOAP_ADDRESSBOOK_PAYLOAD, "Initial", md->tokens[1] );
+
+ return 1;
+}
+
+static xt_status msn_soap_addressbook_group( struct xt_node *node, gpointer data )
+{
+ struct xt_node *p;
+ char *id = NULL, *name = NULL;
+ struct msn_soap_req_data *soap_req = data;
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ if( ( p = xt_find_path( node, "../groupId" ) ) )
+ id = p->text;
+
+ if( ( p = xt_find_node( node->children, "name" ) ) )
+ name = p->text;
+
+ if( id && name )
+ {
+ struct msn_group *mg = g_new0( struct msn_group, 1 );
+ mg->id = g_strdup( id );
+ mg->name = g_strdup( name );
+ md->groups = g_slist_prepend( md->groups, mg );
+ }
+
+ if( getenv( "BITLBEE_DEBUG" ) )
+ printf( "%s %s\n", id, name );
+
+ return XT_HANDLED;
+}
+
+static xt_status msn_soap_addressbook_contact( struct xt_node *node, gpointer data )
+{
+ bee_user_t *bu;
+ struct msn_buddy_data *bd;
+ struct xt_node *p;
+ char *id = NULL, *type = NULL, *handle = NULL, *is_msgr = "false",
+ *display_name = NULL, *group_id = NULL;
+ struct msn_soap_req_data *soap_req = data;
+ struct im_connection *ic = soap_req->ic;
+ struct msn_group *group;
+
+ if( ( p = xt_find_path( node, "../contactId" ) ) )
+ id = p->text;
+ if( ( p = xt_find_node( node->children, "contactType" ) ) )
+ type = p->text;
+ if( ( p = xt_find_node( node->children, "passportName" ) ) )
+ handle = p->text;
+ if( ( p = xt_find_node( node->children, "displayName" ) ) )
+ display_name = p->text;
+ if( ( p = xt_find_node( node->children, "isMessengerUser" ) ) )
+ is_msgr = p->text;
+ if( ( p = xt_find_path( node, "groupIds/guid" ) ) )
+ group_id = p->text;
+
+ if( type && g_strcasecmp( type, "me" ) == 0 )
+ {
+ set_t *set = set_find( &ic->acc->set, "display_name" );
+ g_free( set->value );
+ set->value = g_strdup( display_name );
+
+ /* Try to fetch the profile; if the user has one, that's where
+ we can find the persistent display_name. */
+ if( ( p = xt_find_node( node->children, "CID" ) ) && p->text )
+ msn_soap_profile_get( ic, p->text );
+
+ return XT_HANDLED;
+ }
+
+ if( !bool2int( is_msgr ) || handle == NULL )
+ return XT_HANDLED;
+
+ if( !( bu = bee_user_by_handle( ic->bee, ic, handle ) ) &&
+ !( bu = bee_user_new( ic->bee, ic, handle, 0 ) ) )
+ return XT_HANDLED;
+
+ bd = bu->data;
+ bd->flags |= MSN_BUDDY_FL;
+ g_free( bd->cid );
+ bd->cid = g_strdup( id );
+
+ imcb_rename_buddy( ic, handle, display_name );
+
+ if( group_id && ( group = msn_group_by_id( ic, group_id ) ) )
+ imcb_add_buddy( ic, handle, group->name );
+
+ if( getenv( "BITLBEE_DEBUG" ) )
+ printf( "%s %s %s %s\n", id, type, handle, display_name );
+
+ return XT_HANDLED;
+}
+
+static const struct xt_handler_entry msn_soap_addressbook_parser[] = {
+ { "contactInfo", "Contact", msn_soap_addressbook_contact },
+ { "groupInfo", "Group", msn_soap_addressbook_group },
+ { NULL, NULL, NULL }
+};
+
+static int msn_soap_addressbook_handle_response( struct msn_soap_req_data *soap_req )
+{
+ GSList *l;
+ int wtf = 0;
+
+ for( l = soap_req->ic->bee->users; l; l = l->next )
+ {
+ struct bee_user *bu = l->data;
+ struct msn_buddy_data *bd = bu->data;
+
+ if( bu->ic == soap_req->ic && bd )
+ {
+ msn_buddy_ask( bu );
+
+ if( ( bd->flags & ( MSN_BUDDY_AL | MSN_BUDDY_BL ) ) ==
+ ( MSN_BUDDY_AL | MSN_BUDDY_BL ) )
+ {
+ bd->flags &= ~MSN_BUDDY_BL;
+ wtf++;
+ }
+ }
+ }
+
+ if( wtf )
+ imcb_log( soap_req->ic, "Warning: %d contacts were in both your "
+ "block and your allow list. Assuming they're all "
+ "allowed. Use the official WLM client once to fix "
+ "this.", wtf );
+
+ msn_auth_got_contact_list( soap_req->ic );
+
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_addressbook_free_data( struct msn_soap_req_data *soap_req )
+{
+ return 0;
+}
+
+int msn_soap_addressbook_request( struct im_connection *ic )
+{
+ return msn_soap_start( ic, NULL, msn_soap_addressbook_build_request,
+ msn_soap_addressbook_parser,
+ msn_soap_addressbook_handle_response,
+ msn_soap_addressbook_free_data );
+}
+
+/* Variant: Change our display name. */
+static int msn_soap_ab_namechange_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ soap_req->url = g_strdup( SOAP_ADDRESSBOOK_URL );
+ soap_req->action = g_strdup( SOAP_AB_NAMECHANGE_ACTION );
+ soap_req->payload = msn_soap_abservice_build( SOAP_AB_NAMECHANGE_PAYLOAD,
+ "Timer", md->tokens[1], (char *) soap_req->data );
+
+ return 1;
+}
+
+static int msn_soap_ab_namechange_handle_response( struct msn_soap_req_data *soap_req )
+{
+ /* TODO: Ack the change? Not sure what the NAKs look like.. */
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_ab_namechange_free_data( struct msn_soap_req_data *soap_req )
+{
+ g_free( soap_req->data );
+ return 0;
+}
+
+int msn_soap_addressbook_set_display_name( struct im_connection *ic, const char *new )
+{
+ return msn_soap_start( ic, g_strdup( new ),
+ msn_soap_ab_namechange_build_request,
+ NULL,
+ msn_soap_ab_namechange_handle_response,
+ msn_soap_ab_namechange_free_data );
+}
+
+/* Add a contact. */
+static int msn_soap_ab_contact_add_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+ bee_user_t *bu = soap_req->data;
+
+ soap_req->url = g_strdup( SOAP_ADDRESSBOOK_URL );
+ soap_req->action = g_strdup( SOAP_AB_CONTACT_ADD_ACTION );
+ soap_req->payload = msn_soap_abservice_build( SOAP_AB_CONTACT_ADD_PAYLOAD,
+ "ContactSave", md->tokens[1], bu->handle, bu->fullname ? bu->fullname : bu->handle );
+
+ return 1;
+}
+
+static xt_status msn_soap_ab_contact_add_cid( struct xt_node *node, gpointer data )
+{
+ struct msn_soap_req_data *soap_req = data;
+ bee_user_t *bu = soap_req->data;
+ struct msn_buddy_data *bd = bu->data;
+
+ g_free( bd->cid );
+ bd->cid = g_strdup( node->text );
+
+ return XT_HANDLED;
+}
+
+static const struct xt_handler_entry msn_soap_ab_contact_add_parser[] = {
+ { "guid", "ABContactAddResult", msn_soap_ab_contact_add_cid },
+ { NULL, NULL, NULL }
+};
+
+static int msn_soap_ab_contact_add_handle_response( struct msn_soap_req_data *soap_req )
+{
+ /* TODO: Ack the change? Not sure what the NAKs look like.. */
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_ab_contact_add_free_data( struct msn_soap_req_data *soap_req )
+{
+ return 0;
+}
+
+int msn_soap_ab_contact_add( struct im_connection *ic, bee_user_t *bu )
+{
+ return msn_soap_start( ic, bu,
+ msn_soap_ab_contact_add_build_request,
+ msn_soap_ab_contact_add_parser,
+ msn_soap_ab_contact_add_handle_response,
+ msn_soap_ab_contact_add_free_data );
+}
+
+/* Remove a contact. */
+static int msn_soap_ab_contact_del_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+ const char *cid = soap_req->data;
+
+ soap_req->url = g_strdup( SOAP_ADDRESSBOOK_URL );
+ soap_req->action = g_strdup( SOAP_AB_CONTACT_DEL_ACTION );
+ soap_req->payload = msn_soap_abservice_build( SOAP_AB_CONTACT_DEL_PAYLOAD,
+ "Timer", md->tokens[1], cid );
+
+ return 1;
+}
+
+static int msn_soap_ab_contact_del_handle_response( struct msn_soap_req_data *soap_req )
+{
+ /* TODO: Ack the change? Not sure what the NAKs look like.. */
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_ab_contact_del_free_data( struct msn_soap_req_data *soap_req )
+{
+ g_free( soap_req->data );
+ return 0;
+}
+
+int msn_soap_ab_contact_del( struct im_connection *ic, bee_user_t *bu )
+{
+ struct msn_buddy_data *bd = bu->data;
+
+ return msn_soap_start( ic, g_strdup( bd->cid ),
+ msn_soap_ab_contact_del_build_request,
+ NULL,
+ msn_soap_ab_contact_del_handle_response,
+ msn_soap_ab_contact_del_free_data );
+}
+
+
+
+/* Storage stuff: Fetch profile. */
+static int msn_soap_profile_get_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ soap_req->url = g_strdup( SOAP_STORAGE_URL );
+ soap_req->action = g_strdup( SOAP_PROFILE_GET_ACTION );
+ soap_req->payload = g_markup_printf_escaped( SOAP_PROFILE_GET_PAYLOAD,
+ md->tokens[3], (char*) soap_req->data );
+
+ return 1;
+}
+
+static xt_status msn_soap_profile_get_result( struct xt_node *node, gpointer data )
+{
+ struct msn_soap_req_data *soap_req = data;
+ struct im_connection *ic = soap_req->ic;
+ struct msn_data *md = soap_req->ic->proto_data;
+ struct xt_node *dn;
+
+ if( ( dn = xt_find_node( node->children, "DisplayName" ) ) && dn->text )
+ {
+ set_t *set = set_find( &ic->acc->set, "display_name" );
+ g_free( set->value );
+ set->value = g_strdup( dn->text );
+
+ md->flags |= MSN_GOT_PROFILE_DN;
+ }
+
+ return XT_HANDLED;
+}
+
+static xt_status msn_soap_profile_get_rid( struct xt_node *node, gpointer data )
+{
+ struct msn_soap_req_data *soap_req = data;
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ g_free( md->profile_rid );
+ md->profile_rid = g_strdup( node->text );
+
+ return XT_HANDLED;
+}
+
+static const struct xt_handler_entry msn_soap_profile_get_parser[] = {
+ { "ExpressionProfile", "GetProfileResult", msn_soap_profile_get_result },
+ { "ResourceID", "GetProfileResult", msn_soap_profile_get_rid },
+ { NULL, NULL, NULL }
+};
+
+static int msn_soap_profile_get_handle_response( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ md->flags |= MSN_GOT_PROFILE;
+ msn_ns_finish_login( soap_req->ic );
+
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_profile_get_free_data( struct msn_soap_req_data *soap_req )
+{
+ g_free( soap_req->data );
+ return 0;
+}
+
+int msn_soap_profile_get( struct im_connection *ic, const char *cid )
+{
+ return msn_soap_start( ic, g_strdup( cid ),
+ msn_soap_profile_get_build_request,
+ msn_soap_profile_get_parser,
+ msn_soap_profile_get_handle_response,
+ msn_soap_profile_get_free_data );
+}
+
+/* Update profile (display name). */
+static int msn_soap_profile_set_dn_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ soap_req->url = g_strdup( SOAP_STORAGE_URL );
+ soap_req->action = g_strdup( SOAP_PROFILE_SET_DN_ACTION );
+ soap_req->payload = g_markup_printf_escaped( SOAP_PROFILE_SET_DN_PAYLOAD,
+ md->tokens[3], md->profile_rid, (char*) soap_req->data );
+
+ return 1;
+}
+
+static const struct xt_handler_entry msn_soap_profile_set_dn_parser[] = {
+ { NULL, NULL, NULL }
+};
+
+static int msn_soap_profile_set_dn_handle_response( struct msn_soap_req_data *soap_req )
+{
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_profile_set_dn_free_data( struct msn_soap_req_data *soap_req )
+{
+ g_free( soap_req->data );
+ return 0;
+}
+
+int msn_soap_profile_set_dn( struct im_connection *ic, const char *dn )
+{
+ return msn_soap_start( ic, g_strdup( dn ),
+ msn_soap_profile_set_dn_build_request,
+ msn_soap_profile_set_dn_parser,
+ msn_soap_profile_set_dn_handle_response,
+ msn_soap_profile_set_dn_free_data );
+}
diff --git a/protocols/msn/soap.h b/protocols/msn/soap.h
new file mode 100644
index 00000000..a767e00d
--- /dev/null
+++ b/protocols/msn/soap.h
@@ -0,0 +1,378 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* MSN module - All the SOAPy XML stuff.
+ Some manager at Microsoft apparently thought MSNP wasn't XMLy enough so
+ someone stepped up and changed that. This is the result. Kilobytes and
+ more kilobytes of XML vomit to transfer tiny bits of informaiton. */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+/* Thanks to http://msnpiki.msnfanatic.com/ for lots of info on this! */
+
+#ifndef __SOAP_H__
+#define __SOAP_H__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#endif
+#include "nogaim.h"
+
+
+int msn_soapq_flush( struct im_connection *ic, gboolean resend );
+
+
+#define SOAP_HTTP_REQUEST \
+"POST %s HTTP/1.0\r\n" \
+"Host: %s\r\n" \
+"Accept: */*\r\n" \
+"User-Agent: BitlBee " BITLBEE_VERSION "\r\n" \
+"Content-Type: text/xml; charset=utf-8\r\n" \
+"%s" \
+"Content-Length: %zd\r\n" \
+"Cache-Control: no-cache\r\n" \
+"\r\n" \
+"%s"
+
+
+#define SOAP_PASSPORT_SSO_URL "https://login.live.com/RST.srf"
+#define SOAP_PASSPORT_SSO_URL_MSN "https://msnia.login.live.com/pp900/RST.srf"
+#define MAX_PASSPORT_PWLEN 16
+
+#define SOAP_PASSPORT_SSO_PAYLOAD \
+"<Envelope xmlns=\"http://schemas.xmlsoap.org/soap/envelope/\" " \
+ "xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2003/06/secext\" " \
+ "xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" " \
+ "xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2002/12/policy\" " \
+ "xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" " \
+ "xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/03/addressing\" " \
+ "xmlns:wssc=\"http://schemas.xmlsoap.org/ws/2004/04/sc\" " \
+ "xmlns:wst=\"http://schemas.xmlsoap.org/ws/2004/04/trust\">" \
+ "<Header>" \
+ "<ps:AuthInfo " \
+ "xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\" " \
+ "Id=\"PPAuthInfo\">" \
+ "<ps:HostingApp>{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}</ps:HostingApp>" \
+ "<ps:BinaryVersion>4</ps:BinaryVersion>" \
+ "<ps:UIVersion>1</ps:UIVersion>" \
+ "<ps:Cookies></ps:Cookies>" \
+ "<ps:RequestParams>AQAAAAIAAABsYwQAAAAxMDMz</ps:RequestParams>" \
+ "</ps:AuthInfo>" \
+ "<wsse:Security>" \
+ "<wsse:UsernameToken Id=\"user\">" \
+ "<wsse:Username>%s</wsse:Username>" \
+ "<wsse:Password>%s</wsse:Password>" \
+ "</wsse:UsernameToken>" \
+ "</wsse:Security>" \
+ "</Header>" \
+ "<Body>" \
+ "<ps:RequestMultipleSecurityTokens " \
+ "xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\" " \
+ "Id=\"RSTS\">" \
+ "<wst:RequestSecurityToken Id=\"RST0\">" \
+ "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>" \
+ "<wsp:AppliesTo>" \
+ "<wsa:EndpointReference>" \
+ "<wsa:Address>http://Passport.NET/tb</wsa:Address>" \
+ "</wsa:EndpointReference>" \
+ "</wsp:AppliesTo>" \
+ "</wst:RequestSecurityToken>" \
+ "<wst:RequestSecurityToken Id=\"RST1\">" \
+ "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>" \
+ "<wsp:AppliesTo>" \
+ "<wsa:EndpointReference>" \
+ "<wsa:Address>messengerclear.live.com</wsa:Address>" \
+ "</wsa:EndpointReference>" \
+ "</wsp:AppliesTo>" \
+ "<wsse:PolicyReference URI=\"%s\"></wsse:PolicyReference>" \
+ "</wst:RequestSecurityToken>" \
+ "<wst:RequestSecurityToken Id=\"RST2\">" \
+ "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>" \
+ "<wsp:AppliesTo>" \
+ "<wsa:EndpointReference>" \
+ "<wsa:Address>contacts.msn.com</wsa:Address>" \
+ "</wsa:EndpointReference>" \
+ "</wsp:AppliesTo>" \
+ "<wsse:PolicyReference xmlns=\"http://schemas.xmlsoap.org/ws/2003/06/secext\" URI=\"MBI\"></wsse:PolicyReference>" \
+ "</wst:RequestSecurityToken>" \
+ "<wst:RequestSecurityToken Id=\"RST3\">" \
+ "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>" \
+ "<wsp:AppliesTo>" \
+ "<wsa:EndpointReference>" \
+ "<wsa:Address>messengersecure.live.com</wsa:Address>" \
+ "</wsa:EndpointReference>" \
+ "</wsp:AppliesTo>" \
+ "<wsse:PolicyReference xmlns=\"http://schemas.xmlsoap.org/ws/2003/06/secext\" URI=\"MBI_SSL\"></wsse:PolicyReference>" \
+ "</wst:RequestSecurityToken>" \
+ "<wst:RequestSecurityToken Id=\"RST4\">" \
+ "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>" \
+ "<wsp:AppliesTo>" \
+ "<wsa:EndpointReference>" \
+ "<wsa:Address>storage.msn.com</wsa:Address>" \
+ "</wsa:EndpointReference>" \
+ "</wsp:AppliesTo>" \
+ "<wsse:PolicyReference xmlns=\"http://schemas.xmlsoap.org/ws/2003/06/secext\" URI=\"MBI_SSL\"></wsse:PolicyReference>" \
+ "</wst:RequestSecurityToken>" \
+ "</ps:RequestMultipleSecurityTokens>" \
+ "</Body>" \
+"</Envelope>"
+
+int msn_soap_passport_sso_request( struct im_connection *ic, const char *nonce );
+
+
+#define SOAP_OIM_SEND_URL "https://ows.messenger.msn.com/OimWS/oim.asmx"
+#define SOAP_OIM_SEND_ACTION "http://messenger.live.com/ws/2006/09/oim/Store2"
+
+#define SOAP_OIM_SEND_PAYLOAD \
+"<?xml version=\"1.0\" encoding=\"utf-8\"?>" \
+"<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+"<soap:Header>" \
+ "<From memberName=\"%s\" friendlyName=\"=?utf-8?B?%s?=\" xml:lang=\"nl-nl\" proxy=\"MSNMSGR\" xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\" msnpVer=\"%s\" buildVer=\"%s\"/>" \
+ "<To memberName=\"%s\" xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\"/>" \
+ "<Ticket passport=\"%s\" appid=\"%s\" lockkey=\"%s\" xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\"/>" \
+ "<Sequence xmlns=\"http://schemas.xmlsoap.org/ws/2003/03/rm\">" \
+ "<Identifier xmlns=\"http://schemas.xmlsoap.org/ws/2002/07/utility\">http://messenger.msn.com</Identifier>" \
+ "<MessageNumber>%d</MessageNumber>" \
+ "</Sequence>" \
+"</soap:Header>" \
+"<soap:Body>" \
+ "<MessageType xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\">text</MessageType>" \
+ "<Content xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\">" \
+ "MIME-Version: 1.0\r\n" \
+ "Content-Type: text/plain; charset=UTF-8\r\n" \
+ "Content-Transfer-Encoding: base64\r\n" \
+ "X-OIM-Message-Type: OfflineMessage\r\n" \
+ "X-OIM-Run-Id: {F9A6C9DD-0D94-4E85-9CC6-F9D118CC1CAF}\r\n" \
+ "X-OIM-Sequence-Num: %d\r\n" \
+ "\r\n" \
+ "%s" \
+ "</Content>" \
+"</soap:Body>" \
+"</soap:Envelope>"
+
+int msn_soap_oim_send( struct im_connection *ic, const char *to, const char *msg );
+int msn_soap_oim_send_queue( struct im_connection *ic, GSList **msgq );
+
+
+#define SOAP_ABSERVICE_PAYLOAD \
+"<?xml version=\"1.0\" encoding=\"utf-8\"?>" \
+"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "<soap:Header xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<ApplicationId xmlns=\"http://www.msn.com/webservices/AddressBook\">CFE80F9D-180F-4399-82AB-413F33A1FA11</ApplicationId>" \
+ "<IsMigration xmlns=\"http://www.msn.com/webservices/AddressBook\">false</IsMigration>" \
+ "<PartnerScenario xmlns=\"http://www.msn.com/webservices/AddressBook\">%s</PartnerScenario>" \
+ "</ABApplicationHeader>" \
+ "<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<ManagedGroupRequest xmlns=\"http://www.msn.com/webservices/AddressBook\">false</ManagedGroupRequest>" \
+ "<TicketToken>%s</TicketToken>" \
+ "</ABAuthHeader>" \
+ "</soap:Header>" \
+ "<soap:Body xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "%%s" \
+ "</soap:Body>" \
+"</soap:Envelope>"
+
+#define SOAP_MEMLIST_URL "http://contacts.msn.com/abservice/SharingService.asmx"
+#define SOAP_MEMLIST_ACTION "http://www.msn.com/webservices/AddressBook/FindMembership"
+
+#define SOAP_MEMLIST_PAYLOAD \
+ "<FindMembership xmlns=\"http://www.msn.com/webservices/AddressBook\"><serviceFilter xmlns=\"http://www.msn.com/webservices/AddressBook\"><Types xmlns=\"http://www.msn.com/webservices/AddressBook\"><ServiceType xmlns=\"http://www.msn.com/webservices/AddressBook\">Messenger</ServiceType><ServiceType xmlns=\"http://www.msn.com/webservices/AddressBook\">Invitation</ServiceType><ServiceType xmlns=\"http://www.msn.com/webservices/AddressBook\">SocialNetwork</ServiceType><ServiceType xmlns=\"http://www.msn.com/webservices/AddressBook\">Space</ServiceType><ServiceType xmlns=\"http://www.msn.com/webservices/AddressBook\">Profile</ServiceType></Types></serviceFilter>" \
+ "</FindMembership>"
+
+#define SOAP_MEMLIST_ADD_ACTION "http://www.msn.com/webservices/AddressBook/AddMember"
+#define SOAP_MEMLIST_DEL_ACTION "http://www.msn.com/webservices/AddressBook/DeleteMember"
+
+#define SOAP_MEMLIST_EDIT_PAYLOAD \
+ "<%sMember xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<serviceHandle>" \
+ "<Id>0</Id>" \
+ "<Type>Messenger</Type>" \
+ "<ForeignId></ForeignId>" \
+ "</serviceHandle>" \
+ "<memberships>" \
+ "<Membership>" \
+ "<MemberRole>%s</MemberRole>" \
+ "<Members>" \
+ "<Member xsi:type=\"PassportMember\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" \
+ "<Type>Passport</Type>" \
+ "<State>Accepted</State>" \
+ "<PassportName>%s</PassportName>" \
+ "</Member>" \
+ "</Members>" \
+ "</Membership>" \
+ "</memberships>" \
+ "</%sMember>"
+
+int msn_soap_memlist_request( struct im_connection *ic );
+int msn_soap_memlist_edit( struct im_connection *ic, const char *handle, gboolean add, int list );
+
+
+#define SOAP_ADDRESSBOOK_URL "http://contacts.msn.com/abservice/abservice.asmx"
+#define SOAP_ADDRESSBOOK_ACTION "http://www.msn.com/webservices/AddressBook/ABFindAll"
+
+#define SOAP_ADDRESSBOOK_PAYLOAD \
+ "<ABFindAll xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<abId>00000000-0000-0000-0000-000000000000</abId>" \
+ "<abView>Full</abView>" \
+ "<deltasOnly>false</deltasOnly>" \
+ "<lastChange>0001-01-01T00:00:00.0000000-08:00</lastChange>" \
+ "</ABFindAll>"
+
+#define SOAP_AB_NAMECHANGE_ACTION "http://www.msn.com/webservices/AddressBook/ABContactUpdate"
+
+#define SOAP_AB_NAMECHANGE_PAYLOAD \
+ "<ABContactUpdate xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<abId>00000000-0000-0000-0000-000000000000</abId>" \
+ "<contacts>" \
+ "<Contact xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<contactInfo>" \
+ "<contactType>Me</contactType>" \
+ "<displayName>%s</displayName>" \
+ "</contactInfo>" \
+ "<propertiesChanged>DisplayName</propertiesChanged>" \
+ "</Contact>" \
+ "</contacts>" \
+ "</ABContactUpdate>"
+
+#define SOAP_AB_CONTACT_ADD_ACTION "http://www.msn.com/webservices/AddressBook/ABContactAdd"
+
+#define SOAP_AB_CONTACT_ADD_PAYLOAD \
+ "<ABContactAdd xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<abId>00000000-0000-0000-0000-000000000000</abId>" \
+ "<contacts>" \
+ "<Contact xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<contactInfo>" \
+ "<contactType>LivePending</contactType>" \
+ "<passportName>%s</passportName>" \
+ "<isMessengerUser>true</isMessengerUser>" \
+ "<MessengerMemberInfo>" \
+ "<DisplayName>%s</DisplayName>" \
+ "</MessengerMemberInfo>" \
+ "</contactInfo>" \
+ "</Contact>" \
+ "</contacts>" \
+ "<options>" \
+ "<EnableAllowListManagement>true</EnableAllowListManagement>" \
+ "</options>" \
+ "</ABContactAdd>"
+
+#define SOAP_AB_CONTACT_DEL_ACTION "http://www.msn.com/webservices/AddressBook/ABContactDelete"
+
+#define SOAP_AB_CONTACT_DEL_PAYLOAD \
+ "<ABContactDelete xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<abId>00000000-0000-0000-0000-000000000000</abId>" \
+ "<contacts>" \
+ "<Contact xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<contactId>%s</contactId>" \
+ "</Contact>" \
+ "</contacts>" \
+ "</ABContactDelete>"
+
+int msn_soap_addressbook_request( struct im_connection *ic );
+int msn_soap_addressbook_set_display_name( struct im_connection *ic, const char *new );
+int msn_soap_ab_contact_add( struct im_connection *ic, bee_user_t *bu );
+int msn_soap_ab_contact_del( struct im_connection *ic, bee_user_t *bu );
+
+
+#define SOAP_STORAGE_URL "https://storage.msn.com/storageservice/SchematizedStore.asmx"
+#define SOAP_PROFILE_GET_ACTION "http://www.msn.com/webservices/storage/w10/GetProfile"
+#define SOAP_PROFILE_SET_DN_ACTION "http://www.msn.com/webservices/storage/w10/UpdateProfile"
+
+#define SOAP_PROFILE_GET_PAYLOAD \
+"<?xml version=\"1.0\" encoding=\"utf-8\"?>" \
+"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "<soap:Header xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "<StorageApplicationHeader xmlns=\"http://www.msn.com/webservices/storage/w10\">" \
+ "<ApplicationID>Messenger Client 9.0</ApplicationID>" \
+ "<Scenario>Initial</Scenario>" \
+ "</StorageApplicationHeader>" \
+ "<StorageUserHeader xmlns=\"http://www.msn.com/webservices/storage/w10\">" \
+ "<Puid>0</Puid>" \
+ "<TicketToken>%s</TicketToken>" \
+ "</StorageUserHeader>" \
+ "</soap:Header>" \
+ "<soap:Body xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "<GetProfile xmlns=\"http://www.msn.com/webservices/storage/w10\">" \
+ "<profileHandle>" \
+ "<Alias>" \
+ "<Name>%s</Name>" \
+ "<NameSpace>MyCidStuff</NameSpace>" \
+ "</Alias>" \
+ "<RelationshipName>MyProfile</RelationshipName>" \
+ "</profileHandle>" \
+ "<profileAttributes>" \
+ "<ResourceID>true</ResourceID>" \
+ "<DateModified>true</DateModified>" \
+ "<ExpressionProfileAttributes>" \
+ "<ResourceID>true</ResourceID>" \
+ "<DateModified>true</DateModified>" \
+ "<DisplayName>true</DisplayName>" \
+ "<DisplayNameLastModified>true</DisplayNameLastModified>" \
+ "<PersonalStatus>true</PersonalStatus>" \
+ "<PersonalStatusLastModified>true</PersonalStatusLastModified>" \
+ "<StaticUserTilePublicURL>true</StaticUserTilePublicURL>" \
+ "<Photo>true</Photo>" \
+ "<Flags>true</Flags>" \
+ "</ExpressionProfileAttributes>" \
+ "</profileAttributes>" \
+ "</GetProfile>" \
+ "</soap:Body>" \
+"</soap:Envelope>"
+
+#define SOAP_PROFILE_SET_DN_PAYLOAD \
+"<?xml version=\"1.0\" encoding=\"utf-8\"?>" \
+"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "<soap:Header xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "<StorageApplicationHeader xmlns=\"http://www.msn.com/webservices/storage/w10\">" \
+ "<ApplicationID>Messenger Client 9.0</ApplicationID>" \
+ "<Scenario>Initial</Scenario>" \
+ "</StorageApplicationHeader>" \
+ "<StorageUserHeader xmlns=\"http://www.msn.com/webservices/storage/w10\">" \
+ "<Puid>0</Puid>" \
+ "<TicketToken>%s</TicketToken>" \
+ "</StorageUserHeader>" \
+ "</soap:Header>" \
+ "<soap:Body xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "<UpdateProfile xmlns=\"http://www.msn.com/webservices/storage/w10\">" \
+ "<profile>" \
+ "<ResourceID>%s</ResourceID>" \
+ "<ExpressionProfile>" \
+ "<FreeText>Update</FreeText>" \
+ "<DisplayName>%s</DisplayName>" \
+ "<Flags>0</Flags>" \
+ "</ExpressionProfile>" \
+ "</profile>" \
+ "</UpdateProfile>" \
+ "</soap:Body>" \
+"</soap:Envelope>"
+
+int msn_soap_profile_get( struct im_connection *ic, const char *cid );
+int msn_soap_profile_set_dn( struct im_connection *ic, const char *dn );
+
+#endif /* __SOAP_H__ */
diff --git a/protocols/msn/tables.c b/protocols/msn/tables.c
new file mode 100644
index 00000000..273d291e
--- /dev/null
+++ b/protocols/msn/tables.c
@@ -0,0 +1,157 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* MSN module - Some tables with useful data */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "nogaim.h"
+#include "msn.h"
+
+const struct msn_away_state msn_away_state_list[] =
+{
+ { "NLN", "" },
+ { "AWY", "Away" },
+ { "BSY", "Busy" },
+ { "IDL", "Idle" },
+ { "BRB", "Be Right Back" },
+ { "PHN", "On the Phone" },
+ { "LUN", "Out to Lunch" },
+ { "HDN", "Hidden" },
+ { "", "" }
+};
+
+const struct msn_away_state *msn_away_state_by_code( char *code )
+{
+ int i;
+
+ for( i = 0; *msn_away_state_list[i].code; i ++ )
+ if( g_strcasecmp( msn_away_state_list[i].code, code ) == 0 )
+ return( msn_away_state_list + i );
+
+ return NULL;
+}
+
+const struct msn_away_state *msn_away_state_by_name( char *name )
+{
+ int i;
+
+ for( i = 0; *msn_away_state_list[i].code; i ++ )
+ if( g_strcasecmp( msn_away_state_list[i].name, name ) == 0 )
+ return( msn_away_state_list + i );
+
+ return NULL;
+}
+
+const struct msn_status_code msn_status_code_list[] =
+{
+ { 200, "Invalid syntax", 0 },
+ { 201, "Invalid parameter", 0 },
+ { 205, "Invalid (non-existent) handle", 0 },
+ { 206, "Domain name missing", 0 },
+ { 207, "Already logged in", 0 },
+ { 208, "Invalid handle", STATUS_SB_IM_SPARE },
+ { 209, "Forbidden nickname", 0 },
+ { 210, "Buddy list too long", 0 },
+ { 215, "Handle is already in list", 0 },
+ { 216, "Handle is not in list", STATUS_SB_IM_SPARE },
+ { 217, "Person is off-line or non-existent", STATUS_SB_IM_SPARE },
+ { 218, "Already in that mode", 0 },
+ { 219, "Handle is already in opposite list", 0 },
+ { 223, "Too many groups", 0 },
+ { 224, "Invalid group or already in list", 0 },
+ { 225, "Handle is not in that group", 0 },
+ { 229, "Group name too long", 0 },
+ { 230, "Cannot remove that group", 0 },
+ { 231, "Invalid group", 0 },
+ { 240, "ADL/RML command with corrupted payload", STATUS_FATAL },
+ { 241, "ADL/RML command with invalid modification", 0 },
+ { 280, "Switchboard failed", STATUS_SB_FATAL },
+ { 281, "Transfer to switchboard failed", 0 },
+
+ { 300, "Required field missing", 0 },
+ { 302, "Not logged in", 0 },
+
+ { 500, "Internal server error/Account banned", STATUS_FATAL },
+ { 501, "Database server error", STATUS_FATAL },
+ { 502, "Command disabled", 0 },
+ { 510, "File operation failed", STATUS_FATAL },
+ { 520, "Memory allocation failed", STATUS_FATAL },
+ { 540, "Challenge response invalid", STATUS_FATAL },
+
+ { 600, "Server is busy", STATUS_FATAL },
+ { 601, "Server is unavailable", STATUS_FATAL },
+ { 602, "Peer nameserver is down", STATUS_FATAL },
+ { 603, "Database connection failed", STATUS_FATAL },
+ { 604, "Server is going down", STATUS_FATAL },
+ { 605, "Server is unavailable", STATUS_FATAL },
+
+ { 700, "Could not create connection", STATUS_FATAL },
+ { 710, "Invalid CVR parameters", STATUS_FATAL },
+ { 711, "Write is blocking", STATUS_FATAL },
+ { 712, "Session is overloaded", STATUS_FATAL },
+ { 713, "Calling too rapidly", STATUS_SB_IM_SPARE },
+ { 714, "Too many sessions", STATUS_FATAL },
+ { 715, "Not expected/Invalid argument/action", 0 },
+ { 717, "Bad friend file", STATUS_FATAL },
+ { 731, "Not expected/Invalid argument", 0 },
+
+ { 800, "Changing too rapidly", 0 },
+
+ { 910, "Server is busy", STATUS_FATAL },
+ { 911, "Authentication failed", STATUS_SB_FATAL | STATUS_FATAL },
+ { 912, "Server is busy", STATUS_FATAL },
+ { 913, "Not allowed when hiding", 0 },
+ { 914, "Server is unavailable", STATUS_FATAL },
+ { 915, "Server is unavailable", STATUS_FATAL },
+ { 916, "Server is unavailable", STATUS_FATAL },
+ { 917, "Authentication failed", STATUS_FATAL },
+ { 918, "Server is busy", STATUS_FATAL },
+ { 919, "Server is busy", STATUS_FATAL },
+ { 920, "Not accepting new principals", 0 }, /* When a sb is full? */
+ { 922, "Server is busy", STATUS_FATAL },
+ { 923, "Kids Passport without parental consent", STATUS_FATAL },
+ { 924, "Passport account not yet verified", STATUS_FATAL },
+ { 928, "Bad ticket", STATUS_FATAL },
+ { -1, NULL, 0 }
+};
+
+const struct msn_status_code *msn_status_by_number( int number )
+{
+ static struct msn_status_code *unknown = NULL;
+ int i;
+
+ for( i = 0; msn_status_code_list[i].number >= 0; i ++ )
+ if( msn_status_code_list[i].number == number )
+ return( msn_status_code_list + i );
+
+ if( unknown == NULL )
+ {
+ unknown = g_new0( struct msn_status_code, 1 );
+ unknown->text = g_new0( char, 128 );
+ }
+
+ unknown->number = number;
+ unknown->flags = 0;
+ g_snprintf( unknown->text, 128, "Unknown error (%d)", number );
+
+ return( unknown );
+}
diff --git a/protocols/nogaim.c b/protocols/nogaim.c
new file mode 100644
index 00000000..8fb85ea7
--- /dev/null
+++ b/protocols/nogaim.c
@@ -0,0 +1,708 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/*
+ * nogaim
+ *
+ * Gaim without gaim - for BitlBee
+ *
+ * This file contains functions called by the Gaim IM-modules. It's written
+ * from scratch for BitlBee and doesn't contain any code from Gaim anymore
+ * (except for the function names).
+ */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#define BITLBEE_CORE
+#include <ctype.h>
+
+#include "nogaim.h"
+
+GSList *connections;
+
+#ifdef WITH_PLUGINS
+gboolean load_plugin(char *path)
+{
+ void (*init_function) (void);
+
+ GModule *mod = g_module_open(path, G_MODULE_BIND_LAZY);
+
+ if(!mod) {
+ log_message(LOGLVL_ERROR, "Can't find `%s', not loading (%s)\n", path, g_module_error());
+ return FALSE;
+ }
+
+ if(!g_module_symbol(mod,"init_plugin",(gpointer *) &init_function)) {
+ log_message(LOGLVL_WARNING, "Can't find function `init_plugin' in `%s'\n", path);
+ return FALSE;
+ }
+
+ init_function();
+
+ return TRUE;
+}
+
+void load_plugins(void)
+{
+ GDir *dir;
+ GError *error = NULL;
+
+ dir = g_dir_open(global.conf->plugindir, 0, &error);
+
+ if (dir) {
+ const gchar *entry;
+ char *path;
+
+ while ((entry = g_dir_read_name(dir))) {
+ path = g_build_filename(global.conf->plugindir, entry, NULL);
+ if(!path) {
+ log_message(LOGLVL_WARNING, "Can't build path for %s\n", entry);
+ continue;
+ }
+
+ load_plugin(path);
+
+ g_free(path);
+ }
+
+ g_dir_close(dir);
+ }
+}
+#endif
+
+GList *protocols = NULL;
+
+void register_protocol (struct prpl *p)
+{
+ int i;
+ gboolean refused = global.conf->protocols != NULL;
+
+ for (i = 0; global.conf->protocols && global.conf->protocols[i]; i++)
+ {
+ if (g_strcasecmp(p->name, global.conf->protocols[i]) == 0)
+ refused = FALSE;
+ }
+
+ if (refused)
+ log_message(LOGLVL_WARNING, "Protocol %s disabled\n", p->name);
+ else
+ protocols = g_list_append(protocols, p);
+}
+
+struct prpl *find_protocol(const char *name)
+{
+ GList *gl;
+
+ for( gl = protocols; gl; gl = gl->next )
+ {
+ struct prpl *proto = gl->data;
+
+ if( g_strcasecmp( proto->name, name ) == 0 )
+ return proto;
+ }
+
+ return NULL;
+}
+
+void nogaim_init()
+{
+ extern void msn_initmodule();
+ extern void oscar_initmodule();
+ extern void byahoo_initmodule();
+ extern void jabber_initmodule();
+ extern void twitter_initmodule();
+ extern void purple_initmodule();
+
+#ifdef WITH_MSN
+ msn_initmodule();
+#endif
+
+#ifdef WITH_OSCAR
+ oscar_initmodule();
+#endif
+
+#ifdef WITH_YAHOO
+ byahoo_initmodule();
+#endif
+
+#ifdef WITH_JABBER
+ jabber_initmodule();
+#endif
+
+#ifdef WITH_TWITTER
+ twitter_initmodule();
+#endif
+
+#ifdef WITH_PURPLE
+ purple_initmodule();
+#endif
+
+#ifdef WITH_PLUGINS
+ load_plugins();
+#endif
+}
+
+GSList *get_connections() { return connections; }
+
+struct im_connection *imcb_new( account_t *acc )
+{
+ struct im_connection *ic;
+
+ ic = g_new0( struct im_connection, 1 );
+
+ ic->bee = acc->bee;
+ ic->acc = acc;
+ acc->ic = ic;
+
+ connections = g_slist_append( connections, ic );
+
+ return( ic );
+}
+
+void imc_free( struct im_connection *ic )
+{
+ account_t *a;
+
+ /* Destroy the pointer to this connection from the account list */
+ for( a = ic->bee->accounts; a; a = a->next )
+ if( a->ic == ic )
+ {
+ a->ic = NULL;
+ break;
+ }
+
+ connections = g_slist_remove( connections, ic );
+ g_free( ic );
+}
+
+static void serv_got_crap( struct im_connection *ic, char *format, ... )
+{
+ va_list params;
+ char *text;
+ account_t *a;
+
+ va_start( params, format );
+ text = g_strdup_vprintf( format, params );
+ va_end( params );
+
+ if( ( g_strcasecmp( set_getstr( &ic->bee->set, "strip_html" ), "always" ) == 0 ) ||
+ ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->bee->set, "strip_html" ) ) )
+ strip_html( text );
+
+ /* Try to find a different connection on the same protocol. */
+ for( a = ic->bee->accounts; a; a = a->next )
+ if( a->prpl == ic->acc->prpl && a->ic != ic )
+ break;
+
+ /* If we found one, include the screenname in the message. */
+ if( a )
+ /* FIXME(wilmer): ui_log callback or so */
+ irc_usermsg( ic->bee->ui_data, "%s(%s) - %s", ic->acc->prpl->name, ic->acc->user, text );
+ else
+ irc_usermsg( ic->bee->ui_data, "%s - %s", ic->acc->prpl->name, text );
+
+ g_free( text );
+}
+
+void imcb_log( struct im_connection *ic, char *format, ... )
+{
+ va_list params;
+ char *text;
+
+ va_start( params, format );
+ text = g_strdup_vprintf( format, params );
+ va_end( params );
+
+ if( ic->flags & OPT_LOGGED_IN )
+ serv_got_crap( ic, "%s", text );
+ else
+ serv_got_crap( ic, "Logging in: %s", text );
+
+ g_free( text );
+}
+
+void imcb_error( struct im_connection *ic, char *format, ... )
+{
+ va_list params;
+ char *text;
+
+ va_start( params, format );
+ text = g_strdup_vprintf( format, params );
+ va_end( params );
+
+ if( ic->flags & OPT_LOGGED_IN )
+ serv_got_crap( ic, "Error: %s", text );
+ else
+ serv_got_crap( ic, "Login error: %s", text );
+
+ g_free( text );
+}
+
+static gboolean send_keepalive( gpointer d, gint fd, b_input_condition cond )
+{
+ struct im_connection *ic = d;
+
+ if( ic->acc->prpl->keepalive )
+ ic->acc->prpl->keepalive( ic );
+
+ return TRUE;
+}
+
+void imcb_connected( struct im_connection *ic )
+{
+ /* MSN servers sometimes redirect you to a different server and do
+ the whole login sequence again, so these "late" calls to this
+ function should be handled correctly. (IOW, ignored) */
+ if( ic->flags & OPT_LOGGED_IN )
+ return;
+
+ imcb_log( ic, "Logged in" );
+
+ b_event_remove( ic->keepalive );
+ ic->keepalive = b_timeout_add( 60000, send_keepalive, ic );
+ ic->flags |= OPT_LOGGED_IN;
+
+ /* Necessary to send initial presence status, even if we're not away. */
+ imc_away_send_update( ic );
+
+ /* Apparently we're connected successfully, so reset the
+ exponential backoff timer. */
+ ic->acc->auto_reconnect_delay = 0;
+
+ if( ic->bee->ui->imc_connected )
+ ic->bee->ui->imc_connected( ic );
+}
+
+gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond )
+{
+ account_t *a = data;
+
+ a->reconnect = 0;
+ account_on( a->bee, a );
+
+ return( FALSE ); /* Only have to run the timeout once */
+}
+
+void cancel_auto_reconnect( account_t *a )
+{
+ b_event_remove( a->reconnect );
+ a->reconnect = 0;
+}
+
+void imc_logout( struct im_connection *ic, int allow_reconnect )
+{
+ bee_t *bee = ic->bee;
+ account_t *a;
+ GSList *l;
+ int delay;
+
+ /* Nested calls might happen sometimes, this is probably the best
+ place to catch them. */
+ if( ic->flags & OPT_LOGGING_OUT )
+ return;
+ else
+ ic->flags |= OPT_LOGGING_OUT;
+
+ if( ic->bee->ui->imc_disconnected )
+ ic->bee->ui->imc_disconnected( ic );
+
+ imcb_log( ic, "Signing off.." );
+
+ for( l = bee->users; l; )
+ {
+ bee_user_t *bu = l->data;
+ GSList *next = l->next;
+
+ if( bu->ic == ic )
+ bee_user_free( bee, bu );
+
+ l = next;
+ }
+
+ b_event_remove( ic->keepalive );
+ ic->keepalive = 0;
+ ic->acc->prpl->logout( ic );
+ b_event_remove( ic->inpa );
+
+ g_free( ic->away );
+ ic->away = NULL;
+
+ query_del_by_conn( (irc_t*) ic->bee->ui_data, ic );
+
+ for( a = bee->accounts; a; a = a->next )
+ if( a->ic == ic )
+ break;
+
+ if( !a )
+ {
+ /* Uhm... This is very sick. */
+ }
+ else if( allow_reconnect && set_getbool( &bee->set, "auto_reconnect" ) &&
+ set_getbool( &a->set, "auto_reconnect" ) &&
+ ( delay = account_reconnect_delay( a ) ) > 0 )
+ {
+ imcb_log( ic, "Reconnecting in %d seconds..", delay );
+ a->reconnect = b_timeout_add( delay * 1000, auto_reconnect, a );
+ }
+
+ imc_free( ic );
+}
+
+void imcb_ask( struct im_connection *ic, char *msg, void *data,
+ query_callback doit, query_callback dont )
+{
+ query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, g_free, data );
+}
+
+void imcb_ask_with_free( struct im_connection *ic, char *msg, void *data,
+ query_callback doit, query_callback dont, query_callback myfree )
+{
+ query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, myfree, data );
+}
+
+void imcb_add_buddy( struct im_connection *ic, const char *handle, const char *group )
+{
+ bee_user_t *bu;
+ bee_t *bee = ic->bee;
+ bee_group_t *oldg;
+
+ if( !( bu = bee_user_by_handle( bee, ic, handle ) ) )
+ bu = bee_user_new( bee, ic, handle, 0 );
+
+ oldg = bu->group;
+ bu->group = bee_group_by_name( bee, group, TRUE );
+
+ if( bee->ui->user_group && bu->group != oldg )
+ bee->ui->user_group( bee, bu );
+}
+
+void imcb_rename_buddy( struct im_connection *ic, const char *handle, const char *fullname )
+{
+ bee_t *bee = ic->bee;
+ bee_user_t *bu = bee_user_by_handle( bee, ic, handle );
+
+ if( !bu || !fullname ) return;
+
+ if( !bu->fullname || strcmp( bu->fullname, fullname ) != 0 )
+ {
+ g_free( bu->fullname );
+ bu->fullname = g_strdup( fullname );
+
+ if( bee->ui->user_fullname )
+ bee->ui->user_fullname( bee, bu );
+ }
+}
+
+void imcb_remove_buddy( struct im_connection *ic, const char *handle, char *group )
+{
+ bee_user_free( ic->bee, bee_user_by_handle( ic->bee, ic, handle ) );
+}
+
+/* Mainly meant for ICQ (and now also for Jabber conferences) to allow IM
+ modules to suggest a nickname for a handle. */
+void imcb_buddy_nick_hint( struct im_connection *ic, const char *handle, const char *nick )
+{
+ bee_t *bee = ic->bee;
+ bee_user_t *bu = bee_user_by_handle( bee, ic, handle );
+
+ if( !bu || !nick ) return;
+
+ g_free( bu->nick );
+ bu->nick = g_strdup( nick );
+
+ if( bee->ui->user_nick_hint )
+ bee->ui->user_nick_hint( bee, bu, nick );
+}
+
+
+struct imcb_ask_cb_data
+{
+ struct im_connection *ic;
+ char *handle;
+};
+
+static void imcb_ask_auth_cb_no( void *data )
+{
+ struct imcb_ask_cb_data *cbd = data;
+
+ cbd->ic->acc->prpl->auth_deny( cbd->ic, cbd->handle );
+
+ g_free( cbd->handle );
+ g_free( cbd );
+}
+
+static void imcb_ask_auth_cb_yes( void *data )
+{
+ struct imcb_ask_cb_data *cbd = data;
+
+ cbd->ic->acc->prpl->auth_allow( cbd->ic, cbd->handle );
+
+ g_free( cbd->handle );
+ g_free( cbd );
+}
+
+void imcb_ask_auth( struct im_connection *ic, const char *handle, const char *realname )
+{
+ struct imcb_ask_cb_data *data = g_new0( struct imcb_ask_cb_data, 1 );
+ char *s, *realname_ = NULL;
+
+ if( realname != NULL )
+ realname_ = g_strdup_printf( " (%s)", realname );
+
+ s = g_strdup_printf( "The user %s%s wants to add you to his/her buddy list.",
+ handle, realname_ ? realname_ : "" );
+
+ g_free( realname_ );
+
+ data->ic = ic;
+ data->handle = g_strdup( handle );
+ query_add( (irc_t *) ic->bee->ui_data, ic, s,
+ imcb_ask_auth_cb_yes, imcb_ask_auth_cb_no, g_free, data );
+}
+
+
+static void imcb_ask_add_cb_no( void *data )
+{
+ g_free( ((struct imcb_ask_cb_data*)data)->handle );
+ g_free( data );
+}
+
+static void imcb_ask_add_cb_yes( void *data )
+{
+ struct imcb_ask_cb_data *cbd = data;
+
+ cbd->ic->acc->prpl->add_buddy( cbd->ic, cbd->handle, NULL );
+
+ imcb_ask_add_cb_no( data );
+}
+
+void imcb_ask_add( struct im_connection *ic, const char *handle, const char *realname )
+{
+ struct imcb_ask_cb_data *data = g_new0( struct imcb_ask_cb_data, 1 );
+ char *s;
+
+ /* TODO: Make a setting for this! */
+ if( bee_user_by_handle( ic->bee, ic, handle ) != NULL )
+ return;
+
+ s = g_strdup_printf( "The user %s is not in your buddy list yet. Do you want to add him/her now?", handle );
+
+ data->ic = ic;
+ data->handle = g_strdup( handle );
+ query_add( (irc_t *) ic->bee->ui_data, ic, s,
+ imcb_ask_add_cb_yes, imcb_ask_add_cb_no, g_free, data );
+}
+
+struct bee_user *imcb_buddy_by_handle( struct im_connection *ic, const char *handle )
+{
+ return bee_user_by_handle( ic->bee, ic, handle );
+}
+
+/* The plan is to not allow straight calls to prpl functions anymore, but do
+ them all from some wrappers. We'll start to define some down here: */
+
+int imc_chat_msg( struct groupchat *c, char *msg, int flags )
+{
+ char *buf = NULL;
+
+ if( ( c->ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<html>", 6 ) != 0 ) )
+ {
+ buf = escape_html( msg );
+ msg = buf;
+ }
+
+ c->ic->acc->prpl->chat_msg( c, msg, flags );
+ g_free( buf );
+
+ return 1;
+}
+
+static char *imc_away_state_find( GList *gcm, char *away, char **message );
+
+int imc_away_send_update( struct im_connection *ic )
+{
+ char *away, *msg = NULL;
+
+ if( ic->acc->prpl->away_states == NULL ||
+ ic->acc->prpl->set_away == NULL )
+ return 0;
+
+ away = set_getstr( &ic->acc->set, "away" ) ?
+ : set_getstr( &ic->bee->set, "away" );
+ if( away && *away )
+ {
+ GList *m = ic->acc->prpl->away_states( ic );
+ msg = ic->acc->flags & ACC_FLAG_AWAY_MESSAGE ? away : NULL;
+ away = imc_away_state_find( m, away, &msg ) ? : m->data;
+ }
+ else if( ic->acc->flags & ACC_FLAG_STATUS_MESSAGE )
+ {
+ away = NULL;
+ msg = set_getstr( &ic->acc->set, "status" ) ?
+ : set_getstr( &ic->bee->set, "status" );
+ }
+
+ ic->acc->prpl->set_away( ic, away, msg );
+
+ return 1;
+}
+
+static char *imc_away_alias_list[8][5] =
+{
+ { "Away from computer", "Away", "Extended away", NULL },
+ { "NA", "N/A", "Not available", NULL },
+ { "Busy", "Do not disturb", "DND", "Occupied", NULL },
+ { "Be right back", "BRB", NULL },
+ { "On the phone", "Phone", "On phone", NULL },
+ { "Out to lunch", "Lunch", "Food", NULL },
+ { "Invisible", "Hidden" },
+ { NULL }
+};
+
+static char *imc_away_state_find( GList *gcm, char *away, char **message )
+{
+ GList *m;
+ int i, j;
+
+ for( m = gcm; m; m = m->next )
+ if( g_strncasecmp( m->data, away, strlen( m->data ) ) == 0 )
+ {
+ /* At least the Yahoo! module works better if message
+ contains no data unless it adds something to what
+ we have in state already. */
+ if( strlen( m->data ) == strlen( away ) )
+ *message = NULL;
+
+ return m->data;
+ }
+
+ for( i = 0; *imc_away_alias_list[i]; i ++ )
+ {
+ int keep_message;
+
+ for( j = 0; imc_away_alias_list[i][j]; j ++ )
+ if( g_strncasecmp( away, imc_away_alias_list[i][j], strlen( imc_away_alias_list[i][j] ) ) == 0 )
+ {
+ keep_message = strlen( away ) != strlen( imc_away_alias_list[i][j] );
+ break;
+ }
+
+ if( !imc_away_alias_list[i][j] ) /* If we reach the end, this row */
+ continue; /* is not what we want. Next! */
+
+ /* Now find an entry in this row which exists in gcm */
+ for( j = 0; imc_away_alias_list[i][j]; j ++ )
+ {
+ for( m = gcm; m; m = m->next )
+ if( g_strcasecmp( imc_away_alias_list[i][j], m->data ) == 0 )
+ {
+ if( !keep_message )
+ *message = NULL;
+
+ return imc_away_alias_list[i][j];
+ }
+ }
+
+ /* No need to look further, apparently this state doesn't
+ have any good alias for this protocol. */
+ break;
+ }
+
+ return NULL;
+}
+
+void imc_add_allow( struct im_connection *ic, char *handle )
+{
+ if( g_slist_find_custom( ic->permit, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) == NULL )
+ {
+ ic->permit = g_slist_prepend( ic->permit, g_strdup( handle ) );
+ }
+
+ ic->acc->prpl->add_permit( ic, handle );
+}
+
+void imc_rem_allow( struct im_connection *ic, char *handle )
+{
+ GSList *l;
+
+ if( ( l = g_slist_find_custom( ic->permit, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) )
+ {
+ g_free( l->data );
+ ic->permit = g_slist_delete_link( ic->permit, l );
+ }
+
+ ic->acc->prpl->rem_permit( ic, handle );
+}
+
+void imc_add_block( struct im_connection *ic, char *handle )
+{
+ if( g_slist_find_custom( ic->deny, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) == NULL )
+ {
+ ic->deny = g_slist_prepend( ic->deny, g_strdup( handle ) );
+ }
+
+ ic->acc->prpl->add_deny( ic, handle );
+}
+
+void imc_rem_block( struct im_connection *ic, char *handle )
+{
+ GSList *l;
+
+ if( ( l = g_slist_find_custom( ic->deny, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) )
+ {
+ g_free( l->data );
+ ic->deny = g_slist_delete_link( ic->deny, l );
+ }
+
+ ic->acc->prpl->rem_deny( ic, handle );
+}
+
+void imcb_clean_handle( struct im_connection *ic, char *handle )
+{
+ /* Accepts a handle and does whatever is necessary to make it
+ BitlBee-friendly. Currently this means removing everything
+ outside 33-127 (ASCII printable excl spaces), @ (only one
+ is allowed) and ! and : */
+ char out[strlen(handle)+1];
+ int s, d;
+
+ s = d = 0;
+ while( handle[s] )
+ {
+ if( handle[s] > ' ' && handle[s] != '!' && handle[s] != ':' &&
+ ( handle[s] & 0x80 ) == 0 )
+ {
+ if( handle[s] == '@' )
+ {
+ /* See if we got an @ already? */
+ out[d] = 0;
+ if( strchr( out, '@' ) )
+ continue;
+ }
+
+ out[d++] = handle[s];
+ }
+ s ++;
+ }
+ out[d] = handle[s];
+
+ strcpy( handle, out );
+}
diff --git a/protocols/nogaim.h b/protocols/nogaim.h
new file mode 100644
index 00000000..a98b7054
--- /dev/null
+++ b/protocols/nogaim.h
@@ -0,0 +1,346 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/*
+ * nogaim, soon to be known as im_api. Not a separate product (unless
+ * someone would be interested in such a thing), just a new name.
+ *
+ * Gaim without gaim - for BitlBee
+ *
+ * This file contains functions called by the Gaim IM-modules. It contains
+ * some struct and type definitions from Gaim.
+ *
+ * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
+ * (and possibly other members of the Gaim team)
+ * Copyright 2002-2007 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
+*/
+
+#ifndef _NOGAIM_H
+#define _NOGAIM_H
+
+#if(__sun)
+#include <inttypes.h>
+#else
+#include <stdint.h>
+#endif
+
+#include "bitlbee.h"
+#include "account.h"
+#include "proxy.h"
+#include "query.h"
+#include "md5.h"
+#include "ft.h"
+
+#define BUDDY_ALIAS_MAXLEN 388 /* because MSN names can be 387 characters */
+
+#define WEBSITE "http://www.bitlbee.org/"
+
+/* Sharing flags between all kinds of things. I just hope I won't hit any
+ limits before 32-bit machines become extinct. ;-) */
+#define OPT_LOGGED_IN 0x00000001
+#define OPT_LOGGING_OUT 0x00000002
+#define OPT_AWAY 0x00000004
+#define OPT_MOBILE 0x00000008
+#define OPT_DOES_HTML 0x00000010
+#define OPT_LOCALBUDDY 0x00000020 /* For nicks local to one groupchat */
+#define OPT_SLOW_LOGIN 0x00000040 /* I.e. Twitter Oauth @ login time */
+#define OPT_TYPING 0x00000100 /* Some pieces of code make assumptions */
+#define OPT_THINKING 0x00000200 /* about these values... Stupid me! */
+#define OPT_NOOTR 0x00001000 /* protocol not suitable for OTR */
+
+/* ok. now the fun begins. first we create a connection structure */
+struct im_connection
+{
+ account_t *acc;
+ uint32_t flags;
+
+ /* each connection then can have its own protocol-specific data */
+ void *proto_data;
+
+ /* all connections need an input watcher */
+ int inpa;
+ guint keepalive;
+
+ /* buddy list stuff. there is still a global groups for the buddy list, but
+ * we need to maintain our own set of buddies, and our own permit/deny lists */
+ GSList *permit;
+ GSList *deny;
+ int permdeny;
+
+ char displayname[128];
+ char *away;
+
+ int evil;
+
+ /* BitlBee */
+ bee_t *bee;
+
+ GSList *groupchats;
+};
+
+struct groupchat {
+ struct im_connection *ic;
+
+ /* stuff used just for chat */
+ /* The in_room variable is a list of handles (not nicks!), kind of
+ * "nick list". This is how you can check who is in the group chat
+ * already, for example to avoid adding somebody two times. */
+ GList *in_room;
+ //GList *ignored;
+
+ //struct groupchat *next;
+ /* The title variable contains the ID you gave when you created the
+ * chat using imcb_chat_new(). */
+ char *title;
+ /* Use imcb_chat_topic() to change this variable otherwise the user
+ * won't notice the topic change. */
+ char *topic;
+ char joined;
+ /* This is for you, you can add your own structure here to extend this
+ * structure for your protocol's needs. */
+ void *data;
+ void *ui_data;
+};
+
+struct buddy {
+ char name[80];
+ char show[BUDDY_ALIAS_MAXLEN];
+ int present;
+ int evil;
+ time_t signon;
+ time_t idle;
+ int uc;
+ guint caps; /* woohoo! */
+ void *proto_data; /* what a hack */
+ struct im_connection *ic; /* the connection it belongs to */
+};
+
+struct buddy_action {
+ char *name;
+ char *description;
+};
+
+struct prpl {
+ int options;
+ /* You should set this to the name of your protocol.
+ * - The user sees this name ie. when imcb_log() is used. */
+ const char *name;
+ void *data;
+ /* Maximum Message Size of this protocol.
+ * - Introduced for OTR, in order to fragment large protocol messages.
+ * - 0 means "unlimited". */
+ unsigned int mms;
+
+ /* Added this one to be able to add per-account settings, don't think
+ * it should be used for anything else. You are supposed to use the
+ * set_add() function to add new settings. */
+ void (* init) (account_t *);
+ /* The typical usage of the login() function:
+ * - Create an im_connection using imcb_new() from the account_t parameter.
+ * - Initialize your myproto_data struct - you should store all your protocol-specific data there.
+ * - Save your custom structure to im_connection->proto_data.
+ * - Use proxy_connect() to connect to the server.
+ */
+ void (* login) (account_t *);
+ /* Implementing this function is optional. */
+ void (* keepalive) (struct im_connection *);
+ /* In this function you should:
+ * - Tell the server about you are logging out.
+ * - g_free() your myproto_data struct as BitlBee does not know how to
+ * properly do so.
+ */
+ void (* logout) (struct im_connection *);
+
+ /* This function is called when the user wants to send a message to a handle.
+ * - 'to' is a handle, not a nick
+ * - 'flags' may be ignored
+ */
+ int (* buddy_msg) (struct im_connection *, char *to, char *message, int flags);
+ /* This function is called then the user uses the /away IRC command.
+ * - 'state' contains the away reason.
+ * - 'message' may be ignored if your protocol does not support it.
+ */
+ void (* set_away) (struct im_connection *, char *state, char *message);
+ /* Implementing this function is optional. */
+ void (* get_away) (struct im_connection *, char *who);
+ /* Implementing this function is optional. */
+ int (* send_typing) (struct im_connection *, char *who, int flags);
+
+ /* 'name' is a handle to add/remove. For now BitlBee doesn't really
+ * handle groups, just set it to NULL, so you can ignore that
+ * parameter. */
+ void (* add_buddy) (struct im_connection *, char *name, char *group);
+ void (* remove_buddy) (struct im_connection *, char *name, char *group);
+
+ /* Block list stuff. Implementing these are optional. */
+ void (* add_permit) (struct im_connection *, char *who);
+ void (* add_deny) (struct im_connection *, char *who);
+ void (* rem_permit) (struct im_connection *, char *who);
+ void (* rem_deny) (struct im_connection *, char *who);
+ /* Doesn't actually have UI hooks. */
+ void (* set_permit_deny)(struct im_connection *);
+
+ /* Request profile info. Free-formatted stuff, the IM module gives back
+ this info via imcb_log(). Implementing these are optional. */
+ void (* get_info) (struct im_connection *, char *who);
+ /* set_my_name is *DEPRECATED*, not used by the UI anymore. Use the
+ display_name setting instead. */
+ void (* set_my_name) (struct im_connection *, char *name);
+ void (* set_name) (struct im_connection *, char *who, char *name);
+
+ /* Group chat stuff. */
+ /* This is called when the user uses the /invite IRC command.
+ * - 'who' may be ignored
+ * - 'message' is a handle to invite
+ */
+ void (* chat_invite) (struct groupchat *, char *who, char *message);
+ /* This is called when the user uses the /part IRC command in a group
+ * chat. You just should tell the user about it, nothing more. */
+ void (* chat_leave) (struct groupchat *);
+ /* This is called when the user sends a message to the groupchat.
+ * 'flags' may be ignored. */
+ void (* chat_msg) (struct groupchat *, char *message, int flags);
+ /* This is called when the user uses the /join #nick IRC command.
+ * - 'who' is the handle of the nick
+ */
+ struct groupchat *
+ (* chat_with) (struct im_connection *, char *who);
+ /* This is used when the user uses the /join #channel IRC command. If
+ * your protocol does not support publicly named group chats, then do
+ * not implement this. */
+ struct groupchat *
+ (* chat_join) (struct im_connection *, const char *room,
+ const char *nick, const char *password, set_t **sets);
+ /* Change the topic, if supported. Note that BitlBee expects the IM
+ server to confirm the topic change with a regular topic change
+ event. If it doesn't do that, you have to fake it to make it
+ visible to the user. */
+ void (* chat_topic) (struct groupchat *, char *topic);
+
+ /* If your protocol module needs any special info for joining chatrooms
+ other than a roomname + nickname, add them here. */
+ void (* chat_add_settings) (account_t *acc, set_t **head);
+ void (* chat_free_settings) (account_t *acc, set_t **head);
+
+ /* You can tell what away states your protocol supports, so that
+ * BitlBee will try to map the IRC away reasons to them. If your
+ * protocol doesn't have any, just return one generic "Away". */
+ GList *(* away_states)(struct im_connection *ic);
+
+ /* Mainly for AOL, since they think "Bung hole" == "Bu ngho le". *sigh*
+ * - Most protocols will just want to set this to g_strcasecmp().*/
+ int (* handle_cmp) (const char *who1, const char *who2);
+
+ /* Implement these callbacks if you want to use imcb_ask_auth() */
+ void (* auth_allow) (struct im_connection *, const char *who);
+ void (* auth_deny) (struct im_connection *, const char *who);
+
+ /* Incoming transfer request */
+ void (* transfer_request) (struct im_connection *, file_transfer_t *ft, char *handle );
+
+ void (* buddy_data_add) (struct bee_user *bu);
+ void (* buddy_data_free) (struct bee_user *bu);
+
+ GList *(* buddy_action_list) (struct bee_user *bu);
+ void *(* buddy_action) (struct bee_user *bu, const char *action, char * const args[], void *data);
+
+ /* Some placeholders so eventually older plugins may cooperate with newer BitlBees. */
+ void *resv1;
+ void *resv2;
+ void *resv3;
+ void *resv4;
+ void *resv5;
+};
+
+/* im_api core stuff. */
+void nogaim_init();
+G_MODULE_EXPORT GSList *get_connections();
+G_MODULE_EXPORT struct prpl *find_protocol( const char *name );
+/* When registering a new protocol, you should allocate space for a new prpl
+ * struct, initialize it (set the function pointers to point to your
+ * functions), finally call this function. */
+G_MODULE_EXPORT void register_protocol( struct prpl * );
+
+/* Connection management. */
+/* You will need this function in prpl->login() to get an im_connection from
+ * the account_t parameter. */
+G_MODULE_EXPORT struct im_connection *imcb_new( account_t *acc );
+G_MODULE_EXPORT void imc_free( struct im_connection *ic );
+/* Once you're connected, you should call this function, so that the user will
+ * see the success. */
+G_MODULE_EXPORT void imcb_connected( struct im_connection *ic );
+/* This can be used to disconnect when something went wrong (ie. read error
+ * from the server). You probably want to set the second parameter to TRUE. */
+G_MODULE_EXPORT void imc_logout( struct im_connection *ic, int allow_reconnect );
+
+/* Communicating with the user. */
+/* A printf()-like function to tell the user anything you want. */
+G_MODULE_EXPORT void imcb_log( struct im_connection *ic, char *format, ... ) G_GNUC_PRINTF( 2, 3 );
+/* To tell the user an error, ie. before logging out when an error occurs. */
+G_MODULE_EXPORT void imcb_error( struct im_connection *ic, char *format, ... ) G_GNUC_PRINTF( 2, 3 );
+
+/* To ask a your about something.
+ * - 'msg' is the question.
+ * - 'data' can be your custom struct - it will be passed to the callbacks.
+ * - 'doit' or 'dont' will be called depending of the answer of the user.
+ */
+G_MODULE_EXPORT void imcb_ask( struct im_connection *ic, char *msg, void *data, query_callback doit, query_callback dont );
+G_MODULE_EXPORT void imcb_ask_with_free( struct im_connection *ic, char *msg, void *data, query_callback doit, query_callback dont, query_callback myfree );
+
+/* Two common questions you may want to ask:
+ * - X added you to his contact list, allow?
+ * - X is not in your contact list, want to add?
+ */
+G_MODULE_EXPORT void imcb_ask_auth( struct im_connection *ic, const char *handle, const char *realname );
+G_MODULE_EXPORT void imcb_ask_add( struct im_connection *ic, const char *handle, const char *realname );
+
+/* Buddy management */
+/* This function should be called for each handle which are visible to the
+ * user, usually after a login, or if the user added a buddy and the IM
+ * server confirms that the add was successful. Don't forget to do this! */
+G_MODULE_EXPORT void imcb_add_buddy( struct im_connection *ic, const char *handle, const char *group );
+G_MODULE_EXPORT void imcb_remove_buddy( struct im_connection *ic, const char *handle, char *group );
+G_MODULE_EXPORT struct buddy *imcb_find_buddy( struct im_connection *ic, char *handle );
+G_MODULE_EXPORT void imcb_rename_buddy( struct im_connection *ic, const char *handle, const char *realname );
+G_MODULE_EXPORT void imcb_buddy_nick_hint( struct im_connection *ic, const char *handle, const char *nick );
+G_MODULE_EXPORT void imcb_buddy_action_response( bee_user_t *bu, const char *action, char * const args[], void *data );
+
+G_MODULE_EXPORT void imcb_buddy_typing( struct im_connection *ic, const char *handle, uint32_t flags );
+G_MODULE_EXPORT struct bee_user *imcb_buddy_by_handle( struct im_connection *ic, const char *handle );
+G_MODULE_EXPORT void imcb_clean_handle( struct im_connection *ic, char *handle );
+
+/* Actions, or whatever. */
+int imc_away_send_update( struct im_connection *ic );
+int imc_chat_msg( struct groupchat *c, char *msg, int flags );
+
+void imc_add_allow( struct im_connection *ic, char *handle );
+void imc_rem_allow( struct im_connection *ic, char *handle );
+void imc_add_block( struct im_connection *ic, char *handle );
+void imc_rem_block( struct im_connection *ic, char *handle );
+
+/* Misc. stuff */
+char *set_eval_timezone( set_t *set, char *value );
+gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond );
+void cancel_auto_reconnect( struct account *a );
+
+#endif
diff --git a/protocols/oscar/AUTHORS b/protocols/oscar/AUTHORS
new file mode 100644
index 00000000..5ca13988
--- /dev/null
+++ b/protocols/oscar/AUTHORS
@@ -0,0 +1,31 @@
+
+Anyone else want to be in here?
+
+---
+
+N: Adam Fritzler
+H: mid
+E: mid@auk.cx
+W: http://www.auk.cx/~mid,http://www.auk.cx/faim
+D: Wrote most of the wap of crap that you see before you.
+
+N: Josh Myer
+E: josh@joshisanerd.com
+D: OFT/ODC (not quite finished yet..), random little things, Munger-At-Large, compile-time warnings.
+
+N: Daniel Reed
+H: n, linuxkitty
+E: n@ml.org
+W: http://users.n.ml.org/n/
+D: Fixed aim_snac.c
+
+N: Eric Warmenhoven
+E: warmenhoven@linux.com
+D: Some OFT info, author of the faim interface for gaim
+
+N: Brock Wilcox
+H: awwaiid
+E: awwaiid@auk.cx
+D: Figured out original password roasting
+
+
diff --git a/protocols/oscar/COPYING b/protocols/oscar/COPYING
new file mode 100644
index 00000000..223ede7d
--- /dev/null
+++ b/protocols/oscar/COPYING
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/protocols/oscar/Makefile b/protocols/oscar/Makefile
new file mode 100644
index 00000000..a83830df
--- /dev/null
+++ b/protocols/oscar/Makefile
@@ -0,0 +1,47 @@
+###########################
+## Makefile for BitlBee ##
+## ##
+## Copyright 2002 Lintux ##
+###########################
+
+### DEFINITIONS
+
+-include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/oscar/
+CFLAGS += -I$(SRCDIR)
+endif
+
+# [SH] Program variables
+objects = admin.o auth.o bos.o buddylist.o chat.o chatnav.o conn.o icq.o im.o info.o misc.o msgcookie.o rxhandlers.o rxqueue.o search.o service.o snac.o ssi.o stats.o tlv.o txqueue.o oscar_util.o oscar.o
+
+LFLAGS += -r
+
+# [SH] Phony targets
+all: oscar_mod.o
+check: all
+lcov: check
+gcov:
+ gcov *.c
+
+.PHONY: all clean distclean
+
+clean:
+ rm -f *.o core
+
+distclean: clean
+ rm -rf .depend
+
+### MAIN PROGRAM
+
+$(objects): ../../Makefile.settings Makefile
+
+$(objects): %.o: $(SRCDIR)%.c
+ @echo '*' Compiling $<
+ @$(CC) -c $(CFLAGS) $< -o $@
+
+oscar_mod.o: $(objects)
+ @echo '*' Linking oscar_mod.o
+ @$(LD) $(LFLAGS) $(objects) -o oscar_mod.o
+
+-include .depend/*.d
diff --git a/protocols/oscar/admin.c b/protocols/oscar/admin.c
new file mode 100644
index 00000000..09082c5b
--- /dev/null
+++ b/protocols/oscar/admin.c
@@ -0,0 +1,196 @@
+#include <aim.h>
+#include "admin.h"
+
+/* called for both reply and change-reply */
+static int infochange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+ /*
+ * struct {
+ * guint16 perms;
+ * guint16 tlvcount;
+ * aim_tlv_t tlvs[tlvcount];
+ * } admin_info[n];
+ */
+ while (aim_bstream_empty(bs)) {
+ guint16 perms, tlvcount;
+
+ perms = aimbs_get16(bs);
+ tlvcount = aimbs_get16(bs);
+
+ while (tlvcount && aim_bstream_empty(bs)) {
+ aim_rxcallback_t userfunc;
+ guint16 type, len;
+ guint8 *val;
+ int str = 0;
+
+ type = aimbs_get16(bs);
+ len = aimbs_get16(bs);
+
+ if ((type == 0x0011) || (type == 0x0004))
+ str = 1;
+
+ if (str)
+ val = (guint8 *)aimbs_getstr(bs, len);
+ else
+ val = aimbs_getraw(bs, len);
+
+ /* XXX fix so its only called once for the entire packet */
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ userfunc(sess, rx, (snac->subtype == 0x0005) ? 1 : 0, perms, type, len, val, str);
+
+ g_free(val);
+
+ tlvcount--;
+ }
+ }
+
+ return 1;
+}
+
+static int accountconfirm(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_rxcallback_t userfunc;
+ guint16 status;
+
+ status = aimbs_get16(bs);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ return userfunc(sess, rx, status);
+
+ return 0;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+ if ((snac->subtype == 0x0003) || (snac->subtype == 0x0005))
+ return infochange(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x0007)
+ return accountconfirm(sess, mod, rx, snac, bs);
+
+ return 0;
+}
+
+int admin_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+ mod->family = AIM_CB_FAM_ADM;
+ mod->version = 0x0001;
+ mod->toolid = AIM_TOOL_NEWWIN;
+ mod->toolversion = 0x0629;
+ mod->flags = 0;
+ strncpy(mod->name, "admin", sizeof(mod->name));
+ mod->snachandler = snachandler;
+
+ return 0;
+}
+
+int aim_admin_changepasswd(aim_session_t *sess, aim_conn_t *conn, const char *newpw, const char *curpw)
+{
+ aim_frame_t *tx;
+ aim_tlvlist_t *tl = NULL;
+ aim_snacid_t snacid;
+
+ if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+4+strlen(curpw)+4+strlen(newpw))))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0007, 0x0004, 0x0000, NULL, 0);
+ aim_putsnac(&tx->data, 0x0007, 0x0004, 0x0000, snacid);
+
+ /* new password TLV t(0002) */
+ aim_addtlvtochain_raw(&tl, 0x0002, strlen(newpw), (guint8 *)newpw);
+
+ /* current password TLV t(0012) */
+ aim_addtlvtochain_raw(&tl, 0x0012, strlen(curpw), (guint8 *)curpw);
+
+ aim_writetlvchain(&tx->data, &tl);
+ aim_freetlvchain(&tl);
+
+ aim_tx_enqueue(sess, tx);
+
+ return 0;
+}
+
+/*
+ * Request account confirmation.
+ *
+ * This will cause an email to be sent to the address associated with
+ * the account. By following the instructions in the mail, you can
+ * get the TRIAL flag removed from your account.
+ *
+ */
+int aim_admin_reqconfirm(aim_session_t *sess, aim_conn_t *conn)
+{
+ return aim_genericreq_n(sess, conn, 0x0007, 0x0006);
+}
+
+/*
+ * Request a bit of account info.
+ *
+ * The only known valid tag is 0x0011 (email address).
+ *
+ */
+int aim_admin_getinfo(aim_session_t *sess, aim_conn_t *conn, guint16 info)
+{
+ aim_frame_t *tx;
+ aim_snacid_t snacid;
+
+ if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 14)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0002, 0x0002, 0x0000, NULL, 0);
+ aim_putsnac(&tx->data, 0x0007, 0x0002, 0x0000, snacid);
+
+ aimbs_put16(&tx->data, info);
+ aimbs_put16(&tx->data, 0x0000);
+
+ aim_tx_enqueue(sess, tx);
+
+ return 0;
+}
+
+int aim_admin_setemail(aim_session_t *sess, aim_conn_t *conn, const char *newemail)
+{
+ aim_frame_t *tx;
+ aim_snacid_t snacid;
+ aim_tlvlist_t *tl = NULL;
+
+ if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+2+strlen(newemail))))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0007, 0x0004, 0x0000, NULL, 0);
+ aim_putsnac(&tx->data, 0x0007, 0x0004, 0x0000, snacid);
+
+ aim_addtlvtochain_raw(&tl, 0x0011, strlen(newemail), (guint8 *)newemail);
+
+ aim_writetlvchain(&tx->data, &tl);
+ aim_freetlvchain(&tl);
+
+ aim_tx_enqueue(sess, tx);
+
+ return 0;
+}
+
+int aim_admin_setnick(aim_session_t *sess, aim_conn_t *conn, const char *newnick)
+{
+ aim_frame_t *tx;
+ aim_snacid_t snacid;
+ aim_tlvlist_t *tl = NULL;
+
+ if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+2+strlen(newnick))))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0007, 0x0004, 0x0000, NULL, 0);
+ aim_putsnac(&tx->data, 0x0007, 0x0004, 0x0000, snacid);
+
+ aim_addtlvtochain_raw(&tl, 0x0001, strlen(newnick), (guint8 *)newnick);
+
+ aim_writetlvchain(&tx->data, &tl);
+ aim_freetlvchain(&tl);
+
+ aim_tx_enqueue(sess, tx);
+
+
+ return 0;
+}
diff --git a/protocols/oscar/admin.h b/protocols/oscar/admin.h
new file mode 100644
index 00000000..f9d74cae
--- /dev/null
+++ b/protocols/oscar/admin.h
@@ -0,0 +1,13 @@
+#ifndef __OSCAR_ADMIN_H__
+#define __OSCAR_ADMIN_H__
+
+#define AIM_CB_FAM_ADM 0x0007
+
+/*
+ * SNAC Family: Administrative Services.
+ */
+#define AIM_CB_ADM_ERROR 0x0001
+#define AIM_CB_ADM_INFOCHANGE_REPLY 0x0005
+#define AIM_CB_ADM_DEFAULT 0xffff
+
+#endif /* __OSCAR_ADMIN_H__ */
diff --git a/protocols/oscar/aim.h b/protocols/oscar/aim.h
new file mode 100644
index 00000000..479f8fd0
--- /dev/null
+++ b/protocols/oscar/aim.h
@@ -0,0 +1,914 @@
+/*
+ * Main libfaim header. Must be included in client for prototypes/macros.
+ *
+ * "come on, i turned a chick lesbian; i think this is the hackish equivalent"
+ * -- Josh Meyer
+ *
+ */
+
+#ifndef __AIM_H__
+#define __AIM_H__
+
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <time.h>
+#include <gmodule.h>
+
+#include "bitlbee.h"
+
+/* XXX adjust these based on autoconf-detected platform */
+typedef guint32 aim_snacid_t;
+typedef guint16 flap_seqnum_t;
+
+/* Portability stuff (DMP) */
+
+#if defined(mach) && defined(__APPLE__)
+#define gethostbyname(x) gethostbyname2(x, AF_INET)
+#endif
+
+/*
+ * Current Maximum Length for Screen Names (not including NULL)
+ *
+ * Currently only names up to 16 characters can be registered
+ * however it is aparently legal for them to be larger.
+ */
+#define MAXSNLEN 32
+
+/*
+ * Current Maximum Length for Instant Messages
+ *
+ * This was found basically by experiment, but not wholly
+ * accurate experiment. It should not be regarded
+ * as completely correct. But its a decent approximation.
+ *
+ * Note that although we can send this much, its impossible
+ * for WinAIM clients (up through the latest (4.0.1957)) to
+ * send any more than 1kb. Amaze all your windows friends
+ * with utterly oversized instant messages!
+ *
+ * XXX: the real limit is the total SNAC size at 8192. Fix this.
+ *
+ */
+#define MAXMSGLEN 7987
+
+/*
+ * Maximum size of a Buddy Icon.
+ */
+#define MAXICONLEN 7168
+#define AIM_ICONIDENT "AVT1picture.id"
+
+/*
+ * Current Maximum Length for Chat Room Messages
+ *
+ * This is actually defined by the protocol to be
+ * dynamic, but I have yet to see due cause to
+ * define it dynamically here. Maybe later.
+ *
+ */
+#define MAXCHATMSGLEN 512
+
+/*
+ * Standard size of an AIM authorization cookie
+ */
+#define AIM_COOKIELEN 0x100
+
+#define AIM_MD5_STRING "AOL Instant Messenger (SM)"
+
+/*
+ * Default Authorizer server name and TCP port for the OSCAR farm.
+ *
+ * You shouldn't need to change this unless you're writing
+ * your own server.
+ *
+ * Note that only one server is needed to start the whole
+ * AIM process. The later server addresses come from
+ * the authorizer service.
+ *
+ * This is only here for convenience. Its still up to
+ * the client to connect to it.
+ *
+ */
+#define AIM_DEFAULT_LOGIN_SERVER_AIM "login.messaging.aol.com"
+#define AIM_DEFAULT_LOGIN_SERVER_ICQ "login.icq.com"
+#define AIM_LOGIN_PORT 5190
+
+/*
+ * Size of the SNAC caching hash.
+ *
+ * Default: 16
+ *
+ */
+#define AIM_SNAC_HASH_SIZE 16
+
+/*
+ * Client info. Filled in by the client and passed in to
+ * aim_send_login(). The information ends up getting passed to OSCAR
+ * through the initial login command.
+ *
+ */
+struct client_info_s {
+ const char *clientstring;
+ guint16 clientid;
+ int major;
+ int minor;
+ int point;
+ int build;
+ const char *country; /* two-letter abbrev */
+ const char *lang; /* two-letter abbrev */
+};
+
+#define AIM_CLIENTINFO_KNOWNGOOD_3_5_1670 { \
+ "AOL Instant Messenger (SM), version 3.5.1670/WIN32", \
+ 0x0004, \
+ 0x0003, \
+ 0x0005, \
+ 0x0000, \
+ 0x0686, \
+ "us", \
+ "en", \
+}
+
+#define AIM_CLIENTINFO_KNOWNGOOD_4_1_2010 { \
+ "AOL Instant Messenger (SM), version 4.1.2010/WIN32", \
+ 0x0004, \
+ 0x0004, \
+ 0x0001, \
+ 0x0000, \
+ 0x07da, \
+ "us", \
+ "en", \
+}
+
+#define AIM_CLIENTINFO_KNOWNGOOD_5_1_3036 { \
+ "AOL Instant Messenger, version 5.1.3036/WIN32", \
+ 0x0109, \
+ 0x0005, \
+ 0x0001, \
+ 0x0000, \
+ 0x0bdc, \
+ "us", \
+ "en", \
+}
+
+/*
+ * I would make 4.1.2010 the default, but they seem to have found
+ * an alternate way of breaking that one.
+ *
+ * 3.5.1670 should work fine, however, you will be subjected to the
+ * memory test, which may require you to have a WinAIM binary laying
+ * around. (see login.c::memrequest())
+ */
+#define AIM_CLIENTINFO_KNOWNGOOD AIM_CLIENTINFO_KNOWNGOOD_5_1_3036
+
+#ifndef TRUE
+#define TRUE 1
+#define FALSE 0
+#endif
+
+/*
+ * These could be arbitrary, but its easier to use the actual AIM values
+ */
+#define AIM_CONN_TYPE_AUTH 0x0007
+#define AIM_CONN_TYPE_ADS 0x0005
+#define AIM_CONN_TYPE_BOS 0x0002
+#define AIM_CONN_TYPE_CHAT 0x000e
+#define AIM_CONN_TYPE_CHATNAV 0x000d
+
+/*
+ * Status values returned from aim_conn_new(). ORed together.
+ */
+#define AIM_CONN_STATUS_READY 0x0001
+#define AIM_CONN_STATUS_INTERNALERR 0x0002
+#define AIM_CONN_STATUS_RESOLVERR 0x0040
+#define AIM_CONN_STATUS_CONNERR 0x0080
+#define AIM_CONN_STATUS_INPROGRESS 0x0100
+
+#define AIM_FRAMETYPE_FLAP 0x0000
+
+/*
+ * message type flags
+ */
+#define AIM_MTYPE_PLAIN 0x01
+#define AIM_MTYPE_CHAT 0x02
+#define AIM_MTYPE_FILEREQ 0x03
+#define AIM_MTYPE_URL 0x04
+#define AIM_MTYPE_AUTHREQ 0x06
+#define AIM_MTYPE_AUTHDENY 0x07
+#define AIM_MTYPE_AUTHOK 0x08
+#define AIM_MTYPE_SERVER 0x09
+#define AIM_MTYPE_ADDED 0x0C
+#define AIM_MTYPE_WWP 0x0D
+#define AIM_MTYPE_EEXPRESS 0x0E
+#define AIM_MTYPE_CONTACTS 0x13
+#define AIM_MTYPE_PLUGIN 0x1A
+#define AIM_MTYPE_AUTOAWAY 0xE8
+#define AIM_MTYPE_AUTOBUSY 0xE9
+#define AIM_MTYPE_AUTONA 0xEA
+#define AIM_MTYPE_AUTODND 0xEB
+#define AIM_MTYPE_AUTOFFC 0xEC
+
+typedef struct aim_conn_s {
+ int fd;
+ guint16 type;
+ guint16 subtype;
+ flap_seqnum_t seqnum;
+ guint32 status;
+ void *priv; /* misc data the client may want to store */
+ void *internal; /* internal conn-specific libfaim data */
+ time_t lastactivity; /* time of last transmit */
+ int forcedlatency;
+ void *handlerlist;
+ void *sessv; /* pointer to parent session */
+ void *inside; /* only accessible from inside libfaim */
+ struct aim_conn_s *next;
+} aim_conn_t;
+
+/*
+ * Byte Stream type. Sort of.
+ *
+ * Use of this type serves a couple purposes:
+ * - Buffer/buflen pairs are passed all around everywhere. This turns
+ * that into one value, as well as abstracting it slightly.
+ * - Through the abstraction, it is possible to enable bounds checking
+ * for robustness at the cost of performance. But a clean failure on
+ * weird packets is much better than a segfault.
+ * - I like having variables named "bs".
+ *
+ * Don't touch the insides of this struct. Or I'll have to kill you.
+ *
+ */
+typedef struct aim_bstream_s {
+ guint8 *data;
+ guint32 len;
+ guint32 offset;
+} aim_bstream_t;
+
+typedef struct aim_frame_s {
+ guint8 hdrtype; /* defines which piece of the union to use */
+ union {
+ struct {
+ guint8 type;
+ flap_seqnum_t seqnum;
+ } flap;
+ } hdr;
+ aim_bstream_t data; /* payload stream */
+ guint8 handled; /* 0 = new, !0 = been handled */
+ guint8 nofree; /* 0 = free data on purge, 1 = only unlink */
+ aim_conn_t *conn; /* the connection it came in on... */
+ struct aim_frame_s *next;
+} aim_frame_t;
+
+typedef struct aim_msgcookie_s {
+ unsigned char cookie[8];
+ int type;
+ void *data;
+ time_t addtime;
+ struct aim_msgcookie_s *next;
+} aim_msgcookie_t;
+
+/*
+ * AIM Session: The main client-data interface.
+ *
+ */
+typedef struct aim_session_s {
+
+ /* ---- Client Accessible ------------------------ */
+
+ /* Our screen name. */
+ char sn[MAXSNLEN+1];
+
+ /*
+ * Pointer to anything the client wants to
+ * explicitly associate with this session.
+ *
+ * This is for use in the callbacks mainly. In any
+ * callback, you can access this with sess->aux_data.
+ *
+ */
+ void *aux_data;
+
+ /* ---- Internal Use Only ------------------------ */
+
+ /* Server-stored information (ssi) */
+ struct {
+ int received_data;
+ guint16 revision;
+ struct aim_ssi_item *items;
+ time_t timestamp;
+ int waiting_for_ack;
+ aim_frame_t *holding_queue;
+ } ssi;
+
+ /* Connection information */
+ aim_conn_t *connlist;
+
+ /*
+ * Transmit/receive queues.
+ *
+ * These are only used when you don't use your own lowlevel
+ * I/O. I don't suggest that you use libfaim's internal I/O.
+ * Its really bad and the API/event model is quirky at best.
+ *
+ */
+ aim_frame_t *queue_outgoing;
+ aim_frame_t *queue_incoming;
+
+ /*
+ * Tx Enqueuing function.
+ *
+ * This is how you override the transmit direction of libfaim's
+ * internal I/O. This function will be called whenever it needs
+ * to send something.
+ *
+ */
+ int (*tx_enqueue)(struct aim_session_s *, aim_frame_t *);
+
+ /*
+ * Outstanding snac handling
+ *
+ * XXX: Should these be per-connection? -mid
+ */
+ void *snac_hash[AIM_SNAC_HASH_SIZE];
+ aim_snacid_t snacid_next;
+
+ struct aim_icq_info *icq_info;
+ struct aim_oft_info *oft_info;
+ struct aim_authresp_info *authinfo;
+ struct aim_emailinfo *emailinfo;
+
+ struct {
+ struct aim_userinfo_s *userinfo;
+ struct userinfo_node *torequest;
+ struct userinfo_node *requested;
+ int waiting_for_response;
+ } locate;
+
+ guint32 flags; /* AIM_SESS_FLAGS_ */
+
+ aim_msgcookie_t *msgcookies;
+
+ void *modlistv;
+
+ guint8 aim_icq_state; /* ICQ representation of away state */
+} aim_session_t;
+
+/* Values for sess->flags */
+#define AIM_SESS_FLAGS_SNACLOGIN 0x00000001
+#define AIM_SESS_FLAGS_XORLOGIN 0x00000002
+#define AIM_SESS_FLAGS_NONBLOCKCONNECT 0x00000004
+#define AIM_SESS_FLAGS_DONTTIMEOUTONICBM 0x00000008
+
+/* Valid for calling aim_icq_setstatus() and for aim_userinfo_t->icqinfo.status */
+#define AIM_ICQ_STATE_NORMAL 0x00000000
+#define AIM_ICQ_STATE_AWAY 0x00000001
+#define AIM_ICQ_STATE_DND 0x00000002
+#define AIM_ICQ_STATE_OUT 0x00000004
+#define AIM_ICQ_STATE_BUSY 0x00000010
+#define AIM_ICQ_STATE_CHAT 0x00000020
+#define AIM_ICQ_STATE_INVISIBLE 0x00000100
+#define AIM_ICQ_STATE_WEBAWARE 0x00010000
+#define AIM_ICQ_STATE_HIDEIP 0x00020000
+#define AIM_ICQ_STATE_BIRTHDAY 0x00080000
+#define AIM_ICQ_STATE_DIRECTDISABLED 0x00100000
+#define AIM_ICQ_STATE_ICQHOMEPAGE 0x00200000
+#define AIM_ICQ_STATE_DIRECTREQUIREAUTH 0x10000000
+#define AIM_ICQ_STATE_DIRECTCONTACTLIST 0x20000000
+
+/*
+ * AIM User Info, Standard Form.
+ */
+typedef struct {
+ char sn[MAXSNLEN+1];
+ guint16 warnlevel;
+ guint16 idletime;
+ guint16 flags;
+ guint32 membersince;
+ guint32 onlinesince;
+ guint32 sessionlen;
+ guint32 capabilities;
+ struct {
+ guint32 status;
+ guint32 ipaddr;
+ guint8 crap[0x25]; /* until we figure it out... */
+ } icqinfo;
+ guint32 present;
+} aim_userinfo_t;
+
+#define AIM_USERINFO_PRESENT_FLAGS 0x00000001
+#define AIM_USERINFO_PRESENT_MEMBERSINCE 0x00000002
+#define AIM_USERINFO_PRESENT_ONLINESINCE 0x00000004
+#define AIM_USERINFO_PRESENT_IDLE 0x00000008
+#define AIM_USERINFO_PRESENT_ICQEXTSTATUS 0x00000010
+#define AIM_USERINFO_PRESENT_ICQIPADDR 0x00000020
+#define AIM_USERINFO_PRESENT_ICQDATA 0x00000040
+#define AIM_USERINFO_PRESENT_CAPABILITIES 0x00000080
+#define AIM_USERINFO_PRESENT_SESSIONLEN 0x00000100
+
+const char *aim_userinfo_sn(aim_userinfo_t *ui);
+guint16 aim_userinfo_flags(aim_userinfo_t *ui);
+guint16 aim_userinfo_idle(aim_userinfo_t *ui);
+float aim_userinfo_warnlevel(aim_userinfo_t *ui);
+time_t aim_userinfo_membersince(aim_userinfo_t *ui);
+time_t aim_userinfo_onlinesince(aim_userinfo_t *ui);
+guint32 aim_userinfo_sessionlen(aim_userinfo_t *ui);
+int aim_userinfo_hascap(aim_userinfo_t *ui, guint32 cap);
+
+#define AIM_FLAG_UNCONFIRMED 0x0001 /* "damned transients" */
+#define AIM_FLAG_ADMINISTRATOR 0x0002
+#define AIM_FLAG_AOL 0x0004
+#define AIM_FLAG_OSCAR_PAY 0x0008
+#define AIM_FLAG_FREE 0x0010
+#define AIM_FLAG_AWAY 0x0020
+#define AIM_FLAG_ICQ 0x0040
+#define AIM_FLAG_WIRELESS 0x0080
+#define AIM_FLAG_UNKNOWN100 0x0100
+#define AIM_FLAG_UNKNOWN200 0x0200
+#define AIM_FLAG_ACTIVEBUDDY 0x0400
+#define AIM_FLAG_UNKNOWN800 0x0800
+#define AIM_FLAG_ABINTERNAL 0x1000
+
+#define AIM_FLAG_ALLUSERS 0x001f
+
+/*
+ * TLV handling
+ */
+
+/* Generic TLV structure. */
+typedef struct aim_tlv_s {
+ guint16 type;
+ guint16 length;
+ guint8 *value;
+} aim_tlv_t;
+
+/* List of above. */
+typedef struct aim_tlvlist_s {
+ aim_tlv_t *tlv;
+ struct aim_tlvlist_s *next;
+} aim_tlvlist_t;
+
+/* TLV-handling functions */
+
+#if 0
+/* Very, very raw TLV handling. */
+int aim_puttlv_8(guint8 *buf, const guint16 t, const guint8 v);
+int aim_puttlv_16(guint8 *buf, const guint16 t, const guint16 v);
+int aim_puttlv_32(guint8 *buf, const guint16 t, const guint32 v);
+int aim_puttlv_raw(guint8 *buf, const guint16 t, const guint16 l, const guint8 *v);
+#endif
+
+/* TLV list handling. */
+aim_tlvlist_t *aim_readtlvchain(aim_bstream_t *bs);
+void aim_freetlvchain(aim_tlvlist_t **list);
+aim_tlv_t *aim_gettlv(aim_tlvlist_t *list, guint16 t, const int n);
+char *aim_gettlv_str(aim_tlvlist_t *list, const guint16 t, const int n);
+guint8 aim_gettlv8(aim_tlvlist_t *list, const guint16 type, const int num);
+guint16 aim_gettlv16(aim_tlvlist_t *list, const guint16 t, const int n);
+guint32 aim_gettlv32(aim_tlvlist_t *list, const guint16 t, const int n);
+int aim_writetlvchain(aim_bstream_t *bs, aim_tlvlist_t **list);
+int aim_addtlvtochain8(aim_tlvlist_t **list, const guint16 t, const guint8 v);
+int aim_addtlvtochain16(aim_tlvlist_t **list, const guint16 t, const guint16 v);
+int aim_addtlvtochain32(aim_tlvlist_t **list, const guint16 type, const guint32 v);
+int aim_addtlvtochain_availmsg(aim_tlvlist_t **list, const guint16 type, const char *msg);
+int aim_addtlvtochain_raw(aim_tlvlist_t **list, const guint16 t, const guint16 l, const guint8 *v);
+int aim_addtlvtochain_caps(aim_tlvlist_t **list, const guint16 t, const guint32 caps);
+int aim_addtlvtochain_noval(aim_tlvlist_t **list, const guint16 type);
+int aim_addtlvtochain_chatroom(aim_tlvlist_t **list, guint16 type, guint16 exchange, const char *roomname, guint16 instance);
+int aim_addtlvtochain_userinfo(aim_tlvlist_t **list, guint16 type, aim_userinfo_t *ui);
+int aim_addtlvtochain_frozentlvlist(aim_tlvlist_t **list, guint16 type, aim_tlvlist_t **tl);
+int aim_counttlvchain(aim_tlvlist_t **list);
+int aim_sizetlvchain(aim_tlvlist_t **list);
+
+
+/*
+ * Get command from connections
+ *
+ * aim_get_commmand() is the libfaim lowlevel I/O in the receive direction.
+ * XXX Make this easily overridable.
+ *
+ */
+int aim_get_command(aim_session_t *, aim_conn_t *);
+
+/*
+ * Dispatch commands that are in the rx queue.
+ */
+void aim_rxdispatch(aim_session_t *);
+
+int aim_debugconn_sendconnect(aim_session_t *sess, aim_conn_t *conn);
+
+typedef int (*aim_rxcallback_t)(aim_session_t *, aim_frame_t *, ...);
+
+struct aim_clientrelease {
+ char *name;
+ guint32 build;
+ char *url;
+ char *info;
+};
+
+struct aim_authresp_info {
+ char *sn;
+ guint16 errorcode;
+ char *errorurl;
+ guint16 regstatus;
+ char *email;
+ char *bosip;
+ guint8 *cookie;
+ struct aim_clientrelease latestrelease;
+ struct aim_clientrelease latestbeta;
+};
+
+/* Callback data for redirect. */
+struct aim_redirect_data {
+ guint16 group;
+ const char *ip;
+ const guint8 *cookie;
+ struct { /* group == AIM_CONN_TYPE_CHAT */
+ guint16 exchange;
+ const char *room;
+ guint16 instance;
+ } chat;
+};
+
+int aim_clientready(aim_session_t *sess, aim_conn_t *conn);
+int aim_sendflapver(aim_session_t *sess, aim_conn_t *conn);
+int aim_request_login(aim_session_t *sess, aim_conn_t *conn, const char *sn);
+int aim_send_login(aim_session_t *, aim_conn_t *, const char *, const char *, struct client_info_s *, const char *key);
+int aim_encode_password_md5(const char *password, const char *key, unsigned char *digest);
+void aim_purge_rxqueue(aim_session_t *);
+
+#define AIM_TX_QUEUED 0 /* default */
+#define AIM_TX_IMMEDIATE 1
+#define AIM_TX_USER 2
+int aim_tx_setenqueue(aim_session_t *sess, int what, int (*func)(aim_session_t *, aim_frame_t *));
+
+int aim_tx_flushqueue(aim_session_t *);
+void aim_tx_purgequeue(aim_session_t *);
+
+int aim_conn_setlatency(aim_conn_t *conn, int newval);
+
+void aim_conn_kill(aim_session_t *sess, aim_conn_t **deadconn);
+
+int aim_conn_addhandler(aim_session_t *, aim_conn_t *conn, u_short family, u_short type, aim_rxcallback_t newhandler, u_short flags);
+int aim_clearhandlers(aim_conn_t *conn);
+
+aim_conn_t *aim_conn_findbygroup(aim_session_t *sess, guint16 group);
+aim_session_t *aim_conn_getsess(aim_conn_t *conn);
+void aim_conn_close(aim_conn_t *deadconn);
+aim_conn_t *aim_newconn(aim_session_t *, int type, const char *dest);
+int aim_conngetmaxfd(aim_session_t *);
+aim_conn_t *aim_select(aim_session_t *, struct timeval *, int *);
+int aim_conn_isready(aim_conn_t *);
+int aim_conn_setstatus(aim_conn_t *, int);
+int aim_conn_completeconnect(aim_session_t *sess, aim_conn_t *conn);
+int aim_conn_isconnecting(aim_conn_t *conn);
+
+typedef void (*faim_debugging_callback_t)(aim_session_t *sess, int level, const char *format, va_list va);
+int aim_setdebuggingcb(aim_session_t *sess, faim_debugging_callback_t);
+void aim_session_init(aim_session_t *, guint32 flags, int debuglevel);
+void aim_session_kill(aim_session_t *);
+void aim_setupproxy(aim_session_t *sess, const char *server, const char *username, const char *password);
+aim_conn_t *aim_getconn_type(aim_session_t *, int type);
+aim_conn_t *aim_getconn_type_all(aim_session_t *, int type);
+aim_conn_t *aim_getconn_fd(aim_session_t *, int fd);
+
+/* aim_misc.c */
+
+
+struct aim_chat_roominfo {
+ unsigned short exchange;
+ char *name;
+ unsigned short instance;
+};
+
+struct aim_chat_invitation {
+ struct im_connection * ic;
+ char * name;
+ guint8 exchange;
+};
+
+#define AIM_VISIBILITYCHANGE_PERMITADD 0x05
+#define AIM_VISIBILITYCHANGE_PERMITREMOVE 0x06
+#define AIM_VISIBILITYCHANGE_DENYADD 0x07
+#define AIM_VISIBILITYCHANGE_DENYREMOVE 0x08
+
+#define AIM_PRIVFLAGS_ALLOWIDLE 0x01
+#define AIM_PRIVFLAGS_ALLOWMEMBERSINCE 0x02
+
+#define AIM_WARN_ANON 0x01
+
+int aim_sendpauseack(aim_session_t *sess, aim_conn_t *conn);
+int aim_send_warning(aim_session_t *sess, aim_conn_t *conn, const char *destsn, guint32 flags);
+int aim_nop(aim_session_t *, aim_conn_t *);
+int aim_flap_nop(aim_session_t *sess, aim_conn_t *conn);
+int aim_bos_setidle(aim_session_t *, aim_conn_t *, guint32);
+int aim_bos_changevisibility(aim_session_t *, aim_conn_t *, int, const char *);
+int aim_bos_setbuddylist(aim_session_t *, aim_conn_t *, const char *);
+int aim_bos_setprofile(aim_session_t *sess, aim_conn_t *conn, const char *profile, const char *awaymsg, guint32 caps);
+int aim_bos_setgroupperm(aim_session_t *, aim_conn_t *, guint32 mask);
+int aim_bos_setprivacyflags(aim_session_t *, aim_conn_t *, guint32);
+int aim_reqpersonalinfo(aim_session_t *, aim_conn_t *);
+int aim_reqservice(aim_session_t *, aim_conn_t *, guint16);
+int aim_bos_reqrights(aim_session_t *, aim_conn_t *);
+int aim_bos_reqbuddyrights(aim_session_t *, aim_conn_t *);
+int aim_bos_reqlocaterights(aim_session_t *, aim_conn_t *);
+int aim_setdirectoryinfo(aim_session_t *sess, aim_conn_t *conn, const char *first, const char *middle, const char *last, const char *maiden, const char *nickname, const char *street, const char *city, const char *state, const char *zip, int country, guint16 privacy);
+int aim_setuserinterests(aim_session_t *sess, aim_conn_t *conn, const char *interest1, const char *interest2, const char *interest3, const char *interest4, const char *interest5, guint16 privacy);
+int aim_setextstatus(aim_session_t *sess, aim_conn_t *conn, guint32 status);
+
+struct aim_fileheader_t *aim_getlisting(aim_session_t *sess, FILE *);
+
+#define AIM_CLIENTTYPE_UNKNOWN 0x0000
+#define AIM_CLIENTTYPE_MC 0x0001
+#define AIM_CLIENTTYPE_WINAIM 0x0002
+#define AIM_CLIENTTYPE_WINAIM41 0x0003
+#define AIM_CLIENTTYPE_AOL_TOC 0x0004
+unsigned short aim_fingerprintclient(unsigned char *msghdr, int len);
+
+#define AIM_RATE_CODE_CHANGE 0x0001
+#define AIM_RATE_CODE_WARNING 0x0002
+#define AIM_RATE_CODE_LIMIT 0x0003
+#define AIM_RATE_CODE_CLEARLIMIT 0x0004
+int aim_ads_requestads(aim_session_t *sess, aim_conn_t *conn);
+
+/* aim_im.c */
+
+aim_conn_t *aim_sendfile_initiate(aim_session_t *, const char *destsn, const char *filename, guint16 numfiles, guint32 totsize);
+
+aim_conn_t *aim_getfile_initiate(aim_session_t *sess, aim_conn_t *conn, const char *destsn);
+int aim_oft_getfile_request(aim_session_t *sess, aim_conn_t *conn, const char *name, int size);
+int aim_oft_getfile_ack(aim_session_t *sess, aim_conn_t *conn);
+int aim_oft_getfile_end(aim_session_t *sess, aim_conn_t *conn);
+
+#define AIM_SENDMEMBLOCK_FLAG_ISREQUEST 0
+#define AIM_SENDMEMBLOCK_FLAG_ISHASH 1
+
+int aim_sendmemblock(aim_session_t *sess, aim_conn_t *conn, guint32 offset, guint32 len, const guint8 *buf, guint8 flag);
+
+#define AIM_GETINFO_GENERALINFO 0x00001
+#define AIM_GETINFO_AWAYMESSAGE 0x00003
+#define AIM_GETINFO_CAPABILITIES 0x0004
+
+struct aim_invite_priv {
+ char *sn;
+ char *roomname;
+ guint16 exchange;
+ guint16 instance;
+};
+
+#define AIM_COOKIETYPE_UNKNOWN 0x00
+#define AIM_COOKIETYPE_ICBM 0x01
+#define AIM_COOKIETYPE_ADS 0x02
+#define AIM_COOKIETYPE_BOS 0x03
+#define AIM_COOKIETYPE_IM 0x04
+#define AIM_COOKIETYPE_CHAT 0x05
+#define AIM_COOKIETYPE_CHATNAV 0x06
+#define AIM_COOKIETYPE_INVITE 0x07
+
+int aim_handlerendconnect(aim_session_t *sess, aim_conn_t *cur);
+
+#define AIM_TRANSFER_DENY_NOTSUPPORTED 0x0000
+#define AIM_TRANSFER_DENY_DECLINE 0x0001
+#define AIM_TRANSFER_DENY_NOTACCEPTING 0x0002
+int aim_denytransfer(aim_session_t *sess, const char *sender, const guint8 *cookie, unsigned short code);
+aim_conn_t *aim_accepttransfer(aim_session_t *sess, aim_conn_t *conn, const char *sn, const guint8 *cookie, const guint8 *ip, guint16 listingfiles, guint16 listingtotsize, guint16 listingsize, guint32 listingchecksum, guint16 rendid);
+
+int aim_getinfo(aim_session_t *, aim_conn_t *, const char *, unsigned short);
+int aim_sendbuddyoncoming(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *info);
+int aim_sendbuddyoffgoing(aim_session_t *sess, aim_conn_t *conn, const char *sn);
+
+#define AIM_IMPARAM_FLAG_CHANMSGS_ALLOWED 0x00000001
+#define AIM_IMPARAM_FLAG_MISSEDCALLS_ENABLED 0x00000002
+
+/* This is what the server will give you if you don't set them yourself. */
+#define AIM_IMPARAM_DEFAULTS { \
+ 0, \
+ AIM_IMPARAM_FLAG_CHANMSGS_ALLOWED | AIM_IMPARAM_FLAG_MISSEDCALLS_ENABLED, \
+ 512, /* !! Note how small this is. */ \
+ (99.9)*10, (99.9)*10, \
+ 1000 /* !! And how large this is. */ \
+}
+
+/* This is what most AIM versions use. */
+#define AIM_IMPARAM_REASONABLE { \
+ 0, \
+ AIM_IMPARAM_FLAG_CHANMSGS_ALLOWED | AIM_IMPARAM_FLAG_MISSEDCALLS_ENABLED, \
+ 8000, \
+ (99.9)*10, (99.9)*10, \
+ 0 \
+}
+
+
+struct aim_icbmparameters {
+ guint16 maxchan;
+ guint32 flags; /* AIM_IMPARAM_FLAG_ */
+ guint16 maxmsglen; /* message size that you will accept */
+ guint16 maxsenderwarn; /* this and below are *10 (999=99.9%) */
+ guint16 maxrecverwarn;
+ guint32 minmsginterval; /* in milliseconds? */
+};
+
+int aim_reqicbmparams(aim_session_t *sess);
+int aim_seticbmparam(aim_session_t *sess, struct aim_icbmparameters *params);
+
+/* auth.c */
+int aim_sendcookie(aim_session_t *, aim_conn_t *, const guint8 *);
+
+int aim_admin_changepasswd(aim_session_t *, aim_conn_t *, const char *newpw, const char *curpw);
+int aim_admin_reqconfirm(aim_session_t *sess, aim_conn_t *conn);
+int aim_admin_getinfo(aim_session_t *sess, aim_conn_t *conn, guint16 info);
+int aim_admin_setemail(aim_session_t *sess, aim_conn_t *conn, const char *newemail);
+int aim_admin_setnick(aim_session_t *sess, aim_conn_t *conn, const char *newnick);
+
+/* These apply to exchanges as well. */
+#define AIM_CHATROOM_FLAG_EVILABLE 0x0001
+#define AIM_CHATROOM_FLAG_NAV_ONLY 0x0002
+#define AIM_CHATROOM_FLAG_INSTANCING_ALLOWED 0x0004
+#define AIM_CHATROOM_FLAG_OCCUPANT_PEEK_ALLOWED 0x0008
+
+struct aim_chat_exchangeinfo {
+ guint16 number;
+ guint16 flags;
+ char *name;
+ char *charset1;
+ char *lang1;
+ char *charset2;
+ char *lang2;
+};
+
+#define AIM_CHATFLAGS_NOREFLECT 0x0001
+#define AIM_CHATFLAGS_AWAY 0x0002
+#define AIM_CHATFLAGS_UNICODE 0x0004
+#define AIM_CHATFLAGS_ISO_8859_1 0x0008
+
+int aim_chat_send_im(aim_session_t *sess, aim_conn_t *conn, guint16 flags, const char *msg, int msglen);
+int aim_chat_join(aim_session_t *sess, aim_conn_t *conn, guint16 exchange, const char *roomname, guint16 instance);
+int aim_chat_attachname(aim_conn_t *conn, guint16 exchange, const char *roomname, guint16 instance);
+char *aim_chat_getname(aim_conn_t *conn);
+aim_conn_t *aim_chat_getconn(aim_session_t *, const char *name);
+
+int aim_chatnav_reqrights(aim_session_t *sess, aim_conn_t *conn);
+
+int aim_chat_invite(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *msg, guint16 exchange, const char *roomname, guint16 instance);
+
+int aim_chatnav_createroom(aim_session_t *sess, aim_conn_t *conn, const char *name, guint16 exchange);
+int aim_chat_leaveroom(aim_session_t *sess, const char *name);
+
+/* aim_util.c */
+/*
+ * These are really ugly. You'd think this was LISP. I wish it was.
+ *
+ * XXX With the advent of bstream's, these should be removed to enforce
+ * their use.
+ *
+ */
+#define aimutil_put8(buf, data) ((*(buf) = (u_char)(data)&0xff),1)
+#define aimutil_get8(buf) ((*(buf))&0xff)
+#define aimutil_put16(buf, data) ( \
+ (*(buf) = (u_char)((data)>>8)&0xff), \
+ (*((buf)+1) = (u_char)(data)&0xff), \
+ 2)
+#define aimutil_get16(buf) ((((*(buf))<<8)&0xff00) + ((*((buf)+1)) & 0xff))
+#define aimutil_put32(buf, data) ( \
+ (*((buf)) = (u_char)((data)>>24)&0xff), \
+ (*((buf)+1) = (u_char)((data)>>16)&0xff), \
+ (*((buf)+2) = (u_char)((data)>>8)&0xff), \
+ (*((buf)+3) = (u_char)(data)&0xff), \
+ 4)
+#define aimutil_get32(buf) ((((*(buf))<<24)&0xff000000) + \
+ (((*((buf)+1))<<16)&0x00ff0000) + \
+ (((*((buf)+2))<< 8)&0x0000ff00) + \
+ (((*((buf)+3) )&0x000000ff)))
+
+/* Little-endian versions (damn ICQ) */
+#define aimutil_putle8(buf, data) ( \
+ (*(buf) = (unsigned char)(data) & 0xff), \
+ 1)
+#define aimutil_getle8(buf) ( \
+ (*(buf)) & 0xff \
+ )
+#define aimutil_putle16(buf, data) ( \
+ (*((buf)+0) = (unsigned char)((data) >> 0) & 0xff), \
+ (*((buf)+1) = (unsigned char)((data) >> 8) & 0xff), \
+ 2)
+#define aimutil_getle16(buf) ( \
+ (((*((buf)+0)) << 0) & 0x00ff) + \
+ (((*((buf)+1)) << 8) & 0xff00) \
+ )
+#define aimutil_putle32(buf, data) ( \
+ (*((buf)+0) = (unsigned char)((data) >> 0) & 0xff), \
+ (*((buf)+1) = (unsigned char)((data) >> 8) & 0xff), \
+ (*((buf)+2) = (unsigned char)((data) >> 16) & 0xff), \
+ (*((buf)+3) = (unsigned char)((data) >> 24) & 0xff), \
+ 4)
+#define aimutil_getle32(buf) ( \
+ (((*((buf)+0)) << 0) & 0x000000ff) + \
+ (((*((buf)+1)) << 8) & 0x0000ff00) + \
+ (((*((buf)+2)) << 16) & 0x00ff0000) + \
+ (((*((buf)+3)) << 24) & 0xff000000))
+
+
+int aimutil_putstr(u_char *, const char *, int);
+int aimutil_tokslen(char *toSearch, int index, char dl);
+int aimutil_itemcnt(char *toSearch, char dl);
+char *aimutil_itemidx(char *toSearch, int index, char dl);
+int aim_sncmp(const char *a, const char *b);
+
+#include <aim_internal.h>
+
+/*
+ * SNAC Families.
+ */
+#define AIM_CB_FAM_ACK 0x0000
+#define AIM_CB_FAM_GEN 0x0001
+#define AIM_CB_FAM_SPECIAL 0xffff /* Internal libfaim use */
+
+/*
+ * SNAC Family: Ack.
+ *
+ * Not really a family, but treating it as one really
+ * helps it fit into the libfaim callback structure better.
+ *
+ */
+#define AIM_CB_ACK_ACK 0x0001
+
+/*
+ * SNAC Family: General.
+ */
+#define AIM_CB_GEN_ERROR 0x0001
+#define AIM_CB_GEN_CLIENTREADY 0x0002
+#define AIM_CB_GEN_SERVERREADY 0x0003
+#define AIM_CB_GEN_SERVICEREQ 0x0004
+#define AIM_CB_GEN_REDIRECT 0x0005
+#define AIM_CB_GEN_RATEINFOREQ 0x0006
+#define AIM_CB_GEN_RATEINFO 0x0007
+#define AIM_CB_GEN_RATEINFOACK 0x0008
+#define AIM_CB_GEN_RATECHANGE 0x000a
+#define AIM_CB_GEN_SERVERPAUSE 0x000b
+#define AIM_CB_GEN_SERVERRESUME 0x000d
+#define AIM_CB_GEN_REQSELFINFO 0x000e
+#define AIM_CB_GEN_SELFINFO 0x000f
+#define AIM_CB_GEN_EVIL 0x0010
+#define AIM_CB_GEN_SETIDLE 0x0011
+#define AIM_CB_GEN_MIGRATIONREQ 0x0012
+#define AIM_CB_GEN_MOTD 0x0013
+#define AIM_CB_GEN_SETPRIVFLAGS 0x0014
+#define AIM_CB_GEN_WELLKNOWNURL 0x0015
+#define AIM_CB_GEN_NOP 0x0016
+#define AIM_CB_GEN_DEFAULT 0xffff
+
+/*
+ * SNAC Family: Advertisement Services
+ */
+#define AIM_CB_ADS_ERROR 0x0001
+#define AIM_CB_ADS_DEFAULT 0xffff
+
+/*
+ * OFT Services
+ *
+ * See non-SNAC note below.
+ */
+#define AIM_CB_OFT_DIRECTIMCONNECTREQ 0x0001/* connect request -- actually an OSCAR CAP*/
+#define AIM_CB_OFT_DIRECTIMINCOMING 0x0002
+#define AIM_CB_OFT_DIRECTIMDISCONNECT 0x0003
+#define AIM_CB_OFT_DIRECTIMTYPING 0x0004
+#define AIM_CB_OFT_DIRECTIMINITIATE 0x0005
+
+#define AIM_CB_OFT_GETFILECONNECTREQ 0x0006 /* connect request -- actually an OSCAR CAP*/
+#define AIM_CB_OFT_GETFILELISTINGREQ 0x0007 /* OFT listing.txt request */
+#define AIM_CB_OFT_GETFILEFILEREQ 0x0008 /* received file request */
+#define AIM_CB_OFT_GETFILEFILESEND 0x0009 /* received file request confirm -- send data */
+#define AIM_CB_OFT_GETFILECOMPLETE 0x000a /* received file send complete*/
+#define AIM_CB_OFT_GETFILEINITIATE 0x000b /* request for file get acknowledge */
+#define AIM_CB_OFT_GETFILEDISCONNECT 0x000c /* OFT connection disconnected.*/
+#define AIM_CB_OFT_GETFILELISTING 0x000d /* OFT listing.txt received.*/
+#define AIM_CB_OFT_GETFILERECEIVE 0x000e /* OFT file incoming.*/
+#define AIM_CB_OFT_GETFILELISTINGRXCONFIRM 0x000f
+#define AIM_CB_OFT_GETFILESTATE4 0x0010
+
+#define AIM_CB_OFT_SENDFILEDISCONNECT 0x0020 /* OFT connection disconnected.*/
+
+
+
+/*
+ * SNAC Family: Internal Messages
+ *
+ * This isn't truely a SNAC family either, but using
+ * these, we can integrated non-SNAC services into
+ * the SNAC-centered libfaim callback structure.
+ *
+ */
+#define AIM_CB_SPECIAL_AUTHSUCCESS 0x0001
+#define AIM_CB_SPECIAL_AUTHOTHER 0x0002
+#define AIM_CB_SPECIAL_CONNERR 0x0003
+#define AIM_CB_SPECIAL_CONNCOMPLETE 0x0004
+#define AIM_CB_SPECIAL_FLAPVER 0x0005
+#define AIM_CB_SPECIAL_CONNINITDONE 0x0006
+#define AIM_CB_SPECIAL_IMAGETRANSFER 0x007
+#define AIM_CB_SPECIAL_UNKNOWN 0xffff
+#define AIM_CB_SPECIAL_DEFAULT AIM_CB_SPECIAL_UNKNOWN
+
+#endif /* __AIM_H__ */
diff --git a/protocols/oscar/aim_internal.h b/protocols/oscar/aim_internal.h
new file mode 100644
index 00000000..80a9c758
--- /dev/null
+++ b/protocols/oscar/aim_internal.h
@@ -0,0 +1,214 @@
+/*
+ * aim_internal.h -- prototypes/structs for the guts of libfaim
+ *
+ */
+
+#ifndef __AIM_INTERNAL_H__
+#define __AIM_INTERNAL_H__ 1
+
+typedef struct {
+ guint16 family;
+ guint16 subtype;
+ guint16 flags;
+ guint32 id;
+} aim_modsnac_t;
+
+#define AIM_MODULENAME_MAXLEN 16
+#define AIM_MODFLAG_MULTIFAMILY 0x0001
+typedef struct aim_module_s {
+ guint16 family;
+ guint16 version;
+ guint16 toolid;
+ guint16 toolversion;
+ guint16 flags;
+ char name[AIM_MODULENAME_MAXLEN+1];
+ int (*snachandler)(aim_session_t *sess, struct aim_module_s *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs);
+ void (*shutdown)(aim_session_t *sess, struct aim_module_s *mod);
+ void *priv;
+ struct aim_module_s *next;
+} aim_module_t;
+
+int aim__registermodule(aim_session_t *sess, int (*modfirst)(aim_session_t *, aim_module_t *));
+void aim__shutdownmodules(aim_session_t *sess);
+aim_module_t *aim__findmodulebygroup(aim_session_t *sess, guint16 group);
+
+int buddylist_modfirst(aim_session_t *sess, aim_module_t *mod);
+int admin_modfirst(aim_session_t *sess, aim_module_t *mod);
+int bos_modfirst(aim_session_t *sess, aim_module_t *mod);
+int search_modfirst(aim_session_t *sess, aim_module_t *mod);
+int stats_modfirst(aim_session_t *sess, aim_module_t *mod);
+int auth_modfirst(aim_session_t *sess, aim_module_t *mod);
+int msg_modfirst(aim_session_t *sess, aim_module_t *mod);
+int misc_modfirst(aim_session_t *sess, aim_module_t *mod);
+int chatnav_modfirst(aim_session_t *sess, aim_module_t *mod);
+int chat_modfirst(aim_session_t *sess, aim_module_t *mod);
+int locate_modfirst(aim_session_t *sess, aim_module_t *mod);
+int general_modfirst(aim_session_t *sess, aim_module_t *mod);
+int ssi_modfirst(aim_session_t *sess, aim_module_t *mod);
+int icq_modfirst(aim_session_t *sess, aim_module_t *mod);
+
+int aim_genericreq_n(aim_session_t *, aim_conn_t *conn, guint16 family, guint16 subtype);
+int aim_genericreq_n_snacid(aim_session_t *, aim_conn_t *conn, guint16 family, guint16 subtype);
+int aim_genericreq_l(aim_session_t *, aim_conn_t *conn, guint16 family, guint16 subtype, guint32 *);
+int aim_genericreq_s(aim_session_t *, aim_conn_t *conn, guint16 family, guint16 subtype, guint16 *);
+
+#define AIMBS_CURPOSPAIR(x) ((x)->data + (x)->offset), ((x)->len - (x)->offset)
+
+void aim_rxqueue_cleanbyconn(aim_session_t *sess, aim_conn_t *conn);
+int aim_recv(int fd, void *buf, size_t count);
+int aim_bstream_init(aim_bstream_t *bs, guint8 *data, int len);
+int aim_bstream_empty(aim_bstream_t *bs);
+int aim_bstream_curpos(aim_bstream_t *bs);
+int aim_bstream_setpos(aim_bstream_t *bs, int off);
+void aim_bstream_rewind(aim_bstream_t *bs);
+int aim_bstream_advance(aim_bstream_t *bs, int n);
+guint8 aimbs_get8(aim_bstream_t *bs);
+guint16 aimbs_get16(aim_bstream_t *bs);
+guint32 aimbs_get32(aim_bstream_t *bs);
+guint8 aimbs_getle8(aim_bstream_t *bs);
+guint16 aimbs_getle16(aim_bstream_t *bs);
+guint32 aimbs_getle32(aim_bstream_t *bs);
+int aimbs_put8(aim_bstream_t *bs, guint8 v);
+int aimbs_put16(aim_bstream_t *bs, guint16 v);
+int aimbs_put32(aim_bstream_t *bs, guint32 v);
+int aimbs_putle8(aim_bstream_t *bs, guint8 v);
+int aimbs_putle16(aim_bstream_t *bs, guint16 v);
+int aimbs_putle32(aim_bstream_t *bs, guint32 v);
+int aimbs_getrawbuf(aim_bstream_t *bs, guint8 *buf, int len);
+guint8 *aimbs_getraw(aim_bstream_t *bs, int len);
+char *aimbs_getstr(aim_bstream_t *bs, int len);
+int aimbs_putraw(aim_bstream_t *bs, const guint8 *v, int len);
+int aimbs_putbs(aim_bstream_t *bs, aim_bstream_t *srcbs, int len);
+
+int aim_get_command_rendezvous(aim_session_t *sess, aim_conn_t *conn);
+
+int aim_tx_sendframe(aim_session_t *sess, aim_frame_t *cur);
+flap_seqnum_t aim_get_next_txseqnum(aim_conn_t *);
+aim_frame_t *aim_tx_new(aim_session_t *sess, aim_conn_t *conn, guint8 framing, guint8 chan, int datalen);
+void aim_frame_destroy(aim_frame_t *);
+int aim_tx_enqueue(aim_session_t *, aim_frame_t *);
+int aim_tx_printqueue(aim_session_t *);
+void aim_tx_cleanqueue(aim_session_t *, aim_conn_t *);
+
+aim_rxcallback_t aim_callhandler(aim_session_t *sess, aim_conn_t *conn, u_short family, u_short type);
+
+/*
+ * Generic SNAC structure. Rarely if ever used.
+ */
+typedef struct aim_snac_s {
+ aim_snacid_t id;
+ guint16 family;
+ guint16 type;
+ guint16 flags;
+ void *data;
+ time_t issuetime;
+ struct aim_snac_s *next;
+} aim_snac_t;
+
+void aim_initsnachash(aim_session_t *sess);
+aim_snacid_t aim_cachesnac(aim_session_t *sess, const guint16 family, const guint16 type, const guint16 flags, const void *data, const int datalen);
+aim_snac_t *aim_remsnac(aim_session_t *, aim_snacid_t id);
+void aim_cleansnacs(aim_session_t *, int maxage);
+int aim_putsnac(aim_bstream_t *, guint16 family, guint16 type, guint16 flags, aim_snacid_t id);
+
+aim_conn_t *aim_cloneconn(aim_session_t *sess, aim_conn_t *src);
+void aim_clonehandlers(aim_session_t *sess, aim_conn_t *dest, aim_conn_t *src);
+
+int aim_oft_buildheader(unsigned char *,struct aim_fileheader_t *);
+
+int aim_parse_unknown(aim_session_t *, aim_frame_t *, ...);
+
+/* Stored in ->priv of the service request SNAC for chats. */
+struct chatsnacinfo {
+ guint16 exchange;
+ char name[128];
+ guint16 instance;
+};
+
+/* these are used by aim_*_clientready */
+#define AIM_TOOL_JAVA 0x0001
+#define AIM_TOOL_MAC 0x0002
+#define AIM_TOOL_WIN16 0x0003
+#define AIM_TOOL_WIN32 0x0004
+#define AIM_TOOL_MAC68K 0x0005
+#define AIM_TOOL_MACPPC 0x0006
+#define AIM_TOOL_NEWWIN 0x0010
+struct aim_tool_version {
+ guint16 group;
+ guint16 version;
+ guint16 tool;
+ guint16 toolversion;
+};
+
+/*
+ * In SNACland, the terms 'family' and 'group' are synonymous -- the former
+ * is my term, the latter is AOL's.
+ */
+struct snacgroup {
+ guint16 group;
+ struct snacgroup *next;
+};
+
+struct snacpair {
+ guint16 group;
+ guint16 subtype;
+ struct snacpair *next;
+};
+
+struct rateclass {
+ guint16 classid;
+ guint32 windowsize;
+ guint32 clear;
+ guint32 alert;
+ guint32 limit;
+ guint32 disconnect;
+ guint32 current;
+ guint32 max;
+ guint8 unknown[5]; /* only present in versions >= 3 */
+ struct snacpair *members;
+ struct rateclass *next;
+};
+
+/*
+ * This is inside every connection. But it is a void * to anything
+ * outside of libfaim. It should remain that way. It's called data
+ * abstraction. Maybe you've heard of it. (Probably not if you're a
+ * libfaim user.)
+ *
+ */
+typedef struct aim_conn_inside_s {
+ struct snacgroup *groups;
+ struct rateclass *rates;
+} aim_conn_inside_t;
+
+void aim_conn_addgroup(aim_conn_t *conn, guint16 group);
+
+guint32 aim_getcap(aim_session_t *sess, aim_bstream_t *bs, int len);
+int aim_putcap(aim_bstream_t *bs, guint32 caps);
+
+int aim_cachecookie(aim_session_t *sess, aim_msgcookie_t *cookie);
+aim_msgcookie_t *aim_uncachecookie(aim_session_t *sess, guint8 *cookie, int type);
+aim_msgcookie_t *aim_mkcookie(guint8 *, int, void *);
+aim_msgcookie_t *aim_checkcookie(aim_session_t *sess, const unsigned char *, const int);
+int aim_freecookie(aim_session_t *sess, aim_msgcookie_t *cookie);
+int aim_cookie_free(aim_session_t *sess, aim_msgcookie_t *cookie);
+
+int aim_extractuserinfo(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *);
+int aim_putuserinfo(aim_bstream_t *bs, aim_userinfo_t *info);
+
+int aim_chat_readroominfo(aim_bstream_t *bs, struct aim_chat_roominfo *outinfo);
+
+int aim_request_directim(aim_session_t *sess, const char *destsn, guint8 *ip, guint16 port, guint8 *ckret);
+int aim_request_sendfile(aim_session_t *sess, const char *sn, const char *filename, guint16 numfiles, guint32 totsize, guint8 *ip, guint16 port, guint8 *ckret);
+void aim_conn_close_rend(aim_session_t *sess, aim_conn_t *conn);
+void aim_conn_kill_rend(aim_session_t *sess, aim_conn_t *conn);
+
+void aim_conn_kill_chat(aim_session_t *sess, aim_conn_t *conn);
+
+/* These are all handled internally now. */
+int aim_setversions(aim_session_t *sess, aim_conn_t *conn);
+int aim_reqrates(aim_session_t *, aim_conn_t *);
+int aim_rates_addparam(aim_session_t *, aim_conn_t *);
+int aim_rates_delparam(aim_session_t *, aim_conn_t *);
+
+#endif /* __AIM_INTERNAL_H__ */
diff --git a/protocols/oscar/auth.c b/protocols/oscar/auth.c
new file mode 100644
index 00000000..0f7c8d0f
--- /dev/null
+++ b/protocols/oscar/auth.c
@@ -0,0 +1,538 @@
+/*
+ * Deals with the authorizer (group 0x0017=23, and old-style non-SNAC login).
+ *
+ */
+
+#include <aim.h>
+
+#include "md5.h"
+
+static int aim_encode_password(const char *password, unsigned char *encoded);
+
+/*
+ * This just pushes the passed cookie onto the passed connection, without
+ * the SNAC header or any of that.
+ *
+ * Very commonly used, as every connection except auth will require this to
+ * be the first thing you send.
+ *
+ */
+int aim_sendcookie(aim_session_t *sess, aim_conn_t *conn, const guint8 *chipsahoy)
+{
+ aim_frame_t *fr;
+ aim_tlvlist_t *tl = NULL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x0001, 4+2+2+AIM_COOKIELEN)))
+ return -ENOMEM;
+
+ aimbs_put32(&fr->data, 0x00000001);
+ aim_addtlvtochain_raw(&tl, 0x0006, AIM_COOKIELEN, chipsahoy);
+ aim_writetlvchain(&fr->data, &tl);
+ aim_freetlvchain(&tl);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * Normally the FLAP version is sent as the first few bytes of the cookie,
+ * meaning you generally never call this.
+ *
+ * But there are times when something might want it seperate. Specifically,
+ * libfaim sends this internally when doing SNAC login.
+ *
+ */
+int aim_sendflapver(aim_session_t *sess, aim_conn_t *conn)
+{
+ aim_frame_t *fr;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x01, 4)))
+ return -ENOMEM;
+
+ aimbs_put32(&fr->data, 0x00000001);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * This is a bit confusing.
+ *
+ * Normal SNAC login goes like this:
+ * - connect
+ * - server sends flap version
+ * - client sends flap version
+ * - client sends screen name (17/6)
+ * - server sends hash key (17/7)
+ * - client sends auth request (17/2 -- aim_send_login)
+ * - server yells
+ *
+ * XOR login (for ICQ) goes like this:
+ * - connect
+ * - server sends flap version
+ * - client sends auth request which contains flap version (aim_send_login)
+ * - server yells
+ *
+ * For the client API, we make them implement the most complicated version,
+ * and for the simpler version, we fake it and make it look like the more
+ * complicated process.
+ *
+ * This is done by giving the client a faked key, just so we can convince
+ * them to call aim_send_login right away, which will detect the session
+ * flag that says this is XOR login and ignore the key, sending an ICQ
+ * login request instead of the normal SNAC one.
+ *
+ * As soon as AOL makes ICQ log in the same way as AIM, this is /gone/.
+ *
+ * XXX This may cause problems if the client relies on callbacks only
+ * being called from the context of aim_rxdispatch()...
+ *
+ */
+static int goddamnicq(aim_session_t *sess, aim_conn_t *conn, const char *sn)
+{
+ aim_frame_t fr;
+ aim_rxcallback_t userfunc;
+
+ sess->flags &= ~AIM_SESS_FLAGS_SNACLOGIN;
+ sess->flags |= AIM_SESS_FLAGS_XORLOGIN;
+
+ fr.conn = conn;
+
+ if ((userfunc = aim_callhandler(sess, conn, 0x0017, 0x0007)))
+ userfunc(sess, &fr, "");
+
+ return 0;
+}
+
+/*
+ * In AIM 3.5 protocol, the first stage of login is to request login from the
+ * Authorizer, passing it the screen name for verification. If the name is
+ * invalid, a 0017/0003 is spit back, with the standard error contents. If
+ * valid, a 0017/0007 comes back, which is the signal to send it the main
+ * login command (0017/0002).
+ *
+ */
+int aim_request_login(aim_session_t *sess, aim_conn_t *conn, const char *sn)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ aim_tlvlist_t *tl = NULL;
+ struct im_connection *ic = sess->aux_data;
+
+ if (!sess || !conn || !sn)
+ return -EINVAL;
+
+ if (isdigit(sn[0]) && set_getbool(&ic->acc->set, "old_icq_auth"))
+ return goddamnicq(sess, conn, sn);
+
+ sess->flags |= AIM_SESS_FLAGS_SNACLOGIN;
+
+ aim_sendflapver(sess, conn);
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+2+strlen(sn))))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0017, 0x0006, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0017, 0x0006, 0x0000, snacid);
+
+ aim_addtlvtochain_raw(&tl, 0x0001, strlen(sn), (guint8 *)sn);
+ aim_writetlvchain(&fr->data, &tl);
+ aim_freetlvchain(&tl);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * Part two of the ICQ hack. Note the ignoring of the key and clientinfo.
+ */
+static int goddamnicq2(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *password)
+{
+ static const char clientstr[] = {"ICQ Inc. - Product of ICQ (TM) 2001b.5.17.1.3642.85"};
+ static const char lang[] = {"en"};
+ static const char country[] = {"us"};
+ aim_frame_t *fr;
+ aim_tlvlist_t *tl = NULL;
+ guint8 *password_encoded;
+
+ if (!(password_encoded = (guint8 *) g_malloc(strlen(password))))
+ return -ENOMEM;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x01, 1152))) {
+ g_free(password_encoded);
+ return -ENOMEM;
+ }
+
+ aim_encode_password(password, password_encoded);
+
+ aimbs_put32(&fr->data, 0x00000001);
+ aim_addtlvtochain_raw(&tl, 0x0001, strlen(sn), (guint8 *)sn);
+ aim_addtlvtochain_raw(&tl, 0x0002, strlen(password), password_encoded);
+ aim_addtlvtochain_raw(&tl, 0x0003, strlen(clientstr), (guint8 *)clientstr);
+ aim_addtlvtochain16(&tl, 0x0016, 0x010a); /* cliend ID */
+ aim_addtlvtochain16(&tl, 0x0017, 0x0005); /* major version */
+ aim_addtlvtochain16(&tl, 0x0018, 0x0011); /* minor version */
+ aim_addtlvtochain16(&tl, 0x0019, 0x0001); /* point version */
+ aim_addtlvtochain16(&tl, 0x001a, 0x0e3a); /* build */
+ aim_addtlvtochain32(&tl, 0x0014, 0x00000055); /* distribution chan */
+ aim_addtlvtochain_raw(&tl, 0x000f, strlen(lang), (guint8 *)lang);
+ aim_addtlvtochain_raw(&tl, 0x000e, strlen(country), (guint8 *)country);
+
+ aim_writetlvchain(&fr->data, &tl);
+
+ g_free(password_encoded);
+ aim_freetlvchain(&tl);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * send_login(int socket, char *sn, char *password)
+ *
+ * This is the initial login request packet.
+ *
+ * NOTE!! If you want/need to make use of the aim_sendmemblock() function,
+ * then the client information you send here must exactly match the
+ * executable that you're pulling the data from.
+ *
+ * WinAIM 4.8.2540
+ * clientstring = "AOL Instant Messenger (SM), version 4.8.2540/WIN32"
+ * clientid = 0x0109
+ * major = 0x0004
+ * minor = 0x0008
+ * point = 0x0000
+ * build = 0x09ec
+ * t(0x0014) = 0x000000af
+ * t(0x004a) = 0x01
+ *
+ * WinAIM 4.3.2188:
+ * clientstring = "AOL Instant Messenger (SM), version 4.3.2188/WIN32"
+ * clientid = 0x0109
+ * major = 0x0400
+ * minor = 0x0003
+ * point = 0x0000
+ * build = 0x088c
+ * unknown = 0x00000086
+ * lang = "en"
+ * country = "us"
+ * unknown4a = 0x01
+ *
+ * Latest WinAIM that libfaim can emulate without server-side buddylists:
+ * clientstring = "AOL Instant Messenger (SM), version 4.1.2010/WIN32"
+ * clientid = 0x0004
+ * major = 0x0004
+ * minor = 0x0001
+ * point = 0x0000
+ * build = 0x07da
+ * unknown= 0x0000004b
+ *
+ * WinAIM 3.5.1670:
+ * clientstring = "AOL Instant Messenger (SM), version 3.5.1670/WIN32"
+ * clientid = 0x0004
+ * major = 0x0003
+ * minor = 0x0005
+ * point = 0x0000
+ * build = 0x0686
+ * unknown =0x0000002a
+ *
+ * Java AIM 1.1.19:
+ * clientstring = "AOL Instant Messenger (TM) version 1.1.19 for Java built 03/24/98, freeMem 215871 totalMem 1048567, i686, Linus, #2 SMP Sun Feb 11 03:41:17 UTC 2001 2.4.1-ac9, IBM Corporation, 1.1.8, 45.3, Tue Mar 27 12:09:17 PST 2001"
+ * clientid = 0x0001
+ * major = 0x0001
+ * minor = 0x0001
+ * point = (not sent)
+ * build = 0x0013
+ * unknown= (not sent)
+ *
+ * AIM for Linux 1.1.112:
+ * clientstring = "AOL Instant Messenger (SM)"
+ * clientid = 0x1d09
+ * major = 0x0001
+ * minor = 0x0001
+ * point = 0x0001
+ * build = 0x0070
+ * unknown= 0x0000008b
+ * serverstore = 0x01
+ *
+ */
+int aim_send_login(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *password, struct client_info_s *ci, const char *key)
+{
+ aim_frame_t *fr;
+ aim_tlvlist_t *tl = NULL;
+ guint8 digest[16];
+ aim_snacid_t snacid;
+
+ if (!ci || !sn || !password)
+ return -EINVAL;
+
+ /*
+ * What the XORLOGIN flag _really_ means is that its an ICQ login,
+ * which is really stupid and painful, so its not done here.
+ *
+ */
+ if (sess->flags & AIM_SESS_FLAGS_XORLOGIN)
+ return goddamnicq2(sess, conn, sn, password);
+
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0017, 0x0002, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0017, 0x0002, 0x0000, snacid);
+
+ aim_addtlvtochain_raw(&tl, 0x0001, strlen(sn), (guint8 *)sn);
+
+ aim_encode_password_md5(password, key, digest);
+ aim_addtlvtochain_raw(&tl, 0x0025, 16, digest);
+
+ /*
+ * Newer versions of winaim have an empty type x004c TLV here.
+ */
+
+ if (ci->clientstring)
+ aim_addtlvtochain_raw(&tl, 0x0003, strlen(ci->clientstring), (guint8 *)ci->clientstring);
+ aim_addtlvtochain16(&tl, 0x0016, (guint16)ci->clientid);
+ aim_addtlvtochain16(&tl, 0x0017, (guint16)ci->major);
+ aim_addtlvtochain16(&tl, 0x0018, (guint16)ci->minor);
+ aim_addtlvtochain16(&tl, 0x0019, (guint16)ci->point);
+ aim_addtlvtochain16(&tl, 0x001a, (guint16)ci->build);
+ aim_addtlvtochain_raw(&tl, 0x000e, strlen(ci->country), (guint8 *)ci->country);
+ aim_addtlvtochain_raw(&tl, 0x000f, strlen(ci->lang), (guint8 *)ci->lang);
+
+ /*
+ * If set, old-fashioned buddy lists will not work. You will need
+ * to use SSI.
+ */
+ aim_addtlvtochain8(&tl, 0x004a, 0x01);
+
+ aim_writetlvchain(&fr->data, &tl);
+
+ aim_freetlvchain(&tl);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+int aim_encode_password_md5(const char *password, const char *key, guint8 *digest)
+{
+ md5_state_t state;
+
+ md5_init(&state);
+ md5_append(&state, (const md5_byte_t *)key, strlen(key));
+ md5_append(&state, (const md5_byte_t *)password, strlen(password));
+ md5_append(&state, (const md5_byte_t *)AIM_MD5_STRING, strlen(AIM_MD5_STRING));
+ md5_finish(&state, (md5_byte_t *)digest);
+
+ return 0;
+}
+
+/**
+ * aim_encode_password - Encode a password using old XOR method
+ * @password: incoming password
+ * @encoded: buffer to put encoded password
+ *
+ * This takes a const pointer to a (null terminated) string
+ * containing the unencoded password. It also gets passed
+ * an already allocated buffer to store the encoded password.
+ * This buffer should be the exact length of the password without
+ * the null. The encoded password buffer /is not %NULL terminated/.
+ *
+ * The encoding_table seems to be a fixed set of values. We'll
+ * hope it doesn't change over time!
+ *
+ * This is only used for the XOR method, not the better MD5 method.
+ *
+ */
+static int aim_encode_password(const char *password, guint8 *encoded)
+{
+ guint8 encoding_table[] = {
+ /* v2.1 table, also works for ICQ */
+ 0xf3, 0x26, 0x81, 0xc4,
+ 0x39, 0x86, 0xdb, 0x92,
+ 0x71, 0xa3, 0xb9, 0xe6,
+ 0x53, 0x7a, 0x95, 0x7c
+ };
+ int i;
+
+ for (i = 0; i < strlen(password); i++)
+ encoded[i] = (password[i] ^ encoding_table[i]);
+
+ return 0;
+}
+
+/*
+ * This is sent back as a general response to the login command.
+ * It can be either an error or a success, depending on the
+ * precense of certain TLVs.
+ *
+ * The client should check the value passed as errorcode. If
+ * its nonzero, there was an error.
+ *
+ */
+static int parse(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_tlvlist_t *tlvlist;
+ aim_rxcallback_t userfunc;
+ struct aim_authresp_info info;
+ int ret = 0;
+
+ memset(&info, 0, sizeof(info));
+
+ /*
+ * Read block of TLVs. All further data is derived
+ * from what is parsed here.
+ */
+ tlvlist = aim_readtlvchain(bs);
+
+ /*
+ * No matter what, we should have a screen name.
+ */
+ memset(sess->sn, 0, sizeof(sess->sn));
+ if (aim_gettlv(tlvlist, 0x0001, 1)) {
+ info.sn = aim_gettlv_str(tlvlist, 0x0001, 1);
+ strncpy(sess->sn, info.sn, sizeof(sess->sn));
+ }
+
+ /*
+ * Check for an error code. If so, we should also
+ * have an error url.
+ */
+ if (aim_gettlv(tlvlist, 0x0008, 1))
+ info.errorcode = aim_gettlv16(tlvlist, 0x0008, 1);
+ if (aim_gettlv(tlvlist, 0x0004, 1))
+ info.errorurl = aim_gettlv_str(tlvlist, 0x0004, 1);
+
+ /*
+ * BOS server address.
+ */
+ if (aim_gettlv(tlvlist, 0x0005, 1))
+ info.bosip = aim_gettlv_str(tlvlist, 0x0005, 1);
+
+ /*
+ * Authorization cookie.
+ */
+ if (aim_gettlv(tlvlist, 0x0006, 1)) {
+ aim_tlv_t *tmptlv;
+
+ tmptlv = aim_gettlv(tlvlist, 0x0006, 1);
+
+ info.cookie = tmptlv->value;
+ }
+
+ /*
+ * The email address attached to this account
+ * Not available for ICQ logins.
+ */
+ if (aim_gettlv(tlvlist, 0x0011, 1))
+ info.email = aim_gettlv_str(tlvlist, 0x0011, 1);
+
+ /*
+ * The registration status. (Not real sure what it means.)
+ * Not available for ICQ logins.
+ *
+ * 1 = No disclosure
+ * 2 = Limited disclosure
+ * 3 = Full disclosure
+ *
+ * This has to do with whether your email address is available
+ * to other users or not. AFAIK, this feature is no longer used.
+ *
+ */
+ if (aim_gettlv(tlvlist, 0x0013, 1))
+ info.regstatus = aim_gettlv16(tlvlist, 0x0013, 1);
+
+ if (aim_gettlv(tlvlist, 0x0040, 1))
+ info.latestbeta.build = aim_gettlv32(tlvlist, 0x0040, 1);
+ if (aim_gettlv(tlvlist, 0x0041, 1))
+ info.latestbeta.url = aim_gettlv_str(tlvlist, 0x0041, 1);
+ if (aim_gettlv(tlvlist, 0x0042, 1))
+ info.latestbeta.info = aim_gettlv_str(tlvlist, 0x0042, 1);
+ if (aim_gettlv(tlvlist, 0x0043, 1))
+ info.latestbeta.name = aim_gettlv_str(tlvlist, 0x0043, 1);
+ if (aim_gettlv(tlvlist, 0x0048, 1))
+ ; /* no idea what this is */
+
+ if (aim_gettlv(tlvlist, 0x0044, 1))
+ info.latestrelease.build = aim_gettlv32(tlvlist, 0x0044, 1);
+ if (aim_gettlv(tlvlist, 0x0045, 1))
+ info.latestrelease.url = aim_gettlv_str(tlvlist, 0x0045, 1);
+ if (aim_gettlv(tlvlist, 0x0046, 1))
+ info.latestrelease.info = aim_gettlv_str(tlvlist, 0x0046, 1);
+ if (aim_gettlv(tlvlist, 0x0047, 1))
+ info.latestrelease.name = aim_gettlv_str(tlvlist, 0x0047, 1);
+ if (aim_gettlv(tlvlist, 0x0049, 1))
+ ; /* no idea what this is */
+
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac ? snac->family : 0x0017, snac ? snac->subtype : 0x0003)))
+ ret = userfunc(sess, rx, &info);
+
+ g_free(info.sn);
+ g_free(info.bosip);
+ g_free(info.errorurl);
+ g_free(info.email);
+ g_free(info.latestrelease.name);
+ g_free(info.latestrelease.url);
+ g_free(info.latestrelease.info);
+ g_free(info.latestbeta.name);
+ g_free(info.latestbeta.url);
+ g_free(info.latestbeta.info);
+
+ aim_freetlvchain(&tlvlist);
+
+ return ret;
+}
+
+/*
+ * Middle handler for 0017/0007 SNACs. Contains the auth key prefixed
+ * by only its length in a two byte word.
+ *
+ * Calls the client, which should then use the value to call aim_send_login.
+ *
+ */
+static int keyparse(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ int keylen, ret = 1;
+ aim_rxcallback_t userfunc;
+ char *keystr;
+
+ keylen = aimbs_get16(bs);
+ keystr = aimbs_getstr(bs, keylen);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, keystr);
+
+ g_free(keystr);
+
+ return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+ if (snac->subtype == 0x0003)
+ return parse(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x0007)
+ return keyparse(sess, mod, rx, snac, bs);
+
+ return 0;
+}
+
+int auth_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+ mod->family = 0x0017;
+ mod->version = 0x0000;
+ mod->flags = 0;
+ strncpy(mod->name, "auth", sizeof(mod->name));
+ mod->snachandler = snachandler;
+
+ return 0;
+}
+
diff --git a/protocols/oscar/bos.c b/protocols/oscar/bos.c
new file mode 100644
index 00000000..e7f12f79
--- /dev/null
+++ b/protocols/oscar/bos.c
@@ -0,0 +1,161 @@
+#include <aim.h>
+#include "bos.h"
+
+/* Request BOS rights (group 9, type 2) */
+int aim_bos_reqrights(aim_session_t *sess, aim_conn_t *conn)
+{
+ return aim_genericreq_n(sess, conn, 0x0009, 0x0002);
+}
+
+/* BOS Rights (group 9, type 3) */
+static int rights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_rxcallback_t userfunc;
+ aim_tlvlist_t *tlvlist;
+ guint16 maxpermits = 0, maxdenies = 0;
+ int ret = 0;
+
+ /*
+ * TLVs follow
+ */
+ tlvlist = aim_readtlvchain(bs);
+
+ /*
+ * TLV type 0x0001: Maximum number of buddies on permit list.
+ */
+ if (aim_gettlv(tlvlist, 0x0001, 1))
+ maxpermits = aim_gettlv16(tlvlist, 0x0001, 1);
+
+ /*
+ * TLV type 0x0002: Maximum number of buddies on deny list.
+ */
+ if (aim_gettlv(tlvlist, 0x0002, 1))
+ maxdenies = aim_gettlv16(tlvlist, 0x0002, 1);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, maxpermits, maxdenies);
+
+ aim_freetlvchain(&tlvlist);
+
+ return ret;
+}
+
+/*
+ * Set group permisson mask (group 9, type 4)
+ *
+ * Normally 0x1f (all classes).
+ *
+ * The group permission mask allows you to keep users of a certain
+ * class or classes from talking to you. The mask should be
+ * a bitwise OR of all the user classes you want to see you.
+ *
+ */
+int aim_bos_setgroupperm(aim_session_t *sess, aim_conn_t *conn, guint32 mask)
+{
+ return aim_genericreq_l(sess, conn, 0x0009, 0x0004, &mask);
+}
+
+/*
+ * Modify permit/deny lists (group 9, types 5, 6, 7, and 8)
+ *
+ * Changes your visibility depending on changetype:
+ *
+ * AIM_VISIBILITYCHANGE_PERMITADD: Lets provided list of names see you
+ * AIM_VISIBILITYCHANGE_PERMIDREMOVE: Removes listed names from permit list
+ * AIM_VISIBILITYCHANGE_DENYADD: Hides you from provided list of names
+ * AIM_VISIBILITYCHANGE_DENYREMOVE: Lets list see you again
+ *
+ * list should be a list of
+ * screen names in the form "Screen Name One&ScreenNameTwo&" etc.
+ *
+ * Equivelents to options in WinAIM:
+ * - Allow all users to contact me: Send an AIM_VISIBILITYCHANGE_DENYADD
+ * with only your name on it.
+ * - Allow only users on my Buddy List: Send an
+ * AIM_VISIBILITYCHANGE_PERMITADD with the list the same as your
+ * buddy list
+ * - Allow only the uesrs below: Send an AIM_VISIBILITYCHANGE_PERMITADD
+ * with everyone listed that you want to see you.
+ * - Block all users: Send an AIM_VISIBILITYCHANGE_PERMITADD with only
+ * yourself in the list
+ * - Block the users below: Send an AIM_VISIBILITYCHANGE_DENYADD with
+ * the list of users to be blocked
+ *
+ * XXX ye gods.
+ */
+int aim_bos_changevisibility(aim_session_t *sess, aim_conn_t *conn, int changetype, const char *denylist)
+{
+ aim_frame_t *fr;
+ int packlen = 0;
+ guint16 subtype;
+ char *localcpy = NULL, *tmpptr = NULL;
+ int i;
+ int listcount;
+ aim_snacid_t snacid;
+
+ if (!denylist)
+ return -EINVAL;
+
+ if (changetype == AIM_VISIBILITYCHANGE_PERMITADD)
+ subtype = 0x05;
+ else if (changetype == AIM_VISIBILITYCHANGE_PERMITREMOVE)
+ subtype = 0x06;
+ else if (changetype == AIM_VISIBILITYCHANGE_DENYADD)
+ subtype = 0x07;
+ else if (changetype == AIM_VISIBILITYCHANGE_DENYREMOVE)
+ subtype = 0x08;
+ else
+ return -EINVAL;
+
+ localcpy = g_strdup(denylist);
+
+ listcount = aimutil_itemcnt(localcpy, '&');
+ packlen = aimutil_tokslen(localcpy, 99, '&') + listcount + 9;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, packlen))) {
+ g_free(localcpy);
+ return -ENOMEM;
+ }
+
+ snacid = aim_cachesnac(sess, 0x0009, subtype, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0009, subtype, 0x00, snacid);
+
+ for (i = 0; (i < (listcount - 1)) && (i < 99); i++) {
+ tmpptr = aimutil_itemidx(localcpy, i, '&');
+
+ aimbs_put8(&fr->data, strlen(tmpptr));
+ aimbs_putraw(&fr->data, (guint8 *)tmpptr, strlen(tmpptr));
+
+ g_free(tmpptr);
+ }
+ g_free(localcpy);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+ if (snac->subtype == 0x0003)
+ return rights(sess, mod, rx, snac, bs);
+
+ return 0;
+}
+
+int bos_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+ mod->family = 0x0009;
+ mod->version = 0x0001;
+ mod->toolid = 0x0110;
+ mod->toolversion = 0x0629;
+ mod->flags = 0;
+ strncpy(mod->name, "bos", sizeof(mod->name));
+ mod->snachandler = snachandler;
+
+ return 0;
+}
+
+
diff --git a/protocols/oscar/bos.h b/protocols/oscar/bos.h
new file mode 100644
index 00000000..e7c2cbcd
--- /dev/null
+++ b/protocols/oscar/bos.h
@@ -0,0 +1,14 @@
+#ifndef __OSCAR_BOS_H__
+#define __OSCAR_BOS_H__
+
+#define AIM_CB_FAM_BOS 0x0009
+
+/*
+ * SNAC Family: Misc BOS Services.
+ */
+#define AIM_CB_BOS_ERROR 0x0001
+#define AIM_CB_BOS_RIGHTSQUERY 0x0002
+#define AIM_CB_BOS_RIGHTS 0x0003
+#define AIM_CB_BOS_DEFAULT 0xffff
+
+#endif /* __OSCAR_BOS_H__ */
diff --git a/protocols/oscar/buddylist.c b/protocols/oscar/buddylist.c
new file mode 100644
index 00000000..a7b461f2
--- /dev/null
+++ b/protocols/oscar/buddylist.c
@@ -0,0 +1,150 @@
+#include <aim.h>
+#include "buddylist.h"
+
+/*
+ * Oncoming Buddy notifications contain a subset of the
+ * user information structure. Its close enough to run
+ * through aim_extractuserinfo() however.
+ *
+ * Although the offgoing notification contains no information,
+ * it is still in a format parsable by extractuserinfo.
+ *
+ */
+static int buddychange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_userinfo_t userinfo;
+ aim_rxcallback_t userfunc;
+
+ aim_extractuserinfo(sess, bs, &userinfo);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ return userfunc(sess, rx, &userinfo);
+
+ return 0;
+}
+
+static int rights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_rxcallback_t userfunc;
+ aim_tlvlist_t *tlvlist;
+ guint16 maxbuddies = 0, maxwatchers = 0;
+ int ret = 0;
+
+ /*
+ * TLVs follow
+ */
+ tlvlist = aim_readtlvchain(bs);
+
+ /*
+ * TLV type 0x0001: Maximum number of buddies.
+ */
+ if (aim_gettlv(tlvlist, 0x0001, 1))
+ maxbuddies = aim_gettlv16(tlvlist, 0x0001, 1);
+
+ /*
+ * TLV type 0x0002: Maximum number of watchers.
+ *
+ * Watchers are other users who have you on their buddy
+ * list. (This is called the "reverse list" by a certain
+ * other IM protocol.)
+ *
+ */
+ if (aim_gettlv(tlvlist, 0x0002, 1))
+ maxwatchers = aim_gettlv16(tlvlist, 0x0002, 1);
+
+ /*
+ * TLV type 0x0003: Unknown.
+ *
+ * ICQ only?
+ */
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, maxbuddies, maxwatchers);
+
+ aim_freetlvchain(&tlvlist);
+
+ return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+ if (snac->subtype == 0x0003)
+ return rights(sess, mod, rx, snac, bs);
+ else if ((snac->subtype == 0x000b) || (snac->subtype == 0x000c))
+ return buddychange(sess, mod, rx, snac, bs);
+
+ return 0;
+}
+
+int buddylist_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+ mod->family = 0x0003;
+ mod->version = 0x0001;
+ mod->toolid = 0x0110;
+ mod->toolversion = 0x0629;
+ mod->flags = 0;
+ strncpy(mod->name, "buddylist", sizeof(mod->name));
+ mod->snachandler = snachandler;
+
+ return 0;
+}
+
+/*
+ * aim_add_buddy()
+ *
+ * Adds a single buddy to your buddy list after login.
+ *
+ * XXX this should just be an extension of setbuddylist()
+ *
+ */
+int aim_add_buddy(aim_session_t *sess, aim_conn_t *conn, const char *sn)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!sn || !strlen(sn))
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+1+strlen(sn))))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0003, 0x0004, 0x0000, sn, strlen(sn)+1);
+ aim_putsnac(&fr->data, 0x0003, 0x0004, 0x0000, snacid);
+
+ aimbs_put8(&fr->data, strlen(sn));
+ aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn));
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * XXX generalise to support removing multiple buddies (basically, its
+ * the same as setbuddylist() but with a different snac subtype).
+ *
+ */
+int aim_remove_buddy(aim_session_t *sess, aim_conn_t *conn, const char *sn)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!sn || !strlen(sn))
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+1+strlen(sn))))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0003, 0x0005, 0x0000, sn, strlen(sn)+1);
+ aim_putsnac(&fr->data, 0x0003, 0x0005, 0x0000, snacid);
+
+ aimbs_put8(&fr->data, strlen(sn));
+ aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn));
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
diff --git a/protocols/oscar/buddylist.h b/protocols/oscar/buddylist.h
new file mode 100644
index 00000000..9a325279
--- /dev/null
+++ b/protocols/oscar/buddylist.h
@@ -0,0 +1,23 @@
+#ifndef __OSCAR_BUDDYLIST_H__
+#define __OSCAR_BUDDYLIST_H__
+
+#define AIM_CB_FAM_BUD 0x0003
+
+/*
+ * SNAC Family: Buddy List Management Services.
+ */
+#define AIM_CB_BUD_ERROR 0x0001
+#define AIM_CB_BUD_REQRIGHTS 0x0002
+#define AIM_CB_BUD_RIGHTSINFO 0x0003
+#define AIM_CB_BUD_ADDBUDDY 0x0004
+#define AIM_CB_BUD_REMBUDDY 0x0005
+#define AIM_CB_BUD_REJECT 0x000a
+#define AIM_CB_BUD_ONCOMING 0x000b
+#define AIM_CB_BUD_OFFGOING 0x000c
+#define AIM_CB_BUD_DEFAULT 0xffff
+
+/* aim_buddylist.c */
+int aim_add_buddy(aim_session_t *, aim_conn_t *, const char *);
+int aim_remove_buddy(aim_session_t *, aim_conn_t *, const char *);
+
+#endif /* __OSCAR_BUDDYLIST_H__ */
diff --git a/protocols/oscar/chat.c b/protocols/oscar/chat.c
new file mode 100644
index 00000000..fbf45693
--- /dev/null
+++ b/protocols/oscar/chat.c
@@ -0,0 +1,702 @@
+/*
+ * aim_chat.c
+ *
+ * Routines for the Chat service.
+ *
+ */
+
+#include <aim.h>
+#include <glib.h>
+#include "info.h"
+
+/* Stored in the ->priv of chat connections */
+struct chatconnpriv {
+ guint16 exchange;
+ char *name;
+ guint16 instance;
+};
+
+void aim_conn_kill_chat(aim_session_t *sess, aim_conn_t *conn)
+{
+ struct chatconnpriv *ccp = (struct chatconnpriv *)conn->priv;
+
+ if (ccp)
+ g_free(ccp->name);
+ g_free(ccp);
+
+ return;
+}
+
+char *aim_chat_getname(aim_conn_t *conn)
+{
+ struct chatconnpriv *ccp;
+
+ if (!conn)
+ return NULL;
+
+ if (conn->type != AIM_CONN_TYPE_CHAT)
+ return NULL;
+
+ ccp = (struct chatconnpriv *)conn->priv;
+
+ return ccp->name;
+}
+
+/* XXX get this into conn.c -- evil!! */
+aim_conn_t *aim_chat_getconn(aim_session_t *sess, const char *name)
+{
+ aim_conn_t *cur;
+
+ for (cur = sess->connlist; cur; cur = cur->next) {
+ struct chatconnpriv *ccp = (struct chatconnpriv *)cur->priv;
+
+ if (cur->type != AIM_CONN_TYPE_CHAT)
+ continue;
+ if (!cur->priv) {
+ imcb_error(sess->aux_data, "chat connection with no name!");
+ continue;
+ }
+
+ if (strcmp(ccp->name, name) == 0)
+ break;
+ }
+
+ return cur;
+}
+
+int aim_chat_attachname(aim_conn_t *conn, guint16 exchange, const char *roomname, guint16 instance)
+{
+ struct chatconnpriv *ccp;
+
+ if (!conn || !roomname)
+ return -EINVAL;
+
+ if (conn->priv)
+ g_free(conn->priv);
+
+ if (!(ccp = g_malloc(sizeof(struct chatconnpriv))))
+ return -ENOMEM;
+
+ ccp->exchange = exchange;
+ ccp->name = g_strdup(roomname);
+ ccp->instance = instance;
+
+ conn->priv = (void *)ccp;
+
+ return 0;
+}
+
+/*
+ * Send a Chat Message.
+ *
+ * Possible flags:
+ * AIM_CHATFLAGS_NOREFLECT -- Unset the flag that requests messages
+ * should be sent to their sender.
+ * AIM_CHATFLAGS_AWAY -- Mark the message as an autoresponse
+ * (Note that WinAIM does not honor this,
+ * and displays the message as normal.)
+ *
+ * XXX convert this to use tlvchains
+ */
+int aim_chat_send_im(aim_session_t *sess, aim_conn_t *conn, guint16 flags, const char *msg, int msglen)
+{
+ int i;
+ aim_frame_t *fr;
+ aim_msgcookie_t *cookie;
+ aim_snacid_t snacid;
+ guint8 ckstr[8];
+ aim_tlvlist_t *otl = NULL, *itl = NULL;
+
+ if (!sess || !conn || !msg || (msglen <= 0))
+ return 0;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x000e, 0x0005, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x000e, 0x0005, 0x0000, snacid);
+
+
+ /*
+ * Generate a random message cookie.
+ *
+ * XXX mkcookie should generate the cookie and cache it in one
+ * operation to preserve uniqueness.
+ *
+ */
+ for (i = 0; i < sizeof(ckstr); i++)
+ aimutil_put8(ckstr+i, (guint8) rand());
+
+ cookie = aim_mkcookie(ckstr, AIM_COOKIETYPE_CHAT, NULL);
+ cookie->data = NULL; /* XXX store something useful here */
+
+ aim_cachecookie(sess, cookie);
+
+ for (i = 0; i < sizeof(ckstr); i++)
+ aimbs_put8(&fr->data, ckstr[i]);
+
+
+ /*
+ * Channel ID.
+ */
+ aimbs_put16(&fr->data, 0x0003);
+
+
+ /*
+ * Type 1: Flag meaning this message is destined to the room.
+ */
+ aim_addtlvtochain_noval(&otl, 0x0001);
+
+ /*
+ * Type 6: Reflect
+ */
+ if (!(flags & AIM_CHATFLAGS_NOREFLECT))
+ aim_addtlvtochain_noval(&otl, 0x0006);
+
+ /*
+ * Type 7: Autoresponse
+ */
+ if (flags & AIM_CHATFLAGS_AWAY)
+ aim_addtlvtochain_noval(&otl, 0x0007);
+
+ /* [WvG] This wasn't there originally, but we really should send
+ the right charset flags, as we also do with normal
+ messages. Hope this will work. :-) */
+ /*
+ if (flags & AIM_CHATFLAGS_UNICODE)
+ aimbs_put16(&fr->data, 0x0002);
+ else if (flags & AIM_CHATFLAGS_ISO_8859_1)
+ aimbs_put16(&fr->data, 0x0003);
+ else
+ aimbs_put16(&fr->data, 0x0000);
+
+ aimbs_put16(&fr->data, 0x0000);
+ */
+
+ /*
+ * SubTLV: Type 1: Message
+ */
+ aim_addtlvtochain_raw(&itl, 0x0001, strlen(msg), (guint8 *)msg);
+
+ /*
+ * Type 5: Message block. Contains more TLVs.
+ *
+ * This could include other information... We just
+ * put in a message TLV however.
+ *
+ */
+ aim_addtlvtochain_frozentlvlist(&otl, 0x0005, &itl);
+
+ aim_writetlvchain(&fr->data, &otl);
+
+ aim_freetlvchain(&itl);
+ aim_freetlvchain(&otl);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * Join a room of name roomname. This is the first step to joining an
+ * already created room. It's basically a Service Request for
+ * family 0x000e, with a little added on to specify the exchange and room
+ * name.
+ */
+int aim_chat_join(aim_session_t *sess, aim_conn_t *conn, guint16 exchange, const char *roomname, guint16 instance)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ aim_tlvlist_t *tl = NULL;
+ struct chatsnacinfo csi;
+
+ if (!sess || !conn || !roomname || !strlen(roomname))
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512)))
+ return -ENOMEM;
+
+ memset(&csi, 0, sizeof(csi));
+ csi.exchange = exchange;
+ strncpy(csi.name, roomname, sizeof(csi.name));
+ csi.instance = instance;
+
+ snacid = aim_cachesnac(sess, 0x0001, 0x0004, 0x0000, &csi, sizeof(csi));
+ aim_putsnac(&fr->data, 0x0001, 0x0004, 0x0000, snacid);
+
+ /*
+ * Requesting service chat (0x000e)
+ */
+ aimbs_put16(&fr->data, 0x000e);
+
+ aim_addtlvtochain_chatroom(&tl, 0x0001, exchange, roomname, instance);
+ aim_writetlvchain(&fr->data, &tl);
+ aim_freetlvchain(&tl);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+int aim_chat_readroominfo(aim_bstream_t *bs, struct aim_chat_roominfo *outinfo)
+{
+ int namelen;
+
+ if (!bs || !outinfo)
+ return 0;
+
+ outinfo->exchange = aimbs_get16(bs);
+ namelen = aimbs_get8(bs);
+ outinfo->name = aimbs_getstr(bs, namelen);
+ outinfo->instance = aimbs_get16(bs);
+
+ return 0;
+}
+
+int aim_chat_leaveroom(aim_session_t *sess, const char *name)
+{
+ aim_conn_t *conn;
+
+ if (!(conn = aim_chat_getconn(sess, name)))
+ return -ENOENT;
+
+ aim_conn_close(conn);
+
+ return 0;
+}
+
+/*
+ * conn must be a BOS connection!
+ */
+int aim_chat_invite(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *msg, guint16 exchange, const char *roomname, guint16 instance)
+{
+ int i;
+ aim_frame_t *fr;
+ aim_msgcookie_t *cookie;
+ struct aim_invite_priv *priv;
+ guint8 ckstr[8];
+ aim_snacid_t snacid;
+ aim_tlvlist_t *otl = NULL, *itl = NULL;
+ guint8 *hdr;
+ int hdrlen;
+ aim_bstream_t hdrbs;
+
+ if (!sess || !conn || !sn || !msg || !roomname)
+ return -EINVAL;
+
+ if (conn->type != AIM_CONN_TYPE_BOS)
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152+strlen(sn)+strlen(roomname)+strlen(msg))))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, sn, strlen(sn)+1);
+ aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
+
+
+ /*
+ * Cookie
+ */
+ for (i = 0; i < sizeof(ckstr); i++)
+ aimutil_put8(ckstr, (guint8) rand());
+
+ /* XXX should be uncached by an unwritten 'invite accept' handler */
+ if ((priv = g_malloc(sizeof(struct aim_invite_priv)))) {
+ priv->sn = g_strdup(sn);
+ priv->roomname = g_strdup(roomname);
+ priv->exchange = exchange;
+ priv->instance = instance;
+ }
+
+ if ((cookie = aim_mkcookie(ckstr, AIM_COOKIETYPE_INVITE, priv)))
+ aim_cachecookie(sess, cookie);
+ else
+ g_free(priv);
+
+ for (i = 0; i < sizeof(ckstr); i++)
+ aimbs_put8(&fr->data, ckstr[i]);
+
+
+ /*
+ * Channel (2)
+ */
+ aimbs_put16(&fr->data, 0x0002);
+
+ /*
+ * Dest sn
+ */
+ aimbs_put8(&fr->data, strlen(sn));
+ aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn));
+
+ /*
+ * TLV t(0005)
+ *
+ * Everything else is inside this TLV.
+ *
+ * Sigh. AOL was rather inconsistent right here. So we have
+ * to play some minor tricks. Right inside the type 5 is some
+ * raw data, followed by a series of TLVs.
+ *
+ */
+ hdrlen = 2+8+16+6+4+4+strlen(msg)+4+2+1+strlen(roomname)+2;
+ hdr = g_malloc(hdrlen);
+ aim_bstream_init(&hdrbs, hdr, hdrlen);
+
+ aimbs_put16(&hdrbs, 0x0000); /* Unknown! */
+ aimbs_putraw(&hdrbs, ckstr, sizeof(ckstr)); /* I think... */
+ aim_putcap(&hdrbs, AIM_CAPS_CHAT);
+
+ aim_addtlvtochain16(&itl, 0x000a, 0x0001);
+ aim_addtlvtochain_noval(&itl, 0x000f);
+ aim_addtlvtochain_raw(&itl, 0x000c, strlen(msg), (guint8 *)msg);
+ aim_addtlvtochain_chatroom(&itl, 0x2711, exchange, roomname, instance);
+ aim_writetlvchain(&hdrbs, &itl);
+
+ aim_addtlvtochain_raw(&otl, 0x0005, aim_bstream_curpos(&hdrbs), hdr);
+
+ aim_writetlvchain(&fr->data, &otl);
+
+ g_free(hdr);
+ aim_freetlvchain(&itl);
+ aim_freetlvchain(&otl);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * General room information. Lots of stuff.
+ *
+ * Values I know are in here but I havent attached
+ * them to any of the 'Unknown's:
+ * - Language (English)
+ *
+ * SNAC 000e/0002
+ */
+static int infoupdate(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_userinfo_t *userinfo = NULL;
+ aim_rxcallback_t userfunc;
+ int ret = 0;
+ int usercount = 0;
+ guint8 detaillevel = 0;
+ char *roomname = NULL;
+ struct aim_chat_roominfo roominfo;
+ guint16 tlvcount = 0;
+ aim_tlvlist_t *tlvlist;
+ char *roomdesc = NULL;
+ guint16 flags = 0;
+ guint32 creationtime = 0;
+ guint16 maxmsglen = 0, maxvisiblemsglen = 0;
+ guint16 unknown_d2 = 0, unknown_d5 = 0;
+
+ aim_chat_readroominfo(bs, &roominfo);
+
+ detaillevel = aimbs_get8(bs);
+
+ if (detaillevel != 0x02) {
+ imcb_error(sess->aux_data, "Only detaillevel 0x2 is support at the moment");
+ return 1;
+ }
+
+ tlvcount = aimbs_get16(bs);
+
+ /*
+ * Everything else are TLVs.
+ */
+ tlvlist = aim_readtlvchain(bs);
+
+ /*
+ * TLV type 0x006a is the room name in Human Readable Form.
+ */
+ if (aim_gettlv(tlvlist, 0x006a, 1))
+ roomname = aim_gettlv_str(tlvlist, 0x006a, 1);
+
+ /*
+ * Type 0x006f: Number of occupants.
+ */
+ if (aim_gettlv(tlvlist, 0x006f, 1))
+ usercount = aim_gettlv16(tlvlist, 0x006f, 1);
+
+ /*
+ * Type 0x0073: Occupant list.
+ */
+ if (aim_gettlv(tlvlist, 0x0073, 1)) {
+ int curoccupant = 0;
+ aim_tlv_t *tmptlv;
+ aim_bstream_t occbs;
+
+ tmptlv = aim_gettlv(tlvlist, 0x0073, 1);
+
+ /* Allocate enough userinfo structs for all occupants */
+ userinfo = g_new0(aim_userinfo_t, usercount);
+
+ aim_bstream_init(&occbs, tmptlv->value, tmptlv->length);
+
+ while (curoccupant < usercount)
+ aim_extractuserinfo(sess, &occbs, &userinfo[curoccupant++]);
+ }
+
+ /*
+ * Type 0x00c9: Flags. (AIM_CHATROOM_FLAG)
+ */
+ if (aim_gettlv(tlvlist, 0x00c9, 1))
+ flags = aim_gettlv16(tlvlist, 0x00c9, 1);
+
+ /*
+ * Type 0x00ca: Creation time (4 bytes)
+ */
+ if (aim_gettlv(tlvlist, 0x00ca, 1))
+ creationtime = aim_gettlv32(tlvlist, 0x00ca, 1);
+
+ /*
+ * Type 0x00d1: Maximum Message Length
+ */
+ if (aim_gettlv(tlvlist, 0x00d1, 1))
+ maxmsglen = aim_gettlv16(tlvlist, 0x00d1, 1);
+
+ /*
+ * Type 0x00d2: Unknown. (2 bytes)
+ */
+ if (aim_gettlv(tlvlist, 0x00d2, 1))
+ unknown_d2 = aim_gettlv16(tlvlist, 0x00d2, 1);
+
+ /*
+ * Type 0x00d3: Room Description
+ */
+ if (aim_gettlv(tlvlist, 0x00d3, 1))
+ roomdesc = aim_gettlv_str(tlvlist, 0x00d3, 1);
+
+ /*
+ * Type 0x000d4: Unknown (flag only)
+ */
+ if (aim_gettlv(tlvlist, 0x000d4, 1))
+ ;
+
+ /*
+ * Type 0x00d5: Unknown. (1 byte)
+ */
+ if (aim_gettlv(tlvlist, 0x00d5, 1))
+ unknown_d5 = aim_gettlv8(tlvlist, 0x00d5, 1);
+
+
+ /*
+ * Type 0x00d6: Encoding 1 ("us-ascii")
+ */
+ if (aim_gettlv(tlvlist, 0x000d6, 1))
+ ;
+
+ /*
+ * Type 0x00d7: Language 1 ("en")
+ */
+ if (aim_gettlv(tlvlist, 0x000d7, 1))
+ ;
+
+ /*
+ * Type 0x00d8: Encoding 2 ("us-ascii")
+ */
+ if (aim_gettlv(tlvlist, 0x000d8, 1))
+ ;
+
+ /*
+ * Type 0x00d9: Language 2 ("en")
+ */
+ if (aim_gettlv(tlvlist, 0x000d9, 1))
+ ;
+
+ /*
+ * Type 0x00da: Maximum visible message length
+ */
+ if (aim_gettlv(tlvlist, 0x000da, 1))
+ maxvisiblemsglen = aim_gettlv16(tlvlist, 0x00da, 1);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) {
+ ret = userfunc(sess,
+ rx,
+ &roominfo,
+ roomname,
+ usercount,
+ userinfo,
+ roomdesc,
+ flags,
+ creationtime,
+ maxmsglen,
+ unknown_d2,
+ unknown_d5,
+ maxvisiblemsglen);
+ }
+
+ g_free(roominfo.name);
+ g_free(userinfo);
+ g_free(roomname);
+ g_free(roomdesc);
+ aim_freetlvchain(&tlvlist);
+
+ return ret;
+}
+
+static int userlistchange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_userinfo_t *userinfo = NULL;
+ aim_rxcallback_t userfunc;
+ int curcount = 0, ret = 0;
+
+ while (aim_bstream_empty(bs)) {
+ curcount++;
+ userinfo = g_realloc(userinfo, curcount * sizeof(aim_userinfo_t));
+ aim_extractuserinfo(sess, bs, &userinfo[curcount-1]);
+ }
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, curcount, userinfo);
+
+ g_free(userinfo);
+
+ return ret;
+}
+
+/*
+ * We could probably include this in the normal ICBM parsing
+ * code as channel 0x0003, however, since only the start
+ * would be the same, we might as well do it here.
+ *
+ * General outline of this SNAC:
+ * snac
+ * cookie
+ * channel id
+ * tlvlist
+ * unknown
+ * source user info
+ * name
+ * evility
+ * userinfo tlvs
+ * online time
+ * etc
+ * message metatlv
+ * message tlv
+ * message string
+ * possibly others
+ *
+ */
+static int incomingmsg(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_userinfo_t userinfo;
+ aim_rxcallback_t userfunc;
+ int ret = 0;
+ guint8 *cookie;
+ guint16 channel;
+ aim_tlvlist_t *otl;
+ char *msg = NULL;
+ aim_msgcookie_t *ck;
+
+ memset(&userinfo, 0, sizeof(aim_userinfo_t));
+
+ /*
+ * ICBM Cookie. Uncache it.
+ */
+ cookie = aimbs_getraw(bs, 8);
+
+ if ((ck = aim_uncachecookie(sess, cookie, AIM_COOKIETYPE_CHAT))) {
+ g_free(ck->data);
+ g_free(ck);
+ }
+
+ /*
+ * Channel ID
+ *
+ * Channels 1 and 2 are implemented in the normal ICBM
+ * parser.
+ *
+ * We only do channel 3 here.
+ *
+ */
+ channel = aimbs_get16(bs);
+
+ if (channel != 0x0003) {
+ imcb_error(sess->aux_data, "unknown channel!");
+ return 0;
+ }
+
+ /*
+ * Start parsing TLVs right away.
+ */
+ otl = aim_readtlvchain(bs);
+
+ /*
+ * Type 0x0003: Source User Information
+ */
+ if (aim_gettlv(otl, 0x0003, 1)) {
+ aim_tlv_t *userinfotlv;
+ aim_bstream_t tbs;
+
+ userinfotlv = aim_gettlv(otl, 0x0003, 1);
+
+ aim_bstream_init(&tbs, userinfotlv->value, userinfotlv->length);
+ aim_extractuserinfo(sess, &tbs, &userinfo);
+ }
+
+ /*
+ * Type 0x0001: If present, it means it was a message to the
+ * room (as opposed to a whisper).
+ */
+ if (aim_gettlv(otl, 0x0001, 1))
+ ;
+
+ /*
+ * Type 0x0005: Message Block. Conains more TLVs.
+ */
+ if (aim_gettlv(otl, 0x0005, 1)) {
+ aim_tlvlist_t *itl;
+ aim_tlv_t *msgblock;
+ aim_bstream_t tbs;
+
+ msgblock = aim_gettlv(otl, 0x0005, 1);
+ aim_bstream_init(&tbs, msgblock->value, msgblock->length);
+ itl = aim_readtlvchain(&tbs);
+
+ /*
+ * Type 0x0001: Message.
+ */
+ if (aim_gettlv(itl, 0x0001, 1))
+ msg = aim_gettlv_str(itl, 0x0001, 1);
+
+ aim_freetlvchain(&itl);
+ }
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, &userinfo, msg);
+
+ g_free(cookie);
+ g_free(msg);
+ aim_freetlvchain(&otl);
+
+ return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+ if (snac->subtype == 0x0002)
+ return infoupdate(sess, mod, rx, snac, bs);
+ else if ((snac->subtype == 0x0003) || (snac->subtype == 0x0004))
+ return userlistchange(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x0006)
+ return incomingmsg(sess, mod, rx, snac, bs);
+
+ return 0;
+}
+
+int chat_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+ mod->family = 0x000e;
+ mod->version = 0x0001;
+ mod->toolid = 0x0010;
+ mod->toolversion = 0x0629;
+ mod->flags = 0;
+ strncpy(mod->name, "chat", sizeof(mod->name));
+ mod->snachandler = snachandler;
+
+ return 0;
+}
diff --git a/protocols/oscar/chat.h b/protocols/oscar/chat.h
new file mode 100644
index 00000000..6b360326
--- /dev/null
+++ b/protocols/oscar/chat.h
@@ -0,0 +1,17 @@
+#ifndef __OSCAR_CHAT_H__
+#define __OSCAR_CHAT_H__
+
+#define AIM_CB_FAM_CHT 0x000e /* Chat */
+
+/*
+ * SNAC Family: Chat Services
+ */
+#define AIM_CB_CHT_ERROR 0x0001
+#define AIM_CB_CHT_ROOMINFOUPDATE 0x0002
+#define AIM_CB_CHT_USERJOIN 0x0003
+#define AIM_CB_CHT_USERLEAVE 0x0004
+#define AIM_CB_CHT_OUTGOINGMSG 0x0005
+#define AIM_CB_CHT_INCOMINGMSG 0x0006
+#define AIM_CB_CHT_DEFAULT 0xffff
+
+#endif /* __OSCAR_CHAT_H__ */
diff --git a/protocols/oscar/chatnav.c b/protocols/oscar/chatnav.c
new file mode 100644
index 00000000..7cfc52af
--- /dev/null
+++ b/protocols/oscar/chatnav.c
@@ -0,0 +1,421 @@
+/*
+ * Handle ChatNav.
+ *
+ * [The ChatNav(igation) service does various things to keep chat
+ * alive. It provides room information, room searching and creating,
+ * as well as giving users the right ("permission") to use chat.]
+ *
+ */
+
+#include <aim.h>
+#include "chatnav.h"
+
+/*
+ * conn must be a chatnav connection!
+ */
+int aim_chatnav_reqrights(aim_session_t *sess, aim_conn_t *conn)
+{
+ return aim_genericreq_n_snacid(sess, conn, 0x000d, 0x0002);
+}
+
+int aim_chatnav_createroom(aim_session_t *sess, aim_conn_t *conn, const char *name, guint16 exchange)
+{
+ static const char ck[] = {"create"};
+ static const char lang[] = {"en"};
+ static const char charset[] = {"us-ascii"};
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ aim_tlvlist_t *tl = NULL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x000d, 0x0008, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x000d, 0x0008, 0x0000, snacid);
+
+ /* exchange */
+ aimbs_put16(&fr->data, exchange);
+
+ /*
+ * This looks to be a big hack. You'll note that this entire
+ * SNAC is just a room info structure, but the hard room name,
+ * here, is set to "create".
+ *
+ * Either this goes on the "list of questions concerning
+ * why-the-hell-did-you-do-that", or this value is completly
+ * ignored. Without experimental evidence, but a good knowledge of
+ * AOL style, I'm going to guess that it is the latter, and that
+ * the value of the room name in create requests is ignored.
+ */
+ aimbs_put8(&fr->data, strlen(ck));
+ aimbs_putraw(&fr->data, (guint8 *)ck, strlen(ck));
+
+ /*
+ * instance
+ *
+ * Setting this to 0xffff apparently assigns the last instance.
+ *
+ */
+ aimbs_put16(&fr->data, 0xffff);
+
+ /* detail level */
+ aimbs_put8(&fr->data, 0x01);
+
+ aim_addtlvtochain_raw(&tl, 0x00d3, strlen(name), (guint8 *)name);
+ aim_addtlvtochain_raw(&tl, 0x00d6, strlen(charset), (guint8 *)charset);
+ aim_addtlvtochain_raw(&tl, 0x00d7, strlen(lang), (guint8 *)lang);
+
+ /* tlvcount */
+ aimbs_put16(&fr->data, aim_counttlvchain(&tl));
+ aim_writetlvchain(&fr->data, &tl);
+
+ aim_freetlvchain(&tl);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+static int parseinfo_perms(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs, aim_snac_t *snac2)
+{
+ aim_rxcallback_t userfunc;
+ int ret = 0;
+ struct aim_chat_exchangeinfo *exchanges = NULL;
+ int curexchange;
+ aim_tlv_t *exchangetlv;
+ guint8 maxrooms = 0;
+ aim_tlvlist_t *tlvlist, *innerlist;
+
+ tlvlist = aim_readtlvchain(bs);
+
+ /*
+ * Type 0x0002: Maximum concurrent rooms.
+ */
+ if (aim_gettlv(tlvlist, 0x0002, 1))
+ maxrooms = aim_gettlv8(tlvlist, 0x0002, 1);
+
+ /*
+ * Type 0x0003: Exchange information
+ *
+ * There can be any number of these, each one
+ * representing another exchange.
+ *
+ */
+ for (curexchange = 0; ((exchangetlv = aim_gettlv(tlvlist, 0x0003, curexchange+1))); ) {
+ aim_bstream_t tbs;
+
+ aim_bstream_init(&tbs, exchangetlv->value, exchangetlv->length);
+
+ curexchange++;
+
+ exchanges = g_realloc(exchanges, curexchange * sizeof(struct aim_chat_exchangeinfo));
+
+ /* exchange number */
+ exchanges[curexchange-1].number = aimbs_get16(&tbs);
+ innerlist = aim_readtlvchain(&tbs);
+
+ /*
+ * Type 0x000a: Unknown.
+ *
+ * Usually three bytes: 0x0114 (exchange 1) or 0x010f (others).
+ *
+ */
+ if (aim_gettlv(innerlist, 0x000a, 1))
+ ;
+
+ /*
+ * Type 0x000d: Unknown.
+ */
+ if (aim_gettlv(innerlist, 0x000d, 1))
+ ;
+
+ /*
+ * Type 0x0004: Unknown
+ */
+ if (aim_gettlv(innerlist, 0x0004, 1))
+ ;
+
+ /*
+ * Type 0x0002: Unknown
+ */
+ if (aim_gettlv(innerlist, 0x0002, 1)) {
+ guint16 classperms;
+
+ classperms = aim_gettlv16(innerlist, 0x0002, 1);
+
+ }
+
+ /*
+ * Type 0x00c9: Flags
+ *
+ * 1 Evilable
+ * 2 Nav Only
+ * 4 Instancing Allowed
+ * 8 Occupant Peek Allowed
+ *
+ */
+ if (aim_gettlv(innerlist, 0x00c9, 1))
+ exchanges[curexchange-1].flags = aim_gettlv16(innerlist, 0x00c9, 1);
+
+ /*
+ * Type 0x00ca: Creation Date
+ */
+ if (aim_gettlv(innerlist, 0x00ca, 1))
+ ;
+
+ /*
+ * Type 0x00d0: Mandatory Channels?
+ */
+ if (aim_gettlv(innerlist, 0x00d0, 1))
+ ;
+
+ /*
+ * Type 0x00d1: Maximum Message length
+ */
+ if (aim_gettlv(innerlist, 0x00d1, 1))
+ ;
+
+ /*
+ * Type 0x00d2: Maximum Occupancy?
+ */
+ if (aim_gettlv(innerlist, 0x00d2, 1))
+ ;
+
+ /*
+ * Type 0x00d3: Exchange Description
+ */
+ if (aim_gettlv(innerlist, 0x00d3, 1))
+ exchanges[curexchange-1].name = aim_gettlv_str(innerlist, 0x00d3, 1);
+ else
+ exchanges[curexchange-1].name = NULL;
+
+ /*
+ * Type 0x00d4: Exchange Description URL
+ */
+ if (aim_gettlv(innerlist, 0x00d4, 1))
+ ;
+
+ /*
+ * Type 0x00d5: Creation Permissions
+ *
+ * 0 Creation not allowed
+ * 1 Room creation allowed
+ * 2 Exchange creation allowed
+ *
+ */
+ if (aim_gettlv(innerlist, 0x00d5, 1)) {
+ guint8 createperms;
+
+ createperms = aim_gettlv8(innerlist, 0x00d5, 1);
+ }
+
+ /*
+ * Type 0x00d6: Character Set (First Time)
+ */
+ if (aim_gettlv(innerlist, 0x00d6, 1))
+ exchanges[curexchange-1].charset1 = aim_gettlv_str(innerlist, 0x00d6, 1);
+ else
+ exchanges[curexchange-1].charset1 = NULL;
+
+ /*
+ * Type 0x00d7: Language (First Time)
+ */
+ if (aim_gettlv(innerlist, 0x00d7, 1))
+ exchanges[curexchange-1].lang1 = aim_gettlv_str(innerlist, 0x00d7, 1);
+ else
+ exchanges[curexchange-1].lang1 = NULL;
+
+ /*
+ * Type 0x00d8: Character Set (Second Time)
+ */
+ if (aim_gettlv(innerlist, 0x00d8, 1))
+ exchanges[curexchange-1].charset2 = aim_gettlv_str(innerlist, 0x00d8, 1);
+ else
+ exchanges[curexchange-1].charset2 = NULL;
+
+ /*
+ * Type 0x00d9: Language (Second Time)
+ */
+ if (aim_gettlv(innerlist, 0x00d9, 1))
+ exchanges[curexchange-1].lang2 = aim_gettlv_str(innerlist, 0x00d9, 1);
+ else
+ exchanges[curexchange-1].lang2 = NULL;
+
+ /*
+ * Type 0x00da: Unknown
+ */
+ if (aim_gettlv(innerlist, 0x00da, 1))
+ ;
+
+ aim_freetlvchain(&innerlist);
+ }
+
+ /*
+ * Call client.
+ */
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, snac2->type, maxrooms, curexchange, exchanges);
+
+ for (curexchange--; curexchange >= 0; curexchange--) {
+ g_free(exchanges[curexchange].name);
+ g_free(exchanges[curexchange].charset1);
+ g_free(exchanges[curexchange].lang1);
+ g_free(exchanges[curexchange].charset2);
+ g_free(exchanges[curexchange].lang2);
+ }
+ g_free(exchanges);
+ aim_freetlvchain(&tlvlist);
+
+ return ret;
+}
+
+static int parseinfo_create(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs, aim_snac_t *snac2)
+{
+ aim_rxcallback_t userfunc;
+ aim_tlvlist_t *tlvlist, *innerlist;
+ char *ck = NULL, *fqcn = NULL, *name = NULL;
+ guint16 exchange = 0, instance = 0, unknown = 0, flags = 0, maxmsglen = 0, maxoccupancy = 0;
+ guint32 createtime = 0;
+ guint8 createperms = 0, detaillevel;
+ int cklen;
+ aim_tlv_t *bigblock;
+ int ret = 0;
+ aim_bstream_t bbbs;
+
+ tlvlist = aim_readtlvchain(bs);
+
+ if (!(bigblock = aim_gettlv(tlvlist, 0x0004, 1))) {
+ imcb_error(sess->aux_data, "no bigblock in top tlv in create room response");
+
+ aim_freetlvchain(&tlvlist);
+ return 0;
+ }
+
+ aim_bstream_init(&bbbs, bigblock->value, bigblock->length);
+
+ exchange = aimbs_get16(&bbbs);
+ cklen = aimbs_get8(&bbbs);
+ ck = aimbs_getstr(&bbbs, cklen);
+ instance = aimbs_get16(&bbbs);
+ detaillevel = aimbs_get8(&bbbs);
+
+ if (detaillevel != 0x02) {
+ imcb_error(sess->aux_data, "unknown detaillevel in create room response");
+ aim_freetlvchain(&tlvlist);
+ g_free(ck);
+ return 0;
+ }
+
+ unknown = aimbs_get16(&bbbs);
+
+ innerlist = aim_readtlvchain(&bbbs);
+
+ if (aim_gettlv(innerlist, 0x006a, 1))
+ fqcn = aim_gettlv_str(innerlist, 0x006a, 1);
+
+ if (aim_gettlv(innerlist, 0x00c9, 1))
+ flags = aim_gettlv16(innerlist, 0x00c9, 1);
+
+ if (aim_gettlv(innerlist, 0x00ca, 1))
+ createtime = aim_gettlv32(innerlist, 0x00ca, 1);
+
+ if (aim_gettlv(innerlist, 0x00d1, 1))
+ maxmsglen = aim_gettlv16(innerlist, 0x00d1, 1);
+
+ if (aim_gettlv(innerlist, 0x00d2, 1))
+ maxoccupancy = aim_gettlv16(innerlist, 0x00d2, 1);
+
+ if (aim_gettlv(innerlist, 0x00d3, 1))
+ name = aim_gettlv_str(innerlist, 0x00d3, 1);
+
+ if (aim_gettlv(innerlist, 0x00d5, 1))
+ createperms = aim_gettlv8(innerlist, 0x00d5, 1);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) {
+ ret = userfunc(sess, rx, snac2->type, fqcn, instance, exchange, flags, createtime, maxmsglen, maxoccupancy, createperms, unknown, name, ck);
+ }
+
+ g_free(ck);
+ g_free(name);
+ g_free(fqcn);
+ aim_freetlvchain(&innerlist);
+ aim_freetlvchain(&tlvlist);
+
+ return ret;
+}
+
+/*
+ * Since multiple things can trigger this callback, we must lookup the
+ * snacid to determine the original snac subtype that was called.
+ *
+ * XXX This isn't really how this works. But this is: Every d/9 response
+ * has a 16bit value at the beginning. That matches to:
+ * Short Desc = 1
+ * Full Desc = 2
+ * Instance Info = 4
+ * Nav Short Desc = 8
+ * Nav Instance Info = 16
+ * And then everything is really asynchronous. There is no specific
+ * attachment of a response to a create room request, for example. Creating
+ * the room yields no different a response than requesting the room's info.
+ *
+ */
+static int parseinfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_snac_t *snac2;
+ int ret = 0;
+
+ if (!(snac2 = aim_remsnac(sess, snac->id))) {
+ imcb_error(sess->aux_data, "received response to unknown request!");
+ return 0;
+ }
+
+ if (snac2->family != 0x000d) {
+ imcb_error(sess->aux_data, "received response that maps to corrupt request!");
+ return 0;
+ }
+
+ /*
+ * We now know what the original SNAC subtype was.
+ */
+ if (snac2->type == 0x0002) /* request chat rights */
+ ret = parseinfo_perms(sess, mod, rx, snac, bs, snac2);
+ else if (snac2->type == 0x0003) {} /* request exchange info */
+ else if (snac2->type == 0x0004) {} /* request room info */
+ else if (snac2->type == 0x0005) {} /* request more room info */
+ else if (snac2->type == 0x0006) {} /* request occupant list */
+ else if (snac2->type == 0x0007) {} /* search for a room */
+ else if (snac2->type == 0x0008) /* create room */
+ ret = parseinfo_create(sess, mod, rx, snac, bs, snac2);
+ else
+ imcb_error(sess->aux_data, "unknown request subtype");
+
+ if (snac2)
+ g_free(snac2->data);
+ g_free(snac2);
+
+ return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+ if (snac->subtype == 0x0009)
+ return parseinfo(sess, mod, rx, snac, bs);
+
+ return 0;
+}
+
+int chatnav_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+ mod->family = 0x000d;
+ mod->version = 0x0003;
+ mod->toolid = 0x0010;
+ mod->toolversion = 0x0629;
+ mod->flags = 0;
+ strncpy(mod->name, "chatnav", sizeof(mod->name));
+ mod->snachandler = snachandler;
+
+ return 0;
+}
diff --git a/protocols/oscar/chatnav.h b/protocols/oscar/chatnav.h
new file mode 100644
index 00000000..285decad
--- /dev/null
+++ b/protocols/oscar/chatnav.h
@@ -0,0 +1,14 @@
+#ifndef __OSCAR_CHATNAV_H__
+#define __OSCAR_CHATNAV_H__
+
+#define AIM_CB_FAM_CTN 0x000d /* ChatNav */
+
+/*
+ * SNAC Family: Chat Navigation Services
+ */
+#define AIM_CB_CTN_ERROR 0x0001
+#define AIM_CB_CTN_CREATE 0x0008
+#define AIM_CB_CTN_INFO 0x0009
+#define AIM_CB_CTN_DEFAULT 0xffff
+
+#endif /* __OSCAR_CHATNAV_H__ */
diff --git a/protocols/oscar/conn.c b/protocols/oscar/conn.c
new file mode 100644
index 00000000..77ffd50f
--- /dev/null
+++ b/protocols/oscar/conn.c
@@ -0,0 +1,684 @@
+
+/*
+ * conn.c
+ *
+ * Does all this gloriously nifty connection handling stuff...
+ *
+ */
+
+#include <aim.h>
+#include "sock.h"
+
+static int aim_logoff(aim_session_t *sess);
+
+/*
+ * In OSCAR, every connection has a set of SNAC groups associated
+ * with it. These are the groups that you can send over this connection
+ * without being guarenteed a "Not supported" SNAC error.
+ *
+ * The grand theory of things says that these associations transcend
+ * what libfaim calls "connection types" (conn->type). You can probably
+ * see the elegance here, but since I want to revel in it for a bit, you
+ * get to hear it all spelled out.
+ *
+ * So let us say that you have your core BOS connection running. One
+ * of your modules has just given you a SNAC of the group 0x0004 to send
+ * you. Maybe an IM destined for some twit in Greenland. So you start
+ * at the top of your connection list, looking for a connection that
+ * claims to support group 0x0004. You find one. Why, that neat BOS
+ * connection of yours can do that. So you send it on its way.
+ *
+ * Now, say, that fellow from Greenland has friends and they all want to
+ * meet up with you in a lame chat room. This has landed you a SNAC
+ * in the family 0x000e and you have to admit you're a bit lost. You've
+ * searched your connection list for someone who wants to make your life
+ * easy and deliver this SNAC for you, but there isn't one there.
+ *
+ * Here comes the good bit. Without even letting anyone know, particularly
+ * the module that decided to send this SNAC, and definitly not that twit
+ * in Greenland, you send out a service request. In this request, you have
+ * marked the need for a connection supporting group 0x000e. A few seconds
+ * later, you receive a service redirect with an IP address and a cookie in
+ * it. Great, you say. Now I have something to do. Off you go, making
+ * that connection. One of the first things you get from this new server
+ * is a message saying that indeed it does support the group you were looking
+ * for. So you continue and send rate confirmation and all that.
+ *
+ * Then you remember you had that SNAC to send, and now you have a means to
+ * do it, and you do, and everyone is happy. Except the Greenlander, who is
+ * still stuck in the bitter cold.
+ *
+ * Oh, and this is useful for building the Migration SNACs, too. In the
+ * future, this may help convince me to implement rate limit mitigation
+ * for real. We'll see.
+ *
+ * Just to make me look better, I'll say that I've known about this great
+ * scheme for quite some time now. But I still haven't convinced myself
+ * to make libfaim work that way. It would take a fair amount of effort,
+ * and probably some client API changes as well. (Whenever I don't want
+ * to do something, I just say it would change the client API. Then I
+ * instantly have a couple of supporters of not doing it.)
+ *
+ * Generally, addgroup is only called by the internal handling of the
+ * server ready SNAC. So if you want to do something before that, you'll
+ * have to be more creative. That is done rather early, though, so I don't
+ * think you have to worry about it. Unless you're me. I care deeply
+ * about such inane things.
+ *
+ */
+void aim_conn_addgroup(aim_conn_t *conn, guint16 group)
+{
+ aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside;
+ struct snacgroup *sg;
+
+ if (!(sg = g_malloc(sizeof(struct snacgroup))))
+ return;
+
+ sg->group = group;
+
+ sg->next = ins->groups;
+ ins->groups = sg;
+
+ return;
+}
+
+aim_conn_t *aim_conn_findbygroup(aim_session_t *sess, guint16 group)
+{
+ aim_conn_t *cur;
+
+ for (cur = sess->connlist; cur; cur = cur->next) {
+ aim_conn_inside_t *ins = (aim_conn_inside_t *)cur->inside;
+ struct snacgroup *sg;
+
+ for (sg = ins->groups; sg; sg = sg->next) {
+ if (sg->group == group)
+ return cur;
+ }
+ }
+
+ return NULL;
+}
+
+static void connkill_snacgroups(struct snacgroup **head)
+{
+ struct snacgroup *sg;
+
+ for (sg = *head; sg; ) {
+ struct snacgroup *tmp;
+
+ tmp = sg->next;
+ g_free(sg);
+ sg = tmp;
+ }
+
+ *head = NULL;
+
+ return;
+}
+
+static void connkill_rates(struct rateclass **head)
+{
+ struct rateclass *rc;
+
+ for (rc = *head; rc; ) {
+ struct rateclass *tmp;
+ struct snacpair *sp;
+
+ tmp = rc->next;
+
+ for (sp = rc->members; sp; ) {
+ struct snacpair *tmpsp;
+
+ tmpsp = sp->next;
+ g_free(sp);
+ sp = tmpsp;
+ }
+ g_free(rc);
+
+ rc = tmp;
+ }
+
+ *head = NULL;
+
+ return;
+}
+
+static void connkill_real(aim_session_t *sess, aim_conn_t **deadconn)
+{
+
+ aim_rxqueue_cleanbyconn(sess, *deadconn);
+ aim_tx_cleanqueue(sess, *deadconn);
+
+ if ((*deadconn)->fd != -1)
+ aim_conn_close(*deadconn);
+
+ /*
+ * XXX ->priv should never be touched by the library. I know
+ * it used to be, but I'm getting rid of all that. Use
+ * ->internal instead.
+ */
+ if ((*deadconn)->priv)
+ g_free((*deadconn)->priv);
+
+ /*
+ * This will free ->internal if it necessary...
+ */
+ if ((*deadconn)->type == AIM_CONN_TYPE_CHAT)
+ aim_conn_kill_chat(sess, *deadconn);
+
+ if ((*deadconn)->inside) {
+ aim_conn_inside_t *inside = (aim_conn_inside_t *)(*deadconn)->inside;
+
+ connkill_snacgroups(&inside->groups);
+ connkill_rates(&inside->rates);
+
+ g_free(inside);
+ }
+
+ g_free(*deadconn);
+ *deadconn = NULL;
+
+ return;
+}
+
+/**
+ * aim_connrst - Clears out connection list, killing remaining connections.
+ * @sess: Session to be cleared
+ *
+ * Clears out the connection list and kills any connections left.
+ *
+ */
+static void aim_connrst(aim_session_t *sess)
+{
+
+ if (sess->connlist) {
+ aim_conn_t *cur = sess->connlist, *tmp;
+
+ while (cur) {
+ tmp = cur->next;
+ aim_conn_close(cur);
+ connkill_real(sess, &cur);
+ cur = tmp;
+ }
+ }
+
+ sess->connlist = NULL;
+
+ return;
+}
+
+/**
+ * aim_conn_init - Reset a connection to default values.
+ * @deadconn: Connection to be reset
+ *
+ * Initializes and/or resets a connection structure.
+ *
+ */
+static void aim_conn_init(aim_conn_t *deadconn)
+{
+
+ if (!deadconn)
+ return;
+
+ deadconn->fd = -1;
+ deadconn->subtype = -1;
+ deadconn->type = -1;
+ deadconn->seqnum = 0;
+ deadconn->lastactivity = 0;
+ deadconn->forcedlatency = 0;
+ deadconn->handlerlist = NULL;
+ deadconn->priv = NULL;
+ memset(deadconn->inside, 0, sizeof(aim_conn_inside_t));
+
+ return;
+}
+
+/**
+ * aim_conn_getnext - Gets a new connection structure.
+ * @sess: Session
+ *
+ * Allocate a new empty connection structure.
+ *
+ */
+static aim_conn_t *aim_conn_getnext(aim_session_t *sess)
+{
+ aim_conn_t *newconn;
+
+ if (!(newconn = g_new0(aim_conn_t,1)))
+ return NULL;
+
+ if (!(newconn->inside = g_new0(aim_conn_inside_t,1))) {
+ g_free(newconn);
+ return NULL;
+ }
+
+ aim_conn_init(newconn);
+
+ newconn->next = sess->connlist;
+ sess->connlist = newconn;
+
+ return newconn;
+}
+
+/**
+ * aim_conn_kill - Close and free a connection.
+ * @sess: Session for the connection
+ * @deadconn: Connection to be freed
+ *
+ * Close, clear, and free a connection structure. Should never be
+ * called from within libfaim.
+ *
+ */
+void aim_conn_kill(aim_session_t *sess, aim_conn_t **deadconn)
+{
+ aim_conn_t *cur, **prev;
+
+ if (!deadconn || !*deadconn)
+ return;
+
+ for (prev = &sess->connlist; (cur = *prev); ) {
+ if (cur == *deadconn) {
+ *prev = cur->next;
+ break;
+ }
+ prev = &cur->next;
+ }
+
+ if (!cur)
+ return; /* oops */
+
+ connkill_real(sess, &cur);
+
+ return;
+}
+
+/**
+ * aim_conn_close - Close a connection
+ * @deadconn: Connection to close
+ *
+ * Close (but not free) a connection.
+ *
+ * This leaves everything untouched except for clearing the
+ * handler list and setting the fd to -1 (used to recognize
+ * dead connections). It will also remove cookies if necessary.
+ *
+ */
+void aim_conn_close(aim_conn_t *deadconn)
+{
+
+ if (deadconn->fd >= 3)
+ closesocket(deadconn->fd);
+ deadconn->fd = -1;
+ if (deadconn->handlerlist)
+ aim_clearhandlers(deadconn);
+
+ return;
+}
+
+/**
+ * aim_getconn_type - Find a connection of a specific type
+ * @sess: Session to search
+ * @type: Type of connection to look for
+ *
+ * Searches for a connection of the specified type in the
+ * specified session. Returns the first connection of that
+ * type found.
+ *
+ * XXX except for RENDEZVOUS, all uses of this should be removed and
+ * use aim_conn_findbygroup() instead.
+ */
+aim_conn_t *aim_getconn_type(aim_session_t *sess, int type)
+{
+ aim_conn_t *cur;
+
+ for (cur = sess->connlist; cur; cur = cur->next) {
+ if ((cur->type == type) &&
+ !(cur->status & AIM_CONN_STATUS_INPROGRESS))
+ break;
+ }
+
+ return cur;
+}
+
+aim_conn_t *aim_getconn_type_all(aim_session_t *sess, int type)
+{
+ aim_conn_t *cur;
+
+ for (cur = sess->connlist; cur; cur = cur->next) {
+ if (cur->type == type)
+ break;
+ }
+
+ return cur;
+}
+
+/**
+ * aim_cloneconn - clone an aim_conn_t
+ * @sess: session containing parent
+ * @src: connection to clone
+ *
+ * A new connection is allocated, and the values are filled in
+ * appropriately. Note that this function sets the new connnection's
+ * ->priv pointer to be equal to that of its parent: only the pointer
+ * is copied, not the data it points to.
+ *
+ * This function returns a pointer to the new aim_conn_t, or %NULL on
+ * error
+ */
+aim_conn_t *aim_cloneconn(aim_session_t *sess, aim_conn_t *src)
+{
+ aim_conn_t *conn;
+
+ if (!(conn = aim_conn_getnext(sess)))
+ return NULL;
+
+ conn->fd = src->fd;
+ conn->type = src->type;
+ conn->subtype = src->subtype;
+ conn->seqnum = src->seqnum;
+ conn->priv = src->priv;
+ conn->internal = src->internal;
+ conn->lastactivity = src->lastactivity;
+ conn->forcedlatency = src->forcedlatency;
+ conn->sessv = src->sessv;
+ aim_clonehandlers(sess, conn, src);
+
+ if (src->inside) {
+ /*
+ * XXX should clone this section as well, but since currently
+ * this function only gets called for some of that rendezvous
+ * crap, and not on SNAC connections, its probably okay for
+ * now.
+ *
+ */
+ }
+
+ return conn;
+}
+
+/**
+ * aim_newconn - Open a new connection
+ * @sess: Session to create connection in
+ * @type: Type of connection to create
+ * @dest: Host to connect to (in "host:port" syntax)
+ *
+ * Opens a new connection to the specified dest host of specified
+ * type, using the proxy settings if available. If @host is %NULL,
+ * the connection is allocated and returned, but no connection
+ * is made.
+ *
+ * FIXME: Return errors in a more sane way.
+ *
+ */
+aim_conn_t *aim_newconn(aim_session_t *sess, int type, const char *dest)
+{
+ aim_conn_t *connstruct;
+ guint16 port = AIM_LOGIN_PORT;
+ char *host;
+ int i;
+
+ if (!(connstruct = aim_conn_getnext(sess)))
+ return NULL;
+
+ connstruct->sessv = (void *)sess;
+ connstruct->type = type;
+
+ if (!dest) { /* just allocate a struct */
+ connstruct->fd = -1;
+ connstruct->status = 0;
+ return connstruct;
+ }
+
+ /*
+ * As of 23 Jul 1999, AOL now sends the port number, preceded by a
+ * colon, in the BOS redirect. This fatally breaks all previous
+ * libfaims. Bad, bad AOL.
+ *
+ * We put this here to catch every case.
+ *
+ */
+
+ for(i = 0; i < (int)strlen(dest); i++) {
+ if (dest[i] == ':') {
+ port = atoi(&(dest[i+1]));
+ break;
+ }
+ }
+
+ host = (char *)g_malloc(i+1);
+ strncpy(host, dest, i);
+ host[i] = '\0';
+
+ connstruct->fd = proxy_connect(host, port, NULL, NULL);
+
+ g_free(host);
+
+ return connstruct;
+}
+
+/**
+ * aim_conn_setlatency - Set a forced latency value for connection
+ * @conn: Conn to set latency for
+ * @newval: Number of seconds to force between transmits
+ *
+ * Causes @newval seconds to be spent between transmits on a connection.
+ *
+ * This is my lame attempt at overcoming not understanding the rate
+ * limiting.
+ *
+ * XXX: This should really be replaced with something that scales and
+ * backs off like the real rate limiting does.
+ *
+ */
+int aim_conn_setlatency(aim_conn_t *conn, int newval)
+{
+
+ if (!conn)
+ return -1;
+
+ conn->forcedlatency = newval;
+ conn->lastactivity = 0; /* reset this just to make sure */
+
+ return 0;
+}
+
+/**
+ * aim_session_init - Initializes a session structure
+ * @sess: Session to initialize
+ * @flags: Flags to use. Any of %AIM_SESS_FLAGS %OR'd together.
+ * @debuglevel: Level of debugging output (zero is least)
+ *
+ * Sets up the initial values for a session.
+ *
+ */
+void aim_session_init(aim_session_t *sess, guint32 flags, int debuglevel)
+{
+
+ if (!sess)
+ return;
+
+ memset(sess, 0, sizeof(aim_session_t));
+ aim_connrst(sess);
+ sess->queue_outgoing = NULL;
+ sess->queue_incoming = NULL;
+ aim_initsnachash(sess);
+ sess->msgcookies = NULL;
+ sess->snacid_next = 0x00000001;
+
+ sess->flags = 0;
+
+ sess->modlistv = NULL;
+
+ sess->ssi.received_data = 0;
+ sess->ssi.waiting_for_ack = 0;
+ sess->ssi.holding_queue = NULL;
+ sess->ssi.revision = 0;
+ sess->ssi.items = NULL;
+ sess->ssi.timestamp = (time_t)0;
+
+ sess->locate.userinfo = NULL;
+ sess->locate.torequest = NULL;
+ sess->locate.requested = NULL;
+ sess->locate.waiting_for_response = FALSE;
+
+ sess->icq_info = NULL;
+ sess->authinfo = NULL;
+ sess->emailinfo = NULL;
+ sess->oft_info = NULL;
+
+
+ /*
+ * Default to SNAC login unless XORLOGIN is explicitly set.
+ */
+ if (!(flags & AIM_SESS_FLAGS_XORLOGIN))
+ sess->flags |= AIM_SESS_FLAGS_SNACLOGIN;
+ sess->flags |= flags;
+
+ /*
+ * This must always be set. Default to the queue-based
+ * version for back-compatibility.
+ */
+ aim_tx_setenqueue(sess, AIM_TX_QUEUED, NULL);
+
+
+ /*
+ * Register all the modules for this session...
+ */
+ aim__registermodule(sess, misc_modfirst); /* load the catch-all first */
+ aim__registermodule(sess, general_modfirst);
+ aim__registermodule(sess, locate_modfirst);
+ aim__registermodule(sess, buddylist_modfirst);
+ aim__registermodule(sess, msg_modfirst);
+ aim__registermodule(sess, admin_modfirst);
+ aim__registermodule(sess, bos_modfirst);
+ aim__registermodule(sess, search_modfirst);
+ aim__registermodule(sess, stats_modfirst);
+ aim__registermodule(sess, chatnav_modfirst);
+ aim__registermodule(sess, chat_modfirst);
+ /* missing 0x0f - 0x12 */
+ aim__registermodule(sess, ssi_modfirst);
+ /* missing 0x14 */
+ aim__registermodule(sess, icq_modfirst);
+ /* missing 0x16 */
+ aim__registermodule(sess, auth_modfirst);
+
+ return;
+}
+
+/**
+ * aim_session_kill - Deallocate a session
+ * @sess: Session to kill
+ *
+ */
+void aim_session_kill(aim_session_t *sess)
+{
+ aim_cleansnacs(sess, -1);
+
+ aim_logoff(sess);
+
+ aim__shutdownmodules(sess);
+
+ return;
+}
+
+/*
+ * XXX this is nearly as ugly as proxyconnect().
+ */
+int aim_conn_completeconnect(aim_session_t *sess, aim_conn_t *conn)
+{
+ fd_set fds, wfds;
+ struct timeval tv;
+ int res, error = ETIMEDOUT;
+ aim_rxcallback_t userfunc;
+
+ if (!conn || (conn->fd == -1))
+ return -1;
+
+ if (!(conn->status & AIM_CONN_STATUS_INPROGRESS))
+ return -1;
+
+ FD_ZERO(&fds);
+ FD_SET(conn->fd, &fds);
+ FD_ZERO(&wfds);
+ FD_SET(conn->fd, &wfds);
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ if ((res = select(conn->fd+1, &fds, &wfds, NULL, &tv)) == -1) {
+ error = errno;
+ aim_conn_close(conn);
+ errno = error;
+ return -1;
+ } else if (res == 0) {
+ return 0; /* hasn't really completed yet... */
+ }
+
+ if (FD_ISSET(conn->fd, &fds) || FD_ISSET(conn->fd, &wfds)) {
+ unsigned int len = sizeof(error);
+
+ if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
+ error = errno;
+ }
+
+ if (error) {
+ aim_conn_close(conn);
+ errno = error;
+ return -1;
+ }
+
+ sock_make_blocking(conn->fd);
+
+ conn->status &= ~AIM_CONN_STATUS_INPROGRESS;
+
+ if ((userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNCOMPLETE)))
+ userfunc(sess, NULL, conn);
+
+ /* Flush out the queues if there was something waiting for this conn */
+ aim_tx_flushqueue(sess);
+
+ return 0;
+}
+
+aim_session_t *aim_conn_getsess(aim_conn_t *conn)
+{
+
+ if (!conn)
+ return NULL;
+
+ return (aim_session_t *)conn->sessv;
+}
+
+/*
+ * aim_logoff()
+ *
+ * Closes -ALL- open connections.
+ *
+ */
+static int aim_logoff(aim_session_t *sess)
+{
+
+ aim_connrst(sess); /* in case we want to connect again */
+
+ return 0;
+
+}
+
+/*
+ * aim_flap_nop()
+ *
+ * No-op. WinAIM 4.x sends these _every minute_ to keep
+ * the connection alive.
+ */
+int aim_flap_nop(aim_session_t *sess, aim_conn_t *conn)
+{
+ aim_frame_t *fr;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x05, 0)))
+ return -ENOMEM;
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+
diff --git a/protocols/oscar/icq.c b/protocols/oscar/icq.c
new file mode 100644
index 00000000..f7c02e04
--- /dev/null
+++ b/protocols/oscar/icq.c
@@ -0,0 +1,441 @@
+/*
+ * Encapsulated ICQ.
+ *
+ */
+
+#include <aim.h>
+#include "icq.h"
+
+int aim_icq_reqofflinemsgs(aim_session_t *sess)
+{
+ aim_conn_t *conn;
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ int bslen;
+
+ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015)))
+ return -EINVAL;
+
+ bslen = 2 + 4 + 2 + 2;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid);
+
+ /* For simplicity, don't bother using a tlvlist */
+ aimbs_put16(&fr->data, 0x0001);
+ aimbs_put16(&fr->data, bslen);
+
+ aimbs_putle16(&fr->data, bslen - 2);
+ aimbs_putle32(&fr->data, atoi(sess->sn));
+ aimbs_putle16(&fr->data, 0x003c); /* I command thee. */
+ aimbs_putle16(&fr->data, snacid); /* eh. */
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+int aim_icq_ackofflinemsgs(aim_session_t *sess)
+{
+ aim_conn_t *conn;
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ int bslen;
+
+ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015)))
+ return -EINVAL;
+
+ bslen = 2 + 4 + 2 + 2;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid);
+
+ /* For simplicity, don't bother using a tlvlist */
+ aimbs_put16(&fr->data, 0x0001);
+ aimbs_put16(&fr->data, bslen);
+
+ aimbs_putle16(&fr->data, bslen - 2);
+ aimbs_putle32(&fr->data, atoi(sess->sn));
+ aimbs_putle16(&fr->data, 0x003e); /* I command thee. */
+ aimbs_putle16(&fr->data, snacid); /* eh. */
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+int aim_icq_sendxmlreq(aim_session_t *sess, const char *xml)
+{
+ aim_conn_t *conn;
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ int bslen;
+
+ if (!xml || !strlen(xml))
+ return -EINVAL;
+
+ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015)))
+ return -EINVAL;
+
+ bslen = 2 + 10 + 2 + strlen(xml) + 1;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid);
+
+ /* For simplicity, don't bother using a tlvlist */
+ aimbs_put16(&fr->data, 0x0001);
+ aimbs_put16(&fr->data, bslen);
+
+ aimbs_putle16(&fr->data, bslen - 2);
+ aimbs_putle32(&fr->data, atoi(sess->sn));
+ aimbs_putle16(&fr->data, 0x07d0); /* I command thee. */
+ aimbs_putle16(&fr->data, snacid); /* eh. */
+ aimbs_putle16(&fr->data, 0x0998); /* shrug. */
+ aimbs_putle16(&fr->data, strlen(xml) + 1);
+ aimbs_putraw(&fr->data, (guint8 *)xml, strlen(xml) + 1);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+int aim_icq_getallinfo(aim_session_t *sess, const char *uin)
+{
+ aim_conn_t *conn;
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ int bslen;
+ struct aim_icq_info *info;
+
+ if (!uin || uin[0] < '0' || uin[0] > '9')
+ return -EINVAL;
+
+ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015)))
+ return -EINVAL;
+
+ bslen = 2 + 4 + 2 + 2 + 2 + 4;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid);
+
+ /* For simplicity, don't bother using a tlvlist */
+ aimbs_put16(&fr->data, 0x0001);
+ aimbs_put16(&fr->data, bslen);
+
+ aimbs_putle16(&fr->data, bslen - 2);
+ aimbs_putle32(&fr->data, atoi(sess->sn));
+ aimbs_putle16(&fr->data, 0x07d0); /* I command thee. */
+ aimbs_putle16(&fr->data, snacid); /* eh. */
+ aimbs_putle16(&fr->data, 0x04b2); /* shrug. */
+ aimbs_putle32(&fr->data, atoi(uin));
+
+ aim_tx_enqueue(sess, fr);
+
+ /* Keep track of this request and the ICQ number and request ID */
+ info = g_new0(struct aim_icq_info, 1);
+ info->reqid = snacid;
+ info->uin = atoi(uin);
+ info->next = sess->icq_info;
+ sess->icq_info = info;
+
+ return 0;
+}
+
+int aim_icq_getsimpleinfo(aim_session_t *sess, const char *uin)
+{
+ aim_conn_t *conn;
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ int bslen;
+
+ if (!uin || uin[0] < '0' || uin[0] > '9')
+ return -EINVAL;
+
+ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015)))
+ return -EINVAL;
+
+ bslen = 2 + 4 + 2 + 2 + 2 + 4;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid);
+
+ /* For simplicity, don't bother using a tlvlist */
+ aimbs_put16(&fr->data, 0x0001);
+ aimbs_put16(&fr->data, bslen);
+
+ aimbs_putle16(&fr->data, bslen - 2);
+ aimbs_putle32(&fr->data, atoi(sess->sn));
+ aimbs_putle16(&fr->data, 0x07d0); /* I command thee. */
+ aimbs_putle16(&fr->data, snacid); /* eh. */
+ aimbs_putle16(&fr->data, 0x051f); /* shrug. */
+ aimbs_putle32(&fr->data, atoi(uin));
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+static void aim_icq_freeinfo(struct aim_icq_info *info) {
+ int i;
+
+ if (!info)
+ return;
+ g_free(info->nick);
+ g_free(info->first);
+ g_free(info->last);
+ g_free(info->email);
+ g_free(info->homecity);
+ g_free(info->homestate);
+ g_free(info->homephone);
+ g_free(info->homefax);
+ g_free(info->homeaddr);
+ g_free(info->mobile);
+ g_free(info->homezip);
+ g_free(info->personalwebpage);
+ if (info->email2)
+ for (i = 0; i < info->numaddresses; i++)
+ g_free(info->email2[i]);
+ g_free(info->email2);
+ g_free(info->workcity);
+ g_free(info->workstate);
+ g_free(info->workphone);
+ g_free(info->workfax);
+ g_free(info->workaddr);
+ g_free(info->workzip);
+ g_free(info->workcompany);
+ g_free(info->workdivision);
+ g_free(info->workposition);
+ g_free(info->workwebpage);
+ g_free(info->info);
+ g_free(info);
+}
+
+/**
+ * Subtype 0x0003 - Response to 0x0015/0x002, contains an ICQesque packet.
+ */
+static int icqresponse(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ int ret = 0;
+ aim_tlvlist_t *tl;
+ aim_tlv_t *datatlv;
+ aim_bstream_t qbs;
+ guint32 ouruin;
+ guint16 cmdlen, cmd, reqid;
+
+ if (!(tl = aim_readtlvchain(bs)) || !(datatlv = aim_gettlv(tl, 0x0001, 1))) {
+ aim_freetlvchain(&tl);
+ imcb_error(sess->aux_data, "corrupt ICQ response\n");
+ return 0;
+ }
+
+ aim_bstream_init(&qbs, datatlv->value, datatlv->length);
+
+ cmdlen = aimbs_getle16(&qbs);
+ ouruin = aimbs_getle32(&qbs);
+ cmd = aimbs_getle16(&qbs);
+ reqid = aimbs_getle16(&qbs);
+
+ if (cmd == 0x0041) { /* offline message */
+ guint16 msglen;
+ struct aim_icq_offlinemsg msg;
+ aim_rxcallback_t userfunc;
+
+ memset(&msg, 0, sizeof(msg));
+
+ msg.sender = aimbs_getle32(&qbs);
+ msg.year = aimbs_getle16(&qbs);
+ msg.month = aimbs_getle8(&qbs);
+ msg.day = aimbs_getle8(&qbs);
+ msg.hour = aimbs_getle8(&qbs);
+ msg.minute = aimbs_getle8(&qbs);
+ msg.type = aimbs_getle16(&qbs);
+ msglen = aimbs_getle16(&qbs);
+ msg.msg = aimbs_getstr(&qbs, msglen);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSG)))
+ ret = userfunc(sess, rx, &msg);
+
+ g_free(msg.msg);
+
+ } else if (cmd == 0x0042) {
+ aim_rxcallback_t userfunc;
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSGCOMPLETE)))
+ ret = userfunc(sess, rx);
+ } else if (cmd == 0x07da) { /* information */
+ guint16 subtype;
+ struct aim_icq_info *info;
+ aim_rxcallback_t userfunc;
+
+ subtype = aimbs_getle16(&qbs);
+ aim_bstream_advance(&qbs, 1); /* 0x0a */
+
+ /* find another data from the same request */
+ for (info = sess->icq_info; info && (info->reqid != reqid); info = info->next);
+
+ if (!info) {
+ info = g_new0(struct aim_icq_info, 1);
+ info->reqid = reqid;
+ info->next = sess->icq_info;
+ sess->icq_info = info;
+ }
+
+ switch (subtype) {
+ case 0x00a0: { /* hide ip status */
+ /* nothing */
+ } break;
+ case 0x00aa: { /* password change status */
+ /* nothing */
+ } break;
+ case 0x00c8: { /* general and "home" information */
+ info->nick = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->first = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->last = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->email = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->homecity = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->homestate = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->homephone = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->homefax = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->homeaddr = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->mobile = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->homezip = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->homecountry = aimbs_getle16(&qbs);
+ /* 0x0a 00 02 00 */
+ /* 1 byte timezone? */
+ /* 1 byte hide email flag? */
+ } break;
+ case 0x00dc: { /* personal information */
+ info->age = aimbs_getle8(&qbs);
+ info->unknown = aimbs_getle8(&qbs);
+ info->gender = aimbs_getle8(&qbs);
+ info->personalwebpage = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->birthyear = aimbs_getle16(&qbs);
+ info->birthmonth = aimbs_getle8(&qbs);
+ info->birthday = aimbs_getle8(&qbs);
+ info->language1 = aimbs_getle8(&qbs);
+ info->language2 = aimbs_getle8(&qbs);
+ info->language3 = aimbs_getle8(&qbs);
+ /* 0x00 00 01 00 00 01 00 00 00 00 00 */
+ } break;
+ case 0x00d2: { /* work information */
+ info->workcity = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->workstate = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->workphone = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->workfax = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->workaddr = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->workzip = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->workcountry = aimbs_getle16(&qbs);
+ info->workcompany = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->workdivision = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->workposition = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ aim_bstream_advance(&qbs, 2); /* 0x01 00 */
+ info->workwebpage = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ } break;
+ case 0x00e6: { /* additional personal information */
+ info->info = aimbs_getstr(&qbs, aimbs_getle16(&qbs)-1);
+ } break;
+ case 0x00eb: { /* email address(es) */
+ int i;
+ info->numaddresses = aimbs_getle16(&qbs);
+ info->email2 = g_new0(char *, info->numaddresses);
+ for (i = 0; i < info->numaddresses; i++) {
+ info->email2[i] = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ if (i+1 != info->numaddresses)
+ aim_bstream_advance(&qbs, 1); /* 0x00 */
+ }
+ } break;
+ case 0x00f0: { /* personal interests */
+ } break;
+ case 0x00fa: { /* past background and current organizations */
+ } break;
+ case 0x0104: { /* alias info */
+ info->nick = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->first = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->last = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ aim_bstream_advance(&qbs, aimbs_getle16(&qbs));
+ /* email address? */
+ /* Then 0x00 02 00 */
+ } break;
+ case 0x010e: { /* unknown */
+ /* 0x00 00 */
+ } break;
+
+ case 0x019a: { /* simple info */
+ aim_bstream_advance(&qbs, 2);
+ info->uin = aimbs_getle32(&qbs);
+ info->nick = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->first = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->last = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ info->email = aimbs_getstr(&qbs, aimbs_getle16(&qbs));
+ /* Then 0x00 02 00 00 00 00 00 */
+ } break;
+ } /* End switch statement */
+
+
+ if (!(snac->flags & 0x0001)) {
+ if (subtype != 0x0104)
+ if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_INFO)))
+ ret = userfunc(sess, rx, info);
+
+ /* Bitlbee - not supported, yet
+ if (info->uin && info->nick)
+ if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_ALIAS)))
+ ret = userfunc(sess, rx, info);
+ */
+
+ if (sess->icq_info == info) {
+ sess->icq_info = info->next;
+ } else {
+ struct aim_icq_info *cur;
+ for (cur=sess->icq_info; (cur->next && (cur->next!=info)); cur=cur->next);
+ if (cur->next)
+ cur->next = cur->next->next;
+ }
+ aim_icq_freeinfo(info);
+ }
+ }
+
+ aim_freetlvchain(&tl);
+
+ return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+ if (snac->subtype == 0x0003)
+ return icqresponse(sess, mod, rx, snac, bs);
+
+ return 0;
+}
+
+int icq_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+ mod->family = 0x0015;
+ mod->version = 0x0001;
+ mod->toolid = 0x0110;
+ mod->toolversion = 0x047c;
+ mod->flags = 0;
+ strncpy(mod->name, "icq", sizeof(mod->name));
+ mod->snachandler = snachandler;
+
+ return 0;
+}
+
+
diff --git a/protocols/oscar/icq.h b/protocols/oscar/icq.h
new file mode 100644
index 00000000..c496f39c
--- /dev/null
+++ b/protocols/oscar/icq.h
@@ -0,0 +1,98 @@
+#ifndef __OSCAR_ICQ_H__
+#define __OSCAR_ICQ_H__
+
+#define AIM_CB_FAM_ICQ 0x0015
+
+/*
+ * SNAC Family: ICQ
+ *
+ * Most of these are actually special.
+ */
+#define AIM_CB_ICQ_ERROR 0x0001
+#define AIM_CB_ICQ_OFFLINEMSG 0x00f0
+#define AIM_CB_ICQ_OFFLINEMSGCOMPLETE 0x00f1
+#define AIM_CB_ICQ_SIMPLEINFO 0x00f2
+#define AIM_CB_ICQ_INFO 0x00f2 /* just transitional */
+#define AIM_CB_ICQ_DEFAULT 0xffff
+
+struct aim_icq_offlinemsg {
+ guint32 sender;
+ guint16 year;
+ guint8 month, day, hour, minute;
+ guint16 type;
+ char *msg;
+};
+
+struct aim_icq_simpleinfo {
+ guint32 uin;
+ char *nick;
+ char *first;
+ char *last;
+ char *email;
+};
+
+struct aim_icq_info {
+ gushort reqid;
+
+ /* simple */
+ guint32 uin;
+
+ /* general and "home" information (0x00c8) */
+ char *nick;
+ char *first;
+ char *last;
+ char *email;
+ char *homecity;
+ char *homestate;
+ char *homephone;
+ char *homefax;
+ char *homeaddr;
+ char *mobile;
+ char *homezip;
+ gushort homecountry;
+/* guchar timezone;
+ guchar hideemail; */
+
+ /* personal (0x00dc) */
+ guchar age;
+ guchar unknown;
+ guchar gender;
+ char *personalwebpage;
+ gushort birthyear;
+ guchar birthmonth;
+ guchar birthday;
+ guchar language1;
+ guchar language2;
+ guchar language3;
+
+ /* work (0x00d2) */
+ char *workcity;
+ char *workstate;
+ char *workphone;
+ char *workfax;
+ char *workaddr;
+ char *workzip;
+ gushort workcountry;
+ char *workcompany;
+ char *workdivision;
+ char *workposition;
+ char *workwebpage;
+
+ /* additional personal information (0x00e6) */
+ char *info;
+
+ /* email (0x00eb) */
+ gushort numaddresses;
+ char **email2;
+
+ /* we keep track of these in a linked list because we're 1337 */
+ struct aim_icq_info *next;
+};
+
+
+int aim_icq_reqofflinemsgs(aim_session_t *sess);
+int aim_icq_ackofflinemsgs(aim_session_t *sess);
+int aim_icq_getallinfo(aim_session_t *sess, const char *uin);
+int aim_icq_getsimpleinfo(aim_session_t *sess, const char *uin);
+
+#endif /* __OSCAR_ICQ_H__ */
diff --git a/protocols/oscar/im.c b/protocols/oscar/im.c
new file mode 100644
index 00000000..4169ea4d
--- /dev/null
+++ b/protocols/oscar/im.c
@@ -0,0 +1,2141 @@
+/*
+ * aim_im.c
+ *
+ * The routines for sending/receiving Instant Messages.
+ *
+ * Note the term ICBM (Inter-Client Basic Message) which blankets
+ * all types of genericly routed through-server messages. Within
+ * the ICBM types (family 4), a channel is defined. Each channel
+ * represents a different type of message. Channel 1 is used for
+ * what would commonly be called an "instant message". Channel 2
+ * is used for negotiating "rendezvous". These transactions end in
+ * something more complex happening, such as a chat invitation, or
+ * a file transfer.
+ *
+ * In addition to the channel, every ICBM contains a cookie. For
+ * standard IMs, these are only used for error messages. However,
+ * the more complex rendezvous messages make suitably more complex
+ * use of this field.
+ *
+ */
+
+#include <aim.h>
+#include "im.h"
+#include "info.h"
+
+/*
+ * Takes a msghdr (and a length) and returns a client type
+ * code. Note that this is *only a guess* and has a low likelihood
+ * of actually being accurate.
+ *
+ * Its based on experimental data, with the help of Eric Warmenhoven
+ * who seems to have collected a wide variety of different AIM clients.
+ *
+ *
+ * Heres the current collection:
+ * 0501 0003 0101 0101 01 AOL Mobile Communicator, WinAIM 1.0.414
+ * 0501 0003 0101 0201 01 WinAIM 2.0.847, 2.1.1187, 3.0.1464,
+ * 4.3.2229, 4.4.2286
+ * 0501 0004 0101 0102 0101 WinAIM 4.1.2010, libfaim (right here)
+ * 0501 0001 0101 01 AOL v6.0, CompuServe 2000 v6.0, any
+ * TOC client
+ *
+ * Note that in this function, only the feature bytes are tested, since
+ * the rest will always be the same.
+ *
+ */
+guint16 aim_fingerprintclient(guint8 *msghdr, int len)
+{
+ static const struct {
+ guint16 clientid;
+ int len;
+ guint8 data[10];
+ } fingerprints[] = {
+ /* AOL Mobile Communicator, WinAIM 1.0.414 */
+ { AIM_CLIENTTYPE_MC,
+ 3, {0x01, 0x01, 0x01}},
+
+ /* WinAIM 2.0.847, 2.1.1187, 3.0.1464, 4.3.2229, 4.4.2286 */
+ { AIM_CLIENTTYPE_WINAIM,
+ 3, {0x01, 0x01, 0x02}},
+
+ /* WinAIM 4.1.2010, libfaim */
+ { AIM_CLIENTTYPE_WINAIM41,
+ 4, {0x01, 0x01, 0x01, 0x02}},
+
+ /* AOL v6.0, CompuServe 2000 v6.0, any TOC client */
+ { AIM_CLIENTTYPE_AOL_TOC,
+ 1, {0x01}},
+
+ { 0, 0}
+ };
+ int i;
+
+ if (!msghdr || (len <= 0))
+ return AIM_CLIENTTYPE_UNKNOWN;
+
+ for (i = 0; fingerprints[i].len; i++) {
+ if (fingerprints[i].len != len)
+ continue;
+ if (memcmp(fingerprints[i].data, msghdr, fingerprints[i].len) == 0)
+ return fingerprints[i].clientid;
+ }
+
+ return AIM_CLIENTTYPE_UNKNOWN;
+}
+
+/* This should be endian-safe now... but who knows... */
+guint16 aim_iconsum(const guint8 *buf, int buflen)
+{
+ guint32 sum;
+ int i;
+
+ for (i = 0, sum = 0; i + 1 < buflen; i += 2)
+ sum += (buf[i+1] << 8) + buf[i];
+ if (i < buflen)
+ sum += buf[i];
+
+ sum = ((sum & 0xffff0000) >> 16) + (sum & 0x0000ffff);
+
+ return (guint16)sum;
+}
+
+/*
+ * Send an ICBM (instant message).
+ *
+ *
+ * Possible flags:
+ * AIM_IMFLAGS_AWAY -- Marks the message as an autoresponse
+ * AIM_IMFLAGS_ACK -- Requests that the server send an ack
+ * when the message is received (of type 0x0004/0x000c)
+ * AIM_IMFLAGS_OFFLINE--If destination is offline, store it until they are
+ * online (probably ICQ only).
+ * AIM_IMFLAGS_UNICODE--Instead of ASCII7, the passed message is
+ * made up of UNICODE duples. If you set
+ * this, you'd better be damn sure you know
+ * what you're doing.
+ * AIM_IMFLAGS_ISO_8859_1 -- The message contains the ASCII8 subset
+ * known as ISO-8859-1.
+ *
+ * Generally, you should use the lowest encoding possible to send
+ * your message. If you only use basic punctuation and the generic
+ * Latin alphabet, use ASCII7 (no flags). If you happen to use non-ASCII7
+ * characters, but they are all clearly defined in ISO-8859-1, then
+ * use that. Keep in mind that not all characters in the PC ASCII8
+ * character set are defined in the ISO standard. For those cases (most
+ * notably when the (r) symbol is used), you must use the full UNICODE
+ * encoding for your message. In UNICODE mode, _all_ characters must
+ * occupy 16bits, including ones that are not special. (Remember that
+ * the first 128 UNICODE symbols are equivelent to ASCII7, however they
+ * must be prefixed with a zero high order byte.)
+ *
+ * I strongly discourage the use of UNICODE mode, mainly because none
+ * of the clients I use can parse those messages (and besides that,
+ * wchars are difficult and non-portable to handle in most UNIX environments).
+ * If you really need to include special characters, use the HTML UNICODE
+ * entities. These are of the form &#2026; where 2026 is the hex
+ * representation of the UNICODE index (in this case, UNICODE
+ * "Horizontal Ellipsis", or 133 in in ASCII8).
+ *
+ * Implementation note: Since this is one of the most-used functions
+ * in all of libfaim, it is written with performance in mind. As such,
+ * it is not as clear as it could be in respect to how this message is
+ * supposed to be layed out. Most obviously, tlvlists should be used
+ * instead of writing out the bytes manually.
+ *
+ * XXX more precise verification that we never send SNACs larger than 8192
+ * XXX check SNAC size for multipart
+ *
+ */
+int aim_send_im_ext(aim_session_t *sess, struct aim_sendimext_args *args)
+{
+ static const guint8 deffeatures[] = {
+ 0x01, 0x01, 0x01, 0x02
+ };
+ aim_conn_t *conn;
+ int i, msgtlvlen;
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+ return -EINVAL;
+
+ if (!args)
+ return -EINVAL;
+
+ if (args->flags & AIM_IMFLAGS_MULTIPART) {
+ if (args->mpmsg->numparts <= 0)
+ return -EINVAL;
+ } else {
+ if (!args->msg || (args->msglen <= 0))
+ return -EINVAL;
+
+ if (args->msglen >= MAXMSGLEN)
+ return -E2BIG;
+ }
+
+ /* Painfully calculate the size of the message TLV */
+ msgtlvlen = 1 + 1; /* 0501 */
+
+ if (args->flags & AIM_IMFLAGS_CUSTOMFEATURES)
+ msgtlvlen += 2 + args->featureslen;
+ else
+ msgtlvlen += 2 + sizeof(deffeatures);
+
+ if (args->flags & AIM_IMFLAGS_MULTIPART) {
+ aim_mpmsg_section_t *sec;
+
+ for (sec = args->mpmsg->parts; sec; sec = sec->next) {
+ msgtlvlen += 2 /* 0101 */ + 2 /* block len */;
+ msgtlvlen += 4 /* charset */ + sec->datalen;
+ }
+
+ } else {
+ msgtlvlen += 2 /* 0101 */ + 2 /* block len */;
+ msgtlvlen += 4 /* charset */ + args->msglen;
+ }
+
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, msgtlvlen+128)))
+ return -ENOMEM;
+
+ /* XXX should be optional */
+ snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, args->destsn, strlen(args->destsn)+1);
+ aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
+
+ /*
+ * Generate a random message cookie
+ *
+ * We could cache these like we do SNAC IDs. (In fact, it
+ * might be a good idea.) In the message error functions,
+ * the 8byte message cookie is returned as well as the
+ * SNAC ID.
+ *
+ */
+ for (i = 0; i < 8; i++)
+ aimbs_put8(&fr->data, (guint8) rand());
+
+ /*
+ * Channel ID
+ */
+ aimbs_put16(&fr->data, 0x0001);
+
+ /*
+ * Destination SN (prepended with byte length)
+ */
+ aimbs_put8(&fr->data, strlen(args->destsn));
+ aimbs_putraw(&fr->data, (guint8 *)args->destsn, strlen(args->destsn));
+
+ /*
+ * Message TLV (type 2).
+ */
+ aimbs_put16(&fr->data, 0x0002);
+ aimbs_put16(&fr->data, msgtlvlen);
+
+ /*
+ * Features
+ *
+ */
+ aimbs_put8(&fr->data, 0x05);
+ aimbs_put8(&fr->data, 0x01);
+
+ if (args->flags & AIM_IMFLAGS_CUSTOMFEATURES) {
+ aimbs_put16(&fr->data, args->featureslen);
+ aimbs_putraw(&fr->data, args->features, args->featureslen);
+ } else {
+ aimbs_put16(&fr->data, sizeof(deffeatures));
+ aimbs_putraw(&fr->data, deffeatures, sizeof(deffeatures));
+ }
+
+ if (args->flags & AIM_IMFLAGS_MULTIPART) {
+ aim_mpmsg_section_t *sec;
+
+ for (sec = args->mpmsg->parts; sec; sec = sec->next) {
+ aimbs_put16(&fr->data, 0x0101);
+ aimbs_put16(&fr->data, sec->datalen + 4);
+ aimbs_put16(&fr->data, sec->charset);
+ aimbs_put16(&fr->data, sec->charsubset);
+ aimbs_putraw(&fr->data, sec->data, sec->datalen);
+ }
+
+ } else {
+
+ aimbs_put16(&fr->data, 0x0101);
+
+ /*
+ * Message block length.
+ */
+ aimbs_put16(&fr->data, args->msglen + 0x04);
+
+ /*
+ * Character set.
+ */
+ if (args->flags & AIM_IMFLAGS_CUSTOMCHARSET) {
+
+ aimbs_put16(&fr->data, args->charset);
+ aimbs_put16(&fr->data, args->charsubset);
+
+ } else {
+ if (args->flags & AIM_IMFLAGS_UNICODE)
+ aimbs_put16(&fr->data, 0x0002);
+ else if (args->flags & AIM_IMFLAGS_ISO_8859_1)
+ aimbs_put16(&fr->data, 0x0003);
+ else
+ aimbs_put16(&fr->data, 0x0000);
+
+ aimbs_put16(&fr->data, 0x0000);
+ }
+
+ /*
+ * Message. Not terminated.
+ */
+ aimbs_putraw(&fr->data, (guint8 *)args->msg, args->msglen);
+ }
+
+ /*
+ * Set the Request Acknowledge flag.
+ */
+ if (args->flags & AIM_IMFLAGS_ACK) {
+ aimbs_put16(&fr->data, 0x0003);
+ aimbs_put16(&fr->data, 0x0000);
+ }
+
+ /*
+ * Set the Autoresponse flag.
+ */
+ if (args->flags & AIM_IMFLAGS_AWAY) {
+ aimbs_put16(&fr->data, 0x0004);
+ aimbs_put16(&fr->data, 0x0000);
+ }
+
+ if (args->flags & AIM_IMFLAGS_OFFLINE) {
+ aimbs_put16(&fr->data, 0x0006);
+ aimbs_put16(&fr->data, 0x0000);
+ }
+
+ /*
+ * Set the I HAVE A REALLY PURTY ICON flag.
+ */
+ if (args->flags & AIM_IMFLAGS_HASICON) {
+ aimbs_put16(&fr->data, 0x0008);
+ aimbs_put16(&fr->data, 0x000c);
+ aimbs_put32(&fr->data, args->iconlen);
+ aimbs_put16(&fr->data, 0x0001);
+ aimbs_put16(&fr->data, args->iconsum);
+ aimbs_put32(&fr->data, args->iconstamp);
+ }
+
+ /*
+ * Set the Buddy Icon Requested flag.
+ */
+ if (args->flags & AIM_IMFLAGS_BUDDYREQ) {
+ aimbs_put16(&fr->data, 0x0009);
+ aimbs_put16(&fr->data, 0x0000);
+ }
+
+ aim_tx_enqueue(sess, fr);
+
+ if (!(sess->flags & AIM_SESS_FLAGS_DONTTIMEOUTONICBM))
+ aim_cleansnacs(sess, 60); /* clean out SNACs over 60sec old */
+
+ return 0;
+}
+
+/*
+ * Simple wrapper for aim_send_im_ext()
+ *
+ * You cannot use aim_send_im if you need the HASICON flag. You must
+ * use aim_send_im_ext directly for that.
+ *
+ * aim_send_im also cannot be used if you require UNICODE messages, because
+ * that requires an explicit message length. Use aim_send_im_ext().
+ *
+ */
+int aim_send_im(aim_session_t *sess, const char *destsn, guint16 flags, const char *msg)
+{
+ struct aim_sendimext_args args;
+
+ args.destsn = destsn;
+ args.flags = flags;
+ args.msg = msg;
+ args.msglen = strlen(msg);
+
+ /* Make these don't get set by accident -- they need aim_send_im_ext */
+ args.flags &= ~(AIM_IMFLAGS_CUSTOMFEATURES | AIM_IMFLAGS_HASICON | AIM_IMFLAGS_MULTIPART);
+
+ return aim_send_im_ext(sess, &args);
+}
+
+/*
+ * This is also performance sensitive. (If you can believe it...)
+ *
+ */
+int aim_send_icon(aim_session_t *sess, const char *sn, const guint8 *icon, int iconlen, time_t stamp, guint16 iconsum)
+{
+ aim_conn_t *conn;
+ int i;
+ guint8 ck[8];
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+ return -EINVAL;
+
+ if (!sn || !icon || (iconlen <= 0) || (iconlen >= MAXICONLEN))
+ return -EINVAL;
+
+ for (i = 0; i < 8; i++)
+ aimutil_put8(ck+i, (guint8) rand());
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+8+2+1+strlen(sn)+2+2+2+8+16+2+2+2+2+2+2+2+4+4+4+iconlen+strlen(AIM_ICONIDENT)+2+2)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
+
+ /*
+ * Cookie
+ */
+ aimbs_putraw(&fr->data, ck, 8);
+
+ /*
+ * Channel (2)
+ */
+ aimbs_put16(&fr->data, 0x0002);
+
+ /*
+ * Dest sn
+ */
+ aimbs_put8(&fr->data, strlen(sn));
+ aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn));
+
+ /*
+ * TLV t(0005)
+ *
+ * Encompasses everything below.
+ */
+ aimbs_put16(&fr->data, 0x0005);
+ aimbs_put16(&fr->data, 2+8+16+6+4+4+iconlen+4+4+4+strlen(AIM_ICONIDENT));
+
+ aimbs_put16(&fr->data, 0x0000);
+ aimbs_putraw(&fr->data, ck, 8);
+ aim_putcap(&fr->data, AIM_CAPS_BUDDYICON);
+
+ /* TLV t(000a) */
+ aimbs_put16(&fr->data, 0x000a);
+ aimbs_put16(&fr->data, 0x0002);
+ aimbs_put16(&fr->data, 0x0001);
+
+ /* TLV t(000f) */
+ aimbs_put16(&fr->data, 0x000f);
+ aimbs_put16(&fr->data, 0x0000);
+
+ /* TLV t(2711) */
+ aimbs_put16(&fr->data, 0x2711);
+ aimbs_put16(&fr->data, 4+4+4+iconlen+strlen(AIM_ICONIDENT));
+ aimbs_put16(&fr->data, 0x0000);
+ aimbs_put16(&fr->data, iconsum);
+ aimbs_put32(&fr->data, iconlen);
+ aimbs_put32(&fr->data, stamp);
+ aimbs_putraw(&fr->data, icon, iconlen);
+ aimbs_putraw(&fr->data, (guint8 *)AIM_ICONIDENT, strlen(AIM_ICONIDENT));
+
+ /* TLV t(0003) */
+ aimbs_put16(&fr->data, 0x0003);
+ aimbs_put16(&fr->data, 0x0000);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * This only works for ICQ 2001b (thats 2001 not 2000). Better, only
+ * send it to clients advertising the RTF capability. In fact, if you send
+ * it to a client that doesn't support that capability, the server will gladly
+ * bounce it back to you.
+ *
+ * You'd think this would be in icq.c, but, well, I'm trying to stick with
+ * the one-group-per-file scheme as much as possible. This could easily
+ * be an exception, since Rendezvous IMs are external of the Oscar core,
+ * and therefore are undefined. Really I just need to think of a good way to
+ * make an interface similar to what AOL actually uses. But I'm not using COM.
+ *
+ */
+int aim_send_rtfmsg(aim_session_t *sess, struct aim_sendrtfmsg_args *args)
+{
+ const char rtfcap[] = {"{97B12751-243C-4334-AD22-D6ABF73F1492}"}; /* AIM_CAPS_ICQRTF capability in string form */
+ aim_conn_t *conn;
+ int i;
+ guint8 ck[8];
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ int servdatalen;
+
+ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+ return -EINVAL;
+
+ if (!args || !args->destsn || !args->rtfmsg)
+ return -EINVAL;
+
+ servdatalen = 2+2+16+2+4+1+2 + 2+2+4+4+4 + 2+4+2+strlen(args->rtfmsg)+1 + 4+4+4+strlen(rtfcap)+1;
+
+ for (i = 0; i < 8; i++)
+ aimutil_put8(ck+i, (guint8) rand());
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+128+servdatalen)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
+
+ /*
+ * Cookie
+ */
+ aimbs_putraw(&fr->data, ck, 8);
+
+ /*
+ * Channel (2)
+ */
+ aimbs_put16(&fr->data, 0x0002);
+
+ /*
+ * Dest sn
+ */
+ aimbs_put8(&fr->data, strlen(args->destsn));
+ aimbs_putraw(&fr->data, (guint8 *)args->destsn, strlen(args->destsn));
+
+ /*
+ * TLV t(0005)
+ *
+ * Encompasses everything below.
+ */
+ aimbs_put16(&fr->data, 0x0005);
+ aimbs_put16(&fr->data, 2+8+16 + 2+2+2 + 2+2 + 2+2+servdatalen);
+
+ aimbs_put16(&fr->data, 0x0000);
+ aimbs_putraw(&fr->data, ck, 8);
+ aim_putcap(&fr->data, AIM_CAPS_ICQSERVERRELAY);
+
+ /*
+ * t(000a) l(0002) v(0001)
+ */
+ aimbs_put16(&fr->data, 0x000a);
+ aimbs_put16(&fr->data, 0x0002);
+ aimbs_put16(&fr->data, 0x0001);
+
+ /*
+ * t(000f) l(0000) v()
+ */
+ aimbs_put16(&fr->data, 0x000f);
+ aimbs_put16(&fr->data, 0x0000);
+
+ /*
+ * Service Data TLV
+ */
+ aimbs_put16(&fr->data, 0x2711);
+ aimbs_put16(&fr->data, servdatalen);
+
+ aimbs_putle16(&fr->data, 11 + 16 /* 11 + (sizeof CLSID) */);
+ aimbs_putle16(&fr->data, 9);
+ aim_putcap(&fr->data, AIM_CAPS_EMPTY);
+ aimbs_putle16(&fr->data, 0);
+ aimbs_putle32(&fr->data, 0);
+ aimbs_putle8(&fr->data, 0);
+ aimbs_putle16(&fr->data, 0x03ea); /* trid1 */
+
+ aimbs_putle16(&fr->data, 14);
+ aimbs_putle16(&fr->data, 0x03eb); /* trid2 */
+ aimbs_putle32(&fr->data, 0);
+ aimbs_putle32(&fr->data, 0);
+ aimbs_putle32(&fr->data, 0);
+
+ aimbs_putle16(&fr->data, 0x0001);
+ aimbs_putle32(&fr->data, 0);
+ aimbs_putle16(&fr->data, strlen(args->rtfmsg)+1);
+ aimbs_putraw(&fr->data, (guint8 *)args->rtfmsg, strlen(args->rtfmsg)+1);
+
+ aimbs_putle32(&fr->data, args->fgcolor);
+ aimbs_putle32(&fr->data, args->bgcolor);
+ aimbs_putle32(&fr->data, strlen(rtfcap)+1);
+ aimbs_putraw(&fr->data, (guint8 *)rtfcap, strlen(rtfcap)+1);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+int aim_request_directim(aim_session_t *sess, const char *destsn, guint8 *ip, guint16 port, guint8 *ckret)
+{
+ aim_conn_t *conn;
+ guint8 ck[8];
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ aim_tlvlist_t *tl = NULL, *itl = NULL;
+ int hdrlen, i;
+ guint8 *hdr;
+ aim_bstream_t hdrbs;
+
+ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 256+strlen(destsn))))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
+
+ /*
+ * Generate a random message cookie
+ *
+ * This cookie needs to be alphanumeric and NULL-terminated to be
+ * TOC-compatible.
+ *
+ * XXX have I mentioned these should be generated in msgcookie.c?
+ *
+ */
+ for (i = 0; i < 7; i++)
+ ck[i] = 0x30 + ((guint8) rand() % 10);
+ ck[7] = '\0';
+
+ if (ckret)
+ memcpy(ckret, ck, 8);
+
+ /* Cookie */
+ aimbs_putraw(&fr->data, ck, 8);
+
+ /* Channel */
+ aimbs_put16(&fr->data, 0x0002);
+
+ /* Destination SN */
+ aimbs_put8(&fr->data, strlen(destsn));
+ aimbs_putraw(&fr->data, (guint8 *)destsn, strlen(destsn));
+
+ aim_addtlvtochain_noval(&tl, 0x0003);
+
+ hdrlen = 2+8+16+6+8+6+4;
+ hdr = g_malloc(hdrlen);
+ aim_bstream_init(&hdrbs, hdr, hdrlen);
+
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_putraw(&hdrbs, ck, 8);
+ aim_putcap(&hdrbs, AIM_CAPS_IMIMAGE);
+
+ aim_addtlvtochain16(&itl, 0x000a, 0x0001);
+ aim_addtlvtochain_raw(&itl, 0x0003, 4, ip);
+ aim_addtlvtochain16(&itl, 0x0005, port);
+ aim_addtlvtochain_noval(&itl, 0x000f);
+
+ aim_writetlvchain(&hdrbs, &itl);
+
+ aim_addtlvtochain_raw(&tl, 0x0005, aim_bstream_curpos(&hdrbs), hdr);
+
+ aim_writetlvchain(&fr->data, &tl);
+
+ g_free(hdr);
+ aim_freetlvchain(&itl);
+ aim_freetlvchain(&tl);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+int aim_request_sendfile(aim_session_t *sess, const char *sn, const char *filename, guint16 numfiles, guint32 totsize, guint8 *ip, guint16 port, guint8 *ckret)
+{
+ aim_conn_t *conn;
+ int i;
+ guint8 ck[8];
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+ return -EINVAL;
+
+ if (!sn || !filename)
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+8+2+1+strlen(sn)+2+2+2+8+16+6+8+6+4+2+2+2+2+4+strlen(filename)+4)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
+
+ for (i = 0; i < 7; i++)
+ aimutil_put8(ck+i, 0x30 + ((guint8) rand() % 10));
+ ck[7] = '\0';
+
+ if (ckret)
+ memcpy(ckret, ck, 8);
+
+ /*
+ * Cookie
+ */
+ aimbs_putraw(&fr->data, ck, 8);
+
+ /*
+ * Channel (2)
+ */
+ aimbs_put16(&fr->data, 0x0002);
+
+ /*
+ * Dest sn
+ */
+ aimbs_put8(&fr->data, strlen(sn));
+ aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn));
+
+ /*
+ * TLV t(0005)
+ *
+ * Encompasses everything below. Gee.
+ */
+ aimbs_put16(&fr->data, 0x0005);
+ aimbs_put16(&fr->data, 2+8+16+6+8+6+4+2+2+2+2+4+strlen(filename)+4);
+
+ aimbs_put16(&fr->data, 0x0000);
+ aimbs_putraw(&fr->data, ck, 8);
+ aim_putcap(&fr->data, AIM_CAPS_SENDFILE);
+
+ /* TLV t(000a) */
+ aimbs_put16(&fr->data, 0x000a);
+ aimbs_put16(&fr->data, 0x0002);
+ aimbs_put16(&fr->data, 0x0001);
+
+ /* TLV t(0003) (IP) */
+ aimbs_put16(&fr->data, 0x0003);
+ aimbs_put16(&fr->data, 0x0004);
+ aimbs_putraw(&fr->data, ip, 4);
+
+ /* TLV t(0005) (port) */
+ aimbs_put16(&fr->data, 0x0005);
+ aimbs_put16(&fr->data, 0x0002);
+ aimbs_put16(&fr->data, port);
+
+ /* TLV t(000f) */
+ aimbs_put16(&fr->data, 0x000f);
+ aimbs_put16(&fr->data, 0x0000);
+
+ /* TLV t(2711) */
+ aimbs_put16(&fr->data, 0x2711);
+ aimbs_put16(&fr->data, 2+2+4+strlen(filename)+4);
+
+ /* ? */
+ aimbs_put16(&fr->data, 0x0001);
+ aimbs_put16(&fr->data, numfiles);
+ aimbs_put32(&fr->data, totsize);
+ aimbs_putraw(&fr->data, (guint8 *)filename, strlen(filename));
+
+ /* ? */
+ aimbs_put32(&fr->data, 0x00000000);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/**
+ * Request the status message of the given ICQ user.
+ *
+ * @param sess The oscar session.
+ * @param sn The UIN of the user of whom you wish to request info.
+ * @param type The type of info you wish to request. This should be the current
+ * state of the user, as one of the AIM_ICQ_STATE_* defines.
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_send_im_ch2_geticqmessage(aim_session_t *sess, const char *sn, int type)
+{
+ aim_conn_t *conn;
+ int i;
+ guint8 ck[8];
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)) || !sn)
+ return -EINVAL;
+
+ for (i = 0; i < 8; i++)
+ aimutil_put8(ck+i, (guint8) rand());
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+8+2+1+strlen(sn) + 4+0x5e + 4)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
+
+ /* Cookie */
+ aimbs_putraw(&fr->data, ck, 8);
+
+ /* Channel (2) */
+ aimbs_put16(&fr->data, 0x0002);
+
+ /* Dest sn */
+ aimbs_put8(&fr->data, strlen(sn));
+ aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn));
+
+ /* TLV t(0005) - Encompasses almost everything below. */
+ aimbs_put16(&fr->data, 0x0005); /* T */
+ aimbs_put16(&fr->data, 0x005e); /* L */
+ { /* V */
+ aimbs_put16(&fr->data, 0x0000);
+
+ /* Cookie */
+ aimbs_putraw(&fr->data, ck, 8);
+
+ /* Put the 16 byte server relay capability */
+ aim_putcap(&fr->data, AIM_CAPS_ICQSERVERRELAY);
+
+ /* TLV t(000a) */
+ aimbs_put16(&fr->data, 0x000a);
+ aimbs_put16(&fr->data, 0x0002);
+ aimbs_put16(&fr->data, 0x0001);
+
+ /* TLV t(000f) */
+ aimbs_put16(&fr->data, 0x000f);
+ aimbs_put16(&fr->data, 0x0000);
+
+ /* TLV t(2711) */
+ aimbs_put16(&fr->data, 0x2711);
+ aimbs_put16(&fr->data, 0x0036);
+ { /* V */
+ aimbs_putle16(&fr->data, 0x001b); /* L */
+ aimbs_putle16(&fr->data, 0x0008); /* AAA - Protocol version */
+ aimbs_putle32(&fr->data, 0x00000000); /* Unknown */
+ aimbs_putle32(&fr->data, 0x00000000); /* Unknown */
+ aimbs_putle32(&fr->data, 0x00000000); /* Unknown */
+ aimbs_putle32(&fr->data, 0x00000000); /* Unknown */
+ aimbs_putle16(&fr->data, 0x0000); /* Unknown */
+ aimbs_putle16(&fr->data, 0x0003); /* Client features? */
+ aimbs_putle16(&fr->data, 0x0000); /* Unknown */
+ aimbs_putle8(&fr->data, 0x00); /* Unkizown */
+ aimbs_putle16(&fr->data, 0xffff); /* Sequence number? XXX - This should decrement by 1 with each request */
+
+ aimbs_putle16(&fr->data, 0x000e); /* L */
+ aimbs_putle16(&fr->data, 0xffff); /* Sequence number? XXX - This should decrement by 1 with each request */
+ aimbs_putle32(&fr->data, 0x00000000); /* Unknown */
+ aimbs_putle32(&fr->data, 0x00000000); /* Unknown */
+ aimbs_putle32(&fr->data, 0x00000000); /* Unknown */
+
+ /* The type of status message being requested */
+ if (type & AIM_ICQ_STATE_CHAT)
+ aimbs_putle16(&fr->data, 0x03ec);
+ else if(type & AIM_ICQ_STATE_DND)
+ aimbs_putle16(&fr->data, 0x03eb);
+ else if(type & AIM_ICQ_STATE_OUT)
+ aimbs_putle16(&fr->data, 0x03ea);
+ else if(type & AIM_ICQ_STATE_BUSY)
+ aimbs_putle16(&fr->data, 0x03e9);
+ else if(type & AIM_ICQ_STATE_AWAY)
+ aimbs_putle16(&fr->data, 0x03e8);
+
+ aimbs_putle16(&fr->data, 0x0000); /* Status? */
+ aimbs_putle16(&fr->data, 0x0001); /* Priority of this message? */
+ aimbs_putle16(&fr->data, 0x0001); /* L? */
+ aimbs_putle8(&fr->data, 0x00); /* Null termination? */
+ } /* End TLV t(2711) */
+ } /* End TLV t(0005) */
+
+ /* TLV t(0003) */
+ aimbs_put16(&fr->data, 0x0003);
+ aimbs_put16(&fr->data, 0x0000);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/**
+ * answers status message requests
+ * @param sess the oscar session
+ * @param sender the guy whos asking
+ * @param cookie message id which we are answering for
+ * @param message away message
+ * @param state our current away state the way icq requests it (0xE8 for away, 0xE9 occupied, ...)
+ * @return 0 if no error
+ */
+int aim_send_im_ch2_statusmessage(aim_session_t *sess, const char *sender, const guint8 *cookie,
+ const char *message, const guint8 state, const guint16 dc)
+{
+ aim_conn_t *conn;
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02,
+ 10+8+2+1+strlen(sender)+2+0x1d+0x10+9+strlen(message)+1)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0004, 0x000b, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0004, 0x000b, 0x0000, snacid);
+
+ aimbs_putraw(&fr->data, cookie, 8);
+
+ aimbs_put16(&fr->data, 0x0002); /* channel */
+ aimbs_put8(&fr->data, strlen(sender));
+ aimbs_putraw(&fr->data, (guint8 *)sender, strlen(sender));
+
+ aimbs_put16(&fr->data, 0x0003); /* reason: channel specific */
+
+ aimbs_putle16(&fr->data, 0x001b); /* length of data SEQ1 */
+ aimbs_putle16(&fr->data, 0x0008); /* protocol version */
+
+ aimbs_putle32(&fr->data, 0x0000); /* no plugin -> 16 times 0x00 */
+ aimbs_putle32(&fr->data, 0x0000);
+ aimbs_putle32(&fr->data, 0x0000);
+ aimbs_putle32(&fr->data, 0x0000);
+
+ aimbs_putle16(&fr->data, 0x0000); /* unknown */
+ aimbs_putle32(&fr->data, 0x0003); /* client features */
+ aimbs_putle8(&fr->data, 0x00); /* unknown */
+ aimbs_putle16(&fr->data, dc); /* Sequence number? XXX - This should decrement by 1 with each request */
+ /* end of SEQ1 */
+
+ aimbs_putle16(&fr->data, 0x000e); /* Length of SEQ2 */
+ aimbs_putle16(&fr->data, dc); /* Sequence number? same as above
+ * XXX - This should decrement by 1 with each request */
+ aimbs_putle32(&fr->data, 0x00000000); /* Unknown */
+ aimbs_putle32(&fr->data, 0x00000000); /* Unknown */
+ aimbs_putle32(&fr->data, 0x00000000); /* Unknown */
+ /* end of SEQ2 */
+
+ /* now for the real fun */
+ aimbs_putle8(&fr->data, state); /* away state */
+ aimbs_putle8(&fr->data, 0x03); /* msg-flag: 03 for states */
+ aimbs_putle16(&fr->data, 0x0000); /* status code ? */
+ aimbs_putle16(&fr->data, 0x0000); /* priority code */
+ aimbs_putle16(&fr->data, strlen(message) + 1); /* message length + termination */
+ aimbs_putraw(&fr->data, (guint8 *) message, strlen(message) + 1); /* null terminated string */
+
+ aim_tx_enqueue(sess, fr);
+
+
+ return 0;
+}
+
+
+static int outgoingim(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ int i, ret = 0;
+ aim_rxcallback_t userfunc;
+ guint8 cookie[8];
+ guint16 channel;
+ aim_tlvlist_t *tlvlist;
+ char *sn;
+ int snlen;
+ guint16 icbmflags = 0;
+ guint8 flag1 = 0, flag2 = 0;
+ char *msg = NULL;
+ aim_tlv_t *msgblock;
+
+ /* ICBM Cookie. */
+ for (i = 0; i < 8; i++)
+ cookie[i] = aimbs_get8(bs);
+
+ /* Channel ID */
+ channel = aimbs_get16(bs);
+
+ if (channel != 0x01) {
+ imcb_error(sess->aux_data, "icbm: ICBM received on unsupported channel. Ignoring.");
+ return 0;
+ }
+
+ snlen = aimbs_get8(bs);
+ sn = aimbs_getstr(bs, snlen);
+
+ tlvlist = aim_readtlvchain(bs);
+
+ if (aim_gettlv(tlvlist, 0x0003, 1))
+ icbmflags |= AIM_IMFLAGS_ACK;
+ if (aim_gettlv(tlvlist, 0x0004, 1))
+ icbmflags |= AIM_IMFLAGS_AWAY;
+
+ if ((msgblock = aim_gettlv(tlvlist, 0x0002, 1))) {
+ aim_bstream_t mbs;
+ int featurelen, msglen;
+
+ aim_bstream_init(&mbs, msgblock->value, msgblock->length);
+
+ aimbs_get8(&mbs);
+ aimbs_get8(&mbs);
+ for (featurelen = aimbs_get16(&mbs); featurelen; featurelen--)
+ aimbs_get8(&mbs);
+ aimbs_get8(&mbs);
+ aimbs_get8(&mbs);
+
+ msglen = aimbs_get16(&mbs) - 4; /* final block length */
+
+ flag1 = aimbs_get16(&mbs);
+ flag2 = aimbs_get16(&mbs);
+
+ msg = aimbs_getstr(&mbs, msglen);
+ }
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, channel, sn, msg, icbmflags, flag1, flag2);
+
+ g_free(sn);
+ aim_freetlvchain(&tlvlist);
+
+ return ret;
+}
+
+/*
+ * Ahh, the joys of nearly ridiculous over-engineering.
+ *
+ * Not only do AIM ICBM's support multiple channels. Not only do they
+ * support multiple character sets. But they support multiple character
+ * sets / encodings within the same ICBM.
+ *
+ * These multipart messages allow for complex space savings techniques, which
+ * seem utterly unnecessary by today's standards. In fact, there is only
+ * one client still in popular use that still uses this method: AOL for the
+ * Macintosh, Version 5.0. Obscure, yes, I know.
+ *
+ * In modern (non-"legacy") clients, if the user tries to send a character
+ * that is not ISO-8859-1 or ASCII, the client will send the entire message
+ * as UNICODE, meaning that every character in the message will occupy the
+ * full 16 bit UNICODE field, even if the high order byte would be zero.
+ * Multipart messages prevent this wasted space by allowing the client to
+ * only send the characters in UNICODE that need to be sent that way, and
+ * the rest of the message can be sent in whatever the native character
+ * set is (probably ASCII).
+ *
+ * An important note is that sections will be displayed in the order that
+ * they appear in the ICBM. There is no facility for merging or rearranging
+ * sections at run time. So if you have, say, ASCII then UNICODE then ASCII,
+ * you must supply two ASCII sections with a UNICODE in the middle, and incur
+ * the associated overhead.
+ *
+ * Normally I would have laughed and given a firm 'no' to supporting this
+ * seldom-used feature, but something is attracting me to it. In the future,
+ * it may be possible to abuse this to send mixed-media messages to other
+ * open source clients (like encryption or something) -- see faimtest for
+ * examples of how to do this.
+ *
+ * I would definitly recommend avoiding this feature unless you really
+ * know what you are doing, and/or you have something neat to do with it.
+ *
+ */
+int aim_mpmsg_init(aim_session_t *sess, aim_mpmsg_t *mpm)
+{
+
+ memset(mpm, 0, sizeof(aim_mpmsg_t));
+
+ return 0;
+}
+
+static int mpmsg_addsection(aim_session_t *sess, aim_mpmsg_t *mpm, guint16 charset, guint16 charsubset, guint8 *data, guint16 datalen)
+{
+ aim_mpmsg_section_t *sec;
+
+ if (!(sec = g_new0(aim_mpmsg_section_t,1)))
+ return -1;
+
+ sec->charset = charset;
+ sec->charsubset = charsubset;
+ sec->data = data;
+ sec->datalen = datalen;
+ sec->next = NULL;
+
+ if (!mpm->parts)
+ mpm->parts = sec;
+ else {
+ aim_mpmsg_section_t *cur;
+
+ for (cur = mpm->parts; cur->next; cur = cur->next)
+ ;
+ cur->next = sec;
+ }
+
+ mpm->numparts++;
+
+ return 0;
+}
+
+int aim_mpmsg_addraw(aim_session_t *sess, aim_mpmsg_t *mpm, guint16 charset, guint16 charsubset, const guint8 *data, guint16 datalen)
+{
+ guint8 *dup;
+
+ if (!(dup = g_malloc(datalen)))
+ return -1;
+ memcpy(dup, data, datalen);
+
+ if (mpmsg_addsection(sess, mpm, charset, charsubset, dup, datalen) == -1) {
+ g_free(dup);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* XXX should provide a way of saying ISO-8859-1 specifically */
+int aim_mpmsg_addascii(aim_session_t *sess, aim_mpmsg_t *mpm, const char *ascii)
+{
+ char *dup;
+
+ if (!(dup = g_strdup(ascii)))
+ return -1;
+
+ if (mpmsg_addsection(sess, mpm, 0x0000, 0x0000, (guint8 *)dup, (guint16) strlen(ascii)) == -1) {
+ g_free(dup);
+ return -1;
+ }
+
+ return 0;
+}
+
+int aim_mpmsg_addunicode(aim_session_t *sess, aim_mpmsg_t *mpm, const guint16 *unicode, guint16 unicodelen)
+{
+ guint8 *buf;
+ aim_bstream_t bs;
+ int i;
+
+ if (!(buf = g_malloc(unicodelen * 2)))
+ return -1;
+
+ aim_bstream_init(&bs, buf, unicodelen * 2);
+
+ /* We assume unicode is in /host/ byte order -- convert to network */
+ for (i = 0; i < unicodelen; i++)
+ aimbs_put16(&bs, unicode[i]);
+
+ if (mpmsg_addsection(sess, mpm, 0x0002, 0x0000, buf, aim_bstream_curpos(&bs)) == -1) {
+ g_free(buf);
+ return -1;
+ }
+
+ return 0;
+}
+
+void aim_mpmsg_free(aim_session_t *sess, aim_mpmsg_t *mpm)
+{
+ aim_mpmsg_section_t *cur;
+
+ for (cur = mpm->parts; cur; ) {
+ aim_mpmsg_section_t *tmp;
+
+ tmp = cur->next;
+ g_free(cur->data);
+ g_free(cur);
+ cur = tmp;
+ }
+
+ mpm->numparts = 0;
+ mpm->parts = NULL;
+
+ return;
+}
+
+/*
+ * Start by building the multipart structures, then pick the first
+ * human-readable section and stuff it into args->msg so no one gets
+ * suspicious.
+ *
+ */
+static int incomingim_ch1_parsemsgs(aim_session_t *sess, guint8 *data, int len, struct aim_incomingim_ch1_args *args)
+{
+ static const guint16 charsetpri[] = {
+ 0x0000, /* ASCII first */
+ 0x0003, /* then ISO-8859-1 */
+ 0x0002, /* UNICODE as last resort */
+ };
+ static const int charsetpricount = 3;
+ int i;
+ aim_bstream_t mbs;
+ aim_mpmsg_section_t *sec;
+
+ aim_bstream_init(&mbs, data, len);
+
+ while (aim_bstream_empty(&mbs)) {
+ guint16 msglen, flag1, flag2;
+ char *msgbuf;
+
+ aimbs_get8(&mbs); /* 01 */
+ aimbs_get8(&mbs); /* 01 */
+
+ /* Message string length, including character set info. */
+ msglen = aimbs_get16(&mbs);
+
+ /* Character set info */
+ flag1 = aimbs_get16(&mbs);
+ flag2 = aimbs_get16(&mbs);
+
+ /* Message. */
+ msglen -= 4;
+
+ /*
+ * For now, we don't care what the encoding is. Just copy
+ * it into a multipart struct and deal with it later. However,
+ * always pad the ending with a NULL. This makes it easier
+ * to treat ASCII sections as strings. It won't matter for
+ * UNICODE or binary data, as you should never read past
+ * the specified data length, which will not include the pad.
+ *
+ * XXX There's an API bug here. For sending, the UNICODE is
+ * given in host byte order (aim_mpmsg_addunicode), but here
+ * the received messages are given in network byte order.
+ *
+ */
+ msgbuf = aimbs_getstr(&mbs, msglen);
+ mpmsg_addsection(sess, &args->mpmsg, flag1, flag2, (guint8 *)msgbuf, (guint16) msglen);
+
+ } /* while */
+
+ args->icbmflags |= AIM_IMFLAGS_MULTIPART; /* always set */
+
+ /*
+ * Clients that support multiparts should never use args->msg, as it
+ * will point to an arbitrary section.
+ *
+ * Here, we attempt to provide clients that do not support multipart
+ * messages with something to look at -- hopefully a human-readable
+ * string. But, failing that, a UNICODE message, or nothing at all.
+ *
+ * Which means that even if args->msg is NULL, it does not mean the
+ * message was blank.
+ *
+ */
+ for (i = 0; i < charsetpricount; i++) {
+ for (sec = args->mpmsg.parts; sec; sec = sec->next) {
+
+ if (sec->charset != charsetpri[i])
+ continue;
+
+ /* Great. We found one. Fill it in. */
+ args->charset = sec->charset;
+ args->charsubset = sec->charsubset;
+ args->icbmflags |= AIM_IMFLAGS_CUSTOMCHARSET;
+
+ /* Set up the simple flags */
+ if (args->charset == 0x0000)
+ ; /* ASCII */
+ else if (args->charset == 0x0002)
+ args->icbmflags |= AIM_IMFLAGS_UNICODE;
+ else if (args->charset == 0x0003)
+ args->icbmflags |= AIM_IMFLAGS_ISO_8859_1;
+ else if (args->charset == 0xffff)
+ ; /* no encoding (yeep!) */
+
+ if (args->charsubset == 0x0000)
+ ; /* standard subencoding? */
+ else if (args->charsubset == 0x000b)
+ args->icbmflags |= AIM_IMFLAGS_SUBENC_MACINTOSH;
+ else if (args->charsubset == 0xffff)
+ ; /* no subencoding */
+#if 0
+ /* XXX this isn't really necesary... */
+ if ( ((args.flag1 != 0x0000) &&
+ (args.flag1 != 0x0002) &&
+ (args.flag1 != 0x0003) &&
+ (args.flag1 != 0xffff)) ||
+ ((args.flag2 != 0x0000) &&
+ (args.flag2 != 0x000b) &&
+ (args.flag2 != 0xffff))) {
+ faimdprintf(sess, 0, "icbm: **warning: encoding flags are being used! {%04x, %04x}\n", args.flag1, args.flag2);
+ }
+#endif
+
+ args->msg = (char *)sec->data;
+ args->msglen = sec->datalen;
+
+ return 0;
+ }
+ }
+
+ /* No human-readable sections found. Oh well. */
+ args->charset = args->charsubset = 0xffff;
+ args->msg = NULL;
+ args->msglen = 0;
+
+ return 0;
+}
+
+static int incomingim_ch1(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, guint16 channel, aim_userinfo_t *userinfo, aim_bstream_t *bs, guint8 *cookie)
+{
+ guint16 type, length;
+ aim_rxcallback_t userfunc;
+ int ret = 0;
+ struct aim_incomingim_ch1_args args;
+ int endpos;
+
+ memset(&args, 0, sizeof(args));
+
+ aim_mpmsg_init(sess, &args.mpmsg);
+
+ /*
+ * This used to be done using tlvchains. For performance reasons,
+ * I've changed it to process the TLVs in-place. This avoids lots
+ * of per-IM memory allocations.
+ */
+ while (aim_bstream_empty(bs)) {
+
+ type = aimbs_get16(bs);
+ length = aimbs_get16(bs);
+
+ endpos = aim_bstream_curpos(bs) + length;
+
+ if (type == 0x0002) { /* Message Block */
+
+ /*
+ * This TLV consists of the following:
+ * - 0501 -- Unknown
+ * - Features: Don't know how to interpret these
+ * - 0101 -- Unknown
+ * - Message
+ *
+ */
+
+ aimbs_get8(bs); /* 05 */
+ aimbs_get8(bs); /* 01 */
+
+ args.featureslen = aimbs_get16(bs);
+ /* XXX XXX this is all evil! */
+ args.features = bs->data + bs->offset;
+ aim_bstream_advance(bs, args.featureslen);
+ args.icbmflags |= AIM_IMFLAGS_CUSTOMFEATURES;
+
+ /*
+ * The rest of the TLV contains one or more message
+ * blocks...
+ */
+ incomingim_ch1_parsemsgs(sess, bs->data + bs->offset /* XXX evil!!! */, length - 2 - 2 - args.featureslen, &args);
+
+ } else if (type == 0x0003) { /* Server Ack Requested */
+
+ args.icbmflags |= AIM_IMFLAGS_ACK;
+
+ } else if (type == 0x0004) { /* Message is Auto Response */
+
+ args.icbmflags |= AIM_IMFLAGS_AWAY;
+
+ } else if (type == 0x0006) { /* Message was received offline. */
+
+ /* XXX not sure if this actually gets sent. */
+ args.icbmflags |= AIM_IMFLAGS_OFFLINE;
+
+ } else if (type == 0x0008) { /* I-HAVE-A-REALLY-PURTY-ICON Flag */
+
+ args.iconlen = aimbs_get32(bs);
+ aimbs_get16(bs); /* 0x0001 */
+ args.iconsum = aimbs_get16(bs);
+ args.iconstamp = aimbs_get32(bs);
+
+ /*
+ * This looks to be a client bug. MacAIM 4.3 will
+ * send this tag, but with all zero values, in the
+ * first message of a conversation. This makes no
+ * sense whatsoever, so I'm going to say its a bug.
+ *
+ * You really shouldn't advertise a zero-length icon
+ * anyway.
+ *
+ */
+ if (args.iconlen)
+ args.icbmflags |= AIM_IMFLAGS_HASICON;
+
+ } else if (type == 0x0009) {
+
+ args.icbmflags |= AIM_IMFLAGS_BUDDYREQ;
+
+ } else if (type == 0x0017) {
+
+ args.extdatalen = length;
+ args.extdata = aimbs_getraw(bs, args.extdatalen);
+
+ } else {
+ // imcb_error(sess->aux_data, "Unknown TLV encountered");
+ }
+
+ /*
+ * This is here to protect ourselves from ourselves. That
+ * is, if something above doesn't completly parse its value
+ * section, or, worse, overparses it, this will set the
+ * stream where it needs to be in order to land on the next
+ * TLV when the loop continues.
+ *
+ */
+ aim_bstream_setpos(bs, endpos);
+ }
+
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, channel, userinfo, &args);
+
+ aim_mpmsg_free(sess, &args.mpmsg);
+ g_free(args.extdata);
+
+ return ret;
+}
+
+
+static void incomingim_ch2_chat_free(aim_session_t *sess, struct aim_incomingim_ch2_args *args)
+{
+
+ /* XXX aim_chat_roominfo_free() */
+ g_free(args->info.chat.roominfo.name);
+
+ return;
+}
+
+static void incomingim_ch2_chat(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args, aim_bstream_t *servdata)
+{
+
+ /*
+ * Chat room info.
+ */
+ if (servdata)
+ aim_chat_readroominfo(servdata, &args->info.chat.roominfo);
+
+ args->destructor = (void *)incomingim_ch2_chat_free;
+
+ return;
+}
+
+static void incomingim_ch2_icqserverrelay_free(aim_session_t *sess, struct aim_incomingim_ch2_args *args)
+{
+
+ g_free((char *)args->info.rtfmsg.rtfmsg);
+
+ return;
+}
+
+/*
+ * The relationship between AIM_CAPS_ICQSERVERRELAY and AIM_CAPS_ICQRTF is
+ * kind of odd. This sends the client ICQRTF since that is all that I've seen
+ * SERVERRELAY used for.
+ *
+ * Note that this is all little-endian. Cringe.
+ *
+ * This cap is used for auto status message replies, too [ft]
+ *
+ */
+static void incomingim_ch2_icqserverrelay(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args, aim_bstream_t *servdata)
+{
+ guint16 hdrlen, msglen, dc;
+ guint8 msgtype, msgflags;
+ guint8 *plugin;
+ int i = 0, tmp = 0;
+ struct im_connection *ic = sess->aux_data;
+
+ /* at the moment we just can deal with requests, not with cancel or accept */
+ if (args->status != 0) return;
+
+ hdrlen = aimbs_getle16(servdata);
+
+ aim_bstream_advance(servdata, 0x02); /* protocol version */
+ plugin = aimbs_getraw(servdata, 0x10); /* following data is a message or
+ something plugin specific */
+ /* as there is no plugin handling, just skip the rest */
+ aim_bstream_advance(servdata, hdrlen - 0x12);
+
+ hdrlen = aimbs_getle16(servdata);
+ dc = aimbs_getle16(servdata); /* save the sequence number */
+ aim_bstream_advance(servdata, hdrlen - 0x02);
+
+ /* TODO is it a message or something for a plugin? */
+ for (i = 0; i < 0x10; i++) {
+ tmp |= plugin[i];
+ }
+
+ if (!tmp) { /* message follows */
+
+ msgtype = aimbs_getle8(servdata);
+ msgflags = aimbs_getle8(servdata);
+
+ aim_bstream_advance(servdata, 0x04); /* status code and priority code */
+
+ msglen = aimbs_getle16(servdata); /* message string length */
+ args->info.rtfmsg.rtfmsg = aimbs_getstr(servdata, msglen);
+
+ switch(msgtype) {
+ case AIM_MTYPE_PLAIN:
+
+ args->info.rtfmsg.fgcolor = aimbs_getle32(servdata);
+ args->info.rtfmsg.bgcolor = aimbs_getle32(servdata);
+
+ hdrlen = aimbs_getle32(servdata);
+ aim_bstream_advance(servdata, hdrlen);
+
+ /* XXX This is such a hack. */
+ args->reqclass = AIM_CAPS_ICQRTF;
+ break;
+
+ case AIM_MTYPE_AUTOAWAY:
+ case AIM_MTYPE_AUTOBUSY:
+ case AIM_MTYPE_AUTONA:
+ case AIM_MTYPE_AUTODND:
+ case AIM_MTYPE_AUTOFFC:
+ case 0x9c: /* ICQ 5 seems to send this */
+ aim_send_im_ch2_statusmessage(sess, userinfo->sn, args->cookie,
+ ic->away ? ic->away : "", sess->aim_icq_state, dc);
+ break;
+
+ }
+ } /* message or plugin specific */
+
+ g_free(plugin);
+ args->destructor = (void *)incomingim_ch2_icqserverrelay_free;
+
+ return;
+}
+
+typedef void (*ch2_args_destructor_t)(aim_session_t *sess, struct aim_incomingim_ch2_args *args);
+
+static int incomingim_ch2(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, guint16 channel, aim_userinfo_t *userinfo, aim_tlvlist_t *tlvlist, guint8 *cookie)
+{
+ aim_rxcallback_t userfunc;
+ aim_tlv_t *block1, *servdatatlv;
+ aim_tlvlist_t *list2;
+ struct aim_incomingim_ch2_args args;
+ aim_bstream_t bbs, sdbs, *sdbsptr = NULL;
+ guint8 *cookie2;
+ int ret = 0;
+
+ char clientip1[30] = {""};
+ char clientip2[30] = {""};
+ char verifiedip[30] = {""};
+
+ memset(&args, 0, sizeof(args));
+
+ /*
+ * There's another block of TLVs embedded in the type 5 here.
+ */
+ block1 = aim_gettlv(tlvlist, 0x0005, 1);
+ aim_bstream_init(&bbs, block1->value, block1->length);
+
+ /*
+ * First two bytes represent the status of the connection.
+ *
+ * 0 is a request, 1 is a deny (?), 2 is an accept
+ */
+ args.status = aimbs_get16(&bbs);
+
+ /*
+ * Next comes the cookie. Should match the ICBM cookie.
+ */
+ cookie2 = aimbs_getraw(&bbs, 8);
+ if (memcmp(cookie, cookie2, 8) != 0)
+ imcb_error(sess->aux_data, "rend: warning cookies don't match!");
+ memcpy(args.cookie, cookie2, 8);
+ g_free(cookie2);
+
+ /*
+ * The next 16bytes are a capability block so we can
+ * identify what type of rendezvous this is.
+ */
+ args.reqclass = aim_getcap(sess, &bbs, 0x10);
+
+ /*
+ * What follows may be TLVs or nothing, depending on the
+ * purpose of the message.
+ *
+ * Ack packets for instance have nothing more to them.
+ */
+ list2 = aim_readtlvchain(&bbs);
+
+ /*
+ * IP address from the perspective of the client.
+ */
+ if (aim_gettlv(list2, 0x0002, 1)) {
+ aim_tlv_t *iptlv;
+
+ iptlv = aim_gettlv(list2, 0x0002, 1);
+
+ g_snprintf(clientip1, sizeof(clientip1), "%d.%d.%d.%d",
+ aimutil_get8(iptlv->value+0),
+ aimutil_get8(iptlv->value+1),
+ aimutil_get8(iptlv->value+2),
+ aimutil_get8(iptlv->value+3));
+ }
+
+ /*
+ * Secondary IP address from the perspective of the client.
+ */
+ if (aim_gettlv(list2, 0x0003, 1)) {
+ aim_tlv_t *iptlv;
+
+ iptlv = aim_gettlv(list2, 0x0003, 1);
+
+ g_snprintf(clientip2, sizeof(clientip2), "%d.%d.%d.%d",
+ aimutil_get8(iptlv->value+0),
+ aimutil_get8(iptlv->value+1),
+ aimutil_get8(iptlv->value+2),
+ aimutil_get8(iptlv->value+3));
+ }
+
+ /*
+ * Verified IP address (from the perspective of Oscar).
+ *
+ * This is added by the server.
+ */
+ if (aim_gettlv(list2, 0x0004, 1)) {
+ aim_tlv_t *iptlv;
+
+ iptlv = aim_gettlv(list2, 0x0004, 1);
+
+ g_snprintf(verifiedip, sizeof(verifiedip), "%d.%d.%d.%d",
+ aimutil_get8(iptlv->value+0),
+ aimutil_get8(iptlv->value+1),
+ aimutil_get8(iptlv->value+2),
+ aimutil_get8(iptlv->value+3));
+ }
+
+ /*
+ * Port number for something.
+ */
+ if (aim_gettlv(list2, 0x0005, 1))
+ args.port = aim_gettlv16(list2, 0x0005, 1);
+
+ /*
+ * Error code.
+ */
+ if (aim_gettlv(list2, 0x000b, 1))
+ args.errorcode = aim_gettlv16(list2, 0x000b, 1);
+
+ /*
+ * Invitation message / chat description.
+ */
+ if (aim_gettlv(list2, 0x000c, 1))
+ args.msg = aim_gettlv_str(list2, 0x000c, 1);
+
+ /*
+ * Character set.
+ */
+ if (aim_gettlv(list2, 0x000d, 1))
+ args.encoding = aim_gettlv_str(list2, 0x000d, 1);
+
+ /*
+ * Language.
+ */
+ if (aim_gettlv(list2, 0x000e, 1))
+ args.language = aim_gettlv_str(list2, 0x000e, 1);
+
+ /* Unknown -- two bytes = 0x0001 */
+ if (aim_gettlv(list2, 0x000a, 1))
+ ;
+
+ /* Unknown -- no value */
+ if (aim_gettlv(list2, 0x000f, 1))
+ ;
+
+ if (strlen(clientip1))
+ args.clientip = (char *)clientip1;
+ if (strlen(clientip2))
+ args.clientip2 = (char *)clientip2;
+ if (strlen(verifiedip))
+ args.verifiedip = (char *)verifiedip;
+
+ /*
+ * This is must be present in PROPOSALs, but will probably not
+ * exist in CANCELs and ACCEPTs.
+ *
+ * Service Data blocks are module-specific in format.
+ */
+ if ((servdatatlv = aim_gettlv(list2, 0x2711 /* 10001 */, 1))) {
+
+ aim_bstream_init(&sdbs, servdatatlv->value, servdatatlv->length);
+ sdbsptr = &sdbs;
+ }
+
+ if (args.reqclass & AIM_CAPS_ICQSERVERRELAY)
+ incomingim_ch2_icqserverrelay(sess, mod, rx, snac, userinfo, &args, sdbsptr);
+ else if (args.reqclass & AIM_CAPS_CHAT)
+ incomingim_ch2_chat(sess, mod, rx, snac, userinfo, &args, sdbsptr);
+
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, channel, userinfo, &args);
+
+
+ if (args.destructor)
+ ((ch2_args_destructor_t)args.destructor)(sess, &args);
+
+ g_free((char *)args.msg);
+ g_free((char *)args.encoding);
+ g_free((char *)args.language);
+
+ aim_freetlvchain(&list2);
+
+ return ret;
+}
+
+static int incomingim_ch4(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, guint16 channel, aim_userinfo_t *userinfo, aim_tlvlist_t *tlvlist, guint8 *cookie)
+{
+ aim_bstream_t meat;
+ aim_rxcallback_t userfunc;
+ aim_tlv_t *block;
+ struct aim_incomingim_ch4_args args;
+ int ret = 0;
+
+ /*
+ * Make a bstream for the meaty part. Yum. Meat.
+ */
+ if (!(block = aim_gettlv(tlvlist, 0x0005, 1)))
+ return -1;
+ aim_bstream_init(&meat, block->value, block->length);
+
+ args.uin = aimbs_getle32(&meat);
+ args.type = aimbs_getle16(&meat);
+ args.msg = (char *)aimbs_getraw(&meat, aimbs_getle16(&meat));
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, channel, userinfo, &args);
+
+ g_free(args.msg);
+
+ return ret;
+}
+
+/*
+ * It can easily be said that parsing ICBMs is THE single
+ * most difficult thing to do in the in AIM protocol. In
+ * fact, I think I just did say that.
+ *
+ * Below is the best damned solution I've come up with
+ * over the past sixteen months of battling with it. This
+ * can parse both away and normal messages from every client
+ * I have access to. Its not fast, its not clean. But it works.
+ *
+ */
+static int incomingim(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ int i, ret = 0;
+ guint8 cookie[8];
+ guint16 channel;
+ aim_userinfo_t userinfo;
+
+ memset(&userinfo, 0x00, sizeof(aim_userinfo_t));
+
+ /*
+ * Read ICBM Cookie. And throw away.
+ */
+ for (i = 0; i < 8; i++)
+ cookie[i] = aimbs_get8(bs);
+
+ /*
+ * Channel ID.
+ *
+ * Channel 0x0001 is the message channel. There are
+ * other channels for things called "rendevous"
+ * which represent chat and some of the other new
+ * features of AIM2/3/3.5.
+ *
+ * Channel 0x0002 is the Rendevous channel, which
+ * is where Chat Invitiations and various client-client
+ * connection negotiations come from.
+ *
+ * Channel 0x0004 is used for ICQ authorization, or
+ * possibly any system notice.
+ *
+ */
+ channel = aimbs_get16(bs);
+
+ /*
+ * Extract the standard user info block.
+ *
+ * Note that although this contains TLVs that appear contiguous
+ * with the TLVs read below, they are two different pieces. The
+ * userinfo block contains the number of TLVs that contain user
+ * information, the rest are not even though there is no seperation.
+ * aim_extractuserinfo() returns the number of bytes used by the
+ * userinfo tlvs, so you can start reading the rest of them right
+ * afterward.
+ *
+ * That also means that TLV types can be duplicated between the
+ * userinfo block and the rest of the message, however there should
+ * never be two TLVs of the same type in one block.
+ *
+ */
+ aim_extractuserinfo(sess, bs, &userinfo);
+
+ /*
+ * From here on, its depends on what channel we're on.
+ *
+ * Technically all channels have a TLV list have this, however,
+ * for the common channel 1 case, in-place parsing is used for
+ * performance reasons (less memory allocation).
+ */
+ if (channel == 1) {
+
+ ret = incomingim_ch1(sess, mod, rx, snac, channel, &userinfo, bs, cookie);
+
+ } else if (channel == 2) {
+ aim_tlvlist_t *tlvlist;
+
+ /*
+ * Read block of TLVs (not including the userinfo data). All
+ * further data is derived from what is parsed here.
+ */
+ tlvlist = aim_readtlvchain(bs);
+
+ ret = incomingim_ch2(sess, mod, rx, snac, channel, &userinfo, tlvlist, cookie);
+
+ aim_freetlvchain(&tlvlist);
+
+ } else if (channel == 4) {
+ aim_tlvlist_t *tlvlist;
+
+ tlvlist = aim_readtlvchain(bs);
+ ret = incomingim_ch4(sess, mod, rx, snac, channel, &userinfo, tlvlist, cookie);
+ aim_freetlvchain(&tlvlist);
+
+ } else {
+
+ imcb_error(sess->aux_data, "ICBM received on an unsupported channel. Ignoring.");
+
+ return 0;
+ }
+
+ return ret;
+}
+
+/*
+ * Possible codes:
+ * AIM_TRANSFER_DENY_NOTSUPPORTED -- "client does not support"
+ * AIM_TRANSFER_DENY_DECLINE -- "client has declined transfer"
+ * AIM_TRANSFER_DENY_NOTACCEPTING -- "client is not accepting transfers"
+ *
+ */
+int aim_denytransfer(aim_session_t *sess, const char *sender, const guint8 *cookie, guint16 code)
+{
+ aim_conn_t *conn;
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ aim_tlvlist_t *tl = NULL;
+
+ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+8+2+1+strlen(sender)+6)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0004, 0x000b, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0004, 0x000b, 0x0000, snacid);
+
+ aimbs_putraw(&fr->data, cookie, 8);
+
+ aimbs_put16(&fr->data, 0x0002); /* channel */
+ aimbs_put8(&fr->data, strlen(sender));
+ aimbs_putraw(&fr->data, (guint8 *)sender, strlen(sender));
+
+ aim_addtlvtochain16(&tl, 0x0003, code);
+ aim_writetlvchain(&fr->data, &tl);
+ aim_freetlvchain(&tl);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * aim_reqicbmparaminfo()
+ *
+ * Request ICBM parameter information.
+ *
+ */
+int aim_reqicbmparams(aim_session_t *sess)
+{
+ aim_conn_t *conn;
+
+ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+ return -EINVAL;
+
+ return aim_genericreq_n(sess, conn, 0x0004, 0x0004);
+}
+
+/*
+ *
+ * I definitly recommend sending this. If you don't, you'll be stuck
+ * with the rather unreasonable defaults. You don't want those. Send this.
+ *
+ */
+int aim_seticbmparam(aim_session_t *sess, struct aim_icbmparameters *params)
+{
+ aim_conn_t *conn;
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+ return -EINVAL;
+
+ if (!params)
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+16)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0004, 0x0002, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0004, 0x0002, 0x0000, snacid);
+
+ /* This is read-only (see Parameter Reply). Must be set to zero here. */
+ aimbs_put16(&fr->data, 0x0000);
+
+ /* These are all read-write */
+ aimbs_put32(&fr->data, params->flags);
+ aimbs_put16(&fr->data, params->maxmsglen);
+ aimbs_put16(&fr->data, params->maxsenderwarn);
+ aimbs_put16(&fr->data, params->maxrecverwarn);
+ aimbs_put32(&fr->data, params->minmsginterval);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+static int paraminfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ struct aim_icbmparameters params;
+ aim_rxcallback_t userfunc;
+
+ params.maxchan = aimbs_get16(bs);
+ params.flags = aimbs_get32(bs);
+ params.maxmsglen = aimbs_get16(bs);
+ params.maxsenderwarn = aimbs_get16(bs);
+ params.maxrecverwarn = aimbs_get16(bs);
+ params.minmsginterval = aimbs_get32(bs);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ return userfunc(sess, rx, &params);
+
+ return 0;
+}
+
+static int missedcall(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ int ret = 0;
+ aim_rxcallback_t userfunc;
+ guint16 channel, nummissed, reason;
+ aim_userinfo_t userinfo;
+
+ while (aim_bstream_empty(bs)) {
+
+ channel = aimbs_get16(bs);
+ aim_extractuserinfo(sess, bs, &userinfo);
+ nummissed = aimbs_get16(bs);
+ reason = aimbs_get16(bs);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, channel, &userinfo, nummissed, reason);
+ }
+
+ return ret;
+}
+
+/*
+ * Receive the response from an ICQ status message request. This contains the
+ * ICQ status message. Go figure.
+ */
+static int clientautoresp(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ int ret = 0;
+ aim_rxcallback_t userfunc;
+ guint16 channel, reason;
+ char *sn;
+ guint8 *ck, snlen;
+
+ ck = aimbs_getraw(bs, 8);
+ channel = aimbs_get16(bs);
+ snlen = aimbs_get8(bs);
+ sn = aimbs_getstr(bs, snlen);
+ reason = aimbs_get16(bs);
+
+ switch (reason) {
+ case 0x0003: { /* ICQ status message. Maybe other stuff too, you never know with these people. */
+ guint8 statusmsgtype, *msg;
+ guint16 len;
+ guint32 state;
+
+ len = aimbs_getle16(bs); /* Should be 0x001b */
+ aim_bstream_advance(bs, len); /* Unknown */
+
+ len = aimbs_getle16(bs); /* Should be 0x000e */
+ aim_bstream_advance(bs, len); /* Unknown */
+
+ statusmsgtype = aimbs_getle8(bs);
+ switch (statusmsgtype) {
+ case 0xe8:
+ state = AIM_ICQ_STATE_AWAY;
+ break;
+ case 0xe9:
+ state = AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_BUSY;
+ break;
+ case 0xea:
+ state = AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_OUT;
+ break;
+ case 0xeb:
+ state = AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_DND | AIM_ICQ_STATE_BUSY;
+ break;
+ case 0xec:
+ state = AIM_ICQ_STATE_CHAT;
+ break;
+ default:
+ state = 0;
+ break;
+ }
+
+ aimbs_getle8(bs); /* Unknown - 0x03 Maybe this means this is an auto-reply */
+ aimbs_getle16(bs); /* Unknown - 0x0000 */
+ aimbs_getle16(bs); /* Unknown - 0x0000 */
+
+ len = aimbs_getle16(bs);
+ msg = aimbs_getraw(bs, len);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, channel, sn, reason, state, msg);
+
+ g_free(msg);
+ } break;
+
+ default: {
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, channel, sn, reason);
+ } break;
+ } /* end switch */
+
+ g_free(ck);
+ g_free(sn);
+
+ return ret;
+}
+
+static int msgack(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_rxcallback_t userfunc;
+ guint16 type;
+ guint8 snlen, *ck;
+ char *sn;
+ int ret = 0;
+
+ ck = aimbs_getraw(bs, 8);
+ type = aimbs_get16(bs);
+ snlen = aimbs_get8(bs);
+ sn = aimbs_getstr(bs, snlen);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, type, sn);
+
+ g_free(sn);
+ g_free(ck);
+
+ return ret;
+}
+
+/*
+ * Subtype 0x0014 - Send a mini typing notification (mtn) packet.
+ *
+ * This is supported by winaim5 and newer, MacAIM bleh and newer, iChat bleh and newer,
+ * and Gaim 0.60 and newer.
+ *
+ */
+int aim_im_sendmtn(aim_session_t *sess, guint16 type1, const char *sn, guint16 type2)
+{
+ aim_conn_t *conn;
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0002)))
+ return -EINVAL;
+
+ if (!sn)
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+11+strlen(sn)+2)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0004, 0x0014, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0004, 0x0014, 0x0000, snacid);
+
+ /*
+ * 8 days of light
+ * Er, that is to say, 8 bytes of 0's
+ */
+ aimbs_put16(&fr->data, 0x0000);
+ aimbs_put16(&fr->data, 0x0000);
+ aimbs_put16(&fr->data, 0x0000);
+ aimbs_put16(&fr->data, 0x0000);
+
+ /*
+ * Type 1 (should be 0x0001 for mtn)
+ */
+ aimbs_put16(&fr->data, type1);
+
+ /*
+ * Dest sn
+ */
+ aimbs_put8(&fr->data, strlen(sn));
+ aimbs_putraw(&fr->data, (const guint8*)sn, strlen(sn));
+
+ /*
+ * Type 2 (should be 0x0000, 0x0001, or 0x0002 for mtn)
+ */
+ aimbs_put16(&fr->data, type2);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * Subtype 0x0014 - Receive a mini typing notification (mtn) packet.
+ *
+ * This is supported by winaim5 and newer, MacAIM bleh and newer, iChat bleh and newer,
+ * and Gaim 0.60 and newer.
+ *
+ */
+static int mtn_receive(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ int ret = 0;
+ aim_rxcallback_t userfunc;
+ char *sn;
+ guint8 snlen;
+ guint16 type1, type2;
+
+ aim_bstream_advance(bs, 8); /* Unknown - All 0's */
+ type1 = aimbs_get16(bs);
+ snlen = aimbs_get8(bs);
+ sn = aimbs_getstr(bs, snlen);
+ type2 = aimbs_get16(bs);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, type1, sn, type2);
+
+ g_free(sn);
+
+ return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+ if (snac->subtype == 0x0005)
+ return paraminfo(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x0006)
+ return outgoingim(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x0007)
+ return incomingim(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x000a)
+ return missedcall(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x000b)
+ return clientautoresp(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x000c)
+ return msgack(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x0014)
+ return mtn_receive(sess, mod, rx, snac, bs);
+
+ return 0;
+}
+
+int msg_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+ mod->family = 0x0004;
+ mod->version = 0x0001;
+ mod->toolid = 0x0110;
+ mod->toolversion = 0x0629;
+ mod->flags = 0;
+ strncpy(mod->name, "messaging", sizeof(mod->name));
+ mod->snachandler = snachandler;
+
+ return 0;
+}
diff --git a/protocols/oscar/im.h b/protocols/oscar/im.h
new file mode 100644
index 00000000..42a8a6b1
--- /dev/null
+++ b/protocols/oscar/im.h
@@ -0,0 +1,200 @@
+#ifndef __OSCAR_IM_H__
+#define __OSCAR_IM_H__
+
+#define AIM_CB_FAM_MSG 0x0004
+
+/*
+ * SNAC Family: Messaging Services.
+ */
+#define AIM_CB_MSG_ERROR 0x0001
+#define AIM_CB_MSG_PARAMINFO 0x0005
+#define AIM_CB_MSG_INCOMING 0x0007
+#define AIM_CB_MSG_EVIL 0x0009
+#define AIM_CB_MSG_MISSEDCALL 0x000a
+#define AIM_CB_MSG_CLIENTAUTORESP 0x000b
+#define AIM_CB_MSG_ACK 0x000c
+#define AIM_CB_MSG_MTN 0x0014
+#define AIM_CB_MSG_DEFAULT 0xffff
+
+#define AIM_IMFLAGS_AWAY 0x0001 /* mark as an autoreply */
+#define AIM_IMFLAGS_ACK 0x0002 /* request a receipt notice */
+#define AIM_IMFLAGS_UNICODE 0x0004
+#define AIM_IMFLAGS_ISO_8859_1 0x0008
+#define AIM_IMFLAGS_BUDDYREQ 0x0010 /* buddy icon requested */
+#define AIM_IMFLAGS_HASICON 0x0020 /* already has icon */
+#define AIM_IMFLAGS_SUBENC_MACINTOSH 0x0040 /* damn that Steve Jobs! */
+#define AIM_IMFLAGS_CUSTOMFEATURES 0x0080 /* features field present */
+#define AIM_IMFLAGS_EXTDATA 0x0100
+#define AIM_IMFLAGS_CUSTOMCHARSET 0x0200 /* charset fields set */
+#define AIM_IMFLAGS_MULTIPART 0x0400 /* ->mpmsg section valid */
+#define AIM_IMFLAGS_OFFLINE 0x0800 /* send to offline user */
+
+/*
+ * Multipart message structures.
+ */
+typedef struct aim_mpmsg_section_s {
+ guint16 charset;
+ guint16 charsubset;
+ guint8 *data;
+ guint16 datalen;
+ struct aim_mpmsg_section_s *next;
+} aim_mpmsg_section_t;
+
+typedef struct aim_mpmsg_s {
+ int numparts;
+ aim_mpmsg_section_t *parts;
+} aim_mpmsg_t;
+
+int aim_mpmsg_init(aim_session_t *sess, aim_mpmsg_t *mpm);
+int aim_mpmsg_addraw(aim_session_t *sess, aim_mpmsg_t *mpm, guint16 charset, guint16 charsubset, const guint8 *data, guint16 datalen);
+int aim_mpmsg_addascii(aim_session_t *sess, aim_mpmsg_t *mpm, const char *ascii);
+int aim_mpmsg_addunicode(aim_session_t *sess, aim_mpmsg_t *mpm, const guint16 *unicode, guint16 unicodelen);
+void aim_mpmsg_free(aim_session_t *sess, aim_mpmsg_t *mpm);
+
+/*
+ * Arguments to aim_send_im_ext().
+ *
+ * This is really complicated. But immensely versatile.
+ *
+ */
+struct aim_sendimext_args {
+
+ /* These are _required_ */
+ const char *destsn;
+ guint32 flags; /* often 0 */
+
+ /* Only required if not using multipart messages */
+ const char *msg;
+ int msglen;
+
+ /* Required if ->msg is not provided */
+ aim_mpmsg_t *mpmsg;
+
+ /* Only used if AIM_IMFLAGS_HASICON is set */
+ guint32 iconlen;
+ time_t iconstamp;
+ guint32 iconsum;
+
+ /* Only used if AIM_IMFLAGS_CUSTOMFEATURES is set */
+ guint8 *features;
+ guint8 featureslen;
+
+ /* Only used if AIM_IMFLAGS_CUSTOMCHARSET is set and mpmsg not used */
+ guint16 charset;
+ guint16 charsubset;
+};
+
+/*
+ * Arguments to aim_send_rtfmsg().
+ */
+struct aim_sendrtfmsg_args {
+ const char *destsn;
+ guint32 fgcolor;
+ guint32 bgcolor;
+ const char *rtfmsg; /* must be in RTF */
+};
+
+/*
+ * This information is provided in the Incoming ICBM callback for
+ * Channel 1 ICBM's.
+ *
+ * Note that although CUSTOMFEATURES and CUSTOMCHARSET say they
+ * are optional, both are always set by the current libfaim code.
+ * That may or may not change in the future. It is mainly for
+ * consistency with aim_sendimext_args.
+ *
+ * Multipart messages require some explanation. If you want to use them,
+ * I suggest you read all the comments in im.c.
+ *
+ */
+struct aim_incomingim_ch1_args {
+
+ /* Always provided */
+ aim_mpmsg_t mpmsg;
+ guint32 icbmflags; /* some flags apply only to ->msg, not all mpmsg */
+
+ /* Only provided if message has a human-readable section */
+ char *msg;
+ int msglen;
+
+ /* Only provided if AIM_IMFLAGS_HASICON is set */
+ time_t iconstamp;
+ guint32 iconlen;
+ guint16 iconsum;
+
+ /* Only provided if AIM_IMFLAGS_CUSTOMFEATURES is set */
+ guint8 *features;
+ guint8 featureslen;
+
+ /* Only provided if AIM_IMFLAGS_EXTDATA is set */
+ guint8 extdatalen;
+ guint8 *extdata;
+
+ /* Only used if AIM_IMFLAGS_CUSTOMCHARSET is set */
+ guint16 charset;
+ guint16 charsubset;
+};
+
+/* Valid values for channel 2 args->status */
+#define AIM_RENDEZVOUS_PROPOSE 0x0000
+#define AIM_RENDEZVOUS_CANCEL 0x0001
+#define AIM_RENDEZVOUS_ACCEPT 0x0002
+
+struct aim_incomingim_ch2_args {
+ guint8 cookie[8];
+ guint16 reqclass;
+ guint16 status;
+ guint16 errorcode;
+ const char *clientip;
+ const char *clientip2;
+ const char *verifiedip;
+ guint16 port;
+ const char *msg; /* invite message or file description */
+ const char *encoding;
+ const char *language;
+ union {
+ struct {
+ guint32 checksum;
+ guint32 length;
+ time_t timestamp;
+ guint8 *icon;
+ } icon;
+ struct {
+ struct aim_chat_roominfo roominfo;
+ } chat;
+ struct {
+ guint32 fgcolor;
+ guint32 bgcolor;
+ const char *rtfmsg;
+ } rtfmsg;
+ } info;
+ void *destructor; /* used internally only */
+};
+
+/* Valid values for channel 4 args->type */
+#define AIM_ICQMSG_AUTHREQUEST 0x0006
+#define AIM_ICQMSG_AUTHDENIED 0x0007
+#define AIM_ICQMSG_AUTHGRANTED 0x0008
+
+struct aim_incomingim_ch4_args {
+ guint32 uin; /* Of the sender of the ICBM */
+ guint16 type;
+ char *msg; /* Reason for auth request, deny, or accept */
+};
+
+int aim_send_rtfmsg(aim_session_t *sess, struct aim_sendrtfmsg_args *args);
+int aim_send_im_ext(aim_session_t *sess, struct aim_sendimext_args *args);
+int aim_send_im(aim_session_t *, const char *destsn, unsigned short flags, const char *msg);
+int aim_send_icon(aim_session_t *sess, const char *sn, const guint8 *icon, int iconlen, time_t stamp, guint16 iconsum);
+guint16 aim_iconsum(const guint8 *buf, int buflen);
+int aim_send_typing(aim_session_t *sess, aim_conn_t *conn, int typing);
+int aim_send_im_direct(aim_session_t *, aim_conn_t *, const char *msg, int len);
+const char *aim_directim_getsn(aim_conn_t *conn);
+aim_conn_t *aim_directim_initiate(aim_session_t *, const char *destsn);
+aim_conn_t *aim_directim_connect(aim_session_t *, const char *sn, const char *addr, const guint8 *cookie);
+
+int aim_send_im_ch2_geticqmessage(aim_session_t *sess, const char *sn, int type);
+int aim_im_sendmtn(aim_session_t *sess, guint16 type1, const char *sn, guint16 type2);
+int aim_send_im_ch2_statusmessage(aim_session_t *sess, const char *sender, const guint8 *cookie, const char *message, const guint8 state, const guint16 dc);
+
+#endif /* __OSCAR_IM_H__ */
diff --git a/protocols/oscar/info.c b/protocols/oscar/info.c
new file mode 100644
index 00000000..7cc1dbbc
--- /dev/null
+++ b/protocols/oscar/info.c
@@ -0,0 +1,726 @@
+/*
+ * aim_info.c
+ *
+ * The functions here are responsible for requesting and parsing information-
+ * gathering SNACs. Or something like that.
+ *
+ */
+
+#include <aim.h>
+#include "info.h"
+
+struct aim_priv_inforeq {
+ char sn[MAXSNLEN+1];
+ guint16 infotype;
+};
+
+int aim_getinfo(aim_session_t *sess, aim_conn_t *conn, const char *sn, guint16 infotype)
+{
+ struct aim_priv_inforeq privdata;
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!sess || !conn || !sn)
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 12+1+strlen(sn))))
+ return -ENOMEM;
+
+ strncpy(privdata.sn, sn, sizeof(privdata.sn));
+ privdata.infotype = infotype;
+ snacid = aim_cachesnac(sess, 0x0002, 0x0005, 0x0000, &privdata, sizeof(struct aim_priv_inforeq));
+
+ aim_putsnac(&fr->data, 0x0002, 0x0005, 0x0000, snacid);
+ aimbs_put16(&fr->data, infotype);
+ aimbs_put8(&fr->data, strlen(sn));
+ aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn));
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+const char *aim_userinfo_sn(aim_userinfo_t *ui)
+{
+
+ if (!ui)
+ return NULL;
+
+ return ui->sn;
+}
+
+guint16 aim_userinfo_flags(aim_userinfo_t *ui)
+{
+
+ if (!ui)
+ return 0;
+
+ return ui->flags;
+}
+
+guint16 aim_userinfo_idle(aim_userinfo_t *ui)
+{
+
+ if (!ui)
+ return 0;
+
+ return ui->idletime;
+}
+
+float aim_userinfo_warnlevel(aim_userinfo_t *ui)
+{
+
+ if (!ui)
+ return 0.00;
+
+ return (ui->warnlevel / 10);
+}
+
+time_t aim_userinfo_membersince(aim_userinfo_t *ui)
+{
+
+ if (!ui)
+ return 0;
+
+ return (time_t)ui->membersince;
+}
+
+time_t aim_userinfo_onlinesince(aim_userinfo_t *ui)
+{
+
+ if (!ui)
+ return 0;
+
+ return (time_t)ui->onlinesince;
+}
+
+guint32 aim_userinfo_sessionlen(aim_userinfo_t *ui)
+{
+
+ if (!ui)
+ return 0;
+
+ return ui->sessionlen;
+}
+
+int aim_userinfo_hascap(aim_userinfo_t *ui, guint32 cap)
+{
+
+ if (!ui || !(ui->present & AIM_USERINFO_PRESENT_CAPABILITIES))
+ return -1;
+
+ return !!(ui->capabilities & cap);
+}
+
+
+/*
+ * Capability blocks.
+ *
+ * These are CLSIDs. They should actually be of the form:
+ *
+ * {0x0946134b, 0x4c7f, 0x11d1,
+ * {0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}},
+ *
+ * But, eh.
+ */
+static const struct {
+ guint32 flag;
+ guint8 data[16];
+} aim_caps[] = {
+
+ /*
+ * Chat is oddball.
+ */
+ {AIM_CAPS_CHAT,
+ {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1,
+ 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+ /*
+ * These are mostly in order.
+ */
+ {AIM_CAPS_VOICE,
+ {0x09, 0x46, 0x13, 0x41, 0x4c, 0x7f, 0x11, 0xd1,
+ 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+ {AIM_CAPS_SENDFILE,
+ {0x09, 0x46, 0x13, 0x43, 0x4c, 0x7f, 0x11, 0xd1,
+ 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+ /*
+ * Advertised by the EveryBuddy client.
+ */
+ {AIM_CAPS_ICQ,
+ {0x09, 0x46, 0x13, 0x44, 0x4c, 0x7f, 0x11, 0xd1,
+ 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+ {AIM_CAPS_IMIMAGE,
+ {0x09, 0x46, 0x13, 0x45, 0x4c, 0x7f, 0x11, 0xd1,
+ 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+ {AIM_CAPS_BUDDYICON,
+ {0x09, 0x46, 0x13, 0x46, 0x4c, 0x7f, 0x11, 0xd1,
+ 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+ {AIM_CAPS_SAVESTOCKS,
+ {0x09, 0x46, 0x13, 0x47, 0x4c, 0x7f, 0x11, 0xd1,
+ 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+ {AIM_CAPS_GETFILE,
+ {0x09, 0x46, 0x13, 0x48, 0x4c, 0x7f, 0x11, 0xd1,
+ 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+ /*
+ * Client supports channel 2 extended, TLV(0x2711) based messages.
+ * Currently used only by ICQ clients. ICQ clients and clones use this GUID
+ * as message format sign. Trillian client use another GUID in channel 2
+ * messages to implement its own message format (trillian doesn't use
+ * TLV(x2711) in SecureIM channel 2 messages!).
+ */
+ {AIM_CAPS_ICQSERVERRELAY,
+ {0x09, 0x46, 0x13, 0x49, 0x4c, 0x7f, 0x11, 0xd1,
+ 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+ /*
+ * Indeed, there are two of these. The former appears to be correct,
+ * but in some versions of winaim, the second one is set. Either they
+ * forgot to fix endianness, or they made a typo. It really doesn't
+ * matter which.
+ */
+ {AIM_CAPS_GAMES,
+ {0x09, 0x46, 0x13, 0x4a, 0x4c, 0x7f, 0x11, 0xd1,
+ 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+ {AIM_CAPS_GAMES2,
+ {0x09, 0x46, 0x13, 0x4a, 0x4c, 0x7f, 0x11, 0xd1,
+ 0x22, 0x82, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+ {AIM_CAPS_SENDBUDDYLIST,
+ {0x09, 0x46, 0x13, 0x4b, 0x4c, 0x7f, 0x11, 0xd1,
+ 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+ {AIM_CAPS_UTF8,
+ {0x09, 0x46, 0x13, 0x4E, 0x4C, 0x7F, 0x11, 0xD1,
+ 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+ {AIM_CAPS_ICQRTF,
+ {0x97, 0xb1, 0x27, 0x51, 0x24, 0x3c, 0x43, 0x34,
+ 0xad, 0x22, 0xd6, 0xab, 0xf7, 0x3f, 0x14, 0x92}},
+
+ {AIM_CAPS_ICQUNKNOWN,
+ {0x2e, 0x7a, 0x64, 0x75, 0xfa, 0xdf, 0x4d, 0xc8,
+ 0x88, 0x6f, 0xea, 0x35, 0x95, 0xfd, 0xb6, 0xdf}},
+
+ {AIM_CAPS_EMPTY,
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
+
+ {AIM_CAPS_TRILLIANCRYPT,
+ {0xf2, 0xe7, 0xc7, 0xf4, 0xfe, 0xad, 0x4d, 0xfb,
+ 0xb2, 0x35, 0x36, 0x79, 0x8b, 0xdf, 0x00, 0x00}},
+
+ {AIM_CAPS_APINFO,
+ {0xAA, 0x4A, 0x32, 0xB5, 0xF8, 0x84, 0x48, 0xc6,
+ 0xA3, 0xD7, 0x8C, 0x50, 0x97, 0x19, 0xFD, 0x5B}},
+
+ {AIM_CAPS_INTEROP,
+ {0x09, 0x46, 0x13, 0x4d, 0x4c, 0x7f, 0x11, 0xd1,
+ 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+ {AIM_CAPS_ICHAT,
+ {0x09, 0x46, 0x00, 0x00, 0x4c, 0x7f, 0x11, 0xd1,
+ 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+ {AIM_CAPS_LAST}
+};
+
+/*
+ * This still takes a length parameter even with a bstream because capabilities
+ * are not naturally bounded.
+ *
+ */
+guint32 aim_getcap(aim_session_t *sess, aim_bstream_t *bs, int len)
+{
+ guint32 flags = 0;
+ int offset;
+
+ for (offset = 0; aim_bstream_empty(bs) && (offset < len); offset += 0x10) {
+ guint8 *cap;
+ int i, identified;
+
+ cap = aimbs_getraw(bs, 0x10);
+
+ for (i = 0, identified = 0; !(aim_caps[i].flag & AIM_CAPS_LAST); i++) {
+
+ if (memcmp(&aim_caps[i].data, cap, 0x10) == 0) {
+ flags |= aim_caps[i].flag;
+ identified++;
+ break; /* should only match once... */
+
+ }
+ }
+
+ if (!identified) {
+ /*FIXME*/
+ /*REMOVEME :-)
+ g_strdup_printf("unknown capability: {%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x}\n",
+ cap[0], cap[1], cap[2], cap[3],
+ cap[4], cap[5],
+ cap[6], cap[7],
+ cap[8], cap[9],
+ cap[10], cap[11], cap[12], cap[13],
+ cap[14], cap[15]);
+ */
+ }
+
+ g_free(cap);
+ }
+
+ return flags;
+}
+
+int aim_putcap(aim_bstream_t *bs, guint32 caps)
+{
+ int i;
+
+ if (!bs)
+ return -EINVAL;
+
+ for (i = 0; aim_bstream_empty(bs); i++) {
+
+ if (aim_caps[i].flag == AIM_CAPS_LAST)
+ break;
+
+ if (caps & aim_caps[i].flag)
+ aimbs_putraw(bs, aim_caps[i].data, 0x10);
+
+ }
+
+ return 0;
+}
+
+/*
+ * AIM is fairly regular about providing user info. This is a generic
+ * routine to extract it in its standard form.
+ */
+int aim_extractuserinfo(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *outinfo)
+{
+ int curtlv, tlvcnt;
+ guint8 snlen;
+
+ if (!bs || !outinfo)
+ return -EINVAL;
+
+ /* Clear out old data first */
+ memset(outinfo, 0x00, sizeof(aim_userinfo_t));
+
+ /*
+ * Screen name. Stored as an unterminated string prepended with a
+ * byte containing its length.
+ */
+ snlen = aimbs_get8(bs);
+ aimbs_getrawbuf(bs, (guint8 *)outinfo->sn, snlen);
+
+ /*
+ * Warning Level. Stored as an unsigned short.
+ */
+ outinfo->warnlevel = aimbs_get16(bs);
+
+ /*
+ * TLV Count. Unsigned short representing the number of
+ * Type-Length-Value triples that follow.
+ */
+ tlvcnt = aimbs_get16(bs);
+
+ /*
+ * Parse out the Type-Length-Value triples as they're found.
+ */
+ for (curtlv = 0; curtlv < tlvcnt; curtlv++) {
+ int endpos;
+ guint16 type, length;
+
+ type = aimbs_get16(bs);
+ length = aimbs_get16(bs);
+
+ endpos = aim_bstream_curpos(bs) + length;
+
+ if (type == 0x0001) {
+ /*
+ * Type = 0x0001: User flags
+ *
+ * Specified as any of the following ORed together:
+ * 0x0001 Trial (user less than 60days)
+ * 0x0002 Unknown bit 2
+ * 0x0004 AOL Main Service user
+ * 0x0008 Unknown bit 4
+ * 0x0010 Free (AIM) user
+ * 0x0020 Away
+ * 0x0400 ActiveBuddy
+ *
+ */
+ outinfo->flags = aimbs_get16(bs);
+ outinfo->present |= AIM_USERINFO_PRESENT_FLAGS;
+
+ } else if (type == 0x0002) {
+ /*
+ * Type = 0x0002: Member-Since date.
+ *
+ * The time/date that the user originally registered for
+ * the service, stored in time_t format.
+ */
+ outinfo->membersince = aimbs_get32(bs);
+ outinfo->present |= AIM_USERINFO_PRESENT_MEMBERSINCE;
+
+ } else if (type == 0x0003) {
+ /*
+ * Type = 0x0003: On-Since date.
+ *
+ * The time/date that the user started their current
+ * session, stored in time_t format.
+ */
+ outinfo->onlinesince = aimbs_get32(bs);
+ outinfo->present |= AIM_USERINFO_PRESENT_ONLINESINCE;
+
+ } else if (type == 0x0004) {
+ /*
+ * Type = 0x0004: Idle time.
+ *
+ * Number of seconds since the user actively used the
+ * service.
+ *
+ * Note that the client tells the server when to start
+ * counting idle times, so this may or may not be
+ * related to reality.
+ */
+ outinfo->idletime = aimbs_get16(bs);
+ outinfo->present |= AIM_USERINFO_PRESENT_IDLE;
+
+ } else if (type == 0x0006) {
+ /*
+ * Type = 0x0006: ICQ Online Status
+ *
+ * ICQ's Away/DND/etc "enriched" status. Some decoding
+ * of values done by Scott <darkagl@pcnet.com>
+ */
+ aimbs_get16(bs);
+ outinfo->icqinfo.status = aimbs_get16(bs);
+ outinfo->present |= AIM_USERINFO_PRESENT_ICQEXTSTATUS;
+
+ } else if (type == 0x000a) {
+ /*
+ * Type = 0x000a
+ *
+ * ICQ User IP Address.
+ * Ahh, the joy of ICQ security.
+ */
+ outinfo->icqinfo.ipaddr = aimbs_get32(bs);
+ outinfo->present |= AIM_USERINFO_PRESENT_ICQIPADDR;
+
+ } else if (type == 0x000c) {
+ /*
+ * Type = 0x000c
+ *
+ * random crap containing the IP address,
+ * apparently a port number, and some Other Stuff.
+ *
+ */
+ aimbs_getrawbuf(bs, outinfo->icqinfo.crap, 0x25);
+ outinfo->present |= AIM_USERINFO_PRESENT_ICQDATA;
+
+ } else if (type == 0x000d) {
+ /*
+ * Type = 0x000d
+ *
+ * Capability information.
+ *
+ */
+ outinfo->capabilities = aim_getcap(sess, bs, length);
+ outinfo->present |= AIM_USERINFO_PRESENT_CAPABILITIES;
+
+ } else if (type == 0x000e) {
+ /*
+ * Type = 0x000e
+ *
+ * Unknown. Always of zero length, and always only
+ * on AOL users.
+ *
+ * Ignore.
+ *
+ */
+
+ } else if ((type == 0x000f) || (type == 0x0010)) {
+ /*
+ * Type = 0x000f: Session Length. (AIM)
+ * Type = 0x0010: Session Length. (AOL)
+ *
+ * The duration, in seconds, of the user's current
+ * session.
+ *
+ * Which TLV type this comes in depends on the
+ * service the user is using (AIM or AOL).
+ *
+ */
+ outinfo->sessionlen = aimbs_get32(bs);
+ outinfo->present |= AIM_USERINFO_PRESENT_SESSIONLEN;
+
+ } else {
+
+ /*
+ * Reaching here indicates that either AOL has
+ * added yet another TLV for us to deal with,
+ * or the parsing has gone Terribly Wrong.
+ *
+ * Either way, inform the owner and attempt
+ * recovery.
+ *
+ */
+#ifdef DEBUG
+ // imcb_error(sess->aux_data, G_STRLOC);
+#endif
+
+ }
+
+ /* Save ourselves. */
+ aim_bstream_setpos(bs, endpos);
+ }
+
+ return 0;
+}
+
+/*
+ * Inverse of aim_extractuserinfo()
+ */
+int aim_putuserinfo(aim_bstream_t *bs, aim_userinfo_t *info)
+{
+ aim_tlvlist_t *tlvlist = NULL;
+
+ if (!bs || !info)
+ return -EINVAL;
+
+ aimbs_put8(bs, strlen(info->sn));
+ aimbs_putraw(bs, (guint8 *)info->sn, strlen(info->sn));
+
+ aimbs_put16(bs, info->warnlevel);
+
+
+ aim_addtlvtochain16(&tlvlist, 0x0001, info->flags);
+ aim_addtlvtochain32(&tlvlist, 0x0002, info->membersince);
+ aim_addtlvtochain32(&tlvlist, 0x0003, info->onlinesince);
+ aim_addtlvtochain16(&tlvlist, 0x0004, info->idletime);
+
+#if ICQ_OSCAR_SUPPORT
+ if (atoi(info->sn) != 0) {
+ aim_addtlvtochain16(&tlvlist, 0x0006, info->icqinfo.status);
+ aim_addtlvtochain32(&tlvlist, 0x000a, info->icqinfo.ipaddr);
+ }
+#endif
+
+ aim_addtlvtochain_caps(&tlvlist, 0x000d, info->capabilities);
+
+ aim_addtlvtochain32(&tlvlist, (guint16)((info->flags & AIM_FLAG_AOL) ? 0x0010 : 0x000f), info->sessionlen);
+
+ aimbs_put16(bs, aim_counttlvchain(&tlvlist));
+ aim_writetlvchain(bs, &tlvlist);
+ aim_freetlvchain(&tlvlist);
+
+ return 0;
+}
+
+int aim_sendbuddyoncoming(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *info)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!sess || !conn || !info)
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0003, 0x000b, 0x0000, NULL, 0);
+
+ aim_putsnac(&fr->data, 0x0003, 0x000b, 0x0000, snacid);
+ aim_putuserinfo(&fr->data, info);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+int aim_sendbuddyoffgoing(aim_session_t *sess, aim_conn_t *conn, const char *sn)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!sess || !conn || !sn)
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+1+strlen(sn))))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0003, 0x000c, 0x0000, NULL, 0);
+
+ aim_putsnac(&fr->data, 0x0003, 0x000c, 0x0000, snacid);
+ aimbs_put8(&fr->data, strlen(sn));
+ aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn));
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * Huh? What is this?
+ */
+int aim_0002_000b(aim_session_t *sess, aim_conn_t *conn, const char *sn)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!sess || !conn || !sn)
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+1+strlen(sn))))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0002, 0x000b, 0x0000, NULL, 0);
+
+ aim_putsnac(&fr->data, 0x0002, 0x000b, 0x0000, snacid);
+ aimbs_put8(&fr->data, strlen(sn));
+ aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn));
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * Normally contains:
+ * t(0001) - short containing max profile length (value = 1024)
+ * t(0002) - short - unknown (value = 16) [max MIME type length?]
+ * t(0003) - short - unknown (value = 10)
+ * t(0004) - short - unknown (value = 2048) [ICQ only?]
+ */
+static int rights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_tlvlist_t *tlvlist;
+ aim_rxcallback_t userfunc;
+ int ret = 0;
+ guint16 maxsiglen = 0;
+
+ tlvlist = aim_readtlvchain(bs);
+
+ if (aim_gettlv(tlvlist, 0x0001, 1))
+ maxsiglen = aim_gettlv16(tlvlist, 0x0001, 1);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, maxsiglen);
+
+ aim_freetlvchain(&tlvlist);
+
+ return ret;
+}
+
+static int userinfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_userinfo_t userinfo;
+ char *text_encoding = NULL, *text = NULL;
+ guint16 text_length = 0;
+ aim_rxcallback_t userfunc;
+ aim_tlvlist_t *tlvlist;
+ aim_tlv_t *tlv;
+ aim_snac_t *origsnac = NULL;
+ struct aim_priv_inforeq *inforeq;
+ int ret = 0;
+
+ origsnac = aim_remsnac(sess, snac->id);
+
+ if (!origsnac || !origsnac->data) {
+ imcb_error(sess->aux_data, "major problem: no snac stored!");
+ return 0;
+ }
+
+ inforeq = (struct aim_priv_inforeq *)origsnac->data;
+
+ if ((inforeq->infotype != AIM_GETINFO_GENERALINFO) &&
+ (inforeq->infotype != AIM_GETINFO_AWAYMESSAGE) &&
+ (inforeq->infotype != AIM_GETINFO_CAPABILITIES)) {
+ imcb_error(sess->aux_data, "unknown infotype in request!");
+ return 0;
+ }
+
+ aim_extractuserinfo(sess, bs, &userinfo);
+
+ tlvlist = aim_readtlvchain(bs);
+
+ /*
+ * Depending on what informational text was requested, different
+ * TLVs will appear here.
+ *
+ * Profile will be 1 and 2, away message will be 3 and 4, caps
+ * will be 5.
+ */
+ if (inforeq->infotype == AIM_GETINFO_GENERALINFO) {
+ text_encoding = aim_gettlv_str(tlvlist, 0x0001, 1);
+ if((tlv = aim_gettlv(tlvlist, 0x0002, 1))) {
+ text = g_new0(char, tlv->length);
+ memcpy(text, tlv->value, tlv->length);
+ text_length = tlv->length;
+ }
+ } else if (inforeq->infotype == AIM_GETINFO_AWAYMESSAGE) {
+ text_encoding = aim_gettlv_str(tlvlist, 0x0003, 1);
+ if((tlv = aim_gettlv(tlvlist, 0x0004, 1))) {
+ text = g_new0(char, tlv->length);
+ memcpy(text, tlv->value, tlv->length);
+ text_length = tlv->length;
+ }
+ } else if (inforeq->infotype == AIM_GETINFO_CAPABILITIES) {
+ aim_tlv_t *ct;
+
+ if ((ct = aim_gettlv(tlvlist, 0x0005, 1))) {
+ aim_bstream_t cbs;
+
+ aim_bstream_init(&cbs, ct->value, ct->length);
+
+ userinfo.capabilities = aim_getcap(sess, &cbs, ct->length);
+ userinfo.present = AIM_USERINFO_PRESENT_CAPABILITIES;
+ }
+ }
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, &userinfo, inforeq->infotype, text_encoding, text, text_length);
+
+ g_free(text_encoding);
+ g_free(text);
+
+ aim_freetlvchain(&tlvlist);
+
+ if (origsnac)
+ g_free(origsnac->data);
+ g_free(origsnac);
+
+ return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+ if (snac->subtype == 0x0003)
+ return rights(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x0006)
+ return userinfo(sess, mod, rx, snac, bs);
+
+ return 0;
+}
+
+int locate_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+ mod->family = 0x0002;
+ mod->version = 0x0001;
+ mod->toolid = 0x0110;
+ mod->toolversion = 0x0629;
+ mod->flags = 0;
+ strncpy(mod->name, "locate", sizeof(mod->name));
+ mod->snachandler = snachandler;
+
+ return 0;
+}
diff --git a/protocols/oscar/info.h b/protocols/oscar/info.h
new file mode 100644
index 00000000..b4d99e9f
--- /dev/null
+++ b/protocols/oscar/info.h
@@ -0,0 +1,44 @@
+#ifndef __OSCAR_INFO_H__
+#define __OSCAR_INFO_H__
+
+#define AIM_CB_FAM_LOC 0x0002
+
+/*
+ * SNAC Family: Location Services.
+ */
+#define AIM_CB_LOC_ERROR 0x0001
+#define AIM_CB_LOC_REQRIGHTS 0x0002
+#define AIM_CB_LOC_RIGHTSINFO 0x0003
+#define AIM_CB_LOC_SETUSERINFO 0x0004
+#define AIM_CB_LOC_REQUSERINFO 0x0005
+#define AIM_CB_LOC_USERINFO 0x0006
+#define AIM_CB_LOC_WATCHERSUBREQ 0x0007
+#define AIM_CB_LOC_WATCHERNOT 0x0008
+#define AIM_CB_LOC_DEFAULT 0xffff
+
+#define AIM_CAPS_BUDDYICON 0x00000001
+#define AIM_CAPS_VOICE 0x00000002
+#define AIM_CAPS_IMIMAGE 0x00000004
+#define AIM_CAPS_CHAT 0x00000008
+#define AIM_CAPS_GETFILE 0x00000010
+#define AIM_CAPS_SENDFILE 0x00000020
+#define AIM_CAPS_GAMES 0x00000040
+#define AIM_CAPS_SAVESTOCKS 0x00000080
+#define AIM_CAPS_SENDBUDDYLIST 0x00000100
+#define AIM_CAPS_GAMES2 0x00000200
+#define AIM_CAPS_ICQ 0x00000400
+#define AIM_CAPS_APINFO 0x00000800
+#define AIM_CAPS_ICQRTF 0x00001000
+#define AIM_CAPS_EMPTY 0x00002000
+#define AIM_CAPS_ICQSERVERRELAY 0x00004000
+#define AIM_CAPS_ICQUNKNOWN 0x00008000
+#define AIM_CAPS_TRILLIANCRYPT 0x00010000
+#define AIM_CAPS_UTF8 0x00020000
+#define AIM_CAPS_INTEROP 0x00040000
+#define AIM_CAPS_ICHAT 0x00080000
+#define AIM_CAPS_EXTCHAN2 0x00100000
+#define AIM_CAPS_LAST 0x00200000
+
+int aim_0002_000b(aim_session_t *sess, aim_conn_t *conn, const char *sn);
+
+#endif /* __OSCAR_INFO_H__ */
diff --git a/protocols/oscar/misc.c b/protocols/oscar/misc.c
new file mode 100644
index 00000000..e5c5c26f
--- /dev/null
+++ b/protocols/oscar/misc.c
@@ -0,0 +1,396 @@
+
+/*
+ * aim_misc.c
+ *
+ * TODO: Seperate a lot of this into an aim_bos.c.
+ *
+ * Other things...
+ *
+ * - Idle setting
+ *
+ *
+ */
+
+#include <aim.h>
+
+/*
+ * aim_bos_setbuddylist(buddylist)
+ *
+ * This just builds the "set buddy list" command then queues it.
+ *
+ * buddy_list = "Screen Name One&ScreenNameTwo&";
+ *
+ * TODO: Clean this up.
+ *
+ * XXX: I can't stress the TODO enough.
+ *
+ */
+int aim_bos_setbuddylist(aim_session_t *sess, aim_conn_t *conn, const char *buddy_list)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ int len = 0;
+ char *localcpy = NULL;
+ char *tmpptr = NULL;
+
+ if (!buddy_list || !(localcpy = g_strdup(buddy_list)))
+ return -EINVAL;
+
+ for (tmpptr = strtok(localcpy, "&"); tmpptr; ) {
+ len += 1 + strlen(tmpptr);
+ tmpptr = strtok(NULL, "&");
+ }
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+len)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0003, 0x0004, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0003, 0x0004, 0x0000, snacid);
+
+ strncpy(localcpy, buddy_list, strlen(buddy_list) + 1);
+
+ for (tmpptr = strtok(localcpy, "&"); tmpptr; ) {
+
+ aimbs_put8(&fr->data, strlen(tmpptr));
+ aimbs_putraw(&fr->data, (guint8 *)tmpptr, strlen(tmpptr));
+ tmpptr = strtok(NULL, "&");
+ }
+
+ aim_tx_enqueue(sess, fr);
+
+ g_free(localcpy);
+
+ return 0;
+}
+
+/*
+ * aim_bos_setprofile(profile)
+ *
+ * Gives BOS your profile.
+ *
+ */
+int aim_bos_setprofile(aim_session_t *sess, aim_conn_t *conn, const char *profile, const char *awaymsg, guint32 caps)
+{
+ static const char defencoding[] = {"text/aolrtf; charset=\"utf-8\""};
+ aim_frame_t *fr;
+ aim_tlvlist_t *tl = NULL;
+ aim_snacid_t snacid;
+
+ /* Build to packet first to get real length */
+ if (profile) {
+ aim_addtlvtochain_raw(&tl, 0x0001, strlen(defencoding), (guint8 *)defencoding);
+ aim_addtlvtochain_raw(&tl, 0x0002, strlen(profile), (guint8 *)profile);
+ }
+
+ /*
+ * So here's how this works:
+ * - You are away when you have a non-zero-length type 4 TLV stored.
+ * - You become unaway when you clear the TLV with a zero-length
+ * type 4 TLV.
+ * - If you do not send the type 4 TLV, your status does not change
+ * (that is, if you were away, you'll remain away).
+ */
+ if (awaymsg) {
+ if (strlen(awaymsg)) {
+ aim_addtlvtochain_raw(&tl, 0x0003, strlen(defencoding), (guint8 *)defencoding);
+ aim_addtlvtochain_raw(&tl, 0x0004, strlen(awaymsg), (guint8 *)awaymsg);
+ } else
+ aim_addtlvtochain_noval(&tl, 0x0004);
+ }
+
+ aim_addtlvtochain_caps(&tl, 0x0005, caps);
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + aim_sizetlvchain(&tl))))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0002, 0x0004, 0x0000, NULL, 0);
+
+ aim_putsnac(&fr->data, 0x0002, 0x004, 0x0000, snacid);
+ aim_writetlvchain(&fr->data, &tl);
+ aim_freetlvchain(&tl);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * aim_bos_reqbuddyrights()
+ *
+ * Request Buddy List rights.
+ *
+ */
+int aim_bos_reqbuddyrights(aim_session_t *sess, aim_conn_t *conn)
+{
+ return aim_genericreq_n(sess, conn, 0x0003, 0x0002);
+}
+
+/*
+ * Send a warning to destsn.
+ *
+ * Flags:
+ * AIM_WARN_ANON Send as an anonymous (doesn't count as much)
+ *
+ * returns -1 on error (couldn't alloc packet), 0 on success.
+ *
+ */
+int aim_send_warning(aim_session_t *sess, aim_conn_t *conn, const char *destsn, guint32 flags)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ guint16 outflags = 0x0000;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, strlen(destsn)+13)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0004, 0x0008, 0x0000, destsn, strlen(destsn)+1);
+
+ aim_putsnac(&fr->data, 0x0004, 0x0008, 0x0000, snacid);
+
+ if (flags & AIM_WARN_ANON)
+ outflags |= 0x0001;
+
+ aimbs_put16(&fr->data, outflags);
+ aimbs_put8(&fr->data, strlen(destsn));
+ aimbs_putraw(&fr->data, (guint8 *)destsn, strlen(destsn));
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * Generic routine for sending commands.
+ *
+ *
+ * I know I can do this in a smarter way...but I'm not thinking straight
+ * right now...
+ *
+ * I had one big function that handled all three cases, but then it broke
+ * and I split it up into three. But then I fixed it. I just never went
+ * back to the single. I don't see any advantage to doing it either way.
+ *
+ */
+int aim_genericreq_n(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 subtype)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid = 0x00000000;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10)))
+ return -ENOMEM;
+
+ aim_putsnac(&fr->data, family, subtype, 0x0000, snacid);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+int aim_genericreq_n_snacid(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 subtype)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, family, subtype, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, family, subtype, 0x0000, snacid);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+int aim_genericreq_l(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 subtype, guint32 *longdata)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!longdata)
+ return aim_genericreq_n(sess, conn, family, subtype);
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+4)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, family, subtype, 0x0000, NULL, 0);
+
+ aim_putsnac(&fr->data, family, subtype, 0x0000, snacid);
+ aimbs_put32(&fr->data, *longdata);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+int aim_genericreq_s(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 subtype, guint16 *shortdata)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!shortdata)
+ return aim_genericreq_n(sess, conn, family, subtype);
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, family, subtype, 0x0000, NULL, 0);
+
+ aim_putsnac(&fr->data, family, subtype, 0x0000, snacid);
+ aimbs_put16(&fr->data, *shortdata);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * aim_bos_reqlocaterights()
+ *
+ * Request Location services rights.
+ *
+ */
+int aim_bos_reqlocaterights(aim_session_t *sess, aim_conn_t *conn)
+{
+ return aim_genericreq_n(sess, conn, 0x0002, 0x0002);
+}
+
+/*
+ * Set directory profile data (not the same as aim_bos_setprofile!)
+ *
+ * privacy: 1 to allow searching, 0 to disallow.
+ */
+int aim_setdirectoryinfo(aim_session_t *sess, aim_conn_t *conn, const char *first, const char *middle, const char *last, const char *maiden, const char *nickname, const char *street, const char *city, const char *state, const char *zip, int country, guint16 privacy)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ aim_tlvlist_t *tl = NULL;
+
+
+ aim_addtlvtochain16(&tl, 0x000a, privacy);
+
+ if (first)
+ aim_addtlvtochain_raw(&tl, 0x0001, strlen(first), (guint8 *)first);
+ if (last)
+ aim_addtlvtochain_raw(&tl, 0x0002, strlen(last), (guint8 *)last);
+ if (middle)
+ aim_addtlvtochain_raw(&tl, 0x0003, strlen(middle), (guint8 *)middle);
+ if (maiden)
+ aim_addtlvtochain_raw(&tl, 0x0004, strlen(maiden), (guint8 *)maiden);
+
+ if (state)
+ aim_addtlvtochain_raw(&tl, 0x0007, strlen(state), (guint8 *)state);
+ if (city)
+ aim_addtlvtochain_raw(&tl, 0x0008, strlen(city), (guint8 *)city);
+
+ if (nickname)
+ aim_addtlvtochain_raw(&tl, 0x000c, strlen(nickname), (guint8 *)nickname);
+ if (zip)
+ aim_addtlvtochain_raw(&tl, 0x000d, strlen(zip), (guint8 *)zip);
+
+ if (street)
+ aim_addtlvtochain_raw(&tl, 0x0021, strlen(street), (guint8 *)street);
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+aim_sizetlvchain(&tl))))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0002, 0x0009, 0x0000, NULL, 0);
+
+ aim_putsnac(&fr->data, 0x0002, 0x0009, 0x0000, snacid);
+ aim_writetlvchain(&fr->data, &tl);
+ aim_freetlvchain(&tl);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/* XXX pass these in better */
+int aim_setuserinterests(aim_session_t *sess, aim_conn_t *conn, const char *interest1, const char *interest2, const char *interest3, const char *interest4, const char *interest5, guint16 privacy)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ aim_tlvlist_t *tl = NULL;
+
+ /* ?? privacy ?? */
+ aim_addtlvtochain16(&tl, 0x000a, privacy);
+
+ if (interest1)
+ aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest1), (guint8 *)interest1);
+ if (interest2)
+ aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest2), (guint8 *)interest2);
+ if (interest3)
+ aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest3), (guint8 *)interest3);
+ if (interest4)
+ aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest4), (guint8 *)interest4);
+ if (interest5)
+ aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest5), (guint8 *)interest5);
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+aim_sizetlvchain(&tl))))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0002, 0x000f, 0x0000, NULL, 0);
+
+ aim_putsnac(&fr->data, 0x0002, 0x000f, 0x0000, 0);
+ aim_writetlvchain(&fr->data, &tl);
+ aim_freetlvchain(&tl);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * Should be generic enough to handle the errors for all groups.
+ *
+ */
+static int generror(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ int ret = 0;
+ int error = 0;
+ aim_rxcallback_t userfunc;
+ aim_snac_t *snac2;
+
+ snac2 = aim_remsnac(sess, snac->id);
+
+ if (aim_bstream_empty(bs))
+ error = aimbs_get16(bs);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, error, snac2 ? snac2->data : NULL);
+
+ if (snac2)
+ g_free(snac2->data);
+ g_free(snac2);
+
+ return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+ if (snac->subtype == 0x0001)
+ return generror(sess, mod, rx, snac, bs);
+ else if ((snac->family == 0xffff) && (snac->subtype == 0xffff)) {
+ aim_rxcallback_t userfunc;
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ return userfunc(sess, rx);
+ }
+
+ return 0;
+}
+
+int misc_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+ mod->family = 0xffff;
+ mod->version = 0x0000;
+ mod->flags = AIM_MODFLAG_MULTIFAMILY;
+ strncpy(mod->name, "misc", sizeof(mod->name));
+ mod->snachandler = snachandler;
+
+ return 0;
+}
+
+
diff --git a/protocols/oscar/msgcookie.c b/protocols/oscar/msgcookie.c
new file mode 100644
index 00000000..efeb8cbf
--- /dev/null
+++ b/protocols/oscar/msgcookie.c
@@ -0,0 +1,164 @@
+/*
+ * Cookie Caching stuff. Adam wrote this, apparently just some
+ * derivatives of n's SNAC work. I cleaned it up, added comments.
+ *
+ */
+
+/*
+ * I'm assuming that cookies are type-specific. that is, we can have
+ * "1234578" for type 1 and type 2 concurrently. if i'm wrong, then we
+ * lose some error checking. if we assume cookies are not type-specific and are
+ * wrong, we get quirky behavior when cookies step on each others' toes.
+ */
+
+#include <aim.h>
+#include "info.h"
+
+/**
+ * aim_cachecookie - appends a cookie to the cookie list
+ * @sess: session to add to
+ * @cookie: pointer to struct to append
+ *
+ * if cookie->cookie for type cookie->type is found, updates the
+ * ->addtime of the found structure; otherwise adds the given cookie
+ * to the cache
+ *
+ * returns -1 on error, 0 on append, 1 on update. the cookie you pass
+ * in may be free'd, so don't count on its value after calling this!
+ *
+ */
+int aim_cachecookie(aim_session_t *sess, aim_msgcookie_t *cookie)
+{
+ aim_msgcookie_t *newcook;
+
+ if (!sess || !cookie)
+ return -EINVAL;
+
+ newcook = aim_checkcookie(sess, cookie->cookie, cookie->type);
+
+ if (newcook == cookie) {
+ newcook->addtime = time(NULL);
+ return 1;
+ } else if (newcook)
+ aim_cookie_free(sess, newcook);
+
+ cookie->addtime = time(NULL);
+
+ cookie->next = sess->msgcookies;
+ sess->msgcookies = cookie;
+
+ return 0;
+}
+
+/**
+ * aim_uncachecookie - grabs a cookie from the cookie cache (removes it from the list)
+ * @sess: session to grab cookie from
+ * @cookie: cookie string to look for
+ * @type: cookie type to look for
+ *
+ * takes a cookie string and a cookie type and finds the cookie struct associated with that duple, removing it from the cookie list ikn the process.
+ *
+ * if found, returns the struct; if none found (or on error), returns NULL:
+ */
+aim_msgcookie_t *aim_uncachecookie(aim_session_t *sess, guint8 *cookie, int type)
+{
+ aim_msgcookie_t *cur, **prev;
+
+ if (!cookie || !sess->msgcookies)
+ return NULL;
+
+ for (prev = &sess->msgcookies; (cur = *prev); ) {
+ if ((cur->type == type) &&
+ (memcmp(cur->cookie, cookie, 8) == 0)) {
+ *prev = cur->next;
+ return cur;
+ }
+ prev = &cur->next;
+ }
+
+ return NULL;
+}
+
+/**
+ * aim_mkcookie - generate an aim_msgcookie_t *struct from a cookie string, a type, and a data pointer.
+ * @c: pointer to the cookie string array
+ * @type: cookie type to use
+ * @data: data to be cached with the cookie
+ *
+ * returns NULL on error, a pointer to the newly-allocated cookie on
+ * success.
+ *
+ */
+aim_msgcookie_t *aim_mkcookie(guint8 *c, int type, void *data)
+{
+ aim_msgcookie_t *cookie;
+
+ if (!c)
+ return NULL;
+
+ if (!(cookie = g_new0(aim_msgcookie_t,1)))
+ return NULL;
+
+ cookie->data = data;
+ cookie->type = type;
+ memcpy(cookie->cookie, c, 8);
+
+ return cookie;
+}
+
+/**
+ * aim_checkcookie - check to see if a cookietuple has been cached
+ * @sess: session to check for the cookie in
+ * @cookie: pointer to the cookie string array
+ * @type: type of the cookie to look for
+ *
+ * this returns a pointer to the cookie struct (still in the list) on
+ * success; returns NULL on error/not found
+ *
+ */
+
+aim_msgcookie_t *aim_checkcookie(aim_session_t *sess, const guint8 *cookie, int type)
+{
+ aim_msgcookie_t *cur;
+
+ for (cur = sess->msgcookies; cur; cur = cur->next) {
+ if ((cur->type == type) &&
+ (memcmp(cur->cookie, cookie, 8) == 0))
+ return cur;
+ }
+
+ return NULL;
+}
+
+/**
+ * aim_cookie_free - free an aim_msgcookie_t struct
+ * @sess: session to remove the cookie from
+ * @cookiep: the address of a pointer to the cookie struct to remove
+ *
+ * this function removes the cookie *cookie from teh list of cookies
+ * in sess, and then frees all memory associated with it. including
+ * its data! if you want to use the private data after calling this,
+ * make sure you copy it first.
+ *
+ * returns -1 on error, 0 on success.
+ *
+ */
+int aim_cookie_free(aim_session_t *sess, aim_msgcookie_t *cookie)
+{
+ aim_msgcookie_t *cur, **prev;
+
+ if (!sess || !cookie)
+ return -EINVAL;
+
+ for (prev = &sess->msgcookies; (cur = *prev); ) {
+ if (cur == cookie)
+ *prev = cur->next;
+ else
+ prev = &cur->next;
+ }
+
+ g_free(cookie->data);
+ g_free(cookie);
+
+ return 0;
+}
diff --git a/protocols/oscar/oscar.c b/protocols/oscar/oscar.c
new file mode 100644
index 00000000..0d23b7e8
--- /dev/null
+++ b/protocols/oscar/oscar.c
@@ -0,0 +1,2646 @@
+/*
+ * gaim
+ *
+ * Some code copyright (C) 2002-2006, Jelmer Vernooij <jelmer@samba.org>
+ * and the BitlBee team.
+ * Some code copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
+ * libfaim code copyright 1998, 1999 Adam Fritzler <afritz@auk.cx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <errno.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <glib.h>
+#include "nogaim.h"
+#include "bitlbee.h"
+#include "proxy.h"
+#include "sock.h"
+
+#include "aim.h"
+#include "icq.h"
+#include "bos.h"
+#include "ssi.h"
+#include "im.h"
+#include "info.h"
+#include "buddylist.h"
+#include "chat.h"
+#include "chatnav.h"
+
+/* constants to identify proto_opts */
+#define USEROPT_AUTH 0
+#define USEROPT_AUTHPORT 1
+
+#define UC_AOL 0x02
+#define UC_ADMIN 0x04
+#define UC_UNCONFIRMED 0x08
+#define UC_NORMAL 0x10
+#define UC_AB 0x20
+#define UC_WIRELESS 0x40
+
+#define AIMHASHDATA "http://gaim.sourceforge.net/aim_data.php3"
+
+#define OSCAR_GROUP "Friends"
+
+#define BUF_LEN 2048
+#define BUF_LONG ( BUF_LEN * 2 )
+
+/* Don't know if support for UTF8 is really working. For now it's UTF16 here.
+ static int gaim_caps = AIM_CAPS_UTF8; */
+
+static int gaim_caps = AIM_CAPS_INTEROP | AIM_CAPS_ICHAT | AIM_CAPS_ICQSERVERRELAY | AIM_CAPS_CHAT;
+static guint8 gaim_features[] = {0x01, 0x01, 0x01, 0x02};
+
+struct oscar_data {
+ aim_session_t *sess;
+ aim_conn_t *conn;
+
+ guint cnpa;
+ guint paspa;
+
+ GSList *create_rooms;
+
+ gboolean conf;
+ gboolean reqemail;
+ gboolean setemail;
+ char *email;
+ gboolean setnick;
+ char *newsn;
+ gboolean chpass;
+ char *oldp;
+ char *newp;
+
+ GSList *oscar_chats;
+
+ gboolean killme, no_reconnect;
+ gboolean icq;
+ GSList *evilhack;
+
+ GHashTable *ips;
+
+ struct {
+ guint maxbuddies; /* max users you can watch */
+ guint maxwatchers; /* max users who can watch you */
+ guint maxpermits; /* max users on permit list */
+ guint maxdenies; /* max users on deny list */
+ guint maxsiglen; /* max size (bytes) of profile */
+ guint maxawaymsglen; /* max size (bytes) of posted away message */
+ } rights;
+};
+
+struct create_room {
+ char *name;
+ int exchange;
+};
+
+struct chat_connection {
+ char *name;
+ char *show; /* AOL did something funny to us */
+ guint16 exchange;
+ guint16 instance;
+ int fd; /* this is redundant since we have the conn below */
+ aim_conn_t *conn;
+ int inpa;
+ int id;
+ struct im_connection *ic; /* i hate this. */
+ struct groupchat *cnv; /* bah. */
+ int maxlen;
+ int maxvis;
+};
+
+struct ask_direct {
+ struct im_connection *ic;
+ char *sn;
+ char ip[64];
+ guint8 cookie[8];
+};
+
+struct icq_auth {
+ struct im_connection *ic;
+ guint32 uin;
+};
+
+static char *extract_name(const char *name) {
+ char *tmp;
+ int i, j;
+ char *x = strchr(name, '-');
+ if (!x) return g_strdup(name);
+ x = strchr(++x, '-');
+ if (!x) return g_strdup(name);
+ tmp = g_strdup(++x);
+
+ for (i = 0, j = 0; x[i]; i++) {
+ char hex[3];
+ if (x[i] != '%') {
+ tmp[j++] = x[i];
+ continue;
+ }
+ strncpy(hex, x + ++i, 2); hex[2] = 0;
+ i++;
+ tmp[j++] = (char)strtol(hex, NULL, 16);
+ }
+
+ tmp[j] = 0;
+ return tmp;
+}
+
+static struct chat_connection *find_oscar_chat_by_conn(struct im_connection *ic,
+ aim_conn_t *conn) {
+ GSList *g = ((struct oscar_data *)ic->proto_data)->oscar_chats;
+ struct chat_connection *c = NULL;
+
+ while (g) {
+ c = (struct chat_connection *)g->data;
+ if (c->conn == conn)
+ break;
+ g = g->next;
+ c = NULL;
+ }
+
+ return c;
+}
+
+static int gaim_parse_auth_resp (aim_session_t *, aim_frame_t *, ...);
+static int gaim_parse_login (aim_session_t *, aim_frame_t *, ...);
+static int gaim_parse_logout (aim_session_t *, aim_frame_t *, ...);
+static int gaim_handle_redirect (aim_session_t *, aim_frame_t *, ...);
+static int gaim_parse_oncoming (aim_session_t *, aim_frame_t *, ...);
+static int gaim_parse_offgoing (aim_session_t *, aim_frame_t *, ...);
+static int gaim_parse_incoming_im(aim_session_t *, aim_frame_t *, ...);
+static int gaim_parse_misses (aim_session_t *, aim_frame_t *, ...);
+static int gaim_parse_motd (aim_session_t *, aim_frame_t *, ...);
+static int gaim_chatnav_info (aim_session_t *, aim_frame_t *, ...);
+static int gaim_chat_join (aim_session_t *, aim_frame_t *, ...);
+static int gaim_chat_leave (aim_session_t *, aim_frame_t *, ...);
+static int gaim_chat_info_update (aim_session_t *, aim_frame_t *, ...);
+static int gaim_chat_incoming_msg(aim_session_t *, aim_frame_t *, ...);
+static int gaim_parse_ratechange (aim_session_t *, aim_frame_t *, ...);
+static int gaim_bosrights (aim_session_t *, aim_frame_t *, ...);
+static int conninitdone_bos (aim_session_t *, aim_frame_t *, ...);
+static int conninitdone_admin (aim_session_t *, aim_frame_t *, ...);
+static int conninitdone_chat (aim_session_t *, aim_frame_t *, ...);
+static int conninitdone_chatnav (aim_session_t *, aim_frame_t *, ...);
+static int gaim_parse_msgerr (aim_session_t *, aim_frame_t *, ...);
+static int gaim_parse_locaterights(aim_session_t *, aim_frame_t *, ...);
+static int gaim_parse_buddyrights(aim_session_t *, aim_frame_t *, ...);
+static int gaim_parse_locerr (aim_session_t *, aim_frame_t *, ...);
+static int gaim_icbm_param_info (aim_session_t *, aim_frame_t *, ...);
+static int gaim_parse_genericerr (aim_session_t *, aim_frame_t *, ...);
+static int gaim_selfinfo (aim_session_t *, aim_frame_t *, ...);
+static int gaim_offlinemsg (aim_session_t *, aim_frame_t *, ...);
+static int gaim_offlinemsgdone (aim_session_t *, aim_frame_t *, ...);
+static int gaim_ssi_parserights (aim_session_t *, aim_frame_t *, ...);
+static int gaim_ssi_parselist (aim_session_t *, aim_frame_t *, ...);
+static int gaim_ssi_parseack (aim_session_t *, aim_frame_t *, ...);
+static int gaim_parsemtn (aim_session_t *, aim_frame_t *, ...);
+static int gaim_icqinfo (aim_session_t *, aim_frame_t *, ...);
+static int gaim_parseaiminfo (aim_session_t *, aim_frame_t *, ...);
+
+static char *msgerrreason[] = {
+ "Invalid error",
+ "Invalid SNAC",
+ "Rate to host",
+ "Rate to client",
+ "Not logged in",
+ "Service unavailable",
+ "Service not defined",
+ "Obsolete SNAC",
+ "Not supported by host",
+ "Not supported by client",
+ "Refused by client",
+ "Reply too big",
+ "Responses lost",
+ "Request denied",
+ "Busted SNAC payload",
+ "Insufficient rights",
+ "In local permit/deny",
+ "Too evil (sender)",
+ "Too evil (receiver)",
+ "User temporarily unavailable",
+ "No match",
+ "List overflow",
+ "Request ambiguous",
+ "Queue full",
+ "Not while on AOL"
+};
+static int msgerrreasonlen = 25;
+
+/* Hurray, this function is NOT thread-safe \o/ */
+static char *normalize(const char *s)
+{
+ static char buf[BUF_LEN];
+ char *t, *u;
+ int x = 0;
+
+ g_return_val_if_fail((s != NULL), NULL);
+
+ u = t = g_strdup(s);
+ g_strdown(t);
+
+ while (*t && (x < BUF_LEN - 1)) {
+ if (*t != ' ' && *t != '!') {
+ buf[x] = *t;
+ x++;
+ }
+ t++;
+ }
+ buf[x] = '\0';
+ g_free(u);
+ return buf;
+}
+
+static gboolean oscar_callback(gpointer data, gint source,
+ b_input_condition condition) {
+ aim_conn_t *conn = (aim_conn_t *)data;
+ aim_session_t *sess = aim_conn_getsess(conn);
+ struct im_connection *ic = sess ? sess->aux_data : NULL;
+ struct oscar_data *odata;
+
+ if (!ic) {
+ /* ic is null. we return, else we seg SIGSEG on next line. */
+ return FALSE;
+ }
+
+ if (!g_slist_find(get_connections(), ic)) {
+ /* oh boy. this is probably bad. i guess the only thing we
+ * can really do is return? */
+ return FALSE;
+ }
+
+ odata = (struct oscar_data *)ic->proto_data;
+
+ if (condition & B_EV_IO_READ) {
+ if (aim_get_command(odata->sess, conn) >= 0) {
+ aim_rxdispatch(odata->sess);
+ if (odata->killme)
+ imc_logout(ic, !odata->no_reconnect);
+ } else {
+ if ((conn->type == AIM_CONN_TYPE_BOS) ||
+ !(aim_getconn_type(odata->sess, AIM_CONN_TYPE_BOS))) {
+ imcb_error(ic, _("Disconnected."));
+ imc_logout(ic, TRUE);
+ } else if (conn->type == AIM_CONN_TYPE_CHAT) {
+ struct chat_connection *c = find_oscar_chat_by_conn(ic, conn);
+ c->conn = NULL;
+ if (c->inpa > 0)
+ b_event_remove(c->inpa);
+ c->inpa = 0;
+ c->fd = -1;
+ aim_conn_kill(odata->sess, &conn);
+ imcb_error(sess->aux_data, _("You have been disconnected from chat room %s."), c->name);
+ } else if (conn->type == AIM_CONN_TYPE_CHATNAV) {
+ if (odata->cnpa > 0)
+ b_event_remove(odata->cnpa);
+ odata->cnpa = 0;
+ while (odata->create_rooms) {
+ struct create_room *cr = odata->create_rooms->data;
+ g_free(cr->name);
+ odata->create_rooms =
+ g_slist_remove(odata->create_rooms, cr);
+ g_free(cr);
+ imcb_error(sess->aux_data, _("Chat is currently unavailable"));
+ }
+ aim_conn_kill(odata->sess, &conn);
+ } else if (conn->type == AIM_CONN_TYPE_AUTH) {
+ if (odata->paspa > 0)
+ b_event_remove(odata->paspa);
+ odata->paspa = 0;
+ aim_conn_kill(odata->sess, &conn);
+ } else {
+ aim_conn_kill(odata->sess, &conn);
+ }
+ }
+ } else {
+ /* WTF??? */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean oscar_login_connect(gpointer data, gint source, b_input_condition cond)
+{
+ struct im_connection *ic = data;
+ struct oscar_data *odata;
+ aim_session_t *sess;
+ aim_conn_t *conn;
+
+ if (!g_slist_find(get_connections(), ic)) {
+ closesocket(source);
+ return FALSE;
+ }
+
+ odata = ic->proto_data;
+ sess = odata->sess;
+ conn = aim_getconn_type_all(sess, AIM_CONN_TYPE_AUTH);
+
+ if (source < 0) {
+ imcb_error(ic, _("Couldn't connect to host"));
+ imc_logout(ic, TRUE);
+ return FALSE;
+ }
+
+ aim_conn_completeconnect(sess, conn);
+ ic->inpa = b_input_add(conn->fd, B_EV_IO_READ,
+ oscar_callback, conn);
+
+ return FALSE;
+}
+
+static void oscar_init(account_t *acc)
+{
+ set_t *s;
+ gboolean icq = isdigit(acc->user[0]);
+
+ if (icq) {
+ set_add(&acc->set, "ignore_auth_requests", "false", set_eval_bool, acc);
+ set_add(&acc->set, "old_icq_auth", "false", set_eval_bool, acc);
+ }
+
+ s = set_add(&acc->set, "server",
+ icq ? AIM_DEFAULT_LOGIN_SERVER_ICQ
+ : AIM_DEFAULT_LOGIN_SERVER_AIM, set_eval_account, acc);
+ s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY;
+
+ if (icq) {
+ s = set_add(&acc->set, "web_aware", "false", set_eval_bool, acc);
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+ }
+
+ acc->flags |= ACC_FLAG_AWAY_MESSAGE;
+}
+
+static void oscar_login(account_t *acc) {
+ aim_session_t *sess;
+ aim_conn_t *conn;
+ struct im_connection *ic = imcb_new(acc);
+ struct oscar_data *odata = ic->proto_data = g_new0(struct oscar_data, 1);
+
+ if (isdigit(acc->user[0]))
+ odata->icq = TRUE;
+ else
+ ic->flags |= OPT_DOES_HTML;
+
+ sess = g_new0(aim_session_t, 1);
+
+ aim_session_init(sess, AIM_SESS_FLAGS_NONBLOCKCONNECT, 0);
+
+ /* we need an immediate queue because we don't use a while-loop to
+ * see if things need to be sent. */
+ aim_tx_setenqueue(sess, AIM_TX_IMMEDIATE, NULL);
+ odata->sess = sess;
+ sess->aux_data = ic;
+
+ conn = aim_newconn(sess, AIM_CONN_TYPE_AUTH, NULL);
+ if (conn == NULL) {
+ imcb_error(ic, _("Unable to login to AIM"));
+ imc_logout(ic, TRUE);
+ return;
+ }
+
+ imcb_log(ic, _("Signon: %s"), ic->acc->user);
+
+ aim_conn_addhandler(sess, conn, 0x0017, 0x0007, gaim_parse_login, 0);
+ aim_conn_addhandler(sess, conn, 0x0017, 0x0003, gaim_parse_auth_resp, 0);
+
+ conn->status |= AIM_CONN_STATUS_INPROGRESS;
+ conn->fd = proxy_connect(set_getstr(&acc->set, "server"),
+ AIM_LOGIN_PORT, oscar_login_connect, ic);
+ if (conn->fd < 0) {
+ imcb_error(ic, _("Couldn't connect to host"));
+ imc_logout(ic, TRUE);
+ return;
+ }
+ aim_request_login(sess, conn, ic->acc->user);
+}
+
+static void oscar_logout(struct im_connection *ic) {
+ struct oscar_data *odata = (struct oscar_data *)ic->proto_data;
+
+ while (odata->oscar_chats) {
+ struct chat_connection *n = odata->oscar_chats->data;
+ if (n->inpa > 0)
+ b_event_remove(n->inpa);
+ g_free(n->name);
+ g_free(n->show);
+ odata->oscar_chats = g_slist_remove(odata->oscar_chats, n);
+ g_free(n);
+ }
+ while (odata->create_rooms) {
+ struct create_room *cr = odata->create_rooms->data;
+ g_free(cr->name);
+ odata->create_rooms = g_slist_remove(odata->create_rooms, cr);
+ g_free(cr);
+ }
+ if (odata->ips)
+ g_hash_table_destroy(odata->ips);
+ if (odata->email)
+ g_free(odata->email);
+ if (odata->newp)
+ g_free(odata->newp);
+ if (odata->oldp)
+ g_free(odata->oldp);
+ if (ic->inpa > 0)
+ b_event_remove(ic->inpa);
+ if (odata->cnpa > 0)
+ b_event_remove(odata->cnpa);
+ if (odata->paspa > 0)
+ b_event_remove(odata->paspa);
+ aim_session_kill(odata->sess);
+ g_free(odata->sess);
+ odata->sess = NULL;
+ g_free(ic->proto_data);
+ ic->proto_data = NULL;
+}
+
+static gboolean oscar_bos_connect(gpointer data, gint source, b_input_condition cond) {
+ struct im_connection *ic = data;
+ struct oscar_data *odata;
+ aim_session_t *sess;
+ aim_conn_t *bosconn;
+
+ if (!g_slist_find(get_connections(), ic)) {
+ closesocket(source);
+ return FALSE;
+ }
+
+ odata = ic->proto_data;
+ sess = odata->sess;
+ bosconn = odata->conn;
+
+ if (source < 0) {
+ imcb_error(ic, _("Could Not Connect"));
+ imc_logout(ic, TRUE);
+ return FALSE;
+ }
+
+ aim_conn_completeconnect(sess, bosconn);
+ ic->inpa = b_input_add(bosconn->fd, B_EV_IO_READ,
+ oscar_callback, bosconn);
+ imcb_log(ic, _("Connection established, cookie sent"));
+
+ return FALSE;
+}
+
+static int gaim_parse_auth_resp(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ struct aim_authresp_info *info;
+ int i; char *host; int port;
+ aim_conn_t *bosconn;
+
+ struct im_connection *ic = sess->aux_data;
+ struct oscar_data *od = ic->proto_data;
+ port = AIM_LOGIN_PORT;
+
+ va_start(ap, fr);
+ info = va_arg(ap, struct aim_authresp_info *);
+ va_end(ap);
+
+ if (info->errorcode || !info->bosip || !info->cookie) {
+ switch (info->errorcode) {
+ case 0x05:
+ /* Incorrect nick/password */
+ imcb_error(ic, _("Incorrect nickname or password."));
+ {
+ int max = od->icq ? 8 : 16;
+ if (strlen(ic->acc->pass) > max)
+ imcb_log(ic, "Note that the maximum password "
+ "length supported by this protocol is "
+ "%d characters, try logging in using "
+ "a shorter password.", max);
+ }
+// plugin_event(event_error, (void *)980, 0, 0, 0);
+ break;
+ case 0x11:
+ /* Suspended account */
+ imcb_error(ic, _("Your account is currently suspended."));
+ break;
+ case 0x18:
+ /* connecting too frequently */
+ od->no_reconnect = TRUE;
+ imcb_error(ic, _("You have been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer."));
+ break;
+ case 0x1c:
+ /* client too old */
+ imcb_error(ic, _("The client version you are using is too old. Please upgrade at " WEBSITE));
+ break;
+ default:
+ imcb_error(ic, _("Authentication Failed"));
+ break;
+ }
+ od->killme = TRUE;
+ return 1;
+ }
+
+
+ aim_conn_kill(sess, &fr->conn);
+
+ bosconn = aim_newconn(sess, AIM_CONN_TYPE_BOS, NULL);
+ if (bosconn == NULL) {
+ imcb_error(ic, _("Internal Error"));
+ od->killme = TRUE;
+ return 0;
+ }
+
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE, conninitdone_bos, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BOS, AIM_CB_BOS_RIGHTS, gaim_bosrights, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ACK, AIM_CB_ACK_ACK, NULL, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_REDIRECT, gaim_handle_redirect, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_RIGHTSINFO, gaim_parse_locaterights, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_RIGHTSINFO, gaim_parse_buddyrights, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_ONCOMING, gaim_parse_oncoming, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_OFFGOING, gaim_parse_offgoing, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_INCOMING, gaim_parse_incoming_im, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_ERROR, gaim_parse_locerr, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_MISSEDCALL, gaim_parse_misses, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_RATECHANGE, gaim_parse_ratechange, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_ERROR, gaim_parse_msgerr, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_MOTD, gaim_parse_motd, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_PARAMINFO, gaim_icbm_param_info, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_ERROR, gaim_parse_genericerr, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_ERROR, gaim_parse_genericerr, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BOS, AIM_CB_BOS_ERROR, gaim_parse_genericerr, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_SELFINFO, gaim_selfinfo, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSG, gaim_offlinemsg, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSGCOMPLETE, gaim_offlinemsgdone, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_INFO, gaim_icqinfo, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_RIGHTSINFO, gaim_ssi_parserights, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_LIST, gaim_ssi_parselist, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_SRVACK, gaim_ssi_parseack, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_USERINFO, gaim_parseaiminfo, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_MTN, gaim_parsemtn, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNERR, gaim_parse_logout, 0);
+
+ ((struct oscar_data *)ic->proto_data)->conn = bosconn;
+ for (i = 0; i < (int)strlen(info->bosip); i++) {
+ if (info->bosip[i] == ':') {
+ port = atoi(&(info->bosip[i+1]));
+ break;
+ }
+ }
+ host = g_strndup(info->bosip, i);
+ bosconn->status |= AIM_CONN_STATUS_INPROGRESS;
+ bosconn->fd = proxy_connect(host, port, oscar_bos_connect, ic);
+ g_free(host);
+ if (bosconn->fd < 0) {
+ imcb_error(ic, _("Could Not Connect"));
+ od->killme = TRUE;
+ return 0;
+ }
+ aim_sendcookie(sess, bosconn, info->cookie);
+ b_event_remove(ic->inpa);
+
+ return 1;
+}
+
+/* size of icbmui.ocm, the largest module in AIM 3.5 */
+#define AIM_MAX_FILE_SIZE 98304
+
+static int gaim_parse_login(aim_session_t *sess, aim_frame_t *fr, ...) {
+#if 0
+ struct client_info_s info = {"gaim", 4, 1, 2010, "us", "en", 0x0004, 0x0000, 0x04b};
+#else
+ struct client_info_s info = AIM_CLIENTINFO_KNOWNGOOD;
+#endif
+ char *key;
+ va_list ap;
+ struct im_connection *ic = sess->aux_data;
+
+ va_start(ap, fr);
+ key = va_arg(ap, char *);
+ va_end(ap);
+
+ aim_send_login(sess, fr->conn, ic->acc->user, ic->acc->pass, &info, key);
+
+ return 1;
+}
+
+static int gaim_parse_logout(aim_session_t *sess, aim_frame_t *fr, ...) {
+ struct im_connection *ic = sess->aux_data;
+ struct oscar_data *odata = ic->proto_data;
+ int code;
+ va_list ap;
+
+ va_start(ap, fr);
+ code = va_arg(ap, int);
+ va_end(ap);
+
+ imcb_error( ic, "Connection aborted by server: %s", code == 1 ?
+ "someone else logged in with your account" :
+ "unknown reason" );
+
+ /* Tell BitlBee to disable auto_reconnect if code == 1, since that
+ means a concurrent login somewhere else. */
+ odata->no_reconnect = code == 1;
+
+ /* DO NOT log out here! Just tell the callback to do it. */
+ odata->killme = TRUE;
+
+ return 1;
+}
+
+static int conninitdone_chat(aim_session_t *sess, aim_frame_t *fr, ...) {
+ struct im_connection *ic = sess->aux_data;
+ struct chat_connection *chatcon;
+ struct groupchat *c = NULL;
+ static int id = 1;
+
+ aim_conn_addhandler(sess, fr->conn, 0x000e, 0x0001, gaim_parse_genericerr, 0);
+ aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_USERJOIN, gaim_chat_join, 0);
+ aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_USERLEAVE, gaim_chat_leave, 0);
+ aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_ROOMINFOUPDATE, gaim_chat_info_update, 0);
+ aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_INCOMINGMSG, gaim_chat_incoming_msg, 0);
+
+ aim_clientready(sess, fr->conn);
+
+ chatcon = find_oscar_chat_by_conn(ic, fr->conn);
+ chatcon->id = id;
+
+ c = bee_chat_by_title(ic->bee, ic, chatcon->show);
+ if (c && !c->data)
+ chatcon->cnv = c;
+ else
+ chatcon->cnv = imcb_chat_new(ic, chatcon->show);
+ chatcon->cnv->data = chatcon;
+
+ return 1;
+}
+
+static int conninitdone_chatnav(aim_session_t *sess, aim_frame_t *fr, ...) {
+
+ aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CTN, AIM_CB_CTN_ERROR, gaim_parse_genericerr, 0);
+ aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CTN, AIM_CB_CTN_INFO, gaim_chatnav_info, 0);
+
+ aim_clientready(sess, fr->conn);
+
+ aim_chatnav_reqrights(sess, fr->conn);
+
+ return 1;
+}
+
+static gboolean oscar_chatnav_connect(gpointer data, gint source, b_input_condition cond) {
+ struct im_connection *ic = data;
+ struct oscar_data *odata;
+ aim_session_t *sess;
+ aim_conn_t *tstconn;
+
+ if (!g_slist_find(get_connections(), ic)) {
+ closesocket(source);
+ return FALSE;
+ }
+
+ odata = ic->proto_data;
+ sess = odata->sess;
+ tstconn = aim_getconn_type_all(sess, AIM_CONN_TYPE_CHATNAV);
+
+ if (source < 0) {
+ aim_conn_kill(sess, &tstconn);
+ return FALSE;
+ }
+
+ aim_conn_completeconnect(sess, tstconn);
+ odata->cnpa = b_input_add(tstconn->fd, B_EV_IO_READ,
+ oscar_callback, tstconn);
+
+ return FALSE;
+}
+
+static gboolean oscar_auth_connect(gpointer data, gint source, b_input_condition cond)
+{
+ struct im_connection *ic = data;
+ struct oscar_data *odata;
+ aim_session_t *sess;
+ aim_conn_t *tstconn;
+
+ if (!g_slist_find(get_connections(), ic)) {
+ closesocket(source);
+ return FALSE;
+ }
+
+ odata = ic->proto_data;
+ sess = odata->sess;
+ tstconn = aim_getconn_type_all(sess, AIM_CONN_TYPE_AUTH);
+
+ if (source < 0) {
+ aim_conn_kill(sess, &tstconn);
+ return FALSE;
+ }
+
+ aim_conn_completeconnect(sess, tstconn);
+ odata->paspa = b_input_add(tstconn->fd, B_EV_IO_READ,
+ oscar_callback, tstconn);
+
+ return FALSE;
+}
+
+static gboolean oscar_chat_connect(gpointer data, gint source, b_input_condition cond)
+{
+ struct chat_connection *ccon = data;
+ struct im_connection *ic = ccon->ic;
+ struct oscar_data *odata;
+ aim_session_t *sess;
+ aim_conn_t *tstconn;
+
+ if (!g_slist_find(get_connections(), ic)) {
+ closesocket(source);
+ g_free(ccon->show);
+ g_free(ccon->name);
+ g_free(ccon);
+ return FALSE;
+ }
+
+ odata = ic->proto_data;
+ sess = odata->sess;
+ tstconn = ccon->conn;
+
+ if (source < 0) {
+ aim_conn_kill(sess, &tstconn);
+ g_free(ccon->show);
+ g_free(ccon->name);
+ g_free(ccon);
+ return FALSE;
+ }
+
+ aim_conn_completeconnect(sess, ccon->conn);
+ ccon->inpa = b_input_add(tstconn->fd,
+ B_EV_IO_READ,
+ oscar_callback, tstconn);
+ odata->oscar_chats = g_slist_append(odata->oscar_chats, ccon);
+
+ return FALSE;
+}
+
+/* Hrmph. I don't know how to make this look better. --mid */
+static int gaim_handle_redirect(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ struct aim_redirect_data *redir;
+ struct im_connection *ic = sess->aux_data;
+ aim_conn_t *tstconn;
+ int i;
+ char *host;
+ int port;
+
+ va_start(ap, fr);
+ redir = va_arg(ap, struct aim_redirect_data *);
+ va_end(ap);
+
+ port = AIM_LOGIN_PORT;
+ for (i = 0; i < (int)strlen(redir->ip); i++) {
+ if (redir->ip[i] == ':') {
+ port = atoi(&(redir->ip[i+1]));
+ break;
+ }
+ }
+ host = g_strndup(redir->ip, i);
+
+ switch(redir->group) {
+ case 0x7: /* Authorizer */
+ tstconn = aim_newconn(sess, AIM_CONN_TYPE_AUTH, NULL);
+ if (tstconn == NULL) {
+ g_free(host);
+ return 1;
+ }
+ aim_conn_addhandler(sess, tstconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE, conninitdone_admin, 0);
+// aim_conn_addhandler(sess, tstconn, 0x0007, 0x0003, gaim_info_change, 0);
+// aim_conn_addhandler(sess, tstconn, 0x0007, 0x0005, gaim_info_change, 0);
+// aim_conn_addhandler(sess, tstconn, 0x0007, 0x0007, gaim_account_confirm, 0);
+
+ tstconn->status |= AIM_CONN_STATUS_INPROGRESS;
+ tstconn->fd = proxy_connect(host, port, oscar_auth_connect, ic);
+ if (tstconn->fd < 0) {
+ aim_conn_kill(sess, &tstconn);
+ g_free(host);
+ return 1;
+ }
+ aim_sendcookie(sess, tstconn, redir->cookie);
+ break;
+ case 0xd: /* ChatNav */
+ tstconn = aim_newconn(sess, AIM_CONN_TYPE_CHATNAV, NULL);
+ if (tstconn == NULL) {
+ g_free(host);
+ return 1;
+ }
+ aim_conn_addhandler(sess, tstconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE, conninitdone_chatnav, 0);
+
+ tstconn->status |= AIM_CONN_STATUS_INPROGRESS;
+ tstconn->fd = proxy_connect(host, port, oscar_chatnav_connect, ic);
+ if (tstconn->fd < 0) {
+ aim_conn_kill(sess, &tstconn);
+ g_free(host);
+ return 1;
+ }
+ aim_sendcookie(sess, tstconn, redir->cookie);
+ break;
+ case 0xe: /* Chat */
+ {
+ struct chat_connection *ccon;
+
+ tstconn = aim_newconn(sess, AIM_CONN_TYPE_CHAT, NULL);
+ if (tstconn == NULL) {
+ g_free(host);
+ return 1;
+ }
+
+ aim_conn_addhandler(sess, tstconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE, conninitdone_chat, 0);
+
+ ccon = g_new0(struct chat_connection, 1);
+ ccon->conn = tstconn;
+ ccon->ic = ic;
+ ccon->fd = -1;
+ ccon->name = g_strdup(redir->chat.room);
+ ccon->exchange = redir->chat.exchange;
+ ccon->instance = redir->chat.instance;
+ ccon->show = extract_name(redir->chat.room);
+
+ ccon->conn->status |= AIM_CONN_STATUS_INPROGRESS;
+ ccon->conn->fd = proxy_connect(host, port, oscar_chat_connect, ccon);
+ if (ccon->conn->fd < 0) {
+ aim_conn_kill(sess, &tstconn);
+ g_free(host);
+ g_free(ccon->show);
+ g_free(ccon->name);
+ g_free(ccon);
+ return 1;
+ }
+ aim_sendcookie(sess, tstconn, redir->cookie);
+ }
+ break;
+ default: /* huh? */
+ break;
+ }
+
+ g_free(host);
+ return 1;
+}
+
+static int gaim_parse_oncoming(aim_session_t *sess, aim_frame_t *fr, ...) {
+ struct im_connection *ic = sess->aux_data;
+ struct oscar_data *od = ic->proto_data;
+ aim_userinfo_t *info;
+ time_t time_idle = 0, signon = 0;
+ int flags = OPT_LOGGED_IN;
+ char *tmp, *state_string = NULL;
+
+ va_list ap;
+ va_start(ap, fr);
+ info = va_arg(ap, aim_userinfo_t *);
+ va_end(ap);
+
+ if ((!od->icq) && (info->present & AIM_USERINFO_PRESENT_FLAGS)) {
+ if (info->flags & AIM_FLAG_AWAY)
+ flags |= OPT_AWAY;
+ }
+
+ /* Maybe this should be done just for AIM contacts, not sure. */
+ if (info->flags & AIM_FLAG_WIRELESS)
+ flags |= OPT_MOBILE;
+
+ if (info->present & AIM_USERINFO_PRESENT_ICQEXTSTATUS) {
+ if (!(info->icqinfo.status & AIM_ICQ_STATE_CHAT) &&
+ (info->icqinfo.status != AIM_ICQ_STATE_NORMAL)) {
+ flags |= OPT_AWAY;
+ }
+
+ if( info->icqinfo.status & AIM_ICQ_STATE_DND )
+ state_string = "Do Not Disturb";
+ else if( info->icqinfo.status & AIM_ICQ_STATE_OUT )
+ state_string = "Not Available";
+ else if( info->icqinfo.status & AIM_ICQ_STATE_BUSY )
+ state_string = "Occupied";
+ else if( info->icqinfo.status & AIM_ICQ_STATE_INVISIBLE )
+ state_string = "Invisible";
+ }
+
+ if (info->present & AIM_USERINFO_PRESENT_IDLE) {
+ time(&time_idle);
+ time_idle -= info->idletime*60;
+ }
+
+ if (info->present & AIM_USERINFO_PRESENT_SESSIONLEN)
+ signon = time(NULL) - info->sessionlen;
+
+ if (info->present & AIM_USERINFO_PRESENT_ICQIPADDR) {
+ uint32_t *uin = g_new0(uint32_t, 1);
+
+ if (od->ips == NULL)
+ od->ips = g_hash_table_new_full(g_int_hash, g_int_equal, g_free, NULL);
+
+ if (sscanf(info->sn, "%d", uin) == 1)
+ g_hash_table_insert(od->ips, uin, (gpointer) (long) info->icqinfo.ipaddr);
+ }
+
+ if (!aim_sncmp(ic->acc->user, info->sn))
+ g_snprintf(ic->displayname, sizeof(ic->displayname), "%s", info->sn);
+
+ tmp = normalize(info->sn);
+ imcb_buddy_status(ic, tmp, flags, state_string, NULL);
+ imcb_buddy_times(ic, tmp, signon, time_idle);
+
+
+ return 1;
+}
+
+static int gaim_parse_offgoing(aim_session_t *sess, aim_frame_t *fr, ...) {
+ aim_userinfo_t *info;
+ va_list ap;
+ struct im_connection *ic = sess->aux_data;
+
+ va_start(ap, fr);
+ info = va_arg(ap, aim_userinfo_t *);
+ va_end(ap);
+
+ imcb_buddy_status(ic, normalize(info->sn), 0, NULL, NULL );
+
+ return 1;
+}
+
+static int incomingim_chan1(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch1_args *args) {
+ char *tmp = g_malloc(BUF_LONG + 1);
+ struct im_connection *ic = sess->aux_data;
+ int flags = 0;
+
+ if (args->icbmflags & AIM_IMFLAGS_AWAY)
+ flags |= OPT_AWAY;
+
+ if ((args->icbmflags & AIM_IMFLAGS_UNICODE) || (args->icbmflags & AIM_IMFLAGS_ISO_8859_1)) {
+ char *src;
+
+ if (args->icbmflags & AIM_IMFLAGS_UNICODE)
+ src = "UNICODEBIG";
+ else
+ src = "ISO8859-1";
+
+ /* Try to use iconv first to convert the message to UTF8 - which is what BitlBee expects */
+ if (do_iconv(src, "UTF-8", args->msg, tmp, args->msglen, BUF_LONG) >= 0) {
+ // Successfully converted!
+ } else if (args->icbmflags & AIM_IMFLAGS_UNICODE) {
+ int i;
+
+ for (i = 0, tmp[0] = '\0'; i < args->msglen; i += 2) {
+ unsigned short uni;
+
+ uni = ((args->msg[i] & 0xff) << 8) | (args->msg[i+1] & 0xff);
+
+ if ((uni < 128) || ((uni >= 160) && (uni <= 255))) { /* ISO 8859-1 */
+ g_snprintf(tmp+strlen(tmp), BUF_LONG-strlen(tmp), "%c", uni);
+ } else { /* something else, do UNICODE entity */
+ g_snprintf(tmp+strlen(tmp), BUF_LONG-strlen(tmp), "&#%04x;", uni);
+ }
+ }
+ } else {
+ g_snprintf(tmp, BUF_LONG, "%s", args->msg);
+ }
+ } else if (args->mpmsg.numparts == 0) {
+ g_snprintf(tmp, BUF_LONG, "%s", args->msg);
+ } else {
+ aim_mpmsg_section_t *part;
+
+ *tmp = 0;
+ for (part = args->mpmsg.parts; part; part = part->next) {
+ if (part->data) {
+ g_strlcat(tmp, (char*) part->data, BUF_LONG);
+ g_strlcat(tmp, "\n", BUF_LONG);
+ }
+ }
+ }
+
+ strip_linefeed(tmp);
+ imcb_buddy_msg(ic, normalize(userinfo->sn), tmp, flags, 0);
+ g_free(tmp);
+
+ return 1;
+}
+
+void oscar_accept_chat(void *data);
+void oscar_reject_chat(void *data);
+
+static int incomingim_chan2(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args) {
+ struct im_connection *ic = sess->aux_data;
+
+ if (args->status != AIM_RENDEZVOUS_PROPOSE)
+ return 1;
+
+ if (args->reqclass & AIM_CAPS_CHAT) {
+ char *name = extract_name(args->info.chat.roominfo.name);
+ int *exch = g_new0(int, 1);
+ GList *m = NULL;
+ char txt[1024];
+ struct aim_chat_invitation * inv = g_new0(struct aim_chat_invitation, 1);
+
+ m = g_list_append(m, g_strdup(name ? name : args->info.chat.roominfo.name));
+ *exch = args->info.chat.roominfo.exchange;
+ m = g_list_append(m, exch);
+
+ g_snprintf(txt, 1024, "Got an invitation to chatroom %s from %s: %s", name, userinfo->sn, args->msg);
+
+ inv->ic = ic;
+ inv->exchange = *exch;
+ inv->name = g_strdup(name);
+
+ imcb_ask(ic, txt, inv, oscar_accept_chat, oscar_reject_chat);
+
+ if (name)
+ g_free(name);
+ } else if (args->reqclass & AIM_CAPS_ICQRTF) {
+ // TODO: constify
+ char text[strlen(args->info.rtfmsg.rtfmsg)+1];
+ strncpy(text, args->info.rtfmsg.rtfmsg, sizeof(text));
+ imcb_buddy_msg(ic, normalize(userinfo->sn), text, 0, 0);
+ }
+
+ return 1;
+}
+
+static void gaim_icq_authgrant(void *data_) {
+ struct icq_auth *data = data_;
+ char *uin, message;
+ struct oscar_data *od = (struct oscar_data *)data->ic->proto_data;
+
+ uin = g_strdup_printf("%u", data->uin);
+ message = 0;
+ aim_ssi_auth_reply(od->sess, od->conn, uin, 1, "");
+ // aim_send_im_ch4(od->sess, uin, AIM_ICQMSG_AUTHGRANTED, &message);
+ imcb_ask_add(data->ic, uin, NULL);
+
+ g_free(uin);
+ g_free(data);
+}
+
+static void gaim_icq_authdeny(void *data_) {
+ struct icq_auth *data = data_;
+ char *uin, *message;
+ struct oscar_data *od = (struct oscar_data *)data->ic->proto_data;
+
+ uin = g_strdup_printf("%u", data->uin);
+ message = g_strdup_printf("No reason given.");
+ aim_ssi_auth_reply(od->sess, od->conn, uin, 0, "");
+ // aim_send_im_ch4(od->sess, uin, AIM_ICQMSG_AUTHDENIED, message);
+ g_free(message);
+
+ g_free(uin);
+ g_free(data);
+}
+
+/*
+ * For when other people ask you for authorization
+ */
+static void gaim_icq_authask(struct im_connection *ic, guint32 uin, char *msg) {
+ struct icq_auth *data;
+ char *reason = NULL;
+ char *dialog_msg;
+
+ if (set_getbool(&ic->acc->set, "ignore_auth_requests"))
+ return;
+
+ data = g_new(struct icq_auth, 1);
+
+ if (strlen(msg) > 6)
+ reason = msg + 6;
+
+ dialog_msg = g_strdup_printf("The user %u wants to add you to their buddy list for the following reason: %s", uin, reason ? reason : "No reason given.");
+ data->ic = ic;
+ data->uin = uin;
+ imcb_ask(ic, dialog_msg, data, gaim_icq_authgrant, gaim_icq_authdeny);
+ g_free(dialog_msg);
+}
+
+static int incomingim_chan4(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch4_args *args) {
+ struct im_connection *ic = sess->aux_data;
+
+ switch (args->type) {
+ case 0x0001: { /* An almost-normal instant message. Mac ICQ sends this. It's peculiar. */
+ char *uin, *message;
+ uin = g_strdup_printf("%u", args->uin);
+ message = g_strdup(args->msg);
+ strip_linefeed(message);
+ imcb_buddy_msg(ic, normalize(uin), message, 0, 0);
+ g_free(uin);
+ g_free(message);
+ } break;
+
+ case 0x0004: { /* Someone sent you a URL */
+ char *uin, *message;
+ char **m;
+
+ uin = g_strdup_printf("%u", args->uin);
+ m = g_strsplit(args->msg, "\376", 2);
+
+ if ((strlen(m[0]) != 0)) {
+ message = g_strjoinv(" -- ", m);
+ } else {
+ message = m[1];
+ }
+
+ strip_linefeed(message);
+ imcb_buddy_msg(ic, normalize(uin), message, 0, 0);
+ g_free(uin);
+ g_free(m);
+ g_free(message);
+ } break;
+
+ case 0x0006: { /* Someone requested authorization */
+ gaim_icq_authask(ic, args->uin, args->msg);
+ } break;
+
+ case 0x0007: { /* Someone has denied you authorization */
+ imcb_log(sess->aux_data, "The user %u has denied your request to add them to your contact list for the following reason:\n%s", args->uin, args->msg ? args->msg : _("No reason given.") );
+ } break;
+
+ case 0x0008: { /* Someone has granted you authorization */
+ imcb_log(sess->aux_data, "The user %u has granted your request to add them to your contact list for the following reason:\n%s", args->uin, args->msg ? args->msg : _("No reason given.") );
+ } break;
+
+ case 0x0012: {
+ /* Ack for authorizing/denying someone. Or possibly an ack for sending any system notice */
+ } break;
+
+ default: {;
+ } break;
+ }
+
+ return 1;
+}
+
+static int gaim_parse_incoming_im(aim_session_t *sess, aim_frame_t *fr, ...) {
+ int channel, ret = 0;
+ aim_userinfo_t *userinfo;
+ va_list ap;
+
+ va_start(ap, fr);
+ channel = va_arg(ap, int);
+ userinfo = va_arg(ap, aim_userinfo_t *);
+
+ switch (channel) {
+ case 1: { /* standard message */
+ struct aim_incomingim_ch1_args *args;
+ args = va_arg(ap, struct aim_incomingim_ch1_args *);
+ ret = incomingim_chan1(sess, fr->conn, userinfo, args);
+ } break;
+
+ case 2: { /* rendevous */
+ struct aim_incomingim_ch2_args *args;
+ args = va_arg(ap, struct aim_incomingim_ch2_args *);
+ ret = incomingim_chan2(sess, fr->conn, userinfo, args);
+ } break;
+
+ case 4: { /* ICQ */
+ struct aim_incomingim_ch4_args *args;
+ args = va_arg(ap, struct aim_incomingim_ch4_args *);
+ ret = incomingim_chan4(sess, fr->conn, userinfo, args);
+ } break;
+
+ default: {;
+ } break;
+ }
+
+ va_end(ap);
+
+ return ret;
+}
+
+static int gaim_parse_misses(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ guint16 chan, nummissed, reason;
+ aim_userinfo_t *userinfo;
+
+ va_start(ap, fr);
+ chan = (guint16)va_arg(ap, unsigned int);
+ userinfo = va_arg(ap, aim_userinfo_t *);
+ nummissed = (guint16)va_arg(ap, unsigned int);
+ reason = (guint16)va_arg(ap, unsigned int);
+ va_end(ap);
+
+ switch(reason) {
+ case 0:
+ /* Invalid (0) */
+ imcb_error(sess->aux_data,
+ nummissed == 1 ?
+ _("You missed %d message from %s because it was invalid.") :
+ _("You missed %d messages from %s because they were invalid."),
+ nummissed,
+ userinfo->sn);
+ break;
+ case 1:
+ /* Message too large */
+ imcb_error(sess->aux_data,
+ nummissed == 1 ?
+ _("You missed %d message from %s because it was too large.") :
+ _("You missed %d messages from %s because they were too large."),
+ nummissed,
+ userinfo->sn);
+ break;
+ case 2:
+ /* Rate exceeded */
+ imcb_error(sess->aux_data,
+ nummissed == 1 ?
+ _("You missed %d message from %s because the rate limit has been exceeded.") :
+ _("You missed %d messages from %s because the rate limit has been exceeded."),
+ nummissed,
+ userinfo->sn);
+ break;
+ case 3:
+ /* Evil Sender */
+ imcb_error(sess->aux_data,
+ nummissed == 1 ?
+ _("You missed %d message from %s because it was too evil.") :
+ _("You missed %d messages from %s because they are too evil."),
+ nummissed,
+ userinfo->sn);
+ break;
+ case 4:
+ /* Evil Receiver */
+ imcb_error(sess->aux_data,
+ nummissed == 1 ?
+ _("You missed %d message from %s because you are too evil.") :
+ _("You missed %d messages from %s because you are too evil."),
+ nummissed,
+ userinfo->sn);
+ break;
+ default:
+ imcb_error(sess->aux_data,
+ nummissed == 1 ?
+ _("You missed %d message from %s for unknown reasons.") :
+ _("You missed %d messages from %s for unknown reasons."),
+ nummissed,
+ userinfo->sn);
+ break;
+ }
+
+ return 1;
+}
+
+static int gaim_parse_genericerr(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ guint16 reason;
+
+ va_start(ap, fr);
+ reason = (guint16)va_arg(ap, unsigned int);
+ va_end(ap);
+
+ imcb_error(sess->aux_data, _("SNAC threw error: %s"),
+ reason < msgerrreasonlen ? msgerrreason[reason] : "Unknown error");
+
+ return 1;
+}
+
+static int gaim_parse_msgerr(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ char *destn;
+ guint16 reason;
+
+ va_start(ap, fr);
+ reason = (guint16)va_arg(ap, unsigned int);
+ destn = va_arg(ap, char *);
+ va_end(ap);
+
+ imcb_error(sess->aux_data, _("Your message to %s did not get sent: %s"), destn,
+ (reason < msgerrreasonlen) ? msgerrreason[reason] : _("Reason unknown"));
+
+ return 1;
+}
+
+static int gaim_parse_locerr(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ char *destn;
+ guint16 reason;
+
+ va_start(ap, fr);
+ reason = (guint16)va_arg(ap, unsigned int);
+ destn = va_arg(ap, char *);
+ va_end(ap);
+
+ imcb_error(sess->aux_data, _("User information for %s unavailable: %s"), destn,
+ (reason < msgerrreasonlen) ? msgerrreason[reason] : _("Reason unknown"));
+
+ return 1;
+}
+
+static int gaim_parse_motd(aim_session_t *sess, aim_frame_t *fr, ...) {
+ char *msg;
+ guint16 id;
+ va_list ap;
+
+ va_start(ap, fr);
+ id = (guint16)va_arg(ap, unsigned int);
+ msg = va_arg(ap, char *);
+ va_end(ap);
+
+ if (id < 4)
+ imcb_error(sess->aux_data, _("Your connection may be lost."));
+
+ return 1;
+}
+
+static int gaim_chatnav_info(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ guint16 type;
+ struct im_connection *ic = sess->aux_data;
+ struct oscar_data *odata = (struct oscar_data *)ic->proto_data;
+
+ va_start(ap, fr);
+ type = (guint16)va_arg(ap, unsigned int);
+
+ switch(type) {
+ case 0x0002: {
+ guint8 maxrooms;
+ struct aim_chat_exchangeinfo *exchanges;
+ int exchangecount; // i;
+
+ maxrooms = (guint8)va_arg(ap, unsigned int);
+ exchangecount = va_arg(ap, int);
+ exchanges = va_arg(ap, struct aim_chat_exchangeinfo *);
+ va_end(ap);
+
+ while (odata->create_rooms) {
+ struct create_room *cr = odata->create_rooms->data;
+ aim_chatnav_createroom(sess, fr->conn, cr->name, cr->exchange);
+ g_free(cr->name);
+ odata->create_rooms = g_slist_remove(odata->create_rooms, cr);
+ g_free(cr);
+ }
+ }
+ break;
+ case 0x0008: {
+ char *fqcn, *name, *ck;
+ guint16 instance, flags, maxmsglen, maxoccupancy, unknown, exchange;
+ guint8 createperms;
+ guint32 createtime;
+
+ fqcn = va_arg(ap, char *);
+ instance = (guint16)va_arg(ap, unsigned int);
+ exchange = (guint16)va_arg(ap, unsigned int);
+ flags = (guint16)va_arg(ap, unsigned int);
+ createtime = va_arg(ap, guint32);
+ maxmsglen = (guint16)va_arg(ap, unsigned int);
+ maxoccupancy = (guint16)va_arg(ap, unsigned int);
+ createperms = (guint8)va_arg(ap, int);
+ unknown = (guint16)va_arg(ap, unsigned int);
+ name = va_arg(ap, char *);
+ ck = va_arg(ap, char *);
+ va_end(ap);
+
+ aim_chat_join(odata->sess, odata->conn, exchange, ck, instance);
+ }
+ break;
+ default:
+ va_end(ap);
+ break;
+ }
+ return 1;
+}
+
+static int gaim_chat_join(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ int count, i;
+ aim_userinfo_t *info;
+ struct im_connection *g = sess->aux_data;
+
+ struct chat_connection *c = NULL;
+
+ va_start(ap, fr);
+ count = va_arg(ap, int);
+ info = va_arg(ap, aim_userinfo_t *);
+ va_end(ap);
+
+ c = find_oscar_chat_by_conn(g, fr->conn);
+ if (!c)
+ return 1;
+
+ for (i = 0; i < count; i++)
+ imcb_chat_add_buddy(c->cnv, normalize(info[i].sn));
+
+ return 1;
+}
+
+static int gaim_chat_leave(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ int count, i;
+ aim_userinfo_t *info;
+ struct im_connection *g = sess->aux_data;
+
+ struct chat_connection *c = NULL;
+
+ va_start(ap, fr);
+ count = va_arg(ap, int);
+ info = va_arg(ap, aim_userinfo_t *);
+ va_end(ap);
+
+ c = find_oscar_chat_by_conn(g, fr->conn);
+ if (!c)
+ return 1;
+
+ for (i = 0; i < count; i++)
+ imcb_chat_remove_buddy(c->cnv, normalize(info[i].sn), NULL);
+
+ return 1;
+}
+
+static int gaim_chat_info_update(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ aim_userinfo_t *userinfo;
+ struct aim_chat_roominfo *roominfo;
+ char *roomname;
+ int usercount;
+ char *roomdesc;
+ guint16 unknown_c9, unknown_d2, unknown_d5, maxmsglen, maxvisiblemsglen;
+ guint32 creationtime;
+ struct im_connection *ic = sess->aux_data;
+ struct chat_connection *ccon = find_oscar_chat_by_conn(ic, fr->conn);
+
+ va_start(ap, fr);
+ roominfo = va_arg(ap, struct aim_chat_roominfo *);
+ roomname = va_arg(ap, char *);
+ usercount= va_arg(ap, int);
+ userinfo = va_arg(ap, aim_userinfo_t *);
+ roomdesc = va_arg(ap, char *);
+ unknown_c9 = (guint16)va_arg(ap, int);
+ creationtime = (guint32)va_arg(ap, unsigned long);
+ maxmsglen = (guint16)va_arg(ap, int);
+ unknown_d2 = (guint16)va_arg(ap, int);
+ unknown_d5 = (guint16)va_arg(ap, int);
+ maxvisiblemsglen = (guint16)va_arg(ap, int);
+ va_end(ap);
+
+ ccon->maxlen = maxmsglen;
+ ccon->maxvis = maxvisiblemsglen;
+
+ return 1;
+}
+
+static int gaim_chat_incoming_msg(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ aim_userinfo_t *info;
+ char *msg;
+ struct im_connection *ic = sess->aux_data;
+ struct chat_connection *ccon = find_oscar_chat_by_conn(ic, fr->conn);
+ char *tmp;
+
+ va_start(ap, fr);
+ info = va_arg(ap, aim_userinfo_t *);
+ msg = va_arg(ap, char *);
+
+ tmp = g_malloc(BUF_LONG);
+ g_snprintf(tmp, BUF_LONG, "%s", msg);
+ imcb_chat_msg(ccon->cnv, normalize(info->sn), tmp, 0, 0);
+ g_free(tmp);
+
+ return 1;
+}
+
+static int gaim_parse_ratechange(aim_session_t *sess, aim_frame_t *fr, ...) {
+#if 0
+ static const char *codes[5] = {
+ "invalid",
+ "change",
+ "warning",
+ "limit",
+ "limit cleared",
+ };
+#endif
+ va_list ap;
+ guint16 code, rateclass;
+ guint32 windowsize, clear, alert, limit, disconnect, currentavg, maxavg;
+
+ va_start(ap, fr);
+ code = (guint16)va_arg(ap, unsigned int);
+ rateclass= (guint16)va_arg(ap, unsigned int);
+ windowsize = (guint32)va_arg(ap, unsigned long);
+ clear = (guint32)va_arg(ap, unsigned long);
+ alert = (guint32)va_arg(ap, unsigned long);
+ limit = (guint32)va_arg(ap, unsigned long);
+ disconnect = (guint32)va_arg(ap, unsigned long);
+ currentavg = (guint32)va_arg(ap, unsigned long);
+ maxavg = (guint32)va_arg(ap, unsigned long);
+ va_end(ap);
+
+ /* XXX fix these values */
+ if (code == AIM_RATE_CODE_CHANGE) {
+ if (currentavg >= clear)
+ aim_conn_setlatency(fr->conn, 0);
+ } else if (code == AIM_RATE_CODE_WARNING) {
+ aim_conn_setlatency(fr->conn, windowsize/4);
+ } else if (code == AIM_RATE_CODE_LIMIT) {
+ imcb_error(sess->aux_data, _("The last message was not sent because you are over the rate limit. "
+ "Please wait 10 seconds and try again."));
+ aim_conn_setlatency(fr->conn, windowsize/2);
+ } else if (code == AIM_RATE_CODE_CLEARLIMIT) {
+ aim_conn_setlatency(fr->conn, 0);
+ }
+
+ return 1;
+}
+
+static int gaim_selfinfo(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ aim_userinfo_t *info;
+ struct im_connection *ic = sess->aux_data;
+
+ va_start(ap, fr);
+ info = va_arg(ap, aim_userinfo_t *);
+ va_end(ap);
+
+ ic->evil = info->warnlevel/10;
+ /* ic->correction_time = (info->onlinesince - ic->login_time); */
+
+ return 1;
+}
+
+static int conninitdone_bos(aim_session_t *sess, aim_frame_t *fr, ...) {
+
+ aim_reqpersonalinfo(sess, fr->conn);
+ aim_bos_reqlocaterights(sess, fr->conn);
+ aim_bos_reqbuddyrights(sess, fr->conn);
+
+ aim_reqicbmparams(sess);
+
+ aim_bos_reqrights(sess, fr->conn);
+ aim_bos_setgroupperm(sess, fr->conn, AIM_FLAG_ALLUSERS);
+ aim_bos_setprivacyflags(sess, fr->conn, AIM_PRIVFLAGS_ALLOWIDLE |
+ AIM_PRIVFLAGS_ALLOWMEMBERSINCE);
+
+ return 1;
+}
+
+static int conninitdone_admin(aim_session_t *sess, aim_frame_t *fr, ...) {
+ struct im_connection *ic = sess->aux_data;
+ struct oscar_data *od = ic->proto_data;
+
+ aim_clientready(sess, fr->conn);
+
+ if (od->chpass) {
+ aim_admin_changepasswd(sess, fr->conn, od->newp, od->oldp);
+ g_free(od->oldp);
+ od->oldp = NULL;
+ g_free(od->newp);
+ od->newp = NULL;
+ od->chpass = FALSE;
+ }
+ if (od->setnick) {
+ aim_admin_setnick(sess, fr->conn, od->newsn);
+ g_free(od->newsn);
+ od->newsn = NULL;
+ od->setnick = FALSE;
+ }
+ if (od->conf) {
+ aim_admin_reqconfirm(sess, fr->conn);
+ od->conf = FALSE;
+ }
+ if (od->reqemail) {
+ aim_admin_getinfo(sess, fr->conn, 0x0011);
+ od->reqemail = FALSE;
+ }
+ if (od->setemail) {
+ aim_admin_setemail(sess, fr->conn, od->email);
+ g_free(od->email);
+ od->setemail = FALSE;
+ }
+
+ return 1;
+}
+
+static int gaim_icbm_param_info(aim_session_t *sess, aim_frame_t *fr, ...) {
+ struct aim_icbmparameters *params;
+ va_list ap;
+
+ va_start(ap, fr);
+ params = va_arg(ap, struct aim_icbmparameters *);
+ va_end(ap);
+
+ /* Maybe senderwarn and recverwarn should be user preferences... */
+ params->flags = 0x0000000b;
+ params->maxmsglen = 8000;
+ params->minmsginterval = 0;
+
+ aim_seticbmparam(sess, params);
+
+ return 1;
+}
+
+static int gaim_parse_locaterights(aim_session_t *sess, aim_frame_t *fr, ...)
+{
+ va_list ap;
+ guint16 maxsiglen;
+ struct im_connection *ic = sess->aux_data;
+ struct oscar_data *odata = (struct oscar_data *)ic->proto_data;
+
+ va_start(ap, fr);
+ maxsiglen = va_arg(ap, int);
+ va_end(ap);
+
+ odata->rights.maxsiglen = odata->rights.maxawaymsglen = (guint)maxsiglen;
+
+ /* FIXME: It seems we're not really using this, and it broke now that
+ struct aim_user is dead.
+ aim_bos_setprofile(sess, fr->conn, ic->user->user_info, NULL, gaim_caps);
+ */
+
+ return 1;
+}
+
+static int gaim_parse_buddyrights(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ guint16 maxbuddies, maxwatchers;
+ struct im_connection *ic = sess->aux_data;
+ struct oscar_data *odata = (struct oscar_data *)ic->proto_data;
+
+ va_start(ap, fr);
+ maxbuddies = (guint16)va_arg(ap, unsigned int);
+ maxwatchers = (guint16)va_arg(ap, unsigned int);
+ va_end(ap);
+
+ odata->rights.maxbuddies = (guint)maxbuddies;
+ odata->rights.maxwatchers = (guint)maxwatchers;
+
+ return 1;
+}
+
+static int gaim_bosrights(aim_session_t *sess, aim_frame_t *fr, ...) {
+ guint16 maxpermits, maxdenies;
+ va_list ap;
+ struct im_connection *ic = sess->aux_data;
+ struct oscar_data *odata = (struct oscar_data *)ic->proto_data;
+
+ va_start(ap, fr);
+ maxpermits = (guint16)va_arg(ap, unsigned int);
+ maxdenies = (guint16)va_arg(ap, unsigned int);
+ va_end(ap);
+
+ odata->rights.maxpermits = (guint)maxpermits;
+ odata->rights.maxdenies = (guint)maxdenies;
+
+ aim_clientready(sess, fr->conn);
+
+ aim_reqservice(sess, fr->conn, AIM_CONN_TYPE_CHATNAV);
+
+ aim_ssi_reqrights(sess, fr->conn);
+ aim_ssi_reqalldata(sess, fr->conn);
+
+ return 1;
+}
+
+static int gaim_offlinemsg(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ struct aim_icq_offlinemsg *msg;
+ struct im_connection *ic = sess->aux_data;
+
+ va_start(ap, fr);
+ msg = va_arg(ap, struct aim_icq_offlinemsg *);
+ va_end(ap);
+
+ switch (msg->type) {
+ case 0x0001: { /* Basic offline message */
+ char sender[32];
+ char *dialog_msg = g_strdup(msg->msg);
+ time_t t = get_time(msg->year, msg->month, msg->day, msg->hour, msg->minute, 0);
+ g_snprintf(sender, sizeof(sender), "%u", msg->sender);
+ strip_linefeed(dialog_msg);
+ imcb_buddy_msg(ic, normalize(sender), dialog_msg, 0, t);
+ g_free(dialog_msg);
+ } break;
+
+ case 0x0004: { /* Someone sent you a URL */
+ char sender[32];
+ char *dialog_msg;
+ char **m;
+
+ time_t t = get_time(msg->year, msg->month, msg->day, msg->hour, msg->minute, 0);
+ g_snprintf(sender, sizeof(sender), "%u", msg->sender);
+
+ m = g_strsplit(msg->msg, "\376", 2);
+
+ if ((strlen(m[0]) != 0)) {
+ dialog_msg = g_strjoinv(" -- ", m);
+ } else {
+ dialog_msg = m[1];
+ }
+
+ strip_linefeed(dialog_msg);
+ imcb_buddy_msg(ic, normalize(sender), dialog_msg, 0, t);
+ g_free(dialog_msg);
+ g_free(m);
+ } break;
+
+ case 0x0006: { /* Authorization request */
+ gaim_icq_authask(ic, msg->sender, msg->msg);
+ } break;
+
+ case 0x0007: { /* Someone has denied you authorization */
+ imcb_log(sess->aux_data, "The user %u has denied your request to add them to your contact list for the following reason:\n%s", msg->sender, msg->msg ? msg->msg : _("No reason given.") );
+ } break;
+
+ case 0x0008: { /* Someone has granted you authorization */
+ imcb_log(sess->aux_data, "The user %u has granted your request to add them to your contact list for the following reason:\n%s", msg->sender, msg->msg ? msg->msg : _("No reason given.") );
+ } break;
+
+ case 0x0012: {
+ /* Ack for authorizing/denying someone. Or possibly an ack for sending any system notice */
+ } break;
+
+ default: {;
+ }
+ }
+
+ return 1;
+}
+
+static int gaim_offlinemsgdone(aim_session_t *sess, aim_frame_t *fr, ...)
+{
+ aim_icq_ackofflinemsgs(sess);
+ return 1;
+}
+
+static void oscar_keepalive(struct im_connection *ic) {
+ struct oscar_data *odata = (struct oscar_data *)ic->proto_data;
+ aim_flap_nop(odata->sess, odata->conn);
+}
+
+static int oscar_buddy_msg(struct im_connection *ic, char *name, char *message, int imflags) {
+ struct oscar_data *odata = (struct oscar_data *)ic->proto_data;
+ int ret = 0, len = strlen(message);
+ if (imflags & OPT_AWAY) {
+ ret = aim_send_im(odata->sess, name, AIM_IMFLAGS_AWAY, message);
+ } else {
+ struct aim_sendimext_args args;
+ char *s;
+
+ args.flags = AIM_IMFLAGS_ACK;
+ if (odata->icq)
+ args.flags |= AIM_IMFLAGS_OFFLINE;
+ for (s = message; *s; s++)
+ if (*s & 128)
+ break;
+
+ /* Message contains high ASCII chars, time for some translation! */
+ if (*s) {
+ s = g_malloc(BUF_LONG);
+ /* Try if we can put it in an ISO8859-1 string first.
+ If we can't, fall back to UTF16. */
+ if ((ret = do_iconv("UTF-8", "ISO8859-1", message, s, len, BUF_LONG)) >= 0) {
+ args.flags |= AIM_IMFLAGS_ISO_8859_1;
+ len = ret;
+ } else if ((ret = do_iconv("UTF-8", "UNICODEBIG", message, s, len, BUF_LONG)) >= 0) {
+ args.flags |= AIM_IMFLAGS_UNICODE;
+ len = ret;
+ } else {
+ /* OOF, translation failed... Oh well.. */
+ g_free( s );
+ s = message;
+ }
+ } else {
+ s = message;
+ }
+
+ args.features = gaim_features;
+ args.featureslen = sizeof(gaim_features);
+
+ args.destsn = name;
+ args.msg = s;
+ args.msglen = len;
+
+ ret = aim_send_im_ext(odata->sess, &args);
+
+ if (s != message) {
+ g_free(s);
+ }
+ }
+ if (ret >= 0)
+ return 1;
+ return ret;
+}
+
+static void oscar_get_info(struct im_connection *g, char *name) {
+ struct oscar_data *odata = (struct oscar_data *)g->proto_data;
+ if (odata->icq)
+ aim_icq_getallinfo(odata->sess, name);
+ else {
+ aim_getinfo(odata->sess, odata->conn, name, AIM_GETINFO_AWAYMESSAGE);
+ aim_getinfo(odata->sess, odata->conn, name, AIM_GETINFO_GENERALINFO);
+ }
+}
+
+static void oscar_get_away(struct im_connection *g, char *who) {
+ struct oscar_data *odata = (struct oscar_data *)g->proto_data;
+ if (odata->icq) {
+ /** FIXME(wilmer): Hmm, lost the ability to get away msgs here, do we care to get that back?
+ struct buddy *budlight = imcb_find_buddy(g, who);
+ if (budlight)
+ if ((budlight->uc & 0xff80) >> 7)
+ if (budlight->caps & AIM_CAPS_ICQSERVERRELAY)
+ aim_send_im_ch2_geticqmessage(odata->sess, who, (budlight->uc & 0xff80) >> 7);
+ */
+ } else
+ aim_getinfo(odata->sess, odata->conn, who, AIM_GETINFO_AWAYMESSAGE);
+}
+
+static void oscar_set_away_aim(struct im_connection *ic, struct oscar_data *od, const char *state, const char *message)
+{
+ if (state == NULL)
+ state = "";
+
+ if (!g_strcasecmp(state, _("Visible"))) {
+ aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL);
+ return;
+ } else if (!g_strcasecmp(state, _("Invisible"))) {
+ aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_INVISIBLE);
+ return;
+ } else if (message == NULL) {
+ message = state;
+ }
+
+ if (od->rights.maxawaymsglen == 0)
+ imcb_error(ic, "oscar_set_away_aim called before locate rights received");
+
+ aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL);
+
+ g_free(ic->away);
+ ic->away = NULL;
+
+ if (!message) {
+ aim_bos_setprofile(od->sess, od->conn, NULL, "", gaim_caps);
+ return;
+ }
+
+ if (strlen(message) > od->rights.maxawaymsglen) {
+ imcb_error(ic, "Maximum away message length of %d bytes exceeded, truncating", od->rights.maxawaymsglen);
+ }
+
+ ic->away = g_strndup(message, od->rights.maxawaymsglen);
+ aim_bos_setprofile(od->sess, od->conn, NULL, ic->away, gaim_caps);
+
+ return;
+}
+
+static void oscar_set_away_icq(struct im_connection *ic, struct oscar_data *od, const char *state, const char *message)
+{
+ const char *msg = NULL;
+ gboolean no_message = FALSE;
+
+ /* clean old states */
+ g_free(ic->away);
+ ic->away = NULL;
+ od->sess->aim_icq_state = 0;
+
+ /* if no message, then use an empty message */
+ if (message) {
+ msg = message;
+ } else {
+ msg = "";
+ no_message = TRUE;
+ }
+
+ if (state == NULL) {
+ aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL);
+ } else if (!g_strcasecmp(state, "Away")) {
+ aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_AWAY);
+ ic->away = g_strdup(msg);
+ od->sess->aim_icq_state = AIM_MTYPE_AUTOAWAY;
+ } else if (!g_strcasecmp(state, "Do Not Disturb")) {
+ aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_DND | AIM_ICQ_STATE_BUSY);
+ ic->away = g_strdup(msg);
+ od->sess->aim_icq_state = AIM_MTYPE_AUTODND;
+ } else if (!g_strcasecmp(state, "Not Available")) {
+ aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_OUT | AIM_ICQ_STATE_AWAY);
+ ic->away = g_strdup(msg);
+ od->sess->aim_icq_state = AIM_MTYPE_AUTONA;
+ } else if (!g_strcasecmp(state, "Occupied")) {
+ aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_BUSY);
+ ic->away = g_strdup(msg);
+ od->sess->aim_icq_state = AIM_MTYPE_AUTOBUSY;
+ } else if (!g_strcasecmp(state, "Free For Chat")) {
+ aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_CHAT);
+ ic->away = g_strdup(msg);
+ od->sess->aim_icq_state = AIM_MTYPE_AUTOFFC;
+ } else if (!g_strcasecmp(state, "Invisible")) {
+ aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_INVISIBLE);
+ ic->away = g_strdup(msg);
+ } else {
+ if (no_message) {
+ aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL);
+ } else {
+ aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_AWAY);
+ ic->away = g_strdup(msg);
+ od->sess->aim_icq_state = AIM_MTYPE_AUTOAWAY;
+ }
+ }
+
+ return;
+}
+
+static void oscar_set_away(struct im_connection *ic, char *state, char *message)
+{
+ struct oscar_data *od = (struct oscar_data *)ic->proto_data;
+
+ oscar_set_away_aim(ic, od, state, message);
+ if (od->icq)
+ oscar_set_away_icq(ic, od, state, message);
+
+ return;
+}
+
+static void oscar_add_buddy(struct im_connection *g, char *name, char *group) {
+ struct oscar_data *odata = (struct oscar_data *)g->proto_data;
+ bee_user_t *bu;
+
+ if (group && (bu = bee_user_by_handle(g->bee, g, name)) && bu->group)
+ aim_ssi_movebuddy(odata->sess, odata->conn, bu->group->name, group, name);
+ else
+ aim_ssi_addbuddies(odata->sess, odata->conn, group ? : OSCAR_GROUP, &name, 1, 0);
+}
+
+static void oscar_remove_buddy(struct im_connection *g, char *name, char *group) {
+ struct oscar_data *odata = (struct oscar_data *)g->proto_data;
+ struct aim_ssi_item *ssigroup;
+ while ((ssigroup = aim_ssi_itemlist_findparent(odata->sess->ssi.items, name)) && !aim_ssi_delbuddies(odata->sess, odata->conn, ssigroup->name, &name, 1));
+}
+
+static int gaim_ssi_parserights(aim_session_t *sess, aim_frame_t *fr, ...) {
+ return 1;
+}
+
+static int gaim_ssi_parselist(aim_session_t *sess, aim_frame_t *fr, ...) {
+ struct im_connection *ic = sess->aux_data;
+ struct aim_ssi_item *curitem, *curgroup = NULL;
+ int tmp;
+ char *nrm;
+
+ /* Add from server list to local list */
+ tmp = 0;
+ for (curitem=sess->ssi.items; curitem; curitem=curitem->next) {
+ nrm = curitem->name ? normalize(curitem->name) : NULL;
+
+ switch (curitem->type) {
+ case 0x0000: /* Buddy */
+ if ((curitem->name) && (!imcb_buddy_by_handle(ic, nrm))) {
+ char *realname = NULL;
+
+ if (curitem->data && aim_gettlv(curitem->data, 0x0131, 1))
+ realname = aim_gettlv_str(curitem->data, 0x0131, 1);
+
+ imcb_add_buddy(ic, nrm, curgroup ? (curgroup->gid == curitem->gid ? curgroup->name : NULL) : NULL);
+
+ if (realname) {
+ imcb_buddy_nick_hint(ic, nrm, realname);
+ imcb_rename_buddy(ic, nrm, realname);
+ g_free(realname);
+ }
+ }
+ break;
+
+ case 0x0001: /* Group */
+ curgroup = curitem;
+ break;
+
+ case 0x0002: /* Permit buddy */
+ if (curitem->name) {
+ GSList *list;
+ for (list=ic->permit; (list && aim_sncmp(curitem->name, list->data)); list=list->next);
+ if (!list) {
+ char *name;
+ name = g_strdup(nrm);
+ ic->permit = g_slist_append(ic->permit, name);
+ tmp++;
+ }
+ }
+ break;
+
+ case 0x0003: /* Deny buddy */
+ if (curitem->name) {
+ GSList *list;
+ for (list=ic->deny; (list && aim_sncmp(curitem->name, list->data)); list=list->next);
+ if (!list) {
+ char *name;
+ name = g_strdup(nrm);
+ ic->deny = g_slist_append(ic->deny, name);
+ tmp++;
+ }
+ }
+ break;
+
+ case 0x0004: /* Permit/deny setting */
+ if (curitem->data) {
+ guint8 permdeny;
+ if ((permdeny = aim_ssi_getpermdeny(sess->ssi.items)) && (permdeny != ic->permdeny)) {
+ ic->permdeny = permdeny;
+ tmp++;
+ }
+ }
+ break;
+
+ case 0x0005: /* Presence setting */
+ /* We don't want to change Gaim's setting because it applies to all accounts */
+ break;
+ } /* End of switch on curitem->type */
+ } /* End of for loop */
+
+ aim_ssi_enable(sess, fr->conn);
+
+ /* Request offline messages, now that the buddy list is complete. */
+ aim_icq_reqofflinemsgs(sess);
+
+ /* Now that we have a buddy list, we can tell BitlBee that we're online. */
+ imcb_connected(ic);
+
+ return 1;
+}
+
+static int gaim_ssi_parseack( aim_session_t *sess, aim_frame_t *fr, ... )
+{
+ aim_snac_t *origsnac;
+ va_list ap;
+
+ va_start( ap, fr );
+ origsnac = va_arg( ap, aim_snac_t * );
+ va_end( ap );
+
+ if( origsnac && origsnac->family == AIM_CB_FAM_SSI && origsnac->type == AIM_CB_SSI_ADD && origsnac->data )
+ {
+ int i, st, count = aim_bstream_empty( &fr->data );
+ char *list;
+
+ if( count & 1 )
+ {
+ /* Hmm, the length should be even... */
+ imcb_error( sess->aux_data, "Received SSI ACK package with non-even length");
+ return( 0 );
+ }
+ count >>= 1;
+
+ list = (char *) origsnac->data;
+ for( i = 0; i < count; i ++ )
+ {
+ struct aim_ssi_item *ssigroup = aim_ssi_itemlist_findparent( sess->ssi.items, list );
+ char *group = ssigroup ? ssigroup->name : NULL;
+
+ st = aimbs_get16( &fr->data );
+ if( st == 0x00 )
+ {
+ imcb_add_buddy( sess->aux_data, normalize(list), group );
+ }
+ else if( st == 0x0E )
+ {
+ imcb_log( sess->aux_data, "Buddy %s can't be added without authorization, requesting authorization", list );
+
+ aim_ssi_auth_request( sess, fr->conn, list, "" );
+ aim_ssi_addbuddies( sess, fr->conn, OSCAR_GROUP, &list, 1, 1 );
+ }
+ else
+ {
+ imcb_error( sess->aux_data, "Error while adding buddy: 0x%04x", st );
+ }
+ list += strlen( list ) + 1;
+ }
+ }
+
+ return( 1 );
+}
+
+static void oscar_set_permit_deny(struct im_connection *ic) {
+ struct oscar_data *od = (struct oscar_data *)ic->proto_data;
+ if (od->icq) {
+ GSList *list;
+ char buf[MAXMSGLEN];
+ int at;
+
+ switch(ic->permdeny) {
+ case 1:
+ aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_DENYADD, ic->acc->user);
+ break;
+ case 2:
+ aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_PERMITADD, ic->acc->user);
+ break;
+ case 3:
+ list = ic->permit;
+ at = 0;
+ while (list) {
+ at += g_snprintf(buf + at, sizeof(buf) - at, "%s&", (char *)list->data);
+ list = list->next;
+ }
+ aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_PERMITADD, buf);
+ break;
+ case 4:
+ list = ic->deny;
+ at = 0;
+ while (list) {
+ at += g_snprintf(buf + at, sizeof(buf) - at, "%s&", (char *)list->data);
+ list = list->next;
+ }
+ aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_DENYADD, buf);
+ break;
+ default:
+ break;
+ }
+ } else {
+ if (od->sess->ssi.received_data)
+ aim_ssi_setpermdeny(od->sess, od->conn, ic->permdeny, 0xffffffff);
+ }
+}
+
+static void oscar_add_permit(struct im_connection *ic, char *who) {
+ struct oscar_data *od = (struct oscar_data *)ic->proto_data;
+ if (od->icq) {
+ aim_ssi_auth_reply(od->sess, od->conn, who, 1, "");
+ } else {
+ if (od->sess->ssi.received_data)
+ aim_ssi_addpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_PERMIT);
+ }
+}
+
+static void oscar_add_deny(struct im_connection *ic, char *who) {
+ struct oscar_data *od = (struct oscar_data *)ic->proto_data;
+ if (od->icq) {
+ aim_ssi_auth_reply(od->sess, od->conn, who, 0, "");
+ } else {
+ if (od->sess->ssi.received_data)
+ aim_ssi_addpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_DENY);
+ }
+}
+
+static void oscar_rem_permit(struct im_connection *ic, char *who) {
+ struct oscar_data *od = (struct oscar_data *)ic->proto_data;
+ if (!od->icq) {
+ if (od->sess->ssi.received_data)
+ aim_ssi_delpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_PERMIT);
+ }
+}
+
+static void oscar_rem_deny(struct im_connection *ic, char *who) {
+ struct oscar_data *od = (struct oscar_data *)ic->proto_data;
+ if (!od->icq) {
+ if (od->sess->ssi.received_data)
+ aim_ssi_delpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_DENY);
+ }
+}
+
+static GList *oscar_away_states(struct im_connection *ic)
+{
+ struct oscar_data *od = ic->proto_data;
+
+ if (od->icq) {
+ static GList *m = NULL;
+ m = g_list_append(m, "Away");
+ m = g_list_append(m, "Do Not Disturb");
+ m = g_list_append(m, "Not Available");
+ m = g_list_append(m, "Occupied");
+ m = g_list_append(m, "Free For Chat");
+ m = g_list_append(m, "Invisible");
+ return m;
+ } else {
+ static GList *m = NULL;
+ m = g_list_append(m, "Away");
+ return m;
+ }
+}
+
+static int gaim_icqinfo(aim_session_t *sess, aim_frame_t *fr, ...)
+{
+ struct im_connection *ic = sess->aux_data;
+ struct oscar_data *od = ic->proto_data;
+ gchar who[16];
+ GString *str;
+ va_list ap;
+ struct aim_icq_info *info;
+ uint32_t ip;
+
+ va_start(ap, fr);
+ info = va_arg(ap, struct aim_icq_info *);
+ va_end(ap);
+
+ if (!info->uin)
+ return 0;
+
+ str = g_string_sized_new(512);
+ g_snprintf(who, sizeof(who), "%u", info->uin);
+
+ g_string_printf(str, "%s: %s - %s: %s", _("UIN"), who, _("Nick"),
+ info->nick ? info->nick : "-");
+ g_string_append_printf(str, "\n%s: %s", _("First Name"), info->first);
+ g_string_append_printf(str, "\n%s: %s", _("Last Name"), info->last);
+ g_string_append_printf(str, "\n%s: %s", _("Email Address"), info->email);
+ if (info->numaddresses && info->email2) {
+ int i;
+ for (i = 0; i < info->numaddresses; i++) {
+ g_string_append_printf(str, "\n%s: %s", _("Email Address"), info->email2[i]);
+ }
+ }
+ if (od->ips && (ip = (long) g_hash_table_lookup(od->ips, &info->uin)) != 0) {
+ g_string_append_printf(str, "\n%s: %d.%d.%d.%d", _("Last used IP address"),
+ (ip >> 24), (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff);
+ }
+ g_string_append_printf(str, "\n%s: %s", _("Mobile Phone"), info->mobile);
+ if (info->gender != 0)
+ g_string_append_printf(str, "\n%s: %s", _("Gender"), info->gender==1 ? _("Female") : _("Male"));
+ if (info->birthyear || info->birthmonth || info->birthday) {
+ char date[30];
+ struct tm tm;
+ memset(&tm, 0, sizeof(struct tm));
+ tm.tm_mday = (int)info->birthday;
+ tm.tm_mon = (int)info->birthmonth-1;
+ tm.tm_year = (int)info->birthyear%100;
+ strftime(date, sizeof(date), "%Y-%m-%d", &tm);
+ g_string_append_printf(str, "\n%s: %s", _("Birthday"), date);
+ }
+ if (info->age) {
+ char age[5];
+ g_snprintf(age, sizeof(age), "%hhd", info->age);
+ g_string_append_printf(str, "\n%s: %s", _("Age"), age);
+ }
+ g_string_append_printf(str, "\n%s: %s", _("Personal Web Page"), info->personalwebpage);
+ if (info->info && info->info[0]) {
+ g_string_sprintfa(str, "\n%s:\n%s\n%s", _("Additional Information"),
+ info->info, _("End of Additional Information"));
+ }
+ g_string_append_c(str, '\n');
+ if ((info->homeaddr && (info->homeaddr[0])) || (info->homecity && info->homecity[0]) || (info->homestate && info->homestate[0]) || (info->homezip && info->homezip[0])) {
+ g_string_append_printf(str, "%s:", _("Home Address"));
+ g_string_append_printf(str, "\n%s: %s", _("Address"), info->homeaddr);
+ g_string_append_printf(str, "\n%s: %s", _("City"), info->homecity);
+ g_string_append_printf(str, "\n%s: %s", _("State"), info->homestate);
+ g_string_append_printf(str, "\n%s: %s", _("Zip Code"), info->homezip);
+ g_string_append_c(str, '\n');
+ }
+ if ((info->workaddr && info->workaddr[0]) || (info->workcity && info->workcity[0]) || (info->workstate && info->workstate[0]) || (info->workzip && info->workzip[0])) {
+ g_string_append_printf(str, "%s:", _("Work Address"));
+ g_string_append_printf(str, "\n%s: %s", _("Address"), info->workaddr);
+ g_string_append_printf(str, "\n%s: %s", _("City"), info->workcity);
+ g_string_append_printf(str, "\n%s: %s", _("State"), info->workstate);
+ g_string_append_printf(str, "\n%s: %s", _("Zip Code"), info->workzip);
+ g_string_append_c(str, '\n');
+ }
+ if ((info->workcompany && info->workcompany[0]) || (info->workdivision && info->workdivision[0]) || (info->workposition && info->workposition[0]) || (info->workwebpage && info->workwebpage[0])) {
+ g_string_append_printf(str, "%s:", _("Work Information"));
+ g_string_append_printf(str, "\n%s: %s", _("Company"), info->workcompany);
+ g_string_append_printf(str, "\n%s: %s", _("Division"), info->workdivision);
+ g_string_append_printf(str, "\n%s: %s", _("Position"), info->workposition);
+ if (info->workwebpage && info->workwebpage[0]) {
+ g_string_append_printf(str, "\n%s: %s", _("Web Page"), info->workwebpage);
+ }
+ g_string_append_c(str, '\n');
+ }
+
+ imcb_log(ic, "%s\n%s", _("User Info"), str->str);
+ g_string_free(str, TRUE);
+
+ return 1;
+
+}
+
+static char *oscar_encoding_extract(const char *encoding)
+{
+ char *ret = NULL;
+ char *begin, *end;
+
+ g_return_val_if_fail(encoding != NULL, NULL);
+
+ /* Make sure encoding begins with charset= */
+ if (strncmp(encoding, "text/plain; charset=", 20) &&
+ strncmp(encoding, "text/aolrtf; charset=", 21) &&
+ strncmp(encoding, "text/x-aolrtf; charset=", 23))
+ {
+ return NULL;
+ }
+
+ begin = strchr(encoding, '"');
+ end = strrchr(encoding, '"');
+
+ if ((begin == NULL) || (end == NULL) || (begin >= end))
+ return NULL;
+
+ ret = g_strndup(begin+1, (end-1) - begin);
+
+ return ret;
+}
+
+static char *oscar_encoding_to_utf8(char *encoding, char *text, int textlen)
+{
+ char *utf8 = g_new0(char, 8192);
+
+ if ((encoding == NULL) || encoding[0] == '\0') {
+ /* gaim_debug_info("oscar", "Empty encoding, assuming UTF-8\n");*/
+ } else if (!g_strcasecmp(encoding, "iso-8859-1")) {
+ do_iconv("iso-8859-1", "UTF-8", text, utf8, textlen, 8192);
+ } else if (!g_strcasecmp(encoding, "ISO-8859-1-Windows-3.1-Latin-1")) {
+ do_iconv("Windows-1252", "UTF-8", text, utf8, textlen, 8192);
+ } else if (!g_strcasecmp(encoding, "unicode-2-0")) {
+ do_iconv("UCS-2BE", "UTF-8", text, utf8, textlen, 8192);
+ } else if (g_strcasecmp(encoding, "us-ascii") && strcmp(encoding, "utf-8")) {
+ /* gaim_debug_warning("oscar", "Unrecognized character encoding \"%s\", "
+ "attempting to convert to UTF-8 anyway\n", encoding);*/
+ do_iconv(encoding, "UTF-8", text, utf8, textlen, 8192);
+ }
+
+ /*
+ * If utf8 is still NULL then either the encoding is us-ascii/utf-8 or
+ * we have been unable to convert the text to utf-8 from the encoding
+ * that was specified. So we assume it's UTF-8 and hope for the best.
+ */
+ if (*utf8 == 0) {
+ strncpy(utf8, text, textlen);
+ }
+
+ return utf8;
+}
+
+static int gaim_parseaiminfo(aim_session_t *sess, aim_frame_t *fr, ...)
+{
+ struct im_connection *ic = sess->aux_data;
+ va_list ap;
+ aim_userinfo_t *userinfo;
+ guint16 infotype;
+ char *text_encoding = NULL, *text = NULL, *extracted_encoding = NULL;
+ guint16 text_length;
+ char *utf8 = NULL;
+
+ va_start(ap, fr);
+ userinfo = va_arg(ap, aim_userinfo_t *);
+ infotype = va_arg(ap, int);
+ text_encoding = va_arg(ap, char*);
+ text = va_arg(ap, char*);
+ text_length = va_arg(ap, int);
+ va_end(ap);
+
+ if(text_encoding)
+ extracted_encoding = oscar_encoding_extract(text_encoding);
+ if(infotype == AIM_GETINFO_GENERALINFO) {
+ /*Display idle time*/
+ char buff[256];
+ struct tm idletime;
+ if(userinfo->idletime) {
+ memset(&idletime, 0, sizeof(struct tm));
+ idletime.tm_mday = (userinfo->idletime / 60) / 24;
+ idletime.tm_hour = (userinfo->idletime / 60) % 24;
+ idletime.tm_min = userinfo->idletime % 60;
+ idletime.tm_sec = 0;
+ strftime(buff, 256, _("%d days %H hours %M minutes"), &idletime);
+ imcb_log(ic, "%s: %s", _("Idle Time"), buff);
+ }
+
+ if(text) {
+ utf8 = oscar_encoding_to_utf8(extracted_encoding, text, text_length);
+ imcb_log(ic, "%s\n%s", _("User Info"), utf8);
+ } else {
+ imcb_log(ic, _("No user info available."));
+ }
+ } else if(infotype == AIM_GETINFO_AWAYMESSAGE && userinfo->flags & AIM_FLAG_AWAY) {
+ utf8 = oscar_encoding_to_utf8(extracted_encoding, text, text_length);
+ imcb_log(ic, "%s\n%s", _("Away Message"), utf8);
+ }
+
+ g_free(utf8);
+
+ return 1;
+}
+
+int gaim_parsemtn(aim_session_t *sess, aim_frame_t *fr, ...)
+{
+ struct im_connection * ic = sess->aux_data;
+ va_list ap;
+ guint16 type1, type2;
+ char * sn;
+
+ va_start(ap, fr);
+ type1 = va_arg(ap, int);
+ sn = va_arg(ap, char*);
+ type2 = va_arg(ap, int);
+ va_end(ap);
+
+ if(type2 == 0x0002) {
+ /* User is typing */
+ imcb_buddy_typing(ic, normalize(sn), OPT_TYPING);
+ }
+ else if (type2 == 0x0001) {
+ /* User has typed something, but is not actively typing (stale) */
+ imcb_buddy_typing(ic, normalize(sn), OPT_THINKING);
+ }
+ else {
+ /* User has stopped typing */
+ imcb_buddy_typing(ic, normalize(sn), 0);
+ }
+
+ return 1;
+}
+
+int oscar_send_typing(struct im_connection *ic, char * who, int typing)
+{
+ struct oscar_data *od = ic->proto_data;
+ return( aim_im_sendmtn(od->sess, 1, who, (typing & OPT_TYPING) ? 0x0002 : 0x0000) );
+}
+
+void oscar_chat_msg(struct groupchat *c, char *message, int msgflags)
+{
+ struct im_connection *ic = c->ic;
+ struct oscar_data * od = (struct oscar_data*)ic->proto_data;
+ struct chat_connection * ccon;
+ int ret;
+ guint8 len = strlen(message);
+ guint16 flags;
+ char *s;
+
+ if (!(ccon = c->data))
+ return;
+
+ for (s = message; *s; s++)
+ if (*s & 128)
+ break;
+
+ flags = AIM_CHATFLAGS_NOREFLECT;
+
+ /* Message contains high ASCII chars, time for some translation! */
+ if (*s) {
+ s = g_malloc(BUF_LONG);
+ /* Try if we can put it in an ISO8859-1 string first.
+ If we can't, fall back to UTF16. */
+ if ((ret = do_iconv("UTF-8", "ISO8859-1", message, s, len, BUF_LONG)) >= 0) {
+ flags |= AIM_CHATFLAGS_ISO_8859_1;
+ len = ret;
+ } else if ((ret = do_iconv("UTF-8", "UNICODEBIG", message, s, len, BUF_LONG)) >= 0) {
+ flags |= AIM_CHATFLAGS_UNICODE;
+ len = ret;
+ } else {
+ /* OOF, translation failed... Oh well.. */
+ g_free( s );
+ s = message;
+ }
+ } else {
+ s = message;
+ }
+
+ ret = aim_chat_send_im(od->sess, ccon->conn, flags, s, len);
+
+ if (s != message) {
+ g_free(s);
+ }
+
+/* return (ret >= 0); */
+}
+
+void oscar_chat_invite(struct groupchat *c, char *who, char *message)
+{
+ struct im_connection *ic = c->ic;
+ struct oscar_data * od = (struct oscar_data *)ic->proto_data;
+ struct chat_connection *ccon;
+
+ if (!(ccon = c->data))
+ return;
+
+ aim_chat_invite(od->sess, od->conn, who, message ? message : "",
+ ccon->exchange, ccon->name, 0x0);
+}
+
+void oscar_chat_kill(struct im_connection *ic, struct chat_connection *cc)
+{
+ struct oscar_data *od = (struct oscar_data *)ic->proto_data;
+
+ /* Notify the conversation window that we've left the chat */
+ imcb_chat_free(cc->cnv);
+
+ /* Destroy the chat_connection */
+ od->oscar_chats = g_slist_remove(od->oscar_chats, cc);
+ if (cc->inpa > 0)
+ b_event_remove(cc->inpa);
+ aim_conn_kill(od->sess, &cc->conn);
+ g_free(cc->name);
+ g_free(cc->show);
+ g_free(cc);
+}
+
+void oscar_chat_leave(struct groupchat *c)
+{
+ if (!c->data)
+ return;
+ oscar_chat_kill(c->ic, c->data);
+}
+
+struct groupchat *oscar_chat_join_internal(struct im_connection *ic, const char *room,
+ const char *nick, const char *password, int exchange_number)
+{
+ struct oscar_data * od = (struct oscar_data *)ic->proto_data;
+ struct groupchat *ret = imcb_chat_new(ic, room);
+ aim_conn_t * cur;
+
+ if((cur = aim_getconn_type(od->sess, AIM_CONN_TYPE_CHATNAV))) {
+ int st;
+
+ st = aim_chatnav_createroom(od->sess, cur, room, exchange_number);
+
+ return ret;
+ } else {
+ struct create_room * cr = g_new0(struct create_room, 1);
+
+ cr->exchange = exchange_number;
+ cr->name = g_strdup(room);
+ od->create_rooms = g_slist_append(od->create_rooms, cr);
+ aim_reqservice(od->sess, od->conn, AIM_CONN_TYPE_CHATNAV);
+
+ return ret;
+ }
+}
+
+struct groupchat *oscar_chat_join(struct im_connection *ic, const char *room,
+ const char *nick, const char *password, set_t **sets)
+{
+ return oscar_chat_join_internal(ic, room, nick, password, set_getint(sets, "exchange_number"));
+}
+
+struct groupchat *oscar_chat_with(struct im_connection * ic, char *who)
+{
+ struct oscar_data * od = (struct oscar_data *)ic->proto_data;
+ struct groupchat *ret;
+ static int chat_id = 0;
+ char * chatname, *s;
+ struct groupchat *c;
+
+ chatname = g_strdup_printf("%s%s%d", isdigit(*ic->acc->user) ? "icq" : "",
+ ic->acc->user, chat_id++);
+
+ for (s = chatname; *s; s ++)
+ if (!isalnum(*s))
+ *s = '0';
+
+ c = imcb_chat_new(ic, chatname);
+ ret = oscar_chat_join_internal(ic, chatname, NULL, NULL, 4);
+ aim_chat_invite(od->sess, od->conn, who, "", 4, chatname, 0x0);
+
+ g_free(chatname);
+
+ return NULL;
+}
+
+void oscar_accept_chat(void *data)
+{
+ struct aim_chat_invitation * inv = data;
+
+ oscar_chat_join_internal(inv->ic, inv->name, NULL, NULL, 4);
+ g_free(inv->name);
+ g_free(inv);
+}
+
+void oscar_reject_chat(void *data)
+{
+ struct aim_chat_invitation * inv = data;
+
+ g_free(inv->name);
+ g_free(inv);
+}
+
+void oscar_chat_add_settings(account_t *acc, set_t **head)
+{
+ set_add(head, "exchange_number", "4", set_eval_int, NULL);
+}
+
+void oscar_chat_free_settings(account_t *acc, set_t **head)
+{
+ set_del(head, "exchange_number");
+}
+
+void oscar_initmodule()
+{
+ struct prpl *ret = g_new0(struct prpl, 1);
+ ret->name = "oscar";
+ ret->mms = 2343; /* this guess taken from libotr UPGRADING file */
+ ret->away_states = oscar_away_states;
+ ret->init = oscar_init;
+ ret->login = oscar_login;
+ ret->keepalive = oscar_keepalive;
+ ret->logout = oscar_logout;
+ ret->buddy_msg = oscar_buddy_msg;
+ ret->get_info = oscar_get_info;
+ ret->set_away = oscar_set_away;
+ ret->get_away = oscar_get_away;
+ ret->add_buddy = oscar_add_buddy;
+ ret->remove_buddy = oscar_remove_buddy;
+ ret->chat_msg = oscar_chat_msg;
+ ret->chat_invite = oscar_chat_invite;
+ ret->chat_leave = oscar_chat_leave;
+ ret->chat_with = oscar_chat_with;
+ ret->chat_join = oscar_chat_join;
+ ret->chat_add_settings = oscar_chat_add_settings;
+ ret->chat_free_settings = oscar_chat_free_settings;
+ ret->add_permit = oscar_add_permit;
+ ret->add_deny = oscar_add_deny;
+ ret->rem_permit = oscar_rem_permit;
+ ret->rem_deny = oscar_rem_deny;
+ ret->set_permit_deny = oscar_set_permit_deny;
+ ret->send_typing = oscar_send_typing;
+
+ ret->handle_cmp = aim_sncmp;
+
+ register_protocol(ret);
+}
diff --git a/protocols/oscar/oscar_util.c b/protocols/oscar/oscar_util.c
new file mode 100644
index 00000000..0ce06bd9
--- /dev/null
+++ b/protocols/oscar/oscar_util.c
@@ -0,0 +1,161 @@
+#include <aim.h>
+#include <ctype.h>
+
+int aimutil_putstr(u_char *dest, const char *src, int len)
+{
+ memcpy(dest, src, len);
+ return len;
+}
+
+/*
+ * Tokenizing functions. Used to portably replace strtok/sep.
+ * -- DMP.
+ *
+ */
+int aimutil_tokslen(char *toSearch, int index, char dl)
+{
+ int curCount = 1;
+ char *next;
+ char *last;
+ int toReturn;
+
+ last = toSearch;
+ next = strchr(toSearch, dl);
+
+ while(curCount < index && next != NULL) {
+ curCount++;
+ last = next + 1;
+ next = strchr(last, dl);
+ }
+
+ if ((curCount < index) || (next == NULL))
+ toReturn = strlen(toSearch) - (curCount - 1);
+ else
+ toReturn = next - toSearch - (curCount - 1);
+
+ return toReturn;
+}
+
+int aimutil_itemcnt(char *toSearch, char dl)
+{
+ int curCount;
+ char *next;
+
+ curCount = 1;
+
+ next = strchr(toSearch, dl);
+
+ while(next != NULL) {
+ curCount++;
+ next = strchr(next + 1, dl);
+ }
+
+ return curCount;
+}
+
+char *aimutil_itemidx(char *toSearch, int index, char dl)
+{
+ int curCount;
+ char *next;
+ char *last;
+ char *toReturn;
+
+ curCount = 0;
+
+ last = toSearch;
+ next = strchr(toSearch, dl);
+
+ while (curCount < index && next != NULL) {
+ curCount++;
+ last = next + 1;
+ next = strchr(last, dl);
+ }
+
+ if (curCount < index) {
+ toReturn = g_strdup("");
+ }
+ next = strchr(last, dl);
+
+ if (curCount < index) {
+ toReturn = g_strdup("");
+ } else {
+ if (next == NULL) {
+ toReturn = g_malloc((strlen(last) + 1) * sizeof(char));
+ strcpy(toReturn, last);
+ } else {
+ toReturn = g_malloc((next - last + 1) * sizeof(char));
+ memcpy(toReturn, last, (next - last));
+ toReturn[next - last] = '\0';
+ }
+ }
+ return toReturn;
+}
+
+/*
+* int snlen(const char *)
+*
+* This takes a screen name and returns its length without
+* spaces. If there are no spaces in the SN, then the
+* return is equal to that of strlen().
+*
+*/
+static int aim_snlen(const char *sn)
+{
+ int i = 0;
+ const char *curPtr = NULL;
+
+ if (!sn)
+ return 0;
+
+ curPtr = sn;
+ while ( (*curPtr) != (char) '\0') {
+ if ((*curPtr) != ' ')
+ i++;
+ curPtr++;
+ }
+
+ return i;
+}
+
+/*
+* int sncmp(const char *, const char *)
+*
+* This takes two screen names and compares them using the rules
+* on screen names for AIM/AOL. Mainly, this means case and space
+* insensitivity (all case differences and spacing differences are
+* ignored).
+*
+* Return: 0 if equal
+* non-0 if different
+*
+*/
+
+int aim_sncmp(const char *sn1, const char *sn2)
+{
+ const char *curPtr1 = NULL, *curPtr2 = NULL;
+
+ if (aim_snlen(sn1) != aim_snlen(sn2))
+ return 1;
+
+ curPtr1 = sn1;
+ curPtr2 = sn2;
+ while ( (*curPtr1 != (char) '\0') && (*curPtr2 != (char) '\0') ) {
+ if ( (*curPtr1 == ' ') || (*curPtr2 == ' ') ) {
+ if (*curPtr1 == ' ')
+ curPtr1++;
+ if (*curPtr2 == ' ')
+ curPtr2++;
+ } else {
+ if ( toupper(*curPtr1) != toupper(*curPtr2))
+ return 1;
+ curPtr1++;
+ curPtr2++;
+ }
+ }
+
+ /* Should both be NULL */
+ if (*curPtr1 != *curPtr2)
+ return 1;
+
+ return 0;
+}
diff --git a/protocols/oscar/rxhandlers.c b/protocols/oscar/rxhandlers.c
new file mode 100644
index 00000000..7014e693
--- /dev/null
+++ b/protocols/oscar/rxhandlers.c
@@ -0,0 +1,373 @@
+/*
+ * aim_rxhandlers.c
+ *
+ * This file contains most all of the incoming packet handlers, along
+ * with aim_rxdispatch(), the Rx dispatcher. Queue/list management is
+ * actually done in aim_rxqueue.c.
+ *
+ */
+
+#include <aim.h>
+
+struct aim_rxcblist_s {
+ guint16 family;
+ guint16 type;
+ aim_rxcallback_t handler;
+ u_short flags;
+ struct aim_rxcblist_s *next;
+};
+
+aim_module_t *aim__findmodulebygroup(aim_session_t *sess, guint16 group)
+{
+ aim_module_t *cur;
+
+ for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) {
+ if (cur->family == group)
+ return cur;
+ }
+
+ return NULL;
+}
+
+static aim_module_t *aim__findmodule(aim_session_t *sess, const char *name)
+{
+ aim_module_t *cur;
+
+ for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) {
+ if (strcmp(name, cur->name) == 0)
+ return cur;
+ }
+
+ return NULL;
+}
+
+int aim__registermodule(aim_session_t *sess, int (*modfirst)(aim_session_t *, aim_module_t *))
+{
+ aim_module_t *mod;
+
+ if (!sess || !modfirst)
+ return -1;
+
+ if (!(mod = g_new0(aim_module_t,1)))
+ return -1;
+
+ if (modfirst(sess, mod) == -1) {
+ g_free(mod);
+ return -1;
+ }
+
+ if (aim__findmodule(sess, mod->name)) {
+ if (mod->shutdown)
+ mod->shutdown(sess, mod);
+ g_free(mod);
+ return -1;
+ }
+
+ mod->next = (aim_module_t *)sess->modlistv;
+ sess->modlistv = mod;
+
+
+ return 0;
+}
+
+void aim__shutdownmodules(aim_session_t *sess)
+{
+ aim_module_t *cur;
+
+ for (cur = (aim_module_t *)sess->modlistv; cur; ) {
+ aim_module_t *tmp;
+
+ tmp = cur->next;
+
+ if (cur->shutdown)
+ cur->shutdown(sess, cur);
+
+ g_free(cur);
+
+ cur = tmp;
+ }
+
+ sess->modlistv = NULL;
+
+ return;
+}
+
+static int consumesnac(aim_session_t *sess, aim_frame_t *rx)
+{
+ aim_module_t *cur;
+ aim_modsnac_t snac;
+
+ if (aim_bstream_empty(&rx->data) < 10)
+ return 0;
+
+ snac.family = aimbs_get16(&rx->data);
+ snac.subtype = aimbs_get16(&rx->data);
+ snac.flags = aimbs_get16(&rx->data);
+ snac.id = aimbs_get32(&rx->data);
+
+ /* Contains TLV(s) in the FNAC header */
+ if(snac.flags & 0x8000) {
+ aim_bstream_advance(&rx->data, aimbs_get16(&rx->data));
+ } else if(snac.flags & 0x0001) {
+ /* Following SNAC will be related */
+ }
+
+ for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) {
+
+ if (!(cur->flags & AIM_MODFLAG_MULTIFAMILY) &&
+ (cur->family != snac.family))
+ continue;
+
+ if (cur->snachandler(sess, cur, rx, &snac, &rx->data))
+ return 1;
+
+ }
+
+ return 0;
+}
+
+static int consumenonsnac(aim_session_t *sess, aim_frame_t *rx, guint16 family, guint16 subtype)
+{
+ aim_module_t *cur;
+ aim_modsnac_t snac;
+
+ snac.family = family;
+ snac.subtype = subtype;
+ snac.flags = snac.id = 0;
+
+ for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) {
+
+ if (!(cur->flags & AIM_MODFLAG_MULTIFAMILY) &&
+ (cur->family != snac.family))
+ continue;
+
+ if (cur->snachandler(sess, cur, rx, &snac, &rx->data))
+ return 1;
+
+ }
+
+ return 0;
+}
+
+static int negchan_middle(aim_session_t *sess, aim_frame_t *fr)
+{
+ aim_tlvlist_t *tlvlist;
+ char *msg = NULL;
+ guint16 code = 0;
+ aim_rxcallback_t userfunc;
+ int ret = 1;
+
+ if (aim_bstream_empty(&fr->data) == 0) {
+ /* XXX should do something with this */
+ return 1;
+ }
+
+ /* Used only by the older login protocol */
+ /* XXX remove this special case? */
+ if (fr->conn->type == AIM_CONN_TYPE_AUTH)
+ return consumenonsnac(sess, fr, 0x0017, 0x0003);
+
+ tlvlist = aim_readtlvchain(&fr->data);
+
+ if (aim_gettlv(tlvlist, 0x0009, 1))
+ code = aim_gettlv16(tlvlist, 0x0009, 1);
+
+ if (aim_gettlv(tlvlist, 0x000b, 1))
+ msg = aim_gettlv_str(tlvlist, 0x000b, 1);
+
+ if ((userfunc = aim_callhandler(sess, fr->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNERR)))
+ ret = userfunc(sess, fr, code, msg);
+
+ aim_freetlvchain(&tlvlist);
+
+ g_free(msg);
+
+ return ret;
+}
+
+/*
+ * Some SNACs we do not allow to be hooked, for good reason.
+ */
+static int checkdisallowed(guint16 group, guint16 type)
+{
+ static const struct {
+ guint16 group;
+ guint16 type;
+ } dontuse[] = {
+ {0x0001, 0x0002},
+ {0x0001, 0x0003},
+ {0x0001, 0x0006},
+ {0x0001, 0x0007},
+ {0x0001, 0x0008},
+ {0x0001, 0x0017},
+ {0x0001, 0x0018},
+ {0x0000, 0x0000}
+ };
+ int i;
+
+ for (i = 0; dontuse[i].group != 0x0000; i++) {
+ if ((dontuse[i].group == group) && (dontuse[i].type == type))
+ return 1;
+ }
+
+ return 0;
+}
+
+int aim_conn_addhandler(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 type, aim_rxcallback_t newhandler, guint16 flags)
+{
+ struct aim_rxcblist_s *newcb;
+
+ if (!conn)
+ return -1;
+
+ if (checkdisallowed(family, type)) {
+ g_assert(0);
+ return -1;
+ }
+
+ if (!(newcb = (struct aim_rxcblist_s *)g_new0(struct aim_rxcblist_s, 1)))
+ return -1;
+
+ newcb->family = family;
+ newcb->type = type;
+ newcb->flags = flags;
+ newcb->handler = newhandler;
+ newcb->next = NULL;
+
+ if (!conn->handlerlist)
+ conn->handlerlist = (void *)newcb;
+ else {
+ struct aim_rxcblist_s *cur;
+
+ for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur->next; cur = cur->next)
+ ;
+ cur->next = newcb;
+ }
+
+ return 0;
+}
+
+int aim_clearhandlers(aim_conn_t *conn)
+{
+ struct aim_rxcblist_s *cur;
+
+ if (!conn)
+ return -1;
+
+ for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur; ) {
+ struct aim_rxcblist_s *tmp;
+
+ tmp = cur->next;
+ g_free(cur);
+ cur = tmp;
+ }
+ conn->handlerlist = NULL;
+
+ return 0;
+}
+
+aim_rxcallback_t aim_callhandler(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 type)
+{
+ struct aim_rxcblist_s *cur;
+
+ if (!conn)
+ return NULL;
+
+ for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur; cur = cur->next) {
+ if ((cur->family == family) && (cur->type == type))
+ return cur->handler;
+ }
+
+ if (type == AIM_CB_SPECIAL_DEFAULT) {
+ return NULL; /* prevent infinite recursion */
+ }
+
+ return aim_callhandler(sess, conn, family, AIM_CB_SPECIAL_DEFAULT);
+}
+
+void aim_clonehandlers(aim_session_t *sess, aim_conn_t *dest, aim_conn_t *src)
+{
+ struct aim_rxcblist_s *cur;
+
+ for (cur = (struct aim_rxcblist_s *)src->handlerlist; cur; cur = cur->next) {
+ aim_conn_addhandler(sess, dest, cur->family, cur->type,
+ cur->handler, cur->flags);
+ }
+
+ return;
+}
+
+static int aim_callhandler_noparam(aim_session_t *sess, aim_conn_t *conn,guint16 family, guint16 type, aim_frame_t *ptr)
+{
+ aim_rxcallback_t userfunc;
+
+ if ((userfunc = aim_callhandler(sess, conn, family, type)))
+ return userfunc(sess, ptr);
+
+ return 1; /* XXX */
+}
+
+/*
+ * aim_rxdispatch()
+ *
+ * Basically, heres what this should do:
+ * 1) Determine correct packet handler for this packet
+ * 2) Mark the packet handled (so it can be dequeued in purge_queue())
+ * 3) Send the packet to the packet handler
+ * 4) Go to next packet in the queue and start over
+ * 5) When done, run purge_queue() to purge handled commands
+ *
+ * TODO: Clean up.
+ * TODO: More support for mid-level handlers.
+ * TODO: Allow for NULL handlers.
+ *
+ */
+void aim_rxdispatch(aim_session_t *sess)
+{
+ int i;
+ aim_frame_t *cur;
+
+ for (cur = sess->queue_incoming, i = 0; cur; cur = cur->next, i++) {
+
+ /*
+ * XXX: This is still fairly ugly.
+ */
+
+ if (cur->handled)
+ continue;
+
+ if (cur->hdr.flap.type == 0x01) {
+
+ cur->handled = aim_callhandler_noparam(sess, cur->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_FLAPVER, cur); /* XXX use consumenonsnac */
+
+ continue;
+
+ } else if (cur->hdr.flap.type == 0x02) {
+
+ if ((cur->handled = consumesnac(sess, cur)))
+ continue;
+
+ } else if (cur->hdr.flap.type == 0x04) {
+
+ cur->handled = negchan_middle(sess, cur);
+ continue;
+
+ } else if (cur->hdr.flap.type == 0x05)
+ ;
+
+ if (!cur->handled) {
+ consumenonsnac(sess, cur, 0xffff, 0xffff); /* last chance! */
+ cur->handled = 1;
+ }
+ }
+
+ /*
+ * This doesn't have to be called here. It could easily be done
+ * by a seperate thread or something. It's an administrative operation,
+ * and can take a while. Though the less you call it the less memory
+ * you'll have :)
+ */
+ aim_purge_rxqueue(sess);
+
+ return;
+}
diff --git a/protocols/oscar/rxqueue.c b/protocols/oscar/rxqueue.c
new file mode 100644
index 00000000..34f389af
--- /dev/null
+++ b/protocols/oscar/rxqueue.c
@@ -0,0 +1,500 @@
+/*
+ * aim_rxqueue.c
+ *
+ * This file contains the management routines for the receive
+ * (incoming packet) queue. The actual packet handlers are in
+ * aim_rxhandlers.c.
+ */
+
+#include <aim.h>
+
+#ifndef _WIN32
+#include <sys/socket.h>
+#endif
+
+/*
+ *
+ */
+int aim_recv(int fd, void *buf, size_t count)
+{
+ int left, cur;
+
+ for (cur = 0, left = count; left; ) {
+ int ret;
+
+ ret = recv(fd, ((unsigned char *)buf)+cur, left, 0);
+
+ /* Of course EOF is an error, only morons disagree with that. */
+ if (ret <= 0)
+ return -1;
+
+ cur += ret;
+ left -= ret;
+ }
+
+ return cur;
+}
+
+/*
+ * Read into a byte stream. Will not read more than count, but may read
+ * less if there is not enough room in the stream buffer.
+ */
+static int aim_bstream_recv(aim_bstream_t *bs, int fd, size_t count)
+{
+ int red = 0;
+
+ if (!bs || (fd < 0) || (count < 0))
+ return -1;
+
+ if (count > (bs->len - bs->offset))
+ count = bs->len - bs->offset; /* truncate to remaining space */
+
+ if (count) {
+
+ red = aim_recv(fd, bs->data + bs->offset, count);
+
+ if (red <= 0)
+ return -1;
+ }
+
+ bs->offset += red;
+
+ return red;
+}
+
+int aim_bstream_init(aim_bstream_t *bs, guint8 *data, int len)
+{
+
+ if (!bs)
+ return -1;
+
+ bs->data = data;
+ bs->len = len;
+ bs->offset = 0;
+
+ return 0;
+}
+
+int aim_bstream_empty(aim_bstream_t *bs)
+{
+ return bs->len - bs->offset;
+}
+
+int aim_bstream_curpos(aim_bstream_t *bs)
+{
+ return bs->offset;
+}
+
+int aim_bstream_setpos(aim_bstream_t *bs, int off)
+{
+
+ if (off > bs->len)
+ return -1;
+
+ bs->offset = off;
+
+ return off;
+}
+
+void aim_bstream_rewind(aim_bstream_t *bs)
+{
+
+ aim_bstream_setpos(bs, 0);
+
+ return;
+}
+
+int aim_bstream_advance(aim_bstream_t *bs, int n)
+{
+
+ if (aim_bstream_empty(bs) < n)
+ return 0; /* XXX throw an exception */
+
+ bs->offset += n;
+
+ return n;
+}
+
+guint8 aimbs_get8(aim_bstream_t *bs)
+{
+
+ if (aim_bstream_empty(bs) < 1)
+ return 0; /* XXX throw an exception */
+
+ bs->offset++;
+
+ return aimutil_get8(bs->data + bs->offset - 1);
+}
+
+guint16 aimbs_get16(aim_bstream_t *bs)
+{
+
+ if (aim_bstream_empty(bs) < 2)
+ return 0; /* XXX throw an exception */
+
+ bs->offset += 2;
+
+ return aimutil_get16(bs->data + bs->offset - 2);
+}
+
+guint32 aimbs_get32(aim_bstream_t *bs)
+{
+
+ if (aim_bstream_empty(bs) < 4)
+ return 0; /* XXX throw an exception */
+
+ bs->offset += 4;
+
+ return aimutil_get32(bs->data + bs->offset - 4);
+}
+
+guint8 aimbs_getle8(aim_bstream_t *bs)
+{
+
+ if (aim_bstream_empty(bs) < 1)
+ return 0; /* XXX throw an exception */
+
+ bs->offset++;
+
+ return aimutil_getle8(bs->data + bs->offset - 1);
+}
+
+guint16 aimbs_getle16(aim_bstream_t *bs)
+{
+
+ if (aim_bstream_empty(bs) < 2)
+ return 0; /* XXX throw an exception */
+
+ bs->offset += 2;
+
+ return aimutil_getle16(bs->data + bs->offset - 2);
+}
+
+guint32 aimbs_getle32(aim_bstream_t *bs)
+{
+
+ if (aim_bstream_empty(bs) < 4)
+ return 0; /* XXX throw an exception */
+
+ bs->offset += 4;
+
+ return aimutil_getle32(bs->data + bs->offset - 4);
+}
+
+int aimbs_put8(aim_bstream_t *bs, guint8 v)
+{
+
+ if (aim_bstream_empty(bs) < 1)
+ return 0; /* XXX throw an exception */
+
+ bs->offset += aimutil_put8(bs->data + bs->offset, v);
+
+ return 1;
+}
+
+int aimbs_put16(aim_bstream_t *bs, guint16 v)
+{
+
+ if (aim_bstream_empty(bs) < 2)
+ return 0; /* XXX throw an exception */
+
+ bs->offset += aimutil_put16(bs->data + bs->offset, v);
+
+ return 2;
+}
+
+int aimbs_put32(aim_bstream_t *bs, guint32 v)
+{
+
+ if (aim_bstream_empty(bs) < 4)
+ return 0; /* XXX throw an exception */
+
+ bs->offset += aimutil_put32(bs->data + bs->offset, v);
+
+ return 1;
+}
+
+int aimbs_putle8(aim_bstream_t *bs, guint8 v)
+{
+
+ if (aim_bstream_empty(bs) < 1)
+ return 0; /* XXX throw an exception */
+
+ bs->offset += aimutil_putle8(bs->data + bs->offset, v);
+
+ return 1;
+}
+
+int aimbs_putle16(aim_bstream_t *bs, guint16 v)
+{
+
+ if (aim_bstream_empty(bs) < 2)
+ return 0; /* XXX throw an exception */
+
+ bs->offset += aimutil_putle16(bs->data + bs->offset, v);
+
+ return 2;
+}
+
+int aimbs_putle32(aim_bstream_t *bs, guint32 v)
+{
+
+ if (aim_bstream_empty(bs) < 4)
+ return 0; /* XXX throw an exception */
+
+ bs->offset += aimutil_putle32(bs->data + bs->offset, v);
+
+ return 1;
+}
+
+int aimbs_getrawbuf(aim_bstream_t *bs, guint8 *buf, int len)
+{
+
+ if (aim_bstream_empty(bs) < len)
+ return 0;
+
+ memcpy(buf, bs->data + bs->offset, len);
+ bs->offset += len;
+
+ return len;
+}
+
+guint8 *aimbs_getraw(aim_bstream_t *bs, int len)
+{
+ guint8 *ob;
+
+ if (!(ob = g_malloc(len)))
+ return NULL;
+
+ if (aimbs_getrawbuf(bs, ob, len) < len) {
+ g_free(ob);
+ return NULL;
+ }
+
+ return ob;
+}
+
+char *aimbs_getstr(aim_bstream_t *bs, int len)
+{
+ guint8 *ob;
+
+ if (!(ob = g_malloc(len+1)))
+ return NULL;
+
+ if (aimbs_getrawbuf(bs, ob, len) < len) {
+ g_free(ob);
+ return NULL;
+ }
+
+ ob[len] = '\0';
+
+ return (char *)ob;
+}
+
+int aimbs_putraw(aim_bstream_t *bs, const guint8 *v, int len)
+{
+
+ if (aim_bstream_empty(bs) < len)
+ return 0; /* XXX throw an exception */
+
+ memcpy(bs->data + bs->offset, v, len);
+ bs->offset += len;
+
+ return len;
+}
+
+int aimbs_putbs(aim_bstream_t *bs, aim_bstream_t *srcbs, int len)
+{
+
+ if (aim_bstream_empty(srcbs) < len)
+ return 0; /* XXX throw exception (underrun) */
+
+ if (aim_bstream_empty(bs) < len)
+ return 0; /* XXX throw exception (overflow) */
+
+ memcpy(bs->data + bs->offset, srcbs->data + srcbs->offset, len);
+ bs->offset += len;
+ srcbs->offset += len;
+
+ return len;
+}
+
+/**
+ * aim_frame_destroy - free aim_frame_t
+ * @frame: the frame to free
+ *
+ * returns -1 on error; 0 on success.
+ *
+ */
+void aim_frame_destroy(aim_frame_t *frame)
+{
+
+ g_free(frame->data.data); /* XXX aim_bstream_free */
+
+ g_free(frame);
+}
+
+
+/*
+ * Grab a single command sequence off the socket, and enqueue
+ * it in the incoming event queue in a seperate struct.
+ */
+int aim_get_command(aim_session_t *sess, aim_conn_t *conn)
+{
+ guint8 flaphdr_raw[6];
+ aim_bstream_t flaphdr;
+ aim_frame_t *newrx;
+ guint16 payloadlen;
+
+ if (!sess || !conn)
+ return 0;
+
+ if (conn->fd == -1)
+ return -1; /* its a aim_conn_close()'d connection */
+
+ /* KIDS, THIS IS WHAT HAPPENS IF YOU USE CODE WRITTEN FOR GUIS IN A DAEMON!
+
+ And wouldn't it make sense to return something that prevents this function
+ from being called again IMMEDIATELY (and making the program suck up all
+ CPU time)?...
+
+ if (conn->fd < 3)
+ return 0;
+ */
+
+ if (conn->status & AIM_CONN_STATUS_INPROGRESS)
+ return aim_conn_completeconnect(sess, conn);
+
+ aim_bstream_init(&flaphdr, flaphdr_raw, sizeof(flaphdr_raw));
+
+ /*
+ * Read FLAP header. Six bytes:
+ *
+ * 0 char -- Always 0x2a
+ * 1 char -- Channel ID. Usually 2 -- 1 and 4 are used during login.
+ * 2 short -- Sequence number
+ * 4 short -- Number of data bytes that follow.
+ */
+ if (aim_bstream_recv(&flaphdr, conn->fd, 6) < 6) {
+ aim_conn_close(conn);
+ return -1;
+ }
+
+ aim_bstream_rewind(&flaphdr);
+
+ /*
+ * This shouldn't happen unless the socket breaks, the server breaks,
+ * or we break. We must handle it just in case.
+ */
+ if (aimbs_get8(&flaphdr) != 0x2a) {
+ guint8 start;
+
+ aim_bstream_rewind(&flaphdr);
+ start = aimbs_get8(&flaphdr);
+ imcb_error(sess->aux_data, "FLAP framing disrupted");
+ aim_conn_close(conn);
+ return -1;
+ }
+
+ /* allocate a new struct */
+ if (!(newrx = (aim_frame_t *)g_new0(aim_frame_t,1)))
+ return -1;
+
+ /* we're doing FLAP if we're here */
+ newrx->hdrtype = AIM_FRAMETYPE_FLAP;
+
+ newrx->hdr.flap.type = aimbs_get8(&flaphdr);
+ newrx->hdr.flap.seqnum = aimbs_get16(&flaphdr);
+ payloadlen = aimbs_get16(&flaphdr);
+
+ newrx->nofree = 0; /* free by default */
+
+ if (payloadlen) {
+ guint8 *payload = NULL;
+
+ if (!(payload = (guint8 *) g_malloc(payloadlen))) {
+ aim_frame_destroy(newrx);
+ return -1;
+ }
+
+ aim_bstream_init(&newrx->data, payload, payloadlen);
+
+ /* read the payload */
+ if (aim_bstream_recv(&newrx->data, conn->fd, payloadlen) < payloadlen) {
+ aim_frame_destroy(newrx); /* free's payload */
+ aim_conn_close(conn);
+ return -1;
+ }
+ } else
+ aim_bstream_init(&newrx->data, NULL, 0);
+
+
+ aim_bstream_rewind(&newrx->data);
+
+ newrx->conn = conn;
+
+ newrx->next = NULL; /* this will always be at the bottom */
+
+ if (!sess->queue_incoming)
+ sess->queue_incoming = newrx;
+ else {
+ aim_frame_t *cur;
+
+ for (cur = sess->queue_incoming; cur->next; cur = cur->next)
+ ;
+ cur->next = newrx;
+ }
+
+ newrx->conn->lastactivity = time(NULL);
+
+ return 0;
+}
+
+/*
+ * Purge recieve queue of all handled commands (->handled==1). Also
+ * allows for selective freeing using ->nofree so that the client can
+ * keep the data for various purposes.
+ *
+ * If ->nofree is nonzero, the frame will be delinked from the global list,
+ * but will not be free'ed. The client _must_ keep a pointer to the
+ * data -- libfaim will not! If the client marks ->nofree but
+ * does not keep a pointer, it's lost forever.
+ *
+ */
+void aim_purge_rxqueue(aim_session_t *sess)
+{
+ aim_frame_t *cur, **prev;
+
+ for (prev = &sess->queue_incoming; (cur = *prev); ) {
+ if (cur->handled) {
+
+ *prev = cur->next;
+
+ if (!cur->nofree)
+ aim_frame_destroy(cur);
+
+ } else
+ prev = &cur->next;
+ }
+
+ return;
+}
+
+/*
+ * Since aim_get_command will aim_conn_kill dead connections, we need
+ * to clean up the rxqueue of unprocessed connections on that socket.
+ *
+ * XXX: this is something that was handled better in the old connection
+ * handling method, but eh.
+ */
+void aim_rxqueue_cleanbyconn(aim_session_t *sess, aim_conn_t *conn)
+{
+ aim_frame_t *currx;
+
+ for (currx = sess->queue_incoming; currx; currx = currx->next) {
+ if ((!currx->handled) && (currx->conn == conn))
+ currx->handled = 1;
+ }
+ return;
+}
+
diff --git a/protocols/oscar/search.c b/protocols/oscar/search.c
new file mode 100644
index 00000000..3570e4df
--- /dev/null
+++ b/protocols/oscar/search.c
@@ -0,0 +1,121 @@
+
+/*
+ * aim_search.c
+ *
+ * TODO: Add aim_usersearch_name()
+ *
+ */
+
+#include <aim.h>
+
+int aim_usersearch_address(aim_session_t *sess, aim_conn_t *conn, const char *address)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!sess || !conn || !address)
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+strlen(address))))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x000a, 0x0002, 0x0000, g_strdup(address), strlen(address)+1);
+ aim_putsnac(&fr->data, 0x000a, 0x0002, 0x0000, snacid);
+
+ aimbs_putraw(&fr->data, (guint8 *)address, strlen(address));
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/* XXX can this be integrated with the rest of the error handling? */
+static int error(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ int ret = 0;
+ aim_rxcallback_t userfunc;
+ aim_snac_t *snac2;
+
+ /* XXX the modules interface should have already retrieved this for us */
+ if (!(snac2 = aim_remsnac(sess, snac->id))) {
+ imcb_error(sess->aux_data, "couldn't get snac");
+ return 0;
+ }
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, snac2->data /* address */);
+
+ /* XXX freesnac()? */
+ if (snac2)
+ g_free(snac2->data);
+ g_free(snac2);
+
+ return ret;
+}
+
+static int reply(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ int j = 0, m, ret = 0;
+ aim_tlvlist_t *tlvlist;
+ char *cur = NULL, *buf = NULL;
+ aim_rxcallback_t userfunc;
+ aim_snac_t *snac2;
+ char *searchaddr = NULL;
+
+ if ((snac2 = aim_remsnac(sess, snac->id)))
+ searchaddr = (char *)snac2->data;
+
+ tlvlist = aim_readtlvchain(bs);
+ m = aim_counttlvchain(&tlvlist);
+
+ /* XXX uhm. */
+ while ((cur = aim_gettlv_str(tlvlist, 0x0001, j+1)) && j < m) {
+ buf = g_realloc(buf, (j+1) * (MAXSNLEN+1));
+
+ strncpy(&buf[j * (MAXSNLEN+1)], cur, MAXSNLEN);
+ g_free(cur);
+
+ j++;
+ }
+
+ aim_freetlvchain(&tlvlist);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, searchaddr, j, buf);
+
+ /* XXX freesnac()? */
+ if (snac2)
+ g_free(snac2->data);
+ g_free(snac2);
+
+ g_free(buf);
+
+ return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+ if (snac->subtype == 0x0001)
+ return error(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x0003)
+ return reply(sess, mod, rx, snac, bs);
+
+ return 0;
+}
+
+int search_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+ mod->family = 0x000a;
+ mod->version = 0x0001;
+ mod->toolid = 0x0110;
+ mod->toolversion = 0x0629;
+ mod->flags = 0;
+ strncpy(mod->name, "search", sizeof(mod->name));
+ mod->snachandler = snachandler;
+
+ return 0;
+}
+
+
diff --git a/protocols/oscar/search.h b/protocols/oscar/search.h
new file mode 100644
index 00000000..77eeb265
--- /dev/null
+++ b/protocols/oscar/search.h
@@ -0,0 +1,6 @@
+#ifndef __OSCAR_SEARCH_H__
+#define __OSCAR_SEARCH_H__
+
+int aim_usersearch_address(aim_session_t *, aim_conn_t *, const char *);
+
+#endif /* __OSCAR_SEARCH_H__ */
diff --git a/protocols/oscar/service.c b/protocols/oscar/service.c
new file mode 100644
index 00000000..acd09150
--- /dev/null
+++ b/protocols/oscar/service.c
@@ -0,0 +1,949 @@
+/*
+ * Group 1. This is a very special group. All connections support
+ * this group, as it does some particularly good things (like rate limiting).
+ */
+
+#include <aim.h>
+
+#include "md5.h"
+
+/* Client Online (group 1, subtype 2) */
+int aim_clientready(aim_session_t *sess, aim_conn_t *conn)
+{
+ aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside;
+ struct snacgroup *sg;
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!ins)
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0001, 0x0002, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0001, 0x0002, 0x0000, snacid);
+
+ /*
+ * Send only the tool versions that the server cares about (that it
+ * marked as supporting in the server ready SNAC).
+ */
+ for (sg = ins->groups; sg; sg = sg->next) {
+ aim_module_t *mod;
+
+ if ((mod = aim__findmodulebygroup(sess, sg->group))) {
+ aimbs_put16(&fr->data, mod->family);
+ aimbs_put16(&fr->data, mod->version);
+ aimbs_put16(&fr->data, mod->toolid);
+ aimbs_put16(&fr->data, mod->toolversion);
+ }
+ }
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * Host Online (group 1, type 3)
+ *
+ * See comments in conn.c about how the group associations are supposed
+ * to work, and how they really work.
+ *
+ * This info probably doesn't even need to make it to the client.
+ *
+ * We don't actually call the client here. This starts off the connection
+ * initialization routine required by all AIM connections. The next time
+ * the client is called is the CONNINITDONE callback, which should be
+ * shortly after the rate information is acknowledged.
+ *
+ */
+static int hostonline(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ guint16 *families;
+ int famcount;
+
+
+ if (!(families = g_malloc(aim_bstream_empty(bs))))
+ return 0;
+
+ for (famcount = 0; aim_bstream_empty(bs); famcount++) {
+ families[famcount] = aimbs_get16(bs);
+ aim_conn_addgroup(rx->conn, families[famcount]);
+ }
+
+ g_free(families);
+
+
+ /*
+ * Next step is in the Host Versions handler.
+ *
+ * Note that we must send this before we request rates, since
+ * the format of the rate information depends on the versions we
+ * give it.
+ *
+ */
+ aim_setversions(sess, rx->conn);
+
+ return 1;
+}
+
+/* Service request (group 1, type 4) */
+int aim_reqservice(aim_session_t *sess, aim_conn_t *conn, guint16 serviceid)
+{
+ return aim_genericreq_s(sess, conn, 0x0001, 0x0004, &serviceid);
+}
+
+/* Redirect (group 1, type 5) */
+static int redirect(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ struct aim_redirect_data redir;
+ aim_rxcallback_t userfunc;
+ aim_tlvlist_t *tlvlist;
+ aim_snac_t *origsnac = NULL;
+ int ret = 0;
+
+ memset(&redir, 0, sizeof(redir));
+
+ tlvlist = aim_readtlvchain(bs);
+
+ if (!aim_gettlv(tlvlist, 0x000d, 1) ||
+ !aim_gettlv(tlvlist, 0x0005, 1) ||
+ !aim_gettlv(tlvlist, 0x0006, 1)) {
+ aim_freetlvchain(&tlvlist);
+ return 0;
+ }
+
+ redir.group = aim_gettlv16(tlvlist, 0x000d, 1);
+ redir.ip = aim_gettlv_str(tlvlist, 0x0005, 1);
+ redir.cookie = (guint8 *)aim_gettlv_str(tlvlist, 0x0006, 1);
+
+ /* Fetch original SNAC so we can get csi if needed */
+ origsnac = aim_remsnac(sess, snac->id);
+
+ if ((redir.group == AIM_CONN_TYPE_CHAT) && origsnac) {
+ struct chatsnacinfo *csi = (struct chatsnacinfo *)origsnac->data;
+
+ redir.chat.exchange = csi->exchange;
+ redir.chat.room = csi->name;
+ redir.chat.instance = csi->instance;
+ }
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, &redir);
+
+ g_free((void *)redir.ip);
+ g_free((void *)redir.cookie);
+
+ if (origsnac)
+ g_free(origsnac->data);
+ g_free(origsnac);
+
+ aim_freetlvchain(&tlvlist);
+
+ return ret;
+}
+
+/* Request Rate Information. (group 1, type 6) */
+int aim_reqrates(aim_session_t *sess, aim_conn_t *conn)
+{
+ return aim_genericreq_n(sess, conn, 0x0001, 0x0006);
+}
+
+/*
+ * OSCAR defines several 'rate classes'. Each class has seperate
+ * rate limiting properties (limit level, alert level, disconnect
+ * level, etc), and a set of SNAC family/type pairs associated with
+ * it. The rate classes, their limiting properties, and the definitions
+ * of which SNACs are belong to which class, are defined in the
+ * Rate Response packet at login to each host.
+ *
+ * Logically, all rate offenses within one class count against further
+ * offenses for other SNACs in the same class (ie, sending messages
+ * too fast will limit the number of user info requests you can send,
+ * since those two SNACs are in the same rate class).
+ *
+ * Since the rate classes are defined dynamically at login, the values
+ * below may change. But they seem to be fairly constant.
+ *
+ * Currently, BOS defines five rate classes, with the commonly used
+ * members as follows...
+ *
+ * Rate class 0x0001:
+ * - Everything thats not in any of the other classes
+ *
+ * Rate class 0x0002:
+ * - Buddy list add/remove
+ * - Permit list add/remove
+ * - Deny list add/remove
+ *
+ * Rate class 0x0003:
+ * - User information requests
+ * - Outgoing ICBMs
+ *
+ * Rate class 0x0004:
+ * - A few unknowns: 2/9, 2/b, and f/2
+ *
+ * Rate class 0x0005:
+ * - Chat room create
+ * - Outgoing chat ICBMs
+ *
+ * The only other thing of note is that class 5 (chat) has slightly looser
+ * limiting properties than class 3 (normal messages). But thats just a
+ * small bit of trivia for you.
+ *
+ * The last thing that needs to be learned about the rate limiting
+ * system is how the actual numbers relate to the passing of time. This
+ * seems to be a big mystery.
+ *
+ */
+
+static void rc_addclass(struct rateclass **head, struct rateclass *inrc)
+{
+ struct rateclass *rc, *rc2;
+
+ if (!(rc = g_malloc(sizeof(struct rateclass))))
+ return;
+
+ memcpy(rc, inrc, sizeof(struct rateclass));
+ rc->next = NULL;
+
+ for (rc2 = *head; rc2 && rc2->next; rc2 = rc2->next)
+ ;
+
+ if (!rc2)
+ *head = rc;
+ else
+ rc2->next = rc;
+
+ return;
+}
+
+static struct rateclass *rc_findclass(struct rateclass **head, guint16 id)
+{
+ struct rateclass *rc;
+
+ for (rc = *head; rc; rc = rc->next) {
+ if (rc->classid == id)
+ return rc;
+ }
+
+ return NULL;
+}
+
+static void rc_addpair(struct rateclass *rc, guint16 group, guint16 type)
+{
+ struct snacpair *sp, *sp2;
+
+ if (!(sp = g_new0(struct snacpair, 1)))
+ return;
+
+ sp->group = group;
+ sp->subtype = type;
+ sp->next = NULL;
+
+ for (sp2 = rc->members; sp2 && sp2->next; sp2 = sp2->next)
+ ;
+
+ if (!sp2)
+ rc->members = sp;
+ else
+ sp2->next = sp;
+
+ return;
+}
+
+/* Rate Parameters (group 1, type 7) */
+static int rateresp(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_conn_inside_t *ins = (aim_conn_inside_t *)rx->conn->inside;
+ guint16 numclasses, i;
+ aim_rxcallback_t userfunc;
+
+
+ /*
+ * First are the parameters for each rate class.
+ */
+ numclasses = aimbs_get16(bs);
+ for (i = 0; i < numclasses; i++) {
+ struct rateclass rc;
+
+ memset(&rc, 0, sizeof(struct rateclass));
+
+ rc.classid = aimbs_get16(bs);
+ rc.windowsize = aimbs_get32(bs);
+ rc.clear = aimbs_get32(bs);
+ rc.alert = aimbs_get32(bs);
+ rc.limit = aimbs_get32(bs);
+ rc.disconnect = aimbs_get32(bs);
+ rc.current = aimbs_get32(bs);
+ rc.max = aimbs_get32(bs);
+
+ /*
+ * The server will send an extra five bytes of parameters
+ * depending on the version we advertised in 1/17. If we
+ * didn't send 1/17 (evil!), then this will crash and you
+ * die, as it will default to the old version but we have
+ * the new version hardcoded here.
+ */
+ if (mod->version >= 3)
+ aimbs_getrawbuf(bs, rc.unknown, sizeof(rc.unknown));
+
+ rc_addclass(&ins->rates, &rc);
+ }
+
+ /*
+ * Then the members of each class.
+ */
+ for (i = 0; i < numclasses; i++) {
+ guint16 classid, count;
+ struct rateclass *rc;
+ int j;
+
+ classid = aimbs_get16(bs);
+ count = aimbs_get16(bs);
+
+ rc = rc_findclass(&ins->rates, classid);
+
+ for (j = 0; j < count; j++) {
+ guint16 group, subtype;
+
+ group = aimbs_get16(bs);
+ subtype = aimbs_get16(bs);
+
+ if (rc)
+ rc_addpair(rc, group, subtype);
+ }
+ }
+
+ /*
+ * We don't pass the rate information up to the client, as it really
+ * doesn't care. The information is stored in the connection, however
+ * so that we can do more fun stuff later (not really).
+ */
+
+ /*
+ * Last step in the conn init procedure is to acknowledge that we
+ * agree to these draconian limitations.
+ */
+ aim_rates_addparam(sess, rx->conn);
+
+ /*
+ * Finally, tell the client it's ready to go...
+ */
+ if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE)))
+ userfunc(sess, rx);
+
+
+ return 1;
+}
+
+/* Add Rate Parameter (group 1, type 8) */
+int aim_rates_addparam(aim_session_t *sess, aim_conn_t *conn)
+{
+ aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside;
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ struct rateclass *rc;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0001, 0x0008, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0001, 0x0008, 0x0000, snacid);
+
+ for (rc = ins->rates; rc; rc = rc->next)
+ aimbs_put16(&fr->data, rc->classid);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/* Delete Rate Parameter (group 1, type 9) */
+int aim_rates_delparam(aim_session_t *sess, aim_conn_t *conn)
+{
+ aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside;
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ struct rateclass *rc;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0001, 0x0009, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0001, 0x0009, 0x0000, snacid);
+
+ for (rc = ins->rates; rc; rc = rc->next)
+ aimbs_put16(&fr->data, rc->classid);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/* Rate Change (group 1, type 0x0a) */
+static int ratechange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_rxcallback_t userfunc;
+ guint16 code, rateclass;
+ guint32 currentavg, maxavg, windowsize, clear, alert, limit, disconnect;
+
+ code = aimbs_get16(bs);
+ rateclass = aimbs_get16(bs);
+
+ windowsize = aimbs_get32(bs);
+ clear = aimbs_get32(bs);
+ alert = aimbs_get32(bs);
+ limit = aimbs_get32(bs);
+ disconnect = aimbs_get32(bs);
+ currentavg = aimbs_get32(bs);
+ maxavg = aimbs_get32(bs);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ return userfunc(sess, rx, code, rateclass, windowsize, clear, alert, limit, disconnect, currentavg, maxavg);
+
+ return 0;
+}
+
+/*
+ * How Migrations work.
+ *
+ * The server sends a Server Pause message, which the client should respond to
+ * with a Server Pause Ack, which contains the families it needs on this
+ * connection. The server will send a Migration Notice with an IP address, and
+ * then disconnect. Next the client should open the connection and send the
+ * cookie. Repeat the normal login process and pretend this never happened.
+ *
+ * The Server Pause contains no data.
+ *
+ */
+
+/* Service Pause (group 1, type 0x0b) */
+static int serverpause(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_rxcallback_t userfunc;
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ return userfunc(sess, rx);
+
+ return 0;
+}
+
+/*
+ * Service Pause Acknowledgement (group 1, type 0x0c)
+ *
+ * It is rather important that aim_sendpauseack() gets called for the exact
+ * same connection that the Server Pause callback was called for, since
+ * libfaim extracts the data for the SNAC from the connection structure.
+ *
+ * Of course, if you don't do that, more bad things happen than just what
+ * libfaim can cause.
+ *
+ */
+int aim_sendpauseack(aim_session_t *sess, aim_conn_t *conn)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside;
+ struct snacgroup *sg;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1024)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0001, 0x000c, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0001, 0x000c, 0x0000, snacid);
+
+ /*
+ * This list should have all the groups that the original
+ * Host Online / Server Ready said this host supports. And
+ * we want them all back after the migration.
+ */
+ for (sg = ins->groups; sg; sg = sg->next)
+ aimbs_put16(&fr->data, sg->group);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/* Service Resume (group 1, type 0x0d) */
+static int serverresume(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_rxcallback_t userfunc;
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ return userfunc(sess, rx);
+
+ return 0;
+}
+
+/* Request self-info (group 1, type 0x0e) */
+int aim_reqpersonalinfo(aim_session_t *sess, aim_conn_t *conn)
+{
+ return aim_genericreq_n(sess, conn, 0x0001, 0x000e);
+}
+
+/* Self User Info (group 1, type 0x0f) */
+static int selfinfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_rxcallback_t userfunc;
+ aim_userinfo_t userinfo;
+
+ aim_extractuserinfo(sess, bs, &userinfo);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ return userfunc(sess, rx, &userinfo);
+
+ return 0;
+}
+
+/* Evil Notification (group 1, type 0x10) */
+static int evilnotify(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_rxcallback_t userfunc;
+ guint16 newevil;
+ aim_userinfo_t userinfo;
+
+ memset(&userinfo, 0, sizeof(aim_userinfo_t));
+
+ newevil = aimbs_get16(bs);
+
+ if (aim_bstream_empty(bs))
+ aim_extractuserinfo(sess, bs, &userinfo);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ return userfunc(sess, rx, newevil, &userinfo);
+
+ return 0;
+}
+
+/*
+ * Idle Notification (group 1, type 0x11)
+ *
+ * Should set your current idle time in seconds. Note that this should
+ * never be called consecutively with a non-zero idle time. That makes
+ * OSCAR do funny things. Instead, just set it once you go idle, and then
+ * call it again with zero when you're back.
+ *
+ */
+int aim_bos_setidle(aim_session_t *sess, aim_conn_t *conn, guint32 idletime)
+{
+ return aim_genericreq_l(sess, conn, 0x0001, 0x0011, &idletime);
+}
+
+/*
+ * Service Migrate (group 1, type 0x12)
+ *
+ * This is the final SNAC sent on the original connection during a migration.
+ * It contains the IP and cookie used to connect to the new server, and
+ * optionally a list of the SNAC groups being migrated.
+ *
+ */
+static int migrate(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_rxcallback_t userfunc;
+ int ret = 0;
+ guint16 groupcount, i;
+ aim_tlvlist_t *tl;
+ char *ip = NULL;
+ aim_tlv_t *cktlv;
+
+ /*
+ * Apparently there's some fun stuff that can happen right here. The
+ * migration can actually be quite selective about what groups it
+ * moves to the new server. When not all the groups for a connection
+ * are migrated, or they are all migrated but some groups are moved
+ * to a different server than others, it is called a bifurcated
+ * migration.
+ *
+ * Let's play dumb and not support that.
+ *
+ */
+ groupcount = aimbs_get16(bs);
+ for (i = 0; i < groupcount; i++) {
+ guint16 group;
+
+ group = aimbs_get16(bs);
+
+ imcb_error(sess->aux_data, "bifurcated migration unsupported");
+ }
+
+ tl = aim_readtlvchain(bs);
+
+ if (aim_gettlv(tl, 0x0005, 1))
+ ip = aim_gettlv_str(tl, 0x0005, 1);
+
+ cktlv = aim_gettlv(tl, 0x0006, 1);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, ip, cktlv ? cktlv->value : NULL);
+
+ aim_freetlvchain(&tl);
+ g_free(ip);
+
+ return ret;
+}
+
+/* Message of the Day (group 1, type 0x13) */
+static int motd(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_rxcallback_t userfunc;
+ char *msg = NULL;
+ int ret = 0;
+ aim_tlvlist_t *tlvlist;
+ guint16 id;
+
+ /*
+ * Code.
+ *
+ * Valid values:
+ * 1 Mandatory upgrade
+ * 2 Advisory upgrade
+ * 3 System bulletin
+ * 4 Nothing's wrong ("top o the world" -- normal)
+ * 5 Lets-break-something.
+ *
+ */
+ id = aimbs_get16(bs);
+
+ /*
+ * TLVs follow
+ */
+ tlvlist = aim_readtlvchain(bs);
+
+ msg = aim_gettlv_str(tlvlist, 0x000b, 1);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, id, msg);
+
+ g_free(msg);
+
+ aim_freetlvchain(&tlvlist);
+
+ return ret;
+}
+
+/*
+ * Set privacy flags (group 1, type 0x14)
+ *
+ * Normally 0x03.
+ *
+ * Bit 1: Allows other AIM users to see how long you've been idle.
+ * Bit 2: Allows other AIM users to see how long you've been a member.
+ *
+ */
+int aim_bos_setprivacyflags(aim_session_t *sess, aim_conn_t *conn, guint32 flags)
+{
+ return aim_genericreq_l(sess, conn, 0x0001, 0x0014, &flags);
+}
+
+/*
+ * No-op (group 1, type 0x16)
+ *
+ * WinAIM sends these every 4min or so to keep the connection alive. Its not
+ * real necessary.
+ *
+ */
+int aim_nop(aim_session_t *sess, aim_conn_t *conn)
+{
+ return aim_genericreq_n(sess, conn, 0x0001, 0x0016);
+}
+
+/*
+ * Set client versions (group 1, subtype 0x17)
+ *
+ * If you've seen the clientonline/clientready SNAC you're probably
+ * wondering what the point of this one is. And that point seems to be
+ * that the versions in the client online SNAC are sent too late for the
+ * server to be able to use them to change the protocol for the earlier
+ * login packets (client versions are sent right after Host Online is
+ * received, but client online versions aren't sent until quite a bit later).
+ * We can see them already making use of this by changing the format of
+ * the rate information based on what version of group 1 we advertise here.
+ *
+ */
+int aim_setversions(aim_session_t *sess, aim_conn_t *conn)
+{
+ aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside;
+ struct snacgroup *sg;
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!ins)
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0001, 0x0017, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0001, 0x0017, 0x0000, snacid);
+
+ /*
+ * Send only the versions that the server cares about (that it
+ * marked as supporting in the server ready SNAC).
+ */
+ for (sg = ins->groups; sg; sg = sg->next) {
+ aim_module_t *mod;
+
+ if ((mod = aim__findmodulebygroup(sess, sg->group))) {
+ aimbs_put16(&fr->data, mod->family);
+ aimbs_put16(&fr->data, mod->version);
+ }
+ }
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/* Host versions (group 1, subtype 0x18) */
+static int hostversions(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ int vercount;
+ guint8 *versions;
+
+ /* This is frivolous. (Thank you SmarterChild.) */
+ vercount = aim_bstream_empty(bs)/4;
+ versions = aimbs_getraw(bs, aim_bstream_empty(bs));
+ g_free(versions);
+
+ /*
+ * Now request rates.
+ */
+ aim_reqrates(sess, rx->conn);
+
+ return 1;
+}
+
+/*
+ * Subtype 0x001e - Extended Status
+ *
+ * Sets your ICQ status (available, away, do not disturb, etc.)
+ *
+ * These are the same TLVs seen in user info. You can
+ * also set 0x0008 and 0x000c.
+ */
+int aim_setextstatus(aim_session_t *sess, aim_conn_t *conn, guint32 status)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ aim_tlvlist_t *tl = NULL;
+ guint32 data;
+ int tlvlen;
+ struct im_connection *ic = sess ? sess->aux_data : NULL;
+
+ data = AIM_ICQ_STATE_HIDEIP | status; /* yay for error checking ;^) */
+
+ if (ic && set_getbool(&ic->acc->set, "web_aware"))
+ data |= AIM_ICQ_STATE_WEBAWARE;
+
+ tlvlen = aim_addtlvtochain32(&tl, 0x0006, data);
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 8)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0001, 0x001e, 0x0000, NULL, 0);
+ aim_putsnac(&fr->data, 0x0001, 0x001e, 0x0000, snacid);
+
+ aim_writetlvchain(&fr->data, &tl);
+ aim_freetlvchain(&tl);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * Starting this past week (26 Mar 2001, say), AOL has started sending
+ * this nice little extra SNAC. AFAIK, it has never been used until now.
+ *
+ * The request contains eight bytes. The first four are an offset, the
+ * second four are a length.
+ *
+ * The offset is an offset into aim.exe when it is mapped during execution
+ * on Win32. So far, AOL has only been requesting bytes in static regions
+ * of memory. (I won't put it past them to start requesting data in
+ * less static regions -- regions that are initialized at run time, but still
+ * before the client recieves this request.)
+ *
+ * When the client recieves the request, it adds it to the current ds
+ * (0x00400000) and dereferences it, copying the data into a buffer which
+ * it then runs directly through the MD5 hasher. The 16 byte output of
+ * the hash is then sent back to the server.
+ *
+ * If the client does not send any data back, or the data does not match
+ * the data that the specific client should have, the client will get the
+ * following message from "AOL Instant Messenger":
+ * "You have been disconnected from the AOL Instant Message Service (SM)
+ * for accessing the AOL network using unauthorized software. You can
+ * download a FREE, fully featured, and authorized client, here
+ * http://www.aol.com/aim/download2.html"
+ * The connection is then closed, recieving disconnect code 1, URL
+ * http://www.aim.aol.com/errors/USER_LOGGED_OFF_NEW_LOGIN.html.
+ *
+ * Note, however, that numerous inconsistencies can cause the above error,
+ * not just sending back a bad hash. Do not immediatly suspect this code
+ * if you get disconnected. AOL and the open/free software community have
+ * played this game for a couple years now, generating the above message
+ * on numerous ocassions.
+ *
+ * Anyway, neener. We win again.
+ *
+ */
+/* Client verification (group 1, subtype 0x1f) */
+static int memrequest(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ aim_rxcallback_t userfunc;
+ guint32 offset, len;
+ aim_tlvlist_t *list;
+ char *modname;
+
+ offset = aimbs_get32(bs);
+ len = aimbs_get32(bs);
+ list = aim_readtlvchain(bs);
+
+ modname = aim_gettlv_str(list, 0x0001, 1);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ return userfunc(sess, rx, offset, len, modname);
+
+ g_free(modname);
+ aim_freetlvchain(&list);
+
+ return 0;
+}
+
+/* Client verification reply (group 1, subtype 0x20) */
+int aim_sendmemblock(aim_session_t *sess, aim_conn_t *conn, guint32 offset, guint32 len, const guint8 *buf, guint8 flag)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!sess || !conn)
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+16)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, 0x0001, 0x0020, 0x0000, NULL, 0);
+
+ aim_putsnac(&fr->data, 0x0001, 0x0020, 0x0000, snacid);
+ aimbs_put16(&fr->data, 0x0010); /* md5 is always 16 bytes */
+
+ if ((flag == AIM_SENDMEMBLOCK_FLAG_ISHASH) && buf && (len == 0x10)) { /* we're getting a hash */
+
+ aimbs_putraw(&fr->data, buf, 0x10);
+
+ } else if (buf && (len > 0)) { /* use input buffer */
+ md5_state_t state;
+ md5_byte_t digest[0x10];
+
+ md5_init(&state);
+ md5_append(&state, (const md5_byte_t *)buf, len);
+ md5_finish(&state, digest);
+
+ aimbs_putraw(&fr->data, (guint8 *)digest, 0x10);
+
+ } else if (len == 0) { /* no length, just hash NULL (buf is optional) */
+ md5_state_t state;
+ guint8 nil = '\0';
+ md5_byte_t digest[0x10];
+
+ /*
+ * These MD5 routines are stupid in that you have to have
+ * at least one append. So thats why this doesn't look
+ * real logical.
+ */
+ md5_init(&state);
+ md5_append(&state, (const md5_byte_t *)&nil, 0);
+ md5_finish(&state, digest);
+
+ aimbs_putraw(&fr->data, (guint8 *)digest, 0x10);
+
+ } else {
+
+ /*
+ * This data is correct for AIM 3.5.1670.
+ *
+ * Using these blocks is as close to "legal" as you can get
+ * without using an AIM binary.
+ *
+ */
+ if ((offset == 0x03ffffff) && (len == 0x03ffffff)) {
+
+#if 1 /* with "AnrbnrAqhfzcd" */
+ aimbs_put32(&fr->data, 0x44a95d26);
+ aimbs_put32(&fr->data, 0xd2490423);
+ aimbs_put32(&fr->data, 0x93b8821f);
+ aimbs_put32(&fr->data, 0x51c54b01);
+#else /* no filename */
+ aimbs_put32(&fr->data, 0x1df8cbae);
+ aimbs_put32(&fr->data, 0x5523b839);
+ aimbs_put32(&fr->data, 0xa0e10db3);
+ aimbs_put32(&fr->data, 0xa46d3b39);
+#endif
+
+/* len can't be 0 here anyway...
+ } else if ((offset == 0x00001000) && (len == 0x00000000)) {
+
+ aimbs_put32(&fr->data, 0xd41d8cd9);
+ aimbs_put32(&fr->data, 0x8f00b204);
+ aimbs_put32(&fr->data, 0xe9800998);
+ aimbs_put32(&fr->data, 0xecf8427e);
+*/
+ } else
+ imcb_error(sess->aux_data, "Warning: unknown hash request");
+
+ }
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+ if (snac->subtype == 0x0003)
+ return hostonline(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x0005)
+ return redirect(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x0007)
+ return rateresp(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x000a)
+ return ratechange(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x000b)
+ return serverpause(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x000d)
+ return serverresume(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x000f)
+ return selfinfo(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x0010)
+ return evilnotify(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x0012)
+ return migrate(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x0013)
+ return motd(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x0018)
+ return hostversions(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x001f)
+ return memrequest(sess, mod, rx, snac, bs);
+
+ return 0;
+}
+
+int general_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+ mod->family = 0x0001;
+ mod->version = 0x0003;
+ mod->toolid = 0x0110;
+ mod->toolversion = 0x0629;
+ mod->flags = 0;
+ strncpy(mod->name, "general", sizeof(mod->name));
+ mod->snachandler = snachandler;
+
+ return 0;
+}
+
diff --git a/protocols/oscar/snac.c b/protocols/oscar/snac.c
new file mode 100644
index 00000000..8a75b2a0
--- /dev/null
+++ b/protocols/oscar/snac.c
@@ -0,0 +1,147 @@
+/*
+ *
+ * Various SNAC-related dodads...
+ *
+ * outstanding_snacs is a list of aim_snac_t structs. A SNAC should be added
+ * whenever a new SNAC is sent and it should remain in the list until the
+ * response for it has been receieved.
+ *
+ * cleansnacs() should be called periodically by the client in order
+ * to facilitate the aging out of unreplied-to SNACs. This can and does
+ * happen, so it should be handled.
+ *
+ */
+
+#include <aim.h>
+
+static aim_snacid_t aim_newsnac(aim_session_t *sess, aim_snac_t *newsnac);
+
+/*
+ * Called from aim_session_init() to initialize the hash.
+ */
+void aim_initsnachash(aim_session_t *sess)
+{
+ int i;
+
+ for (i = 0; i < AIM_SNAC_HASH_SIZE; i++)
+ sess->snac_hash[i] = NULL;
+
+ return;
+}
+
+aim_snacid_t aim_cachesnac(aim_session_t *sess, const guint16 family, const guint16 type, const guint16 flags, const void *data, const int datalen)
+{
+ aim_snac_t snac;
+
+ snac.id = sess->snacid_next++;
+ snac.family = family;
+ snac.type = type;
+ snac.flags = flags;
+
+ if (datalen) {
+ if (!(snac.data = g_malloc(datalen)))
+ return 0; /* er... */
+ memcpy(snac.data, data, datalen);
+ } else
+ snac.data = NULL;
+
+ return aim_newsnac(sess, &snac);
+}
+
+/*
+ * Clones the passed snac structure and caches it in the
+ * list/hash.
+ */
+static aim_snacid_t aim_newsnac(aim_session_t *sess, aim_snac_t *newsnac)
+{
+ aim_snac_t *snac;
+ int index;
+
+ if (!newsnac)
+ return 0;
+
+ if (!(snac = g_malloc(sizeof(aim_snac_t))))
+ return 0;
+ memcpy(snac, newsnac, sizeof(aim_snac_t));
+ snac->issuetime = time(NULL);
+
+ index = snac->id % AIM_SNAC_HASH_SIZE;
+
+ snac->next = (aim_snac_t *)sess->snac_hash[index];
+ sess->snac_hash[index] = (void *)snac;
+
+ return snac->id;
+}
+
+/*
+ * Finds a snac structure with the passed SNAC ID,
+ * removes it from the list/hash, and returns a pointer to it.
+ *
+ * The returned structure must be freed by the caller.
+ *
+ */
+aim_snac_t *aim_remsnac(aim_session_t *sess, aim_snacid_t id)
+{
+ aim_snac_t *cur, **prev;
+ int index;
+
+ index = id % AIM_SNAC_HASH_SIZE;
+
+ for (prev = (aim_snac_t **)&sess->snac_hash[index]; (cur = *prev); ) {
+ if (cur->id == id) {
+ *prev = cur->next;
+ return cur;
+ } else
+ prev = &cur->next;
+ }
+
+ return cur;
+}
+
+/*
+ * This is for cleaning up old SNACs that either don't get replies or
+ * a reply was never received for. Garabage collection. Plain and simple.
+ *
+ * maxage is the _minimum_ age in seconds to keep SNACs.
+ *
+ */
+void aim_cleansnacs(aim_session_t *sess, int maxage)
+{
+ int i;
+
+ for (i = 0; i < AIM_SNAC_HASH_SIZE; i++) {
+ aim_snac_t *cur, **prev;
+ time_t curtime;
+
+ if (!sess->snac_hash[i])
+ continue;
+
+ curtime = time(NULL); /* done here in case we waited for the lock */
+
+ for (prev = (aim_snac_t **)&sess->snac_hash[i]; (cur = *prev); ) {
+ if ((curtime - cur->issuetime) > maxage) {
+
+ *prev = cur->next;
+
+ /* XXX should we have destructors here? */
+ g_free(cur->data);
+ g_free(cur);
+
+ } else
+ prev = &cur->next;
+ }
+ }
+
+ return;
+}
+
+int aim_putsnac(aim_bstream_t *bs, guint16 family, guint16 subtype, guint16 flags, aim_snacid_t snacid)
+{
+
+ aimbs_put16(bs, family);
+ aimbs_put16(bs, subtype);
+ aimbs_put16(bs, flags);
+ aimbs_put32(bs, snacid);
+
+ return 10;
+}
diff --git a/protocols/oscar/ssi.c b/protocols/oscar/ssi.c
new file mode 100644
index 00000000..f37d98e5
--- /dev/null
+++ b/protocols/oscar/ssi.c
@@ -0,0 +1,1523 @@
+/*
+ * Server-Side/Stored Information.
+ *
+ * Relatively new facility that allows storing of certain types of information,
+ * such as a users buddy list, permit/deny list, and permit/deny preferences,
+ * to be stored on the server, so that they can be accessed from any client.
+ *
+ * We keep a copy of the ssi data in sess->ssi, because the data needs to be
+ * accessed for various reasons. So all the "aim_ssi_itemlist_bleh" functions
+ * near the top just manage the local data.
+ *
+ * The SNAC sending and receiving functions are lower down in the file, and
+ * they're simpler. They are in the order of the subtypes they deal with,
+ * starting with the request rights function (subtype 0x0002), then parse
+ * rights (subtype 0x0003), then--well, you get the idea.
+ *
+ * This is entirely too complicated.
+ * You don't know the half of it.
+ *
+ * XXX - Test for memory leaks
+ * XXX - Better parsing of rights, and use the rights info to limit adds
+ *
+ */
+
+#include <aim.h>
+#include "ssi.h"
+
+/**
+ * Locally add a new item to the given item list.
+ *
+ * @param list A pointer to a pointer to the current list of items.
+ * @param parent A pointer to the parent group, or NULL if the item should have no
+ * parent group (ie. the group ID# should be 0).
+ * @param name A null terminated string of the name of the new item, or NULL if the
+ * item should have no name.
+ * @param type The type of the item, 0x0001 for a contact, 0x0002 for a group, etc.
+ * @return The newly created item.
+ */
+static struct aim_ssi_item *aim_ssi_itemlist_add(struct aim_ssi_item **list, struct aim_ssi_item *parent, char *name, guint16 type)
+{
+ int i;
+ struct aim_ssi_item *cur, *newitem;
+
+ if (!(newitem = g_new0(struct aim_ssi_item, 1)))
+ return NULL;
+
+ /* Set the name */
+ if (name) {
+ if (!(newitem->name = (char *)g_malloc((strlen(name)+1)*sizeof(char)))) {
+ g_free(newitem);
+ return NULL;
+ }
+ strcpy(newitem->name, name);
+ } else
+ newitem->name = NULL;
+
+ /* Set the group ID# and the buddy ID# */
+ newitem->gid = 0x0000;
+ newitem->bid = 0x0000;
+ if (type == AIM_SSI_TYPE_GROUP) {
+ if (name)
+ do {
+ newitem->gid += 0x0001;
+ for (cur=*list, i=0; ((cur) && (!i)); cur=cur->next)
+ if ((cur->gid == newitem->gid) && (cur->gid == newitem->gid))
+ i=1;
+ } while (i);
+ } else {
+ if (parent)
+ newitem->gid = parent->gid;
+ do {
+ newitem->bid += 0x0001;
+ for (cur=*list, i=0; ((cur) && (!i)); cur=cur->next)
+ if ((cur->bid == newitem->bid) && (cur->gid == newitem->gid))
+ i=1;
+ } while (i);
+ }
+
+ /* Set the rest */
+ newitem->type = type;
+ newitem->data = NULL;
+ newitem->next = *list;
+ *list = newitem;
+
+ return newitem;
+}
+
+/**
+ * Locally rebuild the 0x00c8 TLV in the additional data of the given group.
+ *
+ * @param list A pointer to a pointer to the current list of items.
+ * @param parentgroup A pointer to the group who's additional data you want to rebuild.
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+static int aim_ssi_itemlist_rebuildgroup(struct aim_ssi_item **list, struct aim_ssi_item *parentgroup)
+{
+ int newlen; //, i;
+ struct aim_ssi_item *cur;
+
+ /* Free the old additional data */
+ if (parentgroup->data) {
+ aim_freetlvchain((aim_tlvlist_t **)&parentgroup->data);
+ parentgroup->data = NULL;
+ }
+
+ /* Find the length for the new additional data */
+ newlen = 0;
+ if (parentgroup->gid == 0x0000) {
+ for (cur=*list; cur; cur=cur->next)
+ if ((cur->gid != 0x0000) && (cur->type == AIM_SSI_TYPE_GROUP))
+ newlen += 2;
+ } else {
+ for (cur=*list; cur; cur=cur->next)
+ if ((cur->gid == parentgroup->gid) && (cur->type == AIM_SSI_TYPE_BUDDY))
+ newlen += 2;
+ }
+
+ /* Rebuild the additional data */
+ if (newlen>0) {
+ guint8 *newdata;
+
+ if (!(newdata = (guint8 *)g_malloc((newlen)*sizeof(guint8))))
+ return -ENOMEM;
+ newlen = 0;
+ if (parentgroup->gid == 0x0000) {
+ for (cur=*list; cur; cur=cur->next)
+ if ((cur->gid != 0x0000) && (cur->type == AIM_SSI_TYPE_GROUP))
+ newlen += aimutil_put16(newdata+newlen, cur->gid);
+ } else {
+ for (cur=*list; cur; cur=cur->next)
+ if ((cur->gid == parentgroup->gid) && (cur->type == AIM_SSI_TYPE_BUDDY))
+ newlen += aimutil_put16(newdata+newlen, cur->bid);
+ }
+ aim_addtlvtochain_raw((aim_tlvlist_t **)&(parentgroup->data), 0x00c8, newlen, newdata);
+
+ g_free(newdata);
+ }
+
+ return 0;
+}
+
+/**
+ * Locally free all of the stored buddy list information.
+ *
+ * @param sess The oscar session.
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+static int aim_ssi_freelist(aim_session_t *sess)
+{
+ struct aim_ssi_item *cur, *delitem;
+
+ cur = sess->ssi.items;
+ while (cur) {
+ if (cur->name) g_free(cur->name);
+ if (cur->data) aim_freetlvchain((aim_tlvlist_t **)&cur->data);
+ delitem = cur;
+ cur = cur->next;
+ g_free(delitem);
+ }
+
+ sess->ssi.items = NULL;
+ sess->ssi.revision = 0;
+ sess->ssi.timestamp = (time_t)0;
+
+ return 0;
+}
+
+/**
+ * Locally find an item given a group ID# and a buddy ID#.
+ *
+ * @param list A pointer to the current list of items.
+ * @param gid The group ID# of the desired item.
+ * @param bid The buddy ID# of the desired item.
+ * @return Return a pointer to the item if found, else return NULL;
+ */
+struct aim_ssi_item *aim_ssi_itemlist_find(struct aim_ssi_item *list, guint16 gid, guint16 bid)
+{
+ struct aim_ssi_item *cur;
+ for (cur=list; cur; cur=cur->next)
+ if ((cur->gid == gid) && (cur->bid == bid))
+ return cur;
+ return NULL;
+}
+
+/**
+ * Locally find an item given a group name, screen name, and type. If group name
+ * and screen name are null, then just return the first item of the given type.
+ *
+ * @param list A pointer to the current list of items.
+ * @param gn The group name of the desired item.
+ * @param bn The buddy name of the desired item.
+ * @param type The type of the desired item.
+ * @return Return a pointer to the item if found, else return NULL;
+ */
+struct aim_ssi_item *aim_ssi_itemlist_finditem(struct aim_ssi_item *list, char *gn, char *sn, guint16 type)
+{
+ struct aim_ssi_item *cur;
+ if (!list)
+ return NULL;
+
+ if (gn && sn) { /* For finding buddies in groups */
+ for (cur=list; cur; cur=cur->next)
+ if ((cur->type == type) && (cur->name) && !(aim_sncmp(cur->name, sn))) {
+ struct aim_ssi_item *curg;
+ for (curg=list; curg; curg=curg->next)
+ if ((curg->type == AIM_SSI_TYPE_GROUP) && (curg->gid == cur->gid) && (curg->name) && !(aim_sncmp(curg->name, gn)))
+ return cur;
+ }
+
+ } else if (sn) { /* For finding groups, permits, denies, and ignores */
+ for (cur=list; cur; cur=cur->next)
+ if ((cur->type == type) && (cur->name) && !(aim_sncmp(cur->name, sn)))
+ return cur;
+
+ /* For stuff without names--permit deny setting, visibility mask, etc. */
+ } else for (cur=list; cur; cur=cur->next) {
+ if (cur->type == type)
+ return cur;
+ }
+
+ return NULL;
+}
+
+/**
+ * Locally find the parent item of the given buddy name.
+ *
+ * @param list A pointer to the current list of items.
+ * @param bn The buddy name of the desired item.
+ * @return Return a pointer to the item if found, else return NULL;
+ */
+struct aim_ssi_item *aim_ssi_itemlist_findparent(struct aim_ssi_item *list, char *sn)
+{
+ struct aim_ssi_item *cur, *curg;
+ if (!list || !sn)
+ return NULL;
+ if (!(cur = aim_ssi_itemlist_finditem(list, NULL, sn, AIM_SSI_TYPE_BUDDY)))
+ return NULL;
+ for (curg=list; curg; curg=curg->next)
+ if ((curg->type == AIM_SSI_TYPE_GROUP) && (curg->gid == cur->gid))
+ return curg;
+ return NULL;
+}
+
+/**
+ * Locally find the permit/deny setting item, and return the setting.
+ *
+ * @param list A pointer to the current list of items.
+ * @return Return the current SSI permit deny setting, or 0 if no setting was found.
+ */
+int aim_ssi_getpermdeny(struct aim_ssi_item *list)
+{
+ struct aim_ssi_item *cur = aim_ssi_itemlist_finditem(list, NULL, NULL, AIM_SSI_TYPE_PDINFO);
+ if (cur) {
+ aim_tlvlist_t *tlvlist = cur->data;
+ if (tlvlist) {
+ aim_tlv_t *tlv = aim_gettlv(tlvlist, 0x00ca, 1);
+ if (tlv && tlv->value)
+ return aimutil_get8(tlv->value);
+ }
+ }
+ return 0;
+}
+
+/**
+ * Locally find the presence flag item, and return the setting. The returned setting is a
+ * bitmask of the user flags that you are visible to. See the AIM_FLAG_* #defines
+ * in aim.h
+ *
+ * @param list A pointer to the current list of items.
+ * @return Return the current visibility mask.
+ */
+guint32 aim_ssi_getpresence(struct aim_ssi_item *list)
+{
+ struct aim_ssi_item *cur = aim_ssi_itemlist_finditem(list, NULL, NULL, AIM_SSI_TYPE_PRESENCEPREFS);
+ if (cur) {
+ aim_tlvlist_t *tlvlist = cur->data;
+ if (tlvlist) {
+ aim_tlv_t *tlv = aim_gettlv(tlvlist, 0x00c9, 1);
+ if (tlv && tlv->length)
+ return aimutil_get32(tlv->value);
+ }
+ }
+ return 0xFFFFFFFF;
+}
+
+/**
+ * Add the given packet to the holding queue. We totally need to send SSI SNACs one at
+ * a time, so we have a local queue where packets get put before they are sent, and
+ * then we send stuff one at a time, nice and orderly-like.
+ *
+ * @param sess The oscar session.
+ * @param conn The bos connection for this session.
+ * @param fr The newly created SNAC that you want to send.
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+static int aim_ssi_enqueue(aim_session_t *sess, aim_conn_t *conn, aim_frame_t *fr)
+{
+ aim_frame_t *cur;
+
+ if (!sess || !conn || !fr)
+ return -EINVAL;
+
+ fr->next = NULL;
+ if (sess->ssi.holding_queue == NULL) {
+ sess->ssi.holding_queue = fr;
+ if (!sess->ssi.waiting_for_ack)
+ aim_ssi_modbegin(sess, conn);
+ } else {
+ for (cur = sess->ssi.holding_queue; cur->next; cur = cur->next) ;
+ cur->next = fr;
+ }
+
+ return 0;
+}
+
+/**
+ * Send the next SNAC from the holding queue. This is called
+ * automatically when an ack from an add, mod, or del is received.
+ * If the queue is empty, it sends the modend SNAC.
+ *
+ * @param sess The oscar session.
+ * @param conn The bos connection for this session.
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+static int aim_ssi_dispatch(aim_session_t *sess, aim_conn_t *conn)
+{
+ aim_frame_t *cur;
+
+ if (!sess || !conn)
+ return -EINVAL;
+
+ if (!sess->ssi.waiting_for_ack) {
+ if (sess->ssi.holding_queue) {
+ sess->ssi.waiting_for_ack = 1;
+ cur = sess->ssi.holding_queue->next;
+ sess->ssi.holding_queue->next = NULL;
+ aim_tx_enqueue(sess, sess->ssi.holding_queue);
+ sess->ssi.holding_queue = cur;
+ } else
+ aim_ssi_modend(sess, conn);
+ }
+
+ return 0;
+}
+
+/**
+ * Send SNACs necessary to remove all SSI data from the server list,
+ * and then free the local copy as well.
+ *
+ * @param sess The oscar session.
+ * @param conn The bos connection for this session.
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_ssi_deletelist(aim_session_t *sess, aim_conn_t *conn)
+{
+ int num;
+ struct aim_ssi_item *cur, **items;
+
+ for (cur=sess->ssi.items, num=0; cur; cur=cur->next)
+ num++;
+
+ if (!(items = g_new0(struct aim_ssi_item *, num)))
+ return -ENOMEM;
+
+ for (cur=sess->ssi.items, num=0; cur; cur=cur->next) {
+ items[num] = cur;
+ num++;
+ }
+
+ aim_ssi_addmoddel(sess, conn, items, num, AIM_CB_SSI_DEL);
+ g_free(items);
+ aim_ssi_dispatch(sess, conn);
+ aim_ssi_freelist(sess);
+
+ return 0;
+}
+
+/**
+ * This "cleans" the ssi list. It does a few things, with the intent of making
+ * sure there ain't nothin' wrong with your SSI.
+ * -Make sure all buddies are in a group, and all groups have the correct
+ * additional data.
+ * -Make sure there are no empty groups in the list. While there is nothing
+ * wrong empty groups in the SSI, it's wiser to not have them.
+ *
+ * @param sess The oscar session.
+ * @param conn The bos connection for this session.
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_ssi_cleanlist(aim_session_t *sess, aim_conn_t *conn)
+{
+ unsigned int i;
+ struct aim_ssi_item *cur, *parentgroup;
+
+ /* Make sure we actually need to clean out the list */
+ for (cur=sess->ssi.items, i=0; cur && !i; cur=cur->next)
+ /* Any buddies directly in the master group */
+ if ((cur->type == AIM_SSI_TYPE_BUDDY) && (cur->gid == 0x0000))
+ i++;
+ if (!i)
+ return 0;
+
+ /* Remove all the additional data from all groups */
+ for (cur=sess->ssi.items; cur; cur=cur->next)
+ if ((cur->data) && (cur->type == AIM_SSI_TYPE_GROUP)) {
+ aim_freetlvchain((aim_tlvlist_t **)&cur->data);
+ cur->data = NULL;
+ }
+
+ /* If there are buddies directly in the master group, make sure */
+ /* there is a group to put them in. Any group, any group at all. */
+ for (cur=sess->ssi.items; ((cur) && ((cur->type != AIM_SSI_TYPE_BUDDY) || (cur->gid != 0x0000))); cur=cur->next);
+ if (!cur) {
+ for (parentgroup=sess->ssi.items; ((parentgroup) && (parentgroup->type!=AIM_SSI_TYPE_GROUP) && (parentgroup->gid==0x0000)); parentgroup=parentgroup->next);
+ if (!parentgroup) {
+ char *newgroup;
+ newgroup = (char*)g_malloc(strlen("Unknown")+1);
+ strcpy(newgroup, "Unknown");
+ aim_ssi_addgroups(sess, conn, &newgroup, 1);
+ }
+ }
+
+ /* Set parentgroup equal to any arbitray group */
+ for (parentgroup=sess->ssi.items; parentgroup->gid==0x0000 || parentgroup->type!=AIM_SSI_TYPE_GROUP; parentgroup=parentgroup->next);
+
+ /* If there are any buddies directly in the master group, put them in a real group */
+ for (cur=sess->ssi.items; cur; cur=cur->next)
+ if ((cur->type == AIM_SSI_TYPE_BUDDY) && (cur->gid == 0x0000)) {
+ aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_DEL);
+ cur->gid = parentgroup->gid;
+ aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_ADD);
+ }
+
+ /* Rebuild additional data for all groups */
+ for (parentgroup=sess->ssi.items; parentgroup; parentgroup=parentgroup->next)
+ if (parentgroup->type == AIM_SSI_TYPE_GROUP)
+ aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup);
+
+ /* Send a mod snac for all groups */
+ i = 0;
+ for (cur=sess->ssi.items; cur; cur=cur->next)
+ if (cur->type == AIM_SSI_TYPE_GROUP)
+ i++;
+ if (i > 0) {
+ /* Allocate an array of pointers to each of the groups */
+ struct aim_ssi_item **groups;
+ if (!(groups = g_new0(struct aim_ssi_item *, i)))
+ return -ENOMEM;
+
+ for (cur=sess->ssi.items, i=0; cur; cur=cur->next)
+ if (cur->type == AIM_SSI_TYPE_GROUP)
+ groups[i] = cur;
+
+ aim_ssi_addmoddel(sess, conn, groups, i, AIM_CB_SSI_MOD);
+ g_free(groups);
+ }
+
+ /* Send a del snac for any empty groups */
+ i = 0;
+ for (cur=sess->ssi.items; cur; cur=cur->next)
+ if ((cur->type == AIM_SSI_TYPE_GROUP) && !(cur->data))
+ i++;
+ if (i > 0) {
+ /* Allocate an array of pointers to each of the groups */
+ struct aim_ssi_item **groups;
+ if (!(groups = g_new0(struct aim_ssi_item *, i)))
+ return -ENOMEM;
+
+ for (cur=sess->ssi.items, i=0; cur; cur=cur->next)
+ if ((cur->type == AIM_SSI_TYPE_GROUP) && !(cur->data))
+ groups[i] = cur;
+
+ aim_ssi_addmoddel(sess, conn, groups, i, AIM_CB_SSI_DEL);
+ g_free(groups);
+ }
+
+ /* Begin sending SSI SNACs */
+ aim_ssi_dispatch(sess, conn);
+
+ return 0;
+}
+
+/**
+ * Add an array of screen names to the given group.
+ *
+ * @param sess The oscar session.
+ * @param conn The bos connection for this session.
+ * @param gn The name of the group to which you want to add these names.
+ * @param sn An array of null terminated strings of the names you want to add.
+ * @param num The number of screen names you are adding (size of the sn array).
+ * @param flags 1 - Add with TLV(0x66)
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_ssi_addbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num, unsigned int flags)
+{
+ struct aim_ssi_item *parentgroup, **newitems;
+ guint16 i;
+
+ if (!sess || !conn || !gn || !sn || !num)
+ return -EINVAL;
+
+ /* Look up the parent group */
+ if (!(parentgroup = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn, AIM_SSI_TYPE_GROUP))) {
+ aim_ssi_addgroups(sess, conn, &gn, 1);
+ if (!(parentgroup = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn, AIM_SSI_TYPE_GROUP)))
+ return -ENOMEM;
+ }
+
+ /* Allocate an array of pointers to each of the new items */
+ if (!(newitems = g_new0(struct aim_ssi_item *, num)))
+ return -ENOMEM;
+
+ /* Add items to the local list, and index them in the array */
+ for (i=0; i<num; i++)
+ if (!(newitems[i] = aim_ssi_itemlist_add(&sess->ssi.items, parentgroup, sn[i], AIM_SSI_TYPE_BUDDY))) {
+ g_free(newitems);
+ return -ENOMEM;
+ } else if (flags & 1) {
+ aim_tlvlist_t *tl = NULL;
+ aim_addtlvtochain_noval(&tl, 0x66);
+ newitems[i]->data = tl;
+ }
+
+ /* Send the add item SNAC */
+ if ((i = aim_ssi_addmoddel(sess, conn, newitems, num, AIM_CB_SSI_ADD))) {
+ g_free(newitems);
+ return -i;
+ }
+
+ /* Free the array of pointers to each of the new items */
+ g_free(newitems);
+
+ /* Rebuild the additional data in the parent group */
+ if ((i = aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup)))
+ return i;
+
+ /* Send the mod item SNAC */
+ if ((i = aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD )))
+ return i;
+
+ /* Begin sending SSI SNACs */
+ if (!(i = aim_ssi_dispatch(sess, conn)))
+ return i;
+
+ return 0;
+}
+
+/**
+ * Add the master group (the group containing all groups). This is called by
+ * aim_ssi_addgroups, if necessary.
+ *
+ * @param sess The oscar session.
+ * @param conn The bos connection for this session.
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_ssi_addmastergroup(aim_session_t *sess, aim_conn_t *conn)
+{
+ struct aim_ssi_item *newitem;
+
+ if (!sess || !conn)
+ return -EINVAL;
+
+ /* Add the item to the local list, and keep a pointer to it */
+ if (!(newitem = aim_ssi_itemlist_add(&sess->ssi.items, NULL, NULL, AIM_SSI_TYPE_GROUP)))
+ return -ENOMEM;
+
+ /* If there are any existing groups (technically there shouldn't be, but */
+ /* just in case) then add their group ID#'s to the additional data */
+ aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, newitem);
+
+ /* Send the add item SNAC */
+ aim_ssi_addmoddel(sess, conn, &newitem, 1, AIM_CB_SSI_ADD);
+
+ /* Begin sending SSI SNACs */
+ aim_ssi_dispatch(sess, conn);
+
+ return 0;
+}
+
+/**
+ * Add an array of groups to the list.
+ *
+ * @param sess The oscar session.
+ * @param conn The bos connection for this session.
+ * @param gn An array of null terminated strings of the names you want to add.
+ * @param num The number of groups names you are adding (size of the sn array).
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_ssi_addgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num)
+{
+ struct aim_ssi_item *parentgroup, **newitems;
+ guint16 i;
+
+ if (!sess || !conn || !gn || !num)
+ return -EINVAL;
+
+ /* Look up the parent group */
+ if (!(parentgroup = aim_ssi_itemlist_find(sess->ssi.items, 0, 0))) {
+ aim_ssi_addmastergroup(sess, conn);
+ if (!(parentgroup = aim_ssi_itemlist_find(sess->ssi.items, 0, 0)))
+ return -ENOMEM;
+ }
+
+ /* Allocate an array of pointers to each of the new items */
+ if (!(newitems = g_new0(struct aim_ssi_item *, num)))
+ return -ENOMEM;
+
+ /* Add items to the local list, and index them in the array */
+ for (i=0; i<num; i++)
+ if (!(newitems[i] = aim_ssi_itemlist_add(&sess->ssi.items, parentgroup, gn[i], AIM_SSI_TYPE_GROUP))) {
+ g_free(newitems);
+ return -ENOMEM;
+ }
+
+ /* Send the add item SNAC */
+ if ((i = aim_ssi_addmoddel(sess, conn, newitems, num, AIM_CB_SSI_ADD))) {
+ g_free(newitems);
+ return -i;
+ }
+
+ /* Free the array of pointers to each of the new items */
+ g_free(newitems);
+
+ /* Rebuild the additional data in the parent group */
+ if ((i = aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup)))
+ return i;
+
+ /* Send the mod item SNAC */
+ if ((i = aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD)))
+ return i;
+
+ /* Begin sending SSI SNACs */
+ if (!(i = aim_ssi_dispatch(sess, conn)))
+ return i;
+
+ return 0;
+}
+
+/**
+ * Add an array of a certain type of item to the list. This can be used for
+ * permit buddies, deny buddies, ICQ's ignore buddies, and probably other
+ * types, also.
+ *
+ * @param sess The oscar session.
+ * @param conn The bos connection for this session.
+ * @param sn An array of null terminated strings of the names you want to add.
+ * @param num The number of groups names you are adding (size of the sn array).
+ * @param type The type of item you want to add. See the AIM_SSI_TYPE_BLEH
+ * #defines in aim.h.
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_ssi_addpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type)
+{
+ struct aim_ssi_item **newitems;
+ guint16 i;
+
+ if (!sess || !conn || !sn || !num)
+ return -EINVAL;
+
+ /* Allocate an array of pointers to each of the new items */
+ if (!(newitems = g_new0(struct aim_ssi_item *, num)))
+ return -ENOMEM;
+
+ /* Add items to the local list, and index them in the array */
+ for (i=0; i<num; i++)
+ if (!(newitems[i] = aim_ssi_itemlist_add(&sess->ssi.items, NULL, sn[i], type))) {
+ g_free(newitems);
+ return -ENOMEM;
+ }
+
+ /* Send the add item SNAC */
+ if ((i = aim_ssi_addmoddel(sess, conn, newitems, num, AIM_CB_SSI_ADD))) {
+ g_free(newitems);
+ return -i;
+ }
+
+ /* Free the array of pointers to each of the new items */
+ g_free(newitems);
+
+ /* Begin sending SSI SNACs */
+ if (!(i = aim_ssi_dispatch(sess, conn)))
+ return i;
+
+ return 0;
+}
+
+/**
+ * Move a buddy from one group to another group. This basically just deletes the
+ * buddy and re-adds it.
+ *
+ * @param sess The oscar session.
+ * @param conn The bos connection for this session.
+ * @param oldgn The group that the buddy is currently in.
+ * @param newgn The group that the buddy should be moved in to.
+ * @param sn The name of the buddy to be moved.
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_ssi_movebuddy(aim_session_t *sess, aim_conn_t *conn, char *oldgn, char *newgn, char *sn)
+{
+ struct aim_ssi_item **groups, *buddy, *cur;
+ guint16 i;
+
+ if (!sess || !conn || !oldgn || !newgn || !sn)
+ return -EINVAL;
+
+ /* Look up the buddy */
+ if (!(buddy = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, sn, AIM_SSI_TYPE_BUDDY)))
+ return -ENOMEM;
+
+ /* Allocate an array of pointers to the two groups */
+ if (!(groups = g_new0(struct aim_ssi_item *, 2)))
+ return -ENOMEM;
+
+ /* Look up the old parent group */
+ if (!(groups[0] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, oldgn, AIM_SSI_TYPE_GROUP))) {
+ g_free(groups);
+ return -ENOMEM;
+ }
+
+ /* Look up the new parent group */
+ if (!(groups[1] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, newgn, AIM_SSI_TYPE_GROUP))) {
+ g_free(groups);
+ return -ENOMEM;
+ }
+
+ /* Send the delete item SNAC */
+ aim_ssi_addmoddel(sess, conn, &buddy, 1, AIM_CB_SSI_DEL);
+
+ /* Put the buddy in the new group */
+ buddy->gid = groups[1]->gid;
+
+ /* Assign a new buddy ID#, because the new group might already have a buddy with this ID# */
+ buddy->bid = 0;
+ do {
+ buddy->bid += 0x0001;
+ for (cur=sess->ssi.items, i=0; ((cur) && (!i)); cur=cur->next)
+ if ((cur->bid == buddy->bid) && (cur->gid == buddy->gid) && (cur->type == AIM_SSI_TYPE_BUDDY) && (cur->name) && aim_sncmp(cur->name, buddy->name))
+ i=1;
+ } while (i);
+
+ /* Rebuild the additional data in the two parent groups */
+ aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, groups[0]);
+ aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, groups[1]);
+
+ /* Send the add item SNAC */
+ aim_ssi_addmoddel(sess, conn, &buddy, 1, AIM_CB_SSI_ADD);
+
+ /* Send the mod item SNAC */
+ aim_ssi_addmoddel(sess, conn, groups, 2, AIM_CB_SSI_MOD);
+
+ /* Free the temporary array */
+ g_free(groups);
+
+ /* Begin sending SSI SNACs */
+ aim_ssi_dispatch(sess, conn);
+
+ return 0;
+}
+
+/**
+ * Delete an array of screen names from the given group.
+ *
+ * @param sess The oscar session.
+ * @param conn The bos connection for this session.
+ * @param gn The name of the group from which you want to delete these names.
+ * @param sn An array of null terminated strings of the names you want to delete.
+ * @param num The number of screen names you are deleting (size of the sn array).
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_ssi_delbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num)
+{
+ struct aim_ssi_item *cur, *parentgroup, **delitems;
+ int i;
+
+ if (!sess || !conn || !gn || !sn || !num)
+ return -EINVAL;
+
+ /* Look up the parent group */
+ if (!(parentgroup = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn, AIM_SSI_TYPE_GROUP)))
+ return -EINVAL;
+
+ /* Allocate an array of pointers to each of the items to be deleted */
+ delitems = g_new0(struct aim_ssi_item *, num);
+
+ /* Make the delitems array a pointer to the aim_ssi_item structs to be deleted */
+ for (i=0; i<num; i++) {
+ if (!(delitems[i] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, sn[i], AIM_SSI_TYPE_BUDDY))) {
+ g_free(delitems);
+ return -EINVAL;
+ }
+
+ /* Remove the delitems from the item list */
+ if (sess->ssi.items == delitems[i]) {
+ sess->ssi.items = sess->ssi.items->next;
+ } else {
+ for (cur=sess->ssi.items; (cur->next && (cur->next!=delitems[i])); cur=cur->next);
+ if (cur->next)
+ cur->next = cur->next->next;
+ }
+ }
+
+ /* Send the del item SNAC */
+ aim_ssi_addmoddel(sess, conn, delitems, num, AIM_CB_SSI_DEL);
+
+ /* Free the items */
+ for (i=0; i<num; i++) {
+ if (delitems[i]->name)
+ g_free(delitems[i]->name);
+ if (delitems[i]->data)
+ aim_freetlvchain((aim_tlvlist_t **)&delitems[i]->data);
+ g_free(delitems[i]);
+ }
+ g_free(delitems);
+
+ /* Rebuild the additional data in the parent group */
+ aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup);
+
+ /* Send the mod item SNAC */
+ aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD);
+
+ /* Delete the group, but only if it's empty */
+ if (!parentgroup->data)
+ aim_ssi_delgroups(sess, conn, &parentgroup->name, 1);
+
+ /* Begin sending SSI SNACs */
+ aim_ssi_dispatch(sess, conn);
+
+ return 0;
+}
+
+/**
+ * Delete the master group from the item list. There can be only one.
+ * Er, so just find the one master group and delete it.
+ *
+ * @param sess The oscar session.
+ * @param conn The bos connection for this session.
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_ssi_delmastergroup(aim_session_t *sess, aim_conn_t *conn)
+{
+ struct aim_ssi_item *cur, *delitem;
+
+ if (!sess || !conn)
+ return -EINVAL;
+
+ /* Make delitem a pointer to the aim_ssi_item to be deleted */
+ if (!(delitem = aim_ssi_itemlist_find(sess->ssi.items, 0, 0)))
+ return -EINVAL;
+
+ /* Remove delitem from the item list */
+ if (sess->ssi.items == delitem) {
+ sess->ssi.items = sess->ssi.items->next;
+ } else {
+ for (cur=sess->ssi.items; (cur->next && (cur->next!=delitem)); cur=cur->next);
+ if (cur->next)
+ cur->next = cur->next->next;
+ }
+
+ /* Send the del item SNAC */
+ aim_ssi_addmoddel(sess, conn, &delitem, 1, AIM_CB_SSI_DEL);
+
+ /* Free the item */
+ if (delitem->name)
+ g_free(delitem->name);
+ if (delitem->data)
+ aim_freetlvchain((aim_tlvlist_t **)&delitem->data);
+ g_free(delitem);
+
+ /* Begin sending SSI SNACs */
+ aim_ssi_dispatch(sess, conn);
+
+ return 0;
+}
+
+/**
+ * Delete an array of groups.
+ *
+ * @param sess The oscar session.
+ * @param conn The bos connection for this session.
+ * @param gn An array of null terminated strings of the groups you want to delete.
+ * @param num The number of groups you are deleting (size of the gn array).
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_ssi_delgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num) {
+ struct aim_ssi_item *cur, *parentgroup, **delitems;
+ int i;
+
+ if (!sess || !conn || !gn || !num)
+ return -EINVAL;
+
+ /* Look up the parent group */
+ if (!(parentgroup = aim_ssi_itemlist_find(sess->ssi.items, 0, 0)))
+ return -EINVAL;
+
+ /* Allocate an array of pointers to each of the items to be deleted */
+ delitems = g_new0(struct aim_ssi_item *, num);
+
+ /* Make the delitems array a pointer to the aim_ssi_item structs to be deleted */
+ for (i=0; i<num; i++) {
+ if (!(delitems[i] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn[i], AIM_SSI_TYPE_GROUP))) {
+ g_free(delitems);
+ return -EINVAL;
+ }
+
+ /* Remove the delitems from the item list */
+ if (sess->ssi.items == delitems[i]) {
+ sess->ssi.items = sess->ssi.items->next;
+ } else {
+ for (cur=sess->ssi.items; (cur->next && (cur->next!=delitems[i])); cur=cur->next);
+ if (cur->next)
+ cur->next = cur->next->next;
+ }
+ }
+
+ /* Send the del item SNAC */
+ aim_ssi_addmoddel(sess, conn, delitems, num, AIM_CB_SSI_DEL);
+
+ /* Free the items */
+ for (i=0; i<num; i++) {
+ if (delitems[i]->name)
+ g_free(delitems[i]->name);
+ if (delitems[i]->data)
+ aim_freetlvchain((aim_tlvlist_t **)&delitems[i]->data);
+ g_free(delitems[i]);
+ }
+ g_free(delitems);
+
+ /* Rebuild the additional data in the parent group */
+ aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup);
+
+ /* Send the mod item SNAC */
+ aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD);
+
+ /* Delete the group, but only if it's empty */
+ if (!parentgroup->data)
+ aim_ssi_delmastergroup(sess, conn);
+
+ /* Begin sending SSI SNACs */
+ aim_ssi_dispatch(sess, conn);
+
+ return 0;
+}
+
+/**
+ * Delete an array of a certain type of item from the list. This can be
+ * used for permit buddies, deny buddies, ICQ's ignore buddies, and
+ * probably other types, also.
+ *
+ * @param sess The oscar session.
+ * @param conn The bos connection for this session.
+ * @param sn An array of null terminated strings of the items you want to delete.
+ * @param num The number of items you are deleting (size of the sn array).
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_ssi_delpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type) {
+ struct aim_ssi_item *cur, **delitems;
+ int i;
+
+ if (!sess || !conn || !sn || !num || (type!=AIM_SSI_TYPE_PERMIT && type!=AIM_SSI_TYPE_DENY))
+ return -EINVAL;
+
+ /* Allocate an array of pointers to each of the items to be deleted */
+ delitems = g_new0(struct aim_ssi_item *, num);
+
+ /* Make the delitems array a pointer to the aim_ssi_item structs to be deleted */
+ for (i=0; i<num; i++) {
+ if (!(delitems[i] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, sn[i], type))) {
+ g_free(delitems);
+ return -EINVAL;
+ }
+
+ /* Remove the delitems from the item list */
+ if (sess->ssi.items == delitems[i]) {
+ sess->ssi.items = sess->ssi.items->next;
+ } else {
+ for (cur=sess->ssi.items; (cur->next && (cur->next!=delitems[i])); cur=cur->next);
+ if (cur->next)
+ cur->next = cur->next->next;
+ }
+ }
+
+ /* Send the del item SNAC */
+ aim_ssi_addmoddel(sess, conn, delitems, num, AIM_CB_SSI_DEL);
+
+ /* Free the items */
+ for (i=0; i<num; i++) {
+ if (delitems[i]->name)
+ g_free(delitems[i]->name);
+ if (delitems[i]->data)
+ aim_freetlvchain((aim_tlvlist_t **)&delitems[i]->data);
+ g_free(delitems[i]);
+ }
+ g_free(delitems);
+
+ /* Begin sending SSI SNACs */
+ aim_ssi_dispatch(sess, conn);
+
+ return 0;
+}
+
+/**
+ * Stores your permit/deny setting on the server, and starts using it.
+ *
+ * @param sess The oscar session.
+ * @param conn The bos connection for this session.
+ * @param permdeny Your permit/deny setting. Can be one of the following:
+ * 1 - Allow all users
+ * 2 - Block all users
+ * 3 - Allow only the users below
+ * 4 - Block only the users below
+ * 5 - Allow only users on my buddy list
+ * @param vismask A bitmask of the class of users to whom you want to be
+ * visible. See the AIM_FLAG_BLEH #defines in aim.h
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_ssi_setpermdeny(aim_session_t *sess, aim_conn_t *conn, guint8 permdeny, guint32 vismask) {
+ struct aim_ssi_item *cur; //, *tmp;
+// guint16 j;
+ aim_tlv_t *tlv;
+
+ if (!sess || !conn)
+ return -EINVAL;
+
+ /* Look up the permit/deny settings item */
+ cur = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, NULL, AIM_SSI_TYPE_PDINFO);
+
+ if (cur) {
+ /* The permit/deny item exists */
+ if (cur->data && (tlv = aim_gettlv(cur->data, 0x00ca, 1))) {
+ /* Just change the value of the x00ca TLV */
+ if (tlv->length != 1) {
+ tlv->length = 1;
+ g_free(tlv->value);
+ tlv->value = (guint8 *)g_malloc(sizeof(guint8));
+ }
+ tlv->value[0] = permdeny;
+ } else {
+ /* Need to add the x00ca TLV to the TLV chain */
+ aim_addtlvtochain8((aim_tlvlist_t**)&cur->data, 0x00ca, permdeny);
+ }
+
+ if (cur->data && (tlv = aim_gettlv(cur->data, 0x00cb, 1))) {
+ /* Just change the value of the x00cb TLV */
+ if (tlv->length != 4) {
+ tlv->length = 4;
+ g_free(tlv->value);
+ tlv->value = (guint8 *)g_malloc(4*sizeof(guint8));
+ }
+ aimutil_put32(tlv->value, vismask);
+ } else {
+ /* Need to add the x00cb TLV to the TLV chain */
+ aim_addtlvtochain32((aim_tlvlist_t**)&cur->data, 0x00cb, vismask);
+ }
+
+ /* Send the mod item SNAC */
+ aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_MOD);
+ } else {
+ /* Need to add the permit/deny item */
+ if (!(cur = aim_ssi_itemlist_add(&sess->ssi.items, NULL, NULL, AIM_SSI_TYPE_PDINFO)))
+ return -ENOMEM;
+ aim_addtlvtochain8((aim_tlvlist_t**)&cur->data, 0x00ca, permdeny);
+ aim_addtlvtochain32((aim_tlvlist_t**)&cur->data, 0x00cb, vismask);
+ aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_ADD);
+ }
+
+ /* Begin sending SSI SNACs */
+ aim_ssi_dispatch(sess, conn);
+
+ return 0;
+}
+
+/**
+ * Stores your setting for whether you should show up as idle or not.
+ *
+ * @param sess The oscar session.
+ * @param conn The bos connection for this session.
+ * @param presence I think it's a bitmask, but I only know what one of the bits is:
+ * 0x00000400 - Allow others to see your idle time
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_ssi_setpresence(aim_session_t *sess, aim_conn_t *conn, guint32 presence) {
+ struct aim_ssi_item *cur; //, *tmp;
+// guint16 j;
+ aim_tlv_t *tlv;
+
+ if (!sess || !conn)
+ return -EINVAL;
+
+ /* Look up the item */
+ cur = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, NULL, AIM_SSI_TYPE_PRESENCEPREFS);
+
+ if (cur) {
+ /* The item exists */
+ if (cur->data && (tlv = aim_gettlv(cur->data, 0x00c9, 1))) {
+ /* Just change the value of the x00c9 TLV */
+ if (tlv->length != 4) {
+ tlv->length = 4;
+ g_free(tlv->value);
+ tlv->value = (guint8 *)g_malloc(4*sizeof(guint8));
+ }
+ aimutil_put32(tlv->value, presence);
+ } else {
+ /* Need to add the x00c9 TLV to the TLV chain */
+ aim_addtlvtochain32((aim_tlvlist_t**)&cur->data, 0x00c9, presence);
+ }
+
+ /* Send the mod item SNAC */
+ aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_MOD);
+ } else {
+ /* Need to add the item */
+ if (!(cur = aim_ssi_itemlist_add(&sess->ssi.items, NULL, NULL, AIM_SSI_TYPE_PRESENCEPREFS)))
+ return -ENOMEM;
+ aim_addtlvtochain32((aim_tlvlist_t**)&cur->data, 0x00c9, presence);
+ aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_ADD);
+ }
+
+ /* Begin sending SSI SNACs */
+ aim_ssi_dispatch(sess, conn);
+
+ return 0;
+}
+
+/*
+ * Request SSI Rights.
+ */
+int aim_ssi_reqrights(aim_session_t *sess, aim_conn_t *conn)
+{
+ return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, AIM_CB_SSI_REQRIGHTS);
+}
+
+/*
+ * SSI Rights Information.
+ */
+static int parserights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ int ret = 0;
+ aim_rxcallback_t userfunc;
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx);
+
+ return ret;
+}
+
+/*
+ * Request SSI Data.
+ *
+ * The data will only be sent if it is newer than the posted local
+ * timestamp and revision.
+ *
+ * Note that the client should never increment the revision, only the server.
+ *
+ */
+int aim_ssi_reqdata(aim_session_t *sess, aim_conn_t *conn, time_t localstamp, guint16 localrev)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!sess || !conn)
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+4+2)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, AIM_CB_FAM_SSI, AIM_CB_SSI_REQLIST, 0x0000, NULL, 0);
+
+ aim_putsnac(&fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_REQLIST, 0x0000, snacid);
+ aimbs_put32(&fr->data, localstamp);
+ aimbs_put16(&fr->data, localrev);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+int aim_ssi_reqalldata(aim_session_t *sess, aim_conn_t *conn)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+
+ if (!sess || !conn)
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, AIM_CB_FAM_SSI, AIM_CB_SSI_REQFULLLIST, 0x0000, NULL, 0);
+
+ aim_putsnac(&fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_REQFULLLIST, 0x0000, snacid);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/*
+ * SSI Data.
+ */
+static int parsedata(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ int ret = 0;
+ aim_rxcallback_t userfunc;
+ struct aim_ssi_item *cur = NULL;
+ guint8 fmtver; /* guess */
+ guint16 revision;
+ guint32 timestamp;
+
+ /* When you set the version for the SSI family to 2-4, the beginning of this changes.
+ * Instead of the version and then the revision, there is "0x0006" and then a type
+ * 0x0001 TLV containing the 2 byte SSI family version that you sent earlier. Also,
+ * the SNAC flags go from 0x0000 to 0x8000. I guess the 0x0006 is the length of the
+ * TLV(s) that follow. The rights SNAC does the same thing, with the differing flag
+ * and everything.
+ */
+
+ fmtver = aimbs_get8(bs); /* Version of ssi data. Should be 0x00 */
+ revision = aimbs_get16(bs); /* # of times ssi data has been modified */
+ if (revision != 0)
+ sess->ssi.revision = revision;
+
+ for (cur = sess->ssi.items; cur && cur->next; cur=cur->next) ;
+
+ while (aim_bstream_empty(bs) > 4) { /* last four bytes are stamp */
+ guint16 namelen, tbslen;
+
+ if (!sess->ssi.items) {
+ if (!(sess->ssi.items = g_new0(struct aim_ssi_item, 1)))
+ return -ENOMEM;
+ cur = sess->ssi.items;
+ } else {
+ if (!(cur->next = g_new0(struct aim_ssi_item, 1)))
+ return -ENOMEM;
+ cur = cur->next;
+ }
+
+ if ((namelen = aimbs_get16(bs)))
+ cur->name = aimbs_getstr(bs, namelen);
+ cur->gid = aimbs_get16(bs);
+ cur->bid = aimbs_get16(bs);
+ cur->type = aimbs_get16(bs);
+
+ if ((tbslen = aimbs_get16(bs))) {
+ aim_bstream_t tbs;
+
+ aim_bstream_init(&tbs, bs->data + bs->offset /* XXX */, tbslen);
+ cur->data = (void *)aim_readtlvchain(&tbs);
+ aim_bstream_advance(bs, tbslen);
+ }
+ }
+
+ timestamp = aimbs_get32(bs);
+ if (timestamp != 0)
+ sess->ssi.timestamp = timestamp;
+ sess->ssi.received_data = 1;
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, fmtver, sess->ssi.revision, sess->ssi.timestamp, sess->ssi.items);
+
+ return ret;
+}
+
+/*
+ * SSI Data Enable Presence.
+ *
+ * Should be sent after receiving 13/6 or 13/f to tell the server you
+ * are ready to begin using the list. It will promptly give you the
+ * presence information for everyone in your list and put your permit/deny
+ * settings into effect.
+ *
+ */
+int aim_ssi_enable(aim_session_t *sess, aim_conn_t *conn)
+{
+ return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, 0x0007);
+}
+
+/*
+ * Stuff for SSI authorizations. The code used to work with the old im_ch4
+ * messages, but those are supposed to be obsolete. This is probably
+ * ICQ-specific.
+ */
+
+/**
+ * Request authorization to add someone to the server-side buddy list.
+ *
+ * @param sess The oscar session.
+ * @param conn The bos connection for this session.
+ * @param uin The contact's ICQ UIN.
+ * @param reason The reason string to send with the request.
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_ssi_auth_request( aim_session_t *sess, aim_conn_t *conn, char *uin, char *reason )
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ int snaclen;
+
+ snaclen = 10 + 1 + strlen( uin ) + 2 + strlen( reason ) + 2;
+
+ if( !( fr = aim_tx_new( sess, conn, AIM_FRAMETYPE_FLAP, 0x02, snaclen ) ) )
+ return -ENOMEM;
+
+ snacid = aim_cachesnac( sess, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREQ, 0x0000, NULL, 0 );
+ aim_putsnac( &fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREQ, 0x0000, snacid );
+
+ aimbs_put8( &fr->data, strlen( uin ) );
+ aimbs_putraw( &fr->data, (guint8 *)uin, strlen( uin ) );
+ aimbs_put16( &fr->data, strlen( reason ) );
+ aimbs_putraw( &fr->data, (guint8 *)reason, strlen( reason ) );
+ aimbs_put16( &fr->data, 0 );
+
+ aim_tx_enqueue( sess, fr );
+
+ return( 0 );
+}
+
+/**
+ * Reply to an authorization request to add someone to the server-side buddy list.
+ *
+ * @param sess The oscar session.
+ * @param conn The bos connection for this session.
+ * @param uin The contact's ICQ UIN.
+ * @param yesno 1 == Permit, 0 == Deny
+ * @param reason The reason string to send with the request.
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_ssi_auth_reply( aim_session_t *sess, aim_conn_t *conn, char *uin, int yesno, char *reason )
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ int snaclen;
+
+ snaclen = 10 + 1 + strlen( uin ) + 3 + strlen( reason );
+
+ if( !( fr = aim_tx_new( sess, conn, AIM_FRAMETYPE_FLAP, 0x02, snaclen ) ) )
+ return -ENOMEM;
+
+ snacid = aim_cachesnac( sess, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREP, 0x0000, NULL, 0 );
+ aim_putsnac( &fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREP, 0x0000, snacid );
+
+ aimbs_put8( &fr->data, strlen( uin ) );
+ aimbs_putraw( &fr->data, (guint8 *)uin, strlen( uin ) );
+ aimbs_put8( &fr->data, yesno );
+ aimbs_put16( &fr->data, strlen( reason ) );
+ aimbs_putraw( &fr->data, (guint8 *)reason, strlen( reason ) );
+
+ aim_tx_enqueue( sess, fr );
+
+ return( 0 );
+}
+
+
+/*
+ * SSI Add/Mod/Del Item(s).
+ *
+ * Sends the SNAC to add, modify, or delete an item from the server-stored
+ * information. These 3 SNACs all have an identical structure. The only
+ * difference is the subtype that is set for the SNAC.
+ *
+ */
+int aim_ssi_addmoddel(aim_session_t *sess, aim_conn_t *conn, struct aim_ssi_item **items, unsigned int num, guint16 subtype)
+{
+ aim_frame_t *fr;
+ aim_snacid_t snacid;
+ int i, snaclen, listlen;
+ char *list = NULL;
+
+ if (!sess || !conn || !items || !num)
+ return -EINVAL;
+
+ snaclen = 10; /* For family, subtype, flags, and SNAC ID */
+ listlen = 0;
+ for (i=0; i<num; i++) {
+ snaclen += 10; /* For length, GID, BID, type, and length */
+ if (items[i]->name) {
+ snaclen += strlen(items[i]->name);
+
+ if (subtype == AIM_CB_SSI_ADD) {
+ list = g_realloc(list, listlen + strlen(items[i]->name) + 1);
+ strcpy(list + listlen, items[i]->name);
+ listlen += strlen(items[i]->name) + 1;
+ }
+ } else {
+ if (subtype == AIM_CB_SSI_ADD) {
+ list = g_realloc(list, listlen + 1);
+ list[listlen] = '\0';
+ listlen ++;
+ }
+ }
+ if (items[i]->data)
+ snaclen += aim_sizetlvchain((aim_tlvlist_t **)&items[i]->data);
+ }
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, snaclen)))
+ return -ENOMEM;
+
+ snacid = aim_cachesnac(sess, AIM_CB_FAM_SSI, subtype, 0x0000, list, list ? listlen : 0);
+ aim_putsnac(&fr->data, AIM_CB_FAM_SSI, subtype, 0x0000, snacid);
+
+ g_free(list);
+
+ for (i=0; i<num; i++) {
+ aimbs_put16(&fr->data, items[i]->name ? strlen(items[i]->name) : 0);
+ if (items[i]->name)
+ aimbs_putraw(&fr->data, (guint8 *)items[i]->name, strlen(items[i]->name));
+ aimbs_put16(&fr->data, items[i]->gid);
+ aimbs_put16(&fr->data, items[i]->bid);
+ aimbs_put16(&fr->data, items[i]->type);
+ aimbs_put16(&fr->data, items[i]->data ? aim_sizetlvchain((aim_tlvlist_t **)&items[i]->data) : 0);
+ if (items[i]->data)
+ aim_writetlvchain(&fr->data, (aim_tlvlist_t **)&items[i]->data);
+ }
+
+ aim_ssi_enqueue(sess, conn, fr);
+
+ return 0;
+}
+
+/*
+ * SSI Add/Mod/Del Ack.
+ *
+ * Response to add, modify, or delete SNAC (sent with aim_ssi_addmoddel).
+ *
+ */
+static int parseack(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ int ret = 0;
+ aim_rxcallback_t userfunc;
+ aim_snac_t *origsnac;
+
+ sess->ssi.waiting_for_ack = 0;
+ aim_ssi_dispatch(sess, rx->conn);
+
+ origsnac = aim_remsnac(sess, snac->id);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx, origsnac);
+
+ if (origsnac) {
+ g_free(origsnac->data);
+ g_free(origsnac);
+ }
+
+ return ret;
+}
+
+/*
+ * SSI Begin Data Modification.
+ *
+ * Tells the server you're going to start modifying data.
+ *
+ */
+int aim_ssi_modbegin(aim_session_t *sess, aim_conn_t *conn)
+{
+ return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, AIM_CB_SSI_EDITSTART);
+}
+
+/*
+ * SSI End Data Modification.
+ *
+ * Tells the server you're done modifying data.
+ *
+ */
+int aim_ssi_modend(aim_session_t *sess, aim_conn_t *conn)
+{
+ return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, AIM_CB_SSI_EDITSTOP);
+}
+
+/*
+ * SSI Data Unchanged.
+ *
+ * Response to aim_ssi_reqdata() if the server-side data is not newer than
+ * posted local stamp/revision.
+ *
+ */
+static int parsedataunchanged(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ int ret = 0;
+ aim_rxcallback_t userfunc;
+
+ sess->ssi.received_data = 1;
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ ret = userfunc(sess, rx);
+
+ return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+ if (snac->subtype == AIM_CB_SSI_RIGHTSINFO)
+ return parserights(sess, mod, rx, snac, bs);
+ else if (snac->subtype == AIM_CB_SSI_LIST)
+ return parsedata(sess, mod, rx, snac, bs);
+ else if (snac->subtype == AIM_CB_SSI_SRVACK)
+ return parseack(sess, mod, rx, snac, bs);
+ else if (snac->subtype == AIM_CB_SSI_NOLIST)
+ return parsedataunchanged(sess, mod, rx, snac, bs);
+
+ return 0;
+}
+
+static void ssi_shutdown(aim_session_t *sess, aim_module_t *mod)
+{
+ aim_ssi_freelist(sess);
+
+ return;
+}
+
+int ssi_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+ mod->family = AIM_CB_FAM_SSI;
+ mod->version = 0x0003;
+ mod->toolid = 0x0110;
+ mod->toolversion = 0x0629;
+ mod->flags = 0;
+ strncpy(mod->name, "ssi", sizeof(mod->name));
+ mod->snachandler = snachandler;
+ mod->shutdown = ssi_shutdown;
+
+ return 0;
+}
diff --git a/protocols/oscar/ssi.h b/protocols/oscar/ssi.h
new file mode 100644
index 00000000..94b18d60
--- /dev/null
+++ b/protocols/oscar/ssi.h
@@ -0,0 +1,78 @@
+#ifndef __OSCAR_SSI_H__
+#define __OSCAR_SSI_H__
+
+#define AIM_CB_FAM_SSI 0x0013 /* Server stored information */
+
+/*
+ * SNAC Family: Server-Stored Buddy Lists
+ */
+#define AIM_CB_SSI_ERROR 0x0001
+#define AIM_CB_SSI_REQRIGHTS 0x0002
+#define AIM_CB_SSI_RIGHTSINFO 0x0003
+#define AIM_CB_SSI_REQFULLLIST 0x0004
+#define AIM_CB_SSI_REQLIST 0x0005
+#define AIM_CB_SSI_LIST 0x0006
+#define AIM_CB_SSI_ACTIVATE 0x0007
+#define AIM_CB_SSI_ADD 0x0008
+#define AIM_CB_SSI_MOD 0x0009
+#define AIM_CB_SSI_DEL 0x000A
+#define AIM_CB_SSI_SRVACK 0x000E
+#define AIM_CB_SSI_NOLIST 0x000F
+#define AIM_CB_SSI_EDITSTART 0x0011
+#define AIM_CB_SSI_EDITSTOP 0x0012
+#define AIM_CB_SSI_SENDAUTHREQ 0x0018
+#define AIM_CB_SSI_SERVAUTHREQ 0x0019
+#define AIM_CB_SSI_SENDAUTHREP 0x001A
+#define AIM_CB_SSI_SERVAUTHREP 0x001B
+
+
+#define AIM_SSI_TYPE_BUDDY 0x0000
+#define AIM_SSI_TYPE_GROUP 0x0001
+#define AIM_SSI_TYPE_PERMIT 0x0002
+#define AIM_SSI_TYPE_DENY 0x0003
+#define AIM_SSI_TYPE_PDINFO 0x0004
+#define AIM_SSI_TYPE_PRESENCEPREFS 0x0005
+
+struct aim_ssi_item {
+ char *name;
+ guint16 gid;
+ guint16 bid;
+ guint16 type;
+ void *data;
+ struct aim_ssi_item *next;
+};
+
+/* These build the actual SNACs and queue them to be sent */
+int aim_ssi_reqrights(aim_session_t *sess, aim_conn_t *conn);
+int aim_ssi_reqdata(aim_session_t *sess, aim_conn_t *conn, time_t localstamp, guint16 localrev);
+int aim_ssi_reqalldata(aim_session_t *sess, aim_conn_t *conn);
+int aim_ssi_enable(aim_session_t *sess, aim_conn_t *conn);
+int aim_ssi_addmoddel(aim_session_t *sess, aim_conn_t *conn, struct aim_ssi_item **items, unsigned int num, guint16 subtype);
+int aim_ssi_modbegin(aim_session_t *sess, aim_conn_t *conn);
+int aim_ssi_modend(aim_session_t *sess, aim_conn_t *conn);
+
+/* These handle the local variables */
+struct aim_ssi_item *aim_ssi_itemlist_find(struct aim_ssi_item *list, guint16 gid, guint16 bid);
+struct aim_ssi_item *aim_ssi_itemlist_finditem(struct aim_ssi_item *list, char *gn, char *sn, guint16 type);
+struct aim_ssi_item *aim_ssi_itemlist_findparent(struct aim_ssi_item *list, char *sn);
+int aim_ssi_getpermdeny(struct aim_ssi_item *list);
+guint32 aim_ssi_getpresence(struct aim_ssi_item *list);
+
+/* Send packets */
+int aim_ssi_cleanlist(aim_session_t *sess, aim_conn_t *conn);
+int aim_ssi_addbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num, unsigned int flags);
+int aim_ssi_addmastergroup(aim_session_t *sess, aim_conn_t *conn);
+int aim_ssi_addgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num);
+int aim_ssi_addpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type);
+int aim_ssi_movebuddy(aim_session_t *sess, aim_conn_t *conn, char *oldgn, char *newgn, char *sn);
+int aim_ssi_delbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num);
+int aim_ssi_delmastergroup(aim_session_t *sess, aim_conn_t *conn);
+int aim_ssi_delgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num);
+int aim_ssi_deletelist(aim_session_t *sess, aim_conn_t *conn);
+int aim_ssi_delpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type);
+int aim_ssi_setpermdeny(aim_session_t *sess, aim_conn_t *conn, guint8 permdeny, guint32 vismask);
+int aim_ssi_setpresence(aim_session_t *sess, aim_conn_t *conn, guint32 presence);
+int aim_ssi_auth_request(aim_session_t *sess, aim_conn_t *conn, char *uin, char *reason);
+int aim_ssi_auth_reply(aim_session_t *sess, aim_conn_t *conn, char *uin, int yesno, char *reason);
+
+#endif /* __OSCAR_SSI_H__ */
diff --git a/protocols/oscar/stats.c b/protocols/oscar/stats.c
new file mode 100644
index 00000000..affd82fe
--- /dev/null
+++ b/protocols/oscar/stats.c
@@ -0,0 +1,38 @@
+
+#include <aim.h>
+
+static int reportinterval(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+ guint16 interval;
+ aim_rxcallback_t userfunc;
+
+ interval = aimbs_get16(bs);
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+ return userfunc(sess, rx, interval);
+
+ return 0;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+ if (snac->subtype == 0x0002)
+ return reportinterval(sess, mod, rx, snac, bs);
+
+ return 0;
+}
+
+int stats_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+ mod->family = 0x000b;
+ mod->version = 0x0001;
+ mod->toolid = 0x0104;
+ mod->toolversion = 0x0001;
+ mod->flags = 0;
+ strncpy(mod->name, "stats", sizeof(mod->name));
+ mod->snachandler = snachandler;
+
+ return 0;
+}
diff --git a/protocols/oscar/tlv.c b/protocols/oscar/tlv.c
new file mode 100644
index 00000000..9d827caf
--- /dev/null
+++ b/protocols/oscar/tlv.c
@@ -0,0 +1,599 @@
+#include <aim.h>
+
+static void freetlv(aim_tlv_t **oldtlv)
+{
+ if (!oldtlv || !*oldtlv)
+ return;
+
+ g_free((*oldtlv)->value);
+ g_free(*oldtlv);
+ *oldtlv = NULL;
+}
+
+/**
+ * aim_readtlvchain - Read a TLV chain from a buffer.
+ * @buf: Input buffer
+ * @maxlen: Length of input buffer
+ *
+ * Reads and parses a series of TLV patterns from a data buffer; the
+ * returned structure is manipulatable with the rest of the TLV
+ * routines. When done with a TLV chain, aim_freetlvchain() should
+ * be called to free the dynamic substructures.
+ *
+ * XXX There should be a flag setable here to have the tlvlist contain
+ * bstream references, so that at least the ->value portion of each
+ * element doesn't need to be malloc/memcpy'd. This could prove to be
+ * just as effecient as the in-place TLV parsing used in a couple places
+ * in libfaim.
+ *
+ */
+aim_tlvlist_t *aim_readtlvchain(aim_bstream_t *bs)
+{
+ aim_tlvlist_t *list = NULL, *cur;
+ guint16 type, length;
+
+ while (aim_bstream_empty(bs)) {
+
+ type = aimbs_get16(bs);
+ length = aimbs_get16(bs);
+
+ cur = g_new0(aim_tlvlist_t, 1);
+
+ cur->tlv = g_new0(aim_tlv_t, 1);
+ cur->tlv->type = type;
+ if ((cur->tlv->length = length))
+ cur->tlv->value = aimbs_getraw(bs, length);
+
+ cur->next = list;
+ list = cur;
+ }
+
+ return list;
+}
+
+/**
+ * aim_freetlvchain - Free a TLV chain structure
+ * @list: Chain to be freed
+ *
+ * Walks the list of TLVs in the passed TLV chain and
+ * frees each one. Note that any references to this data
+ * should be removed before calling this.
+ *
+ */
+void aim_freetlvchain(aim_tlvlist_t **list)
+{
+ aim_tlvlist_t *cur;
+
+ if (!list || !*list)
+ return;
+
+ for (cur = *list; cur; ) {
+ aim_tlvlist_t *tmp;
+
+ freetlv(&cur->tlv);
+
+ tmp = cur->next;
+ g_free(cur);
+ cur = tmp;
+ }
+
+ list = NULL;
+
+ return;
+}
+
+/**
+ * aim_counttlvchain - Count the number of TLVs in a chain
+ * @list: Chain to be counted
+ *
+ * Returns the number of TLVs stored in the passed chain.
+ *
+ */
+int aim_counttlvchain(aim_tlvlist_t **list)
+{
+ aim_tlvlist_t *cur;
+ int count;
+
+ if (!list || !*list)
+ return 0;
+
+ for (cur = *list, count = 0; cur; cur = cur->next)
+ count++;
+
+ return count;
+}
+
+/**
+ * aim_sizetlvchain - Count the number of bytes in a TLV chain
+ * @list: Chain to be sized
+ *
+ * Returns the number of bytes that would be needed to
+ * write the passed TLV chain to a data buffer.
+ *
+ */
+int aim_sizetlvchain(aim_tlvlist_t **list)
+{
+ aim_tlvlist_t *cur;
+ int size;
+
+ if (!list || !*list)
+ return 0;
+
+ for (cur = *list, size = 0; cur; cur = cur->next)
+ size += (4 + cur->tlv->length);
+
+ return size;
+}
+
+/**
+ * aim_addtlvtochain_str - Add a string to a TLV chain
+ * @list: Desination chain (%NULL pointer if empty)
+ * @type: TLV type
+ * @str: String to add
+ * @len: Length of string to add (not including %NULL)
+ *
+ * Adds the passed string as a TLV element of the passed type
+ * to the TLV chain.
+ *
+ */
+int aim_addtlvtochain_raw(aim_tlvlist_t **list, const guint16 t, const guint16 l, const guint8 *v)
+{
+ aim_tlvlist_t *newtlv, *cur;
+
+ if (!list)
+ return 0;
+
+ if (!(newtlv = g_new0(aim_tlvlist_t, 1)))
+ return 0;
+
+ if (!(newtlv->tlv = g_new0(aim_tlv_t, 1))) {
+ g_free(newtlv);
+ return 0;
+ }
+ newtlv->tlv->type = t;
+ if ((newtlv->tlv->length = l)) {
+ newtlv->tlv->value = (guint8 *)g_malloc(newtlv->tlv->length);
+ memcpy(newtlv->tlv->value, v, newtlv->tlv->length);
+ }
+
+ if (!*list)
+ *list = newtlv;
+ else {
+ for(cur = *list; cur->next; cur = cur->next)
+ ;
+ cur->next = newtlv;
+ }
+
+ return newtlv->tlv->length;
+}
+
+/**
+ * aim_addtlvtochain8 - Add a 8bit integer to a TLV chain
+ * @list: Destination chain
+ * @type: TLV type to add
+ * @val: Value to add
+ *
+ * Adds a one-byte unsigned integer to a TLV chain.
+ *
+ */
+int aim_addtlvtochain8(aim_tlvlist_t **list, const guint16 t, const guint8 v)
+{
+ guint8 v8[1];
+
+ aimutil_put8(v8, v);
+
+ return aim_addtlvtochain_raw(list, t, 1, v8);
+}
+
+/**
+ * aim_addtlvtochain16 - Add a 16bit integer to a TLV chain
+ * @list: Destination chain
+ * @type: TLV type to add
+ * @val: Value to add
+ *
+ * Adds a two-byte unsigned integer to a TLV chain.
+ *
+ */
+int aim_addtlvtochain16(aim_tlvlist_t **list, const guint16 t, const guint16 v)
+{
+ guint8 v16[2];
+
+ aimutil_put16(v16, v);
+
+ return aim_addtlvtochain_raw(list, t, 2, v16);
+}
+
+/**
+ * aim_addtlvtochain32 - Add a 32bit integer to a TLV chain
+ * @list: Destination chain
+ * @type: TLV type to add
+ * @val: Value to add
+ *
+ * Adds a four-byte unsigned integer to a TLV chain.
+ *
+ */
+int aim_addtlvtochain32(aim_tlvlist_t **list, const guint16 t, const guint32 v)
+{
+ guint8 v32[4];
+
+ aimutil_put32(v32, v);
+
+ return aim_addtlvtochain_raw(list, t, 4, v32);
+}
+
+/**
+ * aim_addtlvtochain_availmsg - Add a ICQ availability message to a TLV chain
+ * @list: Destination chain
+ * @type: TLV type to add
+ * @val: Value to add
+ *
+ * Adds a available message to a TLV chain
+ *
+ */
+int aim_addtlvtochain_availmsg(aim_tlvlist_t **list, const guint16 t, const char *msg)
+{
+ int ret;
+ guint16 unknown_data = 0x00;
+ guint8 add_data_len = 4;
+ guint16 msg_len = strlen(msg);
+ guint8 total_len = strlen(msg) + add_data_len;
+ guint8 *data, *cur;
+ guint8 alloc_len = msg_len + (3*sizeof(guint16)) + (2*sizeof(guint8));
+ data = cur = g_malloc(alloc_len);
+
+ cur += aimutil_put16(cur, 2);
+ cur += aimutil_put8(cur, add_data_len);
+ cur += aimutil_put8(cur, total_len);
+ cur += aimutil_put16(cur, msg_len);
+ cur += aimutil_putstr(cur, msg, msg_len);
+ cur += aimutil_put16(cur, unknown_data);
+
+ ret = aim_addtlvtochain_raw(list, t, alloc_len, data);
+ g_free(data);
+
+ return ret;
+}
+
+/**
+ * aim_addtlvtochain_caps - Add a capability block to a TLV chain
+ * @list: Destination chain
+ * @type: TLV type to add
+ * @caps: Bitfield of capability flags to send
+ *
+ * Adds a block of capability blocks to a TLV chain. The bitfield
+ * passed in should be a bitwise %OR of any of the %AIM_CAPS constants:
+ *
+ */
+int aim_addtlvtochain_caps(aim_tlvlist_t **list, const guint16 t, const guint32 caps)
+{
+ guint8 buf[16*16]; /* XXX icky fixed length buffer */
+ aim_bstream_t bs;
+
+ if (!caps)
+ return 0; /* nothing there anyway */
+
+ aim_bstream_init(&bs, buf, sizeof(buf));
+
+ aim_putcap(&bs, caps);
+
+ return aim_addtlvtochain_raw(list, t, aim_bstream_curpos(&bs), buf);
+}
+
+int aim_addtlvtochain_userinfo(aim_tlvlist_t **list, guint16 type, aim_userinfo_t *ui)
+{
+ guint8 buf[1024]; /* bleh */
+ aim_bstream_t bs;
+
+ aim_bstream_init(&bs, buf, sizeof(buf));
+
+ aim_putuserinfo(&bs, ui);
+
+ return aim_addtlvtochain_raw(list, type, aim_bstream_curpos(&bs), buf);
+}
+
+/**
+ * aim_addtlvtochain_noval - Add a blank TLV to a TLV chain
+ * @list: Destination chain
+ * @type: TLV type to add
+ *
+ * Adds a TLV with a zero length to a TLV chain.
+ *
+ */
+int aim_addtlvtochain_noval(aim_tlvlist_t **list, const guint16 t)
+{
+ return aim_addtlvtochain_raw(list, t, 0, NULL);
+}
+
+/*
+ * Note that the inner TLV chain will not be modifiable as a tlvchain once
+ * it is written using this. Or rather, it can be, but updates won't be
+ * made to this.
+ *
+ * XXX should probably support sublists for real.
+ *
+ * This is so neat.
+ *
+ */
+int aim_addtlvtochain_frozentlvlist(aim_tlvlist_t **list, guint16 type, aim_tlvlist_t **tl)
+{
+ guint8 *buf;
+ int buflen;
+ aim_bstream_t bs;
+
+ buflen = aim_sizetlvchain(tl);
+
+ if (buflen <= 0)
+ return 0;
+
+ if (!(buf = g_malloc(buflen)))
+ return 0;
+
+ aim_bstream_init(&bs, buf, buflen);
+
+ aim_writetlvchain(&bs, tl);
+
+ aim_addtlvtochain_raw(list, type, aim_bstream_curpos(&bs), buf);
+
+ g_free(buf);
+
+ return buflen;
+}
+
+int aim_addtlvtochain_chatroom(aim_tlvlist_t **list, guint16 type, guint16 exchange, const char *roomname, guint16 instance)
+{
+ guint8 *buf;
+ int buflen;
+ aim_bstream_t bs;
+
+ buflen = 2 + 1 + strlen(roomname) + 2;
+
+ if (!(buf = g_malloc(buflen)))
+ return 0;
+
+ aim_bstream_init(&bs, buf, buflen);
+
+ aimbs_put16(&bs, exchange);
+ aimbs_put8(&bs, strlen(roomname));
+ aimbs_putraw(&bs, (guint8 *)roomname, strlen(roomname));
+ aimbs_put16(&bs, instance);
+
+ aim_addtlvtochain_raw(list, type, aim_bstream_curpos(&bs), buf);
+
+ g_free(buf);
+
+ return 0;
+}
+
+/**
+ * aim_writetlvchain - Write a TLV chain into a data buffer.
+ * @buf: Destination buffer
+ * @buflen: Maximum number of bytes that will be written to buffer
+ * @list: Source TLV chain
+ *
+ * Copies a TLV chain into a raw data buffer, writing only the number
+ * of bytes specified. This operation does not free the chain;
+ * aim_freetlvchain() must still be called to free up the memory used
+ * by the chain structures.
+ *
+ * XXX clean this up, make better use of bstreams
+ */
+int aim_writetlvchain(aim_bstream_t *bs, aim_tlvlist_t **list)
+{
+ int goodbuflen;
+ aim_tlvlist_t *cur;
+
+ /* do an initial run to test total length */
+ for (cur = *list, goodbuflen = 0; cur; cur = cur->next) {
+ goodbuflen += 2 + 2; /* type + len */
+ goodbuflen += cur->tlv->length;
+ }
+
+ if (goodbuflen > aim_bstream_empty(bs))
+ return 0; /* not enough buffer */
+
+ /* do the real write-out */
+ for (cur = *list; cur; cur = cur->next) {
+ aimbs_put16(bs, cur->tlv->type);
+ aimbs_put16(bs, cur->tlv->length);
+ if (cur->tlv->length)
+ aimbs_putraw(bs, cur->tlv->value, cur->tlv->length);
+ }
+
+ return 1; /* XXX this is a nonsensical return */
+}
+
+
+/**
+ * aim_gettlv - Grab the Nth TLV of type type in the TLV list list.
+ * @list: Source chain
+ * @type: Requested TLV type
+ * @nth: Index of TLV of type to get
+ *
+ * Returns a pointer to an aim_tlv_t of the specified type;
+ * %NULL on error. The @nth parameter is specified starting at %1.
+ * In most cases, there will be no more than one TLV of any type
+ * in a chain.
+ *
+ */
+aim_tlv_t *aim_gettlv(aim_tlvlist_t *list, const guint16 t, const int n)
+{
+ aim_tlvlist_t *cur;
+ int i;
+
+ for (cur = list, i = 0; cur; cur = cur->next) {
+ if (cur && cur->tlv) {
+ if (cur->tlv->type == t)
+ i++;
+ if (i >= n)
+ return cur->tlv;
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * aim_gettlv_str - Retrieve the Nth TLV in chain as a string.
+ * @list: Source TLV chain
+ * @type: TLV type to search for
+ * @nth: Index of TLV to return
+ *
+ * Same as aim_gettlv(), except that the return value is a %NULL-
+ * terminated string instead of an aim_tlv_t. This is a
+ * dynamic buffer and must be freed by the caller.
+ *
+ */
+char *aim_gettlv_str(aim_tlvlist_t *list, const guint16 t, const int n)
+{
+ aim_tlv_t *tlv;
+ char *newstr;
+
+ if (!(tlv = aim_gettlv(list, t, n)))
+ return NULL;
+
+ newstr = (char *) g_malloc(tlv->length + 1);
+ memcpy(newstr, tlv->value, tlv->length);
+ *(newstr + tlv->length) = '\0';
+
+ return newstr;
+}
+
+/**
+ * aim_gettlv8 - Retrieve the Nth TLV in chain as a 8bit integer.
+ * @list: Source TLV chain
+ * @type: TLV type to search for
+ * @nth: Index of TLV to return
+ *
+ * Same as aim_gettlv(), except that the return value is a
+ * 8bit integer instead of an aim_tlv_t.
+ *
+ */
+guint8 aim_gettlv8(aim_tlvlist_t *list, const guint16 t, const int n)
+{
+ aim_tlv_t *tlv;
+
+ if (!(tlv = aim_gettlv(list, t, n)))
+ return 0; /* erm */
+ return aimutil_get8(tlv->value);
+}
+
+/**
+ * aim_gettlv16 - Retrieve the Nth TLV in chain as a 16bit integer.
+ * @list: Source TLV chain
+ * @type: TLV type to search for
+ * @nth: Index of TLV to return
+ *
+ * Same as aim_gettlv(), except that the return value is a
+ * 16bit integer instead of an aim_tlv_t.
+ *
+ */
+guint16 aim_gettlv16(aim_tlvlist_t *list, const guint16 t, const int n)
+{
+ aim_tlv_t *tlv;
+
+ if (!(tlv = aim_gettlv(list, t, n)))
+ return 0; /* erm */
+ return aimutil_get16(tlv->value);
+}
+
+/**
+ * aim_gettlv32 - Retrieve the Nth TLV in chain as a 32bit integer.
+ * @list: Source TLV chain
+ * @type: TLV type to search for
+ * @nth: Index of TLV to return
+ *
+ * Same as aim_gettlv(), except that the return value is a
+ * 32bit integer instead of an aim_tlv_t.
+ *
+ */
+guint32 aim_gettlv32(aim_tlvlist_t *list, const guint16 t, const int n)
+{
+ aim_tlv_t *tlv;
+
+ if (!(tlv = aim_gettlv(list, t, n)))
+ return 0; /* erm */
+ return aimutil_get32(tlv->value);
+}
+
+#if 0
+/**
+ * aim_puttlv_8 - Write a one-byte TLV.
+ * @buf: Destination buffer
+ * @t: TLV type
+ * @v: Value
+ *
+ * Writes a TLV with a one-byte integer value portion.
+ *
+ */
+int aim_puttlv_8(guint8 *buf, const guint16 t, const guint8 v)
+{
+ guint8 v8[1];
+
+ aimutil_put8(v8, v);
+
+ return aim_puttlv_raw(buf, t, 1, v8);
+}
+
+/**
+ * aim_puttlv_16 - Write a two-byte TLV.
+ * @buf: Destination buffer
+ * @t: TLV type
+ * @v: Value
+ *
+ * Writes a TLV with a two-byte integer value portion.
+ *
+ */
+int aim_puttlv_16(guint8 *buf, const guint16 t, const guint16 v)
+{
+ guint8 v16[2];
+
+ aimutil_put16(v16, v);
+
+ return aim_puttlv_raw(buf, t, 2, v16);
+}
+
+
+/**
+ * aim_puttlv_32 - Write a four-byte TLV.
+ * @buf: Destination buffer
+ * @t: TLV type
+ * @v: Value
+ *
+ * Writes a TLV with a four-byte integer value portion.
+ *
+ */
+int aim_puttlv_32(guint8 *buf, const guint16 t, const guint32 v)
+{
+ guint8 v32[4];
+
+ aimutil_put32(v32, v);
+
+ return aim_puttlv_raw(buf, t, 4, v32);
+}
+
+/**
+ * aim_puttlv_raw - Write a raw TLV.
+ * @buf: Destination buffer
+ * @t: TLV type
+ * @l: Length of string
+ * @v: String to write
+ *
+ * Writes a TLV with a raw value portion. (Only the first @l
+ * bytes of the passed buffer will be written, which should not
+ * include a terminating NULL.)
+ *
+ */
+int aim_puttlv_raw(guint8 *buf, const guint16 t, const guint16 l, const guint8 *v)
+{
+ int i;
+
+ i = aimutil_put16(buf, t);
+ i += aimutil_put16(buf+i, l);
+ if (l)
+ memcpy(buf+i, v, l);
+ i += l;
+
+ return i;
+}
+#endif
+
diff --git a/protocols/oscar/txqueue.c b/protocols/oscar/txqueue.c
new file mode 100644
index 00000000..d38986d0
--- /dev/null
+++ b/protocols/oscar/txqueue.c
@@ -0,0 +1,358 @@
+/*
+ * aim_txqueue.c
+ *
+ * Herein lies all the mangement routines for the transmit (Tx) queue.
+ *
+ */
+
+#include <aim.h>
+#include "im.h"
+
+#ifndef _WIN32
+#include <sys/socket.h>
+#endif
+
+/*
+ * Allocate a new tx frame.
+ *
+ * This is more for looks than anything else.
+ *
+ * Right now, that is. If/when we implement a pool of transmit
+ * frames, this will become the request-an-unused-frame part.
+ *
+ * framing = AIM_FRAMETYPE_OFT/FLAP
+ * chan = channel for FLAP, hdrtype for OFT
+ *
+ */
+aim_frame_t *aim_tx_new(aim_session_t *sess, aim_conn_t *conn, guint8 framing, guint8 chan, int datalen)
+{
+ aim_frame_t *fr;
+
+ if (!conn) {
+ imcb_error(sess->aux_data, "no connection specified");
+ return NULL;
+ }
+
+ if (!(fr = (aim_frame_t *)g_new0(aim_frame_t,1)))
+ return NULL;
+
+ fr->conn = conn;
+
+ fr->hdrtype = framing;
+
+ if (fr->hdrtype == AIM_FRAMETYPE_FLAP) {
+
+ fr->hdr.flap.type = chan;
+
+ } else
+ imcb_error(sess->aux_data, "unknown framing");
+
+ if (datalen > 0) {
+ guint8 *data;
+
+ if (!(data = (unsigned char *)g_malloc(datalen))) {
+ aim_frame_destroy(fr);
+ return NULL;
+ }
+
+ aim_bstream_init(&fr->data, data, datalen);
+ }
+
+ return fr;
+}
+
+/*
+ * aim_tx_enqeue__queuebased()
+ *
+ * The overall purpose here is to enqueue the passed in command struct
+ * into the outgoing (tx) queue. Basically...
+ * 1) Make a scope-irrelevent copy of the struct
+ * 3) Mark as not-sent-yet
+ * 4) Enqueue the struct into the list
+ * 6) Return
+ *
+ * Note that this is only used when doing queue-based transmitting;
+ * that is, when sess->tx_enqueue is set to &aim_tx_enqueue__queuebased.
+ *
+ */
+static int aim_tx_enqueue__queuebased(aim_session_t *sess, aim_frame_t *fr)
+{
+
+ if (!fr->conn) {
+ imcb_error(sess->aux_data, "Warning: enqueueing packet with no connection");
+ fr->conn = aim_getconn_type(sess, AIM_CONN_TYPE_BOS);
+ }
+
+ if (fr->hdrtype == AIM_FRAMETYPE_FLAP) {
+ /* assign seqnum -- XXX should really not assign until hardxmit */
+ fr->hdr.flap.seqnum = aim_get_next_txseqnum(fr->conn);
+ }
+
+ fr->handled = 0; /* not sent yet */
+
+ /* see overhead note in aim_rxqueue counterpart */
+ if (!sess->queue_outgoing)
+ sess->queue_outgoing = fr;
+ else {
+ aim_frame_t *cur;
+
+ for (cur = sess->queue_outgoing; cur->next; cur = cur->next)
+ ;
+ cur->next = fr;
+ }
+
+ return 0;
+}
+
+/*
+ * aim_tx_enqueue__immediate()
+ *
+ * Parallel to aim_tx_enqueue__queuebased, however, this bypasses
+ * the whole queue mess when you want immediate writes to happen.
+ *
+ * Basically the same as its __queuebased couterpart, however
+ * instead of doing a list append, it just calls aim_tx_sendframe()
+ * right here.
+ *
+ */
+static int aim_tx_enqueue__immediate(aim_session_t *sess, aim_frame_t *fr)
+{
+
+ if (!fr->conn) {
+ imcb_error(sess->aux_data, "packet has no connection");
+ aim_frame_destroy(fr);
+ return 0;
+ }
+
+ if (fr->hdrtype == AIM_FRAMETYPE_FLAP)
+ fr->hdr.flap.seqnum = aim_get_next_txseqnum(fr->conn);
+
+ fr->handled = 0; /* not sent yet */
+
+ aim_tx_sendframe(sess, fr);
+
+ aim_frame_destroy(fr);
+
+ return 0;
+}
+
+int aim_tx_setenqueue(aim_session_t *sess, int what, int (*func)(aim_session_t *, aim_frame_t *))
+{
+
+ if (what == AIM_TX_QUEUED)
+ sess->tx_enqueue = &aim_tx_enqueue__queuebased;
+ else if (what == AIM_TX_IMMEDIATE)
+ sess->tx_enqueue = &aim_tx_enqueue__immediate;
+ else if (what == AIM_TX_USER) {
+ if (!func)
+ return -EINVAL;
+ sess->tx_enqueue = func;
+ } else
+ return -EINVAL; /* unknown action */
+
+ return 0;
+}
+
+int aim_tx_enqueue(aim_session_t *sess, aim_frame_t *fr)
+{
+
+ /*
+ * If we want to send a connection thats inprogress, we have to force
+ * them to use the queue based version. Otherwise, use whatever they
+ * want.
+ */
+ if (fr && fr->conn &&
+ (fr->conn->status & AIM_CONN_STATUS_INPROGRESS)) {
+ return aim_tx_enqueue__queuebased(sess, fr);
+ }
+
+ return (*sess->tx_enqueue)(sess, fr);
+}
+
+/*
+ * aim_get_next_txseqnum()
+ *
+ * This increments the tx command count, and returns the seqnum
+ * that should be stamped on the next FLAP packet sent. This is
+ * normally called during the final step of packet preparation
+ * before enqueuement (in aim_tx_enqueue()).
+ *
+ */
+flap_seqnum_t aim_get_next_txseqnum(aim_conn_t *conn)
+{
+ flap_seqnum_t ret;
+
+ ret = ++conn->seqnum;
+
+ return ret;
+}
+
+static int aim_send(int fd, const void *buf, size_t count)
+{
+ int left, cur;
+
+ for (cur = 0, left = count; left; ) {
+ int ret;
+
+ ret = send(fd, ((unsigned char *)buf)+cur, left, 0);
+ if (ret == -1)
+ return -1;
+ else if (ret == 0)
+ return cur;
+
+ cur += ret;
+ left -= ret;
+ }
+
+ return cur;
+}
+
+static int aim_bstream_send(aim_bstream_t *bs, aim_conn_t *conn, size_t count)
+{
+ int wrote = 0;
+ if (!bs || !conn || (count < 0))
+ return -EINVAL;
+
+ if (count > aim_bstream_empty(bs))
+ count = aim_bstream_empty(bs); /* truncate to remaining space */
+
+ if (count) {
+ if (count - wrote) {
+ wrote = wrote + aim_send(conn->fd, bs->data + bs->offset + wrote, count - wrote);
+ }
+
+ }
+
+ bs->offset += wrote;
+
+ return wrote;
+}
+
+static int sendframe_flap(aim_session_t *sess, aim_frame_t *fr)
+{
+ aim_bstream_t obs;
+ guint8 *obs_raw;
+ int payloadlen, err = 0, obslen;
+
+ payloadlen = aim_bstream_curpos(&fr->data);
+
+ if (!(obs_raw = g_malloc(6 + payloadlen)))
+ return -ENOMEM;
+
+ aim_bstream_init(&obs, obs_raw, 6 + payloadlen);
+
+ /* FLAP header */
+ aimbs_put8(&obs, 0x2a);
+ aimbs_put8(&obs, fr->hdr.flap.type);
+ aimbs_put16(&obs, fr->hdr.flap.seqnum);
+ aimbs_put16(&obs, payloadlen);
+
+ /* payload */
+ aim_bstream_rewind(&fr->data);
+ aimbs_putbs(&obs, &fr->data, payloadlen);
+
+ obslen = aim_bstream_curpos(&obs);
+ aim_bstream_rewind(&obs);
+ if (aim_bstream_send(&obs, fr->conn, obslen) != obslen)
+ err = -errno;
+
+ g_free(obs_raw); /* XXX aim_bstream_free */
+
+ fr->handled = 1;
+ fr->conn->lastactivity = time(NULL);
+
+ return err;
+}
+
+int aim_tx_sendframe(aim_session_t *sess, aim_frame_t *fr)
+{
+ if (fr->hdrtype == AIM_FRAMETYPE_FLAP)
+ return sendframe_flap(sess, fr);
+ return -1;
+}
+
+int aim_tx_flushqueue(aim_session_t *sess)
+{
+ aim_frame_t *cur;
+
+ for (cur = sess->queue_outgoing; cur; cur = cur->next) {
+
+ if (cur->handled)
+ continue; /* already been sent */
+
+ if (cur->conn && (cur->conn->status & AIM_CONN_STATUS_INPROGRESS))
+ continue;
+
+ /*
+ * And now for the meager attempt to force transmit
+ * latency and avoid missed messages.
+ */
+ if ((cur->conn->lastactivity + cur->conn->forcedlatency) >= time(NULL)) {
+ /*
+ * XXX should be a break! we dont want to block the
+ * upper layers
+ *
+ * XXX or better, just do this right.
+ *
+ */
+ sleep((cur->conn->lastactivity + cur->conn->forcedlatency) - time(NULL));
+ }
+
+ /* XXX this should call the custom "queuing" function!! */
+ aim_tx_sendframe(sess, cur);
+ }
+
+ /* purge sent commands from queue */
+ aim_tx_purgequeue(sess);
+
+ return 0;
+}
+
+/*
+ * aim_tx_purgequeue()
+ *
+ * This is responsable for removing sent commands from the transmit
+ * queue. This is not a required operation, but it of course helps
+ * reduce memory footprint at run time!
+ *
+ */
+void aim_tx_purgequeue(aim_session_t *sess)
+{
+ aim_frame_t *cur, **prev;
+
+ for (prev = &sess->queue_outgoing; (cur = *prev); ) {
+
+ if (cur->handled) {
+ *prev = cur->next;
+
+ aim_frame_destroy(cur);
+
+ } else
+ prev = &cur->next;
+ }
+
+ return;
+}
+
+/**
+ * aim_tx_cleanqueue - get rid of packets waiting for tx on a dying conn
+ * @sess: session
+ * @conn: connection that's dying
+ *
+ * for now this simply marks all packets as sent and lets them
+ * disappear without warning.
+ *
+ */
+void aim_tx_cleanqueue(aim_session_t *sess, aim_conn_t *conn)
+{
+ aim_frame_t *cur;
+
+ for (cur = sess->queue_outgoing; cur; cur = cur->next) {
+ if (cur->conn == conn)
+ cur->handled = 1;
+ }
+
+ return;
+}
+
+
diff --git a/protocols/purple/Makefile b/protocols/purple/Makefile
new file mode 100644
index 00000000..5a096777
--- /dev/null
+++ b/protocols/purple/Makefile
@@ -0,0 +1,47 @@
+###########################
+## Makefile for BitlBee ##
+## ##
+## Copyright 2002 Lintux ##
+###########################
+
+### DEFINITIONS
+
+-include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/purple/
+endif
+
+# [SH] Program variables
+objects = ft.o purple.o
+
+CFLAGS += -Wall $(PURPLE_CFLAGS)
+LFLAGS += -r
+
+# [SH] Phony targets
+all: purple_mod.o
+check: all
+lcov: check
+gcov:
+ gcov *.c
+
+.PHONY: all clean distclean
+
+clean:
+ rm -f *.o core
+
+distclean: clean
+ rm -rf .depend
+
+### MAIN PROGRAM
+
+$(objects): ../../Makefile.settings Makefile
+
+$(objects): %.o: $(SRCDIR)%.c
+ @echo '*' Compiling $<
+ @$(CC) -c $(CFLAGS) $< -o $@
+
+purple_mod.o: $(objects)
+ @echo '*' Linking purple_mod.o
+ @$(LD) $(LFLAGS) $(objects) -o purple_mod.o
+
+-include .depend/*.d
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..55678f92
--- /dev/null
+++ b/protocols/purple/purple.c
@@ -0,0 +1,1342 @@
+/***************************************************************************\
+* *
+* 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;
+ static gboolean dir_fixed = FALSE;
+
+ /* Layer violation coming up: Making an exception for libpurple here.
+ Dig in the IRC state a bit to get a username. Ideally we should
+ check if s/he identified but this info doesn't seem *that* important.
+ It's just that fecking libpurple can't *not* store this shit.
+
+ Remember that libpurple is not really meant to be used on public
+ servers anyway! */
+ if( !dir_fixed )
+ {
+ irc_t *irc = acc->bee->ui_data;
+ char *dir;
+
+ dir = g_strdup_printf( "%s/purple/%s", global.conf->configdir, irc->user->nick );
+ purple_util_set_user_dir( dir );
+ g_free( dir );
+
+ purple_blist_load();
+ purple_prefs_load();
+ dir_fixed = TRUE;
+ }
+
+ help = g_string_new( "" );
+ g_string_printf( help, "BitlBee libpurple module %s (%s).\n\nSupported settings:",
+ (char*) acc->prpl->name, prpl->info->name );
+
+ /* 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;
+ }
+
+ if( strcmp( prpl->info->name, "Gadu-Gadu" ) == 0 )
+ s = set_add( &acc->set, "gg_sync_contacts", "true", set_eval_bool, acc );
+
+ /* Go through all away states to figure out if away/status messages
+ are possible. */
+ pa = purple_account_new( acc->user, (char*) acc->prpl->data );
+ for( st = purple_account_get_status_types( pa ); st; st = st->next )
+ {
+ PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data );
+
+ if( prim == PURPLE_STATUS_AVAILABLE )
+ {
+ if( purple_status_type_get_attr( st->data, "message" ) )
+ acc->flags |= ACC_FLAG_STATUS_MESSAGE;
+ }
+ else if( prim != PURPLE_STATUS_OFFLINE )
+ {
+ if( purple_status_type_get_attr( st->data, "message" ) )
+ acc->flags |= ACC_FLAG_AWAY_MESSAGE;
+ }
+ }
+ purple_accounts_remove( pa );
+}
+
+static void purple_sync_settings( account_t *acc, PurpleAccount *pa )
+{
+ PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
+ PurplePluginProtocolInfo *pi = prpl->info->extra_info;
+ GList *i;
+
+ for( i = pi->protocol_options; i; i = i->next )
+ {
+ PurpleAccountOption *o = i->data;
+ const char *name;
+ set_t *s;
+
+ name = purple_account_option_get_setting( o );
+ s = set_find( &acc->set, name );
+ if( s->value == NULL )
+ continue;
+
+ switch( purple_account_option_get_type( o ) )
+ {
+ case PURPLE_PREF_STRING:
+ case PURPLE_PREF_STRING_LIST:
+ purple_account_set_string( pa, name, set_getstr( &acc->set, name ) );
+ break;
+
+ case PURPLE_PREF_INT:
+ purple_account_set_int( pa, name, set_getint( &acc->set, name ) );
+ break;
+
+ case PURPLE_PREF_BOOLEAN:
+ purple_account_set_bool( pa, name, set_getbool( &acc->set, name ) );
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if( pi->options & OPT_PROTO_MAIL_CHECK )
+ purple_account_set_check_mail( pa, set_getbool( &acc->set, "mail_notifications" ) );
+}
+
+static void purple_login( account_t *acc )
+{
+ struct im_connection *ic = imcb_new( acc );
+ PurpleAccount *pa;
+
+ if( ( local_bee != NULL && local_bee != acc->bee ) ||
+ ( global.conf->runmode == RUNMODE_DAEMON && !getenv( "BITLBEE_DEBUG" ) ) )
+ {
+ imcb_error( ic, "Daemon mode detected. Do *not* try to use libpurple in daemon mode! "
+ "Please use inetd or ForkDaemon mode instead." );
+ imc_logout( ic, FALSE );
+ return;
+ }
+ local_bee = acc->bee;
+
+ /* For now this is needed in the _connected() handlers if using
+ GLib event handling, to make sure we're not handling events
+ on dead connections. */
+ purple_connections = g_slist_prepend( purple_connections, ic );
+
+ ic->proto_data = pa = purple_account_new( acc->user, (char*) acc->prpl->data );
+ purple_account_set_password( pa, acc->pass );
+ purple_sync_settings( acc, pa );
+
+ purple_account_set_enabled( pa, "BitlBee", TRUE );
+}
+
+static void purple_logout( struct im_connection *ic )
+{
+ PurpleAccount *pa = ic->proto_data;
+
+ purple_account_set_enabled( pa, "BitlBee", FALSE );
+ purple_connections = g_slist_remove( purple_connections, ic );
+ purple_accounts_remove( pa );
+}
+
+static int purple_buddy_msg( struct im_connection *ic, char *who, char *message, int flags )
+{
+ PurpleConversation *conv;
+
+ if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM,
+ who, ic->proto_data ) ) == NULL )
+ {
+ conv = purple_conversation_new( PURPLE_CONV_TYPE_IM,
+ ic->proto_data, who );
+ }
+
+ purple_conv_im_send( purple_conversation_get_im_data( conv ), message );
+
+ return 1;
+}
+
+static GList *purple_away_states( struct im_connection *ic )
+{
+ PurpleAccount *pa = ic->proto_data;
+ GList *st, *ret = NULL;
+
+ for( st = purple_account_get_status_types( pa ); st; st = st->next )
+ {
+ PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data );
+ if( prim != PURPLE_STATUS_AVAILABLE && prim != PURPLE_STATUS_OFFLINE )
+ ret = g_list_append( ret, (void*) purple_status_type_get_name( st->data ) );
+ }
+
+ return ret;
+}
+
+static void purple_set_away( struct im_connection *ic, char *state_txt, char *message )
+{
+ PurpleAccount *pa = ic->proto_data;
+ GList *status_types = purple_account_get_status_types( pa ), *st;
+ PurpleStatusType *pst = NULL;
+ GList *args = NULL;
+
+ for( st = status_types; st; st = st->next )
+ {
+ pst = st->data;
+
+ if( state_txt == NULL &&
+ purple_status_type_get_primitive( pst ) == PURPLE_STATUS_AVAILABLE )
+ break;
+
+ if( state_txt != NULL &&
+ g_strcasecmp( state_txt, purple_status_type_get_name( pst ) ) == 0 )
+ break;
+ }
+
+ if( message && purple_status_type_get_attr( pst, "message" ) )
+ {
+ args = g_list_append( args, "message" );
+ args = g_list_append( args, message );
+ }
+
+ purple_account_set_status_list( pa, st ? purple_status_type_get_id( pst ) : "away",
+ TRUE, args );
+
+ g_list_free( args );
+}
+
+static char *set_eval_display_name( set_t *set, char *value )
+{
+ account_t *acc = set->data;
+ struct im_connection *ic = acc->ic;
+
+ if( ic )
+ imcb_log( ic, "Changing display_name not currently supported with libpurple!" );
+
+ return NULL;
+}
+
+/* Bad bad gadu-gadu, not saving buddy list by itself */
+static void purple_gg_buddylist_export( PurpleConnection *gc )
+{
+ struct im_connection *ic = purple_ic_by_gc( gc );
+
+ if( set_getstr( &ic->acc->set, "gg_sync_contacts" ) )
+ {
+ GList *actions = gc->prpl->info->actions( gc->prpl, gc );
+ GList *p;
+ for( p = g_list_first(actions); p; p = p->next )
+ {
+ if( ((PurplePluginAction*)p->data) &&
+ purple_menu_cmp( ((PurplePluginAction*)p->data)->label, "Upload buddylist to Server" ) == 0)
+ {
+ PurplePluginAction action;
+ action.plugin = gc->prpl;
+ action.context = gc;
+ action.user_data = NULL;
+ ((PurplePluginAction*)p->data)->callback(&action);
+ break;
+ }
+ }
+ g_list_free( actions );
+ }
+}
+
+static void purple_gg_buddylist_import( PurpleConnection *gc )
+{
+ struct im_connection *ic = purple_ic_by_gc( gc );
+
+ if( set_getstr( &ic->acc->set, "gg_sync_contacts" ) )
+ {
+ GList *actions = gc->prpl->info->actions( gc->prpl, gc );
+ GList *p;
+ for( p = g_list_first(actions); p; p = p->next )
+ {
+ if( ((PurplePluginAction*)p->data) &&
+ purple_menu_cmp( ((PurplePluginAction*)p->data)->label, "Download buddylist from Server" ) == 0 )
+ {
+ PurplePluginAction action;
+ action.plugin = gc->prpl;
+ action.context = gc;
+ action.user_data = NULL;
+ ((PurplePluginAction*)p->data)->callback(&action);
+ break;
+ }
+ }
+ g_list_free( actions );
+ }
+}
+
+static void purple_add_buddy( struct im_connection *ic, char *who, char *group )
+{
+ PurpleBuddy *pb;
+ PurpleGroup *pg = NULL;
+
+ if( group && !( pg = purple_find_group( group ) ) )
+ {
+ pg = purple_group_new( group );
+ purple_blist_add_group( pg, NULL );
+ }
+
+ pb = purple_buddy_new( (PurpleAccount*) ic->proto_data, who, NULL );
+ purple_blist_add_buddy( pb, NULL, pg, NULL );
+ purple_account_add_buddy( (PurpleAccount*) ic->proto_data, pb );
+
+ purple_gg_buddylist_export( ((PurpleAccount*)ic->proto_data)->gc );
+}
+
+static void purple_remove_buddy( struct im_connection *ic, char *who, char *group )
+{
+ PurpleBuddy *pb;
+
+ pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who );
+ if( pb != NULL )
+ {
+ PurpleGroup *group;
+
+ group = purple_buddy_get_group( pb );
+ purple_account_remove_buddy( (PurpleAccount*) ic->proto_data, pb, group );
+
+ purple_blist_remove_buddy( pb );
+ }
+
+ purple_gg_buddylist_export( ((PurpleAccount*)ic->proto_data)->gc );
+}
+
+static void purple_add_permit( struct im_connection *ic, char *who )
+{
+ PurpleAccount *pa = ic->proto_data;
+
+ purple_privacy_permit_add( pa, who, FALSE );
+}
+
+static void purple_add_deny( struct im_connection *ic, char *who )
+{
+ PurpleAccount *pa = ic->proto_data;
+
+ purple_privacy_deny_add( pa, who, FALSE );
+}
+
+static void purple_rem_permit( struct im_connection *ic, char *who )
+{
+ PurpleAccount *pa = ic->proto_data;
+
+ purple_privacy_permit_remove( pa, who, FALSE );
+}
+
+static void purple_rem_deny( struct im_connection *ic, char *who )
+{
+ PurpleAccount *pa = ic->proto_data;
+
+ purple_privacy_deny_remove( pa, who, FALSE );
+}
+
+static void purple_get_info( struct im_connection *ic, char *who )
+{
+ serv_get_info( purple_account_get_connection( ic->proto_data ), who );
+}
+
+static void purple_keepalive( struct im_connection *ic )
+{
+}
+
+static int purple_send_typing( struct im_connection *ic, char *who, int flags )
+{
+ PurpleTypingState state = PURPLE_NOT_TYPING;
+ PurpleAccount *pa = ic->proto_data;
+
+ if( flags & OPT_TYPING )
+ state = PURPLE_TYPING;
+ else if( flags & OPT_THINKING )
+ state = PURPLE_TYPED;
+
+ serv_send_typing( purple_account_get_connection( pa ), who, state );
+
+ return 1;
+}
+
+static void purple_chat_msg( struct groupchat *gc, char *message, int flags )
+{
+ PurpleConversation *pc = gc->data;
+
+ purple_conv_chat_send( purple_conversation_get_chat_data( pc ), message );
+}
+
+struct groupchat *purple_chat_with( struct im_connection *ic, char *who )
+{
+ /* No, "of course" this won't work this way. Or in fact, it almost
+ does, but it only lets you send msgs to it, you won't receive
+ any. Instead, we have to click the virtual menu item.
+ PurpleAccount *pa = ic->proto_data;
+ PurpleConversation *pc;
+ PurpleConvChat *pcc;
+ struct groupchat *gc;
+
+ gc = imcb_chat_new( ic, "BitlBee-libpurple groupchat" );
+ gc->data = pc = purple_conversation_new( PURPLE_CONV_TYPE_CHAT, pa, "BitlBee-libpurple groupchat" );
+ pc->ui_data = gc;
+
+ pcc = PURPLE_CONV_CHAT( pc );
+ purple_conv_chat_add_user( pcc, ic->acc->user, "", 0, TRUE );
+ purple_conv_chat_invite_user( pcc, who, "Please join my chat", FALSE );
+ //purple_conv_chat_add_user( pcc, who, "", 0, TRUE );
+ */
+
+ /* There went my nice afternoon. :-( */
+
+ PurpleAccount *pa = ic->proto_data;
+ PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
+ PurplePluginProtocolInfo *pi = prpl->info->extra_info;
+ PurpleBuddy *pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who );
+ PurpleMenuAction *mi;
+ GList *menu;
+ void (*callback)(PurpleBlistNode *, gpointer); /* FFFFFFFFFFFFFUUUUUUUUUUUUUU */
+
+ if( !pb || !pi || !pi->blist_node_menu )
+ return NULL;
+
+ menu = pi->blist_node_menu( &pb->node );
+ while( menu )
+ {
+ mi = menu->data;
+ if( purple_menu_cmp( mi->label, "initiate chat" ) ||
+ purple_menu_cmp( mi->label, "initiate conference" ) )
+ break;
+ menu = menu->next;
+ }
+
+ if( menu == NULL )
+ return NULL;
+
+ /* Call the fucker. */
+ callback = (void*) mi->callback;
+ callback( &pb->node, menu->data );
+
+ return NULL;
+}
+
+void purple_chat_invite( struct groupchat *gc, char *who, char *message )
+{
+ PurpleConversation *pc = gc->data;
+ PurpleConvChat *pcc = PURPLE_CONV_CHAT( pc );
+
+ serv_chat_invite( purple_account_get_connection( gc->ic->proto_data ),
+ purple_conv_chat_get_id( pcc ),
+ message && *message ? message : "Please join my chat",
+ who );
+}
+
+void purple_chat_leave( struct groupchat *gc )
+{
+ PurpleConversation *pc = gc->data;
+
+ purple_conversation_destroy( pc );
+}
+
+struct groupchat *purple_chat_join( struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets )
+{
+ PurpleAccount *pa = ic->proto_data;
+ PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
+ PurplePluginProtocolInfo *pi = prpl->info->extra_info;
+ GHashTable *chat_hash;
+ PurpleConversation *conv;
+ GList *info, *l;
+
+ if( !pi->chat_info || !pi->chat_info_defaults ||
+ !( info = pi->chat_info( purple_account_get_connection( pa ) ) ) )
+ {
+ imcb_error( ic, "Joining chatrooms not supported by this protocol" );
+ return NULL;
+ }
+
+ if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_CHAT, room, pa ) ) )
+ purple_conversation_destroy( conv );
+
+ chat_hash = pi->chat_info_defaults( purple_account_get_connection( pa ), room );
+
+ for( l = info; l; l = l->next )
+ {
+ struct proto_chat_entry *pce = l->data;
+
+ if( strcmp( pce->identifier, "handle" ) == 0 )
+ g_hash_table_replace( chat_hash, "handle", g_strdup( nick ) );
+ else if( strcmp( pce->identifier, "password" ) == 0 )
+ g_hash_table_replace( chat_hash, "password", g_strdup( password ) );
+ else if( strcmp( pce->identifier, "passwd" ) == 0 )
+ g_hash_table_replace( chat_hash, "passwd", g_strdup( password ) );
+ }
+
+ serv_join_chat( purple_account_get_connection( pa ), chat_hash );
+
+ return NULL;
+}
+
+void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle );
+
+static void purple_ui_init();
+
+GHashTable *prplcb_ui_info()
+{
+ static GHashTable *ret;
+
+ if( ret == NULL )
+ {
+ ret = g_hash_table_new(g_str_hash, g_str_equal);
+ g_hash_table_insert( ret, "name", "BitlBee" );
+ g_hash_table_insert( ret, "version", BITLBEE_VERSION );
+ }
+
+ return ret;
+}
+
+static PurpleCoreUiOps bee_core_uiops =
+{
+ NULL,
+ NULL,
+ purple_ui_init,
+ NULL,
+ prplcb_ui_info,
+};
+
+static void prplcb_conn_progress( PurpleConnection *gc, const char *text, size_t step, size_t step_count )
+{
+ struct im_connection *ic = purple_ic_by_gc( gc );
+
+ imcb_log( ic, "%s", text );
+}
+
+static void prplcb_conn_connected( PurpleConnection *gc )
+{
+ struct im_connection *ic = purple_ic_by_gc( gc );
+ const char *dn;
+ set_t *s;
+
+ imcb_connected( ic );
+
+ if( ( dn = purple_connection_get_display_name( gc ) ) &&
+ ( s = set_find( &ic->acc->set, "display_name" ) ) )
+ {
+ g_free( s->value );
+ s->value = g_strdup( dn );
+ }
+
+ // user list needs to be requested for Gadu-Gadu
+ purple_gg_buddylist_import( gc );
+
+ if( gc->flags & PURPLE_CONNECTION_HTML )
+ ic->flags |= OPT_DOES_HTML;
+}
+
+static void prplcb_conn_disconnected( PurpleConnection *gc )
+{
+ struct im_connection *ic = purple_ic_by_gc( gc );
+
+ if( ic != NULL )
+ {
+ imc_logout( ic, !gc->wants_to_die );
+ }
+}
+
+static void prplcb_conn_notice( PurpleConnection *gc, const char *text )
+{
+ struct im_connection *ic = purple_ic_by_gc( gc );
+
+ if( ic != NULL )
+ imcb_log( ic, "%s", text );
+}
+
+static void prplcb_conn_report_disconnect_reason( PurpleConnection *gc, PurpleConnectionError reason, const char *text )
+{
+ struct im_connection *ic = purple_ic_by_gc( gc );
+
+ /* PURPLE_CONNECTION_ERROR_NAME_IN_USE means concurrent login,
+ should probably handle that. */
+ if( ic != NULL )
+ imcb_error( ic, "%s", text );
+}
+
+static PurpleConnectionUiOps bee_conn_uiops =
+{
+ prplcb_conn_progress,
+ prplcb_conn_connected,
+ prplcb_conn_disconnected,
+ prplcb_conn_notice,
+ NULL,
+ NULL,
+ NULL,
+ prplcb_conn_report_disconnect_reason,
+};
+
+static void prplcb_blist_update( PurpleBuddyList *list, PurpleBlistNode *node )
+{
+ if( node->type == PURPLE_BLIST_BUDDY_NODE )
+ {
+ PurpleBuddy *bud = (PurpleBuddy*) node;
+ PurpleGroup *group = purple_buddy_get_group( bud );
+ struct im_connection *ic = purple_ic_by_pa( bud->account );
+ PurpleStatus *as;
+ int flags = 0;
+
+ if( ic == NULL )
+ return;
+
+ if( bud->server_alias )
+ imcb_rename_buddy( ic, bud->name, bud->server_alias );
+
+ if( group )
+ imcb_add_buddy( ic, bud->name, purple_group_get_name( group ) );
+
+ flags |= purple_presence_is_online( bud->presence ) ? OPT_LOGGED_IN : 0;
+ flags |= purple_presence_is_available( bud->presence ) ? 0 : OPT_AWAY;
+
+ as = purple_presence_get_active_status( bud->presence );
+
+ imcb_buddy_status( ic, bud->name, flags, purple_status_get_name( as ),
+ purple_status_get_attr_string( as, "message" ) );
+
+ imcb_buddy_times( ic, bud->name,
+ purple_presence_get_login_time( bud->presence ),
+ purple_presence_get_idle_time( bud->presence ) );
+ }
+}
+
+static void prplcb_blist_new( PurpleBlistNode *node )
+{
+ if( node->type == PURPLE_BLIST_BUDDY_NODE )
+ {
+ PurpleBuddy *bud = (PurpleBuddy*) node;
+ struct im_connection *ic = purple_ic_by_pa( bud->account );
+
+ if( ic == NULL )
+ return;
+
+ imcb_add_buddy( ic, bud->name, NULL );
+
+ prplcb_blist_update( NULL, node );
+ }
+}
+
+static void prplcb_blist_remove( PurpleBuddyList *list, PurpleBlistNode *node )
+{
+/*
+ PurpleBuddy *bud = (PurpleBuddy*) node;
+
+ if( node->type == PURPLE_BLIST_BUDDY_NODE )
+ {
+ struct im_connection *ic = purple_ic_by_pa( bud->account );
+
+ if( ic == NULL )
+ return;
+
+ imcb_remove_buddy( ic, bud->name, NULL );
+ }
+*/
+}
+
+static PurpleBlistUiOps bee_blist_uiops =
+{
+ NULL,
+ prplcb_blist_new,
+ NULL,
+ prplcb_blist_update,
+ prplcb_blist_remove,
+};
+
+void prplcb_conv_new( PurpleConversation *conv )
+{
+ if( conv->type == PURPLE_CONV_TYPE_CHAT )
+ {
+ struct im_connection *ic = purple_ic_by_pa( conv->account );
+ struct groupchat *gc;
+
+ gc = imcb_chat_new( ic, conv->name );
+ conv->ui_data = gc;
+ gc->data = conv;
+
+ /* libpurple brokenness: Whatever. Show that we join right away,
+ there's no clear "This is you!" signaling in _add_users so
+ don't even try. */
+ imcb_chat_add_buddy( gc, gc->ic->acc->user );
+ }
+}
+
+void prplcb_conv_free( PurpleConversation *conv )
+{
+ struct groupchat *gc = conv->ui_data;
+
+ imcb_chat_free( gc );
+}
+
+void prplcb_conv_add_users( PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals )
+{
+ struct groupchat *gc = conv->ui_data;
+ GList *b;
+
+ for( b = cbuddies; b; b = b->next )
+ {
+ PurpleConvChatBuddy *pcb = b->data;
+
+ imcb_chat_add_buddy( gc, pcb->name );
+ }
+}
+
+void prplcb_conv_del_users( PurpleConversation *conv, GList *cbuddies )
+{
+ struct groupchat *gc = conv->ui_data;
+ GList *b;
+
+ for( b = cbuddies; b; b = b->next )
+ imcb_chat_remove_buddy( gc, b->data, "" );
+}
+
+void prplcb_conv_chat_msg( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime )
+{
+ struct groupchat *gc = conv->ui_data;
+ PurpleBuddy *buddy;
+
+ /* ..._SEND means it's an outgoing message, no need to echo those. */
+ if( flags & PURPLE_MESSAGE_SEND )
+ return;
+
+ buddy = purple_find_buddy( conv->account, who );
+ if( buddy != NULL )
+ who = purple_buddy_get_name( buddy );
+
+ imcb_chat_msg( gc, who, (char*) message, 0, mtime );
+}
+
+static void prplcb_conv_im( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime )
+{
+ struct im_connection *ic = purple_ic_by_pa( conv->account );
+ PurpleBuddy *buddy;
+
+ /* ..._SEND means it's an outgoing message, no need to echo those. */
+ if( flags & PURPLE_MESSAGE_SEND )
+ return;
+
+ buddy = purple_find_buddy( conv->account, who );
+ if( buddy != NULL )
+ who = purple_buddy_get_name( buddy );
+
+ imcb_buddy_msg( ic, (char*) who, (char*) message, 0, mtime );
+}
+
+/* No, this is not a ui_op but a signal. */
+static void prplcb_buddy_typing( PurpleAccount *account, const char *who, gpointer null )
+{
+ PurpleConversation *conv;
+ PurpleConvIm *im;
+ int state;
+
+ if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM, who, account ) ) == NULL )
+ return;
+
+ im = PURPLE_CONV_IM(conv);
+ switch( purple_conv_im_get_typing_state( im ) )
+ {
+ case PURPLE_TYPING:
+ state = OPT_TYPING;
+ break;
+ case PURPLE_TYPED:
+ state = OPT_THINKING;
+ break;
+ default:
+ state = 0;
+ }
+
+ imcb_buddy_typing( purple_ic_by_pa( account ), who, state );
+}
+
+static PurpleConversationUiOps bee_conv_uiops =
+{
+ prplcb_conv_new, /* create_conversation */
+ prplcb_conv_free, /* destroy_conversation */
+ prplcb_conv_chat_msg, /* write_chat */
+ prplcb_conv_im, /* write_im */
+ NULL, /* write_conv */
+ prplcb_conv_add_users, /* chat_add_users */
+ NULL, /* chat_rename_user */
+ prplcb_conv_del_users, /* chat_remove_users */
+ NULL, /* chat_update_user */
+ NULL, /* present */
+ NULL, /* has_focus */
+ NULL, /* custom_smiley_add */
+ NULL, /* custom_smiley_write */
+ NULL, /* custom_smiley_close */
+ NULL, /* send_confirm */
+};
+
+struct prplcb_request_action_data
+{
+ void *user_data, *bee_data;
+ PurpleRequestActionCb yes, no;
+ int yes_i, no_i;
+};
+
+static void prplcb_request_action_yes( void *data )
+{
+ struct prplcb_request_action_data *pqad = data;
+
+ if( pqad->yes )
+ pqad->yes( pqad->user_data, pqad->yes_i );
+ g_free( pqad );
+}
+
+static void prplcb_request_action_no( void *data )
+{
+ struct prplcb_request_action_data *pqad = data;
+
+ if( pqad->no )
+ pqad->no( pqad->user_data, pqad->no_i );
+ g_free( pqad );
+}
+
+static void *prplcb_request_action( const char *title, const char *primary, const char *secondary,
+ int default_action, PurpleAccount *account, const char *who,
+ PurpleConversation *conv, void *user_data, size_t action_count,
+ va_list actions )
+{
+ struct prplcb_request_action_data *pqad;
+ int i;
+ char *q;
+
+ pqad = g_new0( struct prplcb_request_action_data, 1 );
+
+ for( i = 0; i < action_count; i ++ )
+ {
+ char *caption;
+ void *fn;
+
+ caption = va_arg( actions, char* );
+ fn = va_arg( actions, void* );
+
+ if( strstr( caption, "Accept" ) || strstr( caption, "OK" ) )
+ {
+ pqad->yes = fn;
+ pqad->yes_i = i;
+ }
+ else if( strstr( caption, "Reject" ) || strstr( caption, "Cancel" ) )
+ {
+ pqad->no = fn;
+ pqad->no_i = i;
+ }
+ }
+
+ pqad->user_data = user_data;
+
+ /* TODO: IRC stuff here :-( */
+ q = g_strdup_printf( "Request: %s\n\n%s\n\n%s", title, primary, secondary );
+ pqad->bee_data = query_add( local_bee->ui_data, purple_ic_by_pa( account ), q,
+ prplcb_request_action_yes, prplcb_request_action_no, g_free, pqad );
+
+ g_free( q );
+
+ return pqad;
+}
+
+/*
+static void prplcb_request_test()
+{
+ fprintf( stderr, "bla\n" );
+}
+*/
+
+static PurpleRequestUiOps bee_request_uiops =
+{
+ NULL,
+ NULL,
+ prplcb_request_action,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+static void prplcb_privacy_permit_added( PurpleAccount *account, const char *name )
+{
+ struct im_connection *ic = purple_ic_by_pa( account );
+
+ if( !g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) )
+ ic->permit = g_slist_prepend( ic->permit, g_strdup( name ) );
+}
+
+static void prplcb_privacy_permit_removed( PurpleAccount *account, const char *name )
+{
+ struct im_connection *ic = purple_ic_by_pa( account );
+ void *n;
+
+ n = g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp );
+ ic->permit = g_slist_remove( ic->permit, n );
+}
+
+static void prplcb_privacy_deny_added( PurpleAccount *account, const char *name )
+{
+ struct im_connection *ic = purple_ic_by_pa( account );
+
+ if( !g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) )
+ ic->deny = g_slist_prepend( ic->deny, g_strdup( name ) );
+}
+
+static void prplcb_privacy_deny_removed( PurpleAccount *account, const char *name )
+{
+ struct im_connection *ic = purple_ic_by_pa( account );
+ void *n;
+
+ n = g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp );
+ ic->deny = g_slist_remove( ic->deny, n );
+}
+
+static PurplePrivacyUiOps bee_privacy_uiops =
+{
+ prplcb_privacy_permit_added,
+ prplcb_privacy_permit_removed,
+ prplcb_privacy_deny_added,
+ prplcb_privacy_deny_removed,
+};
+
+static void prplcb_debug_print( PurpleDebugLevel level, const char *category, const char *arg_s )
+{
+ fprintf( stderr, "DEBUG %s: %s", category, arg_s );
+}
+
+static PurpleDebugUiOps bee_debug_uiops =
+{
+ prplcb_debug_print,
+};
+
+static guint prplcb_ev_timeout_add( guint interval, GSourceFunc func, gpointer udata )
+{
+ return b_timeout_add( interval, (b_event_handler) func, udata );
+}
+
+static guint prplcb_ev_input_add( int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata )
+{
+ return b_input_add( fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata );
+}
+
+static gboolean prplcb_ev_remove( guint id )
+{
+ b_event_remove( (gint) id );
+ return TRUE;
+}
+
+static PurpleEventLoopUiOps glib_eventloops =
+{
+ prplcb_ev_timeout_add,
+ prplcb_ev_remove,
+ prplcb_ev_input_add,
+ prplcb_ev_remove,
+};
+
+static void *prplcb_notify_email( PurpleConnection *gc, const char *subject, const char *from,
+ const char *to, const char *url )
+{
+ struct im_connection *ic = purple_ic_by_gc( gc );
+
+ imcb_log( ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url );
+
+ return NULL;
+}
+
+static void *prplcb_notify_userinfo( PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info )
+{
+ struct im_connection *ic = purple_ic_by_gc( gc );
+ GString *info = g_string_new( "" );
+ GList *l = purple_notify_user_info_get_entries( user_info );
+ char *key;
+ const char *value;
+ int n;
+
+ while( l )
+ {
+ PurpleNotifyUserInfoEntry *e = l->data;
+
+ switch( purple_notify_user_info_entry_get_type( e ) )
+ {
+ case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR:
+ case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER:
+ key = g_strdup( purple_notify_user_info_entry_get_label( e ) );
+ value = purple_notify_user_info_entry_get_value( e );
+
+ if( key )
+ {
+ strip_html( key );
+ g_string_append_printf( info, "%s: ", key );
+
+ if( value )
+ {
+ n = strlen( value ) - 1;
+ while( isspace( value[n] ) )
+ n --;
+ g_string_append_len( info, value, n + 1 );
+ }
+ g_string_append_c( info, '\n' );
+ g_free( key );
+ }
+
+ break;
+ case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK:
+ g_string_append( info, "------------------------\n" );
+ break;
+ }
+
+ l = l->next;
+ }
+
+ imcb_log( ic, "User %s info:\n%s", who, info->str );
+ g_string_free( info, TRUE );
+
+ return NULL;
+}
+
+static PurpleNotifyUiOps bee_notify_uiops =
+{
+ NULL,
+ prplcb_notify_email,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ prplcb_notify_userinfo,
+};
+
+static void *prplcb_account_request_authorize( PurpleAccount *account, const char *remote_user,
+ const char *id, const char *alias, const char *message, gboolean on_list,
+ PurpleAccountRequestAuthorizationCb authorize_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data )
+{
+ struct im_connection *ic = purple_ic_by_pa( account );
+ char *q;
+
+ if( alias )
+ q = g_strdup_printf( "%s (%s) wants to add you to his/her contact "
+ "list. (%s)", alias, remote_user, message );
+ else
+ q = g_strdup_printf( "%s wants to add you to his/her contact "
+ "list. (%s)", remote_user, message );
+
+ imcb_ask_with_free( ic, q, user_data, authorize_cb, deny_cb, NULL );
+ g_free( q );
+
+ return NULL;
+}
+
+static PurpleAccountUiOps bee_account_uiops =
+{
+ NULL,
+ NULL,
+ NULL,
+ prplcb_account_request_authorize,
+ NULL,
+};
+
+extern PurpleXferUiOps bee_xfer_uiops;
+
+static void purple_ui_init()
+{
+ purple_connections_set_ui_ops( &bee_conn_uiops );
+ purple_blist_set_ui_ops( &bee_blist_uiops );
+ purple_conversations_set_ui_ops( &bee_conv_uiops );
+ purple_request_set_ui_ops( &bee_request_uiops );
+ purple_privacy_set_ui_ops( &bee_privacy_uiops );
+ purple_notify_set_ui_ops( &bee_notify_uiops );
+ purple_accounts_set_ui_ops( &bee_account_uiops );
+ purple_xfers_set_ui_ops( &bee_xfer_uiops );
+
+ if( getenv( "BITLBEE_DEBUG" ) )
+ purple_debug_set_ui_ops( &bee_debug_uiops );
+}
+
+void purple_initmodule()
+{
+ struct prpl funcs;
+ GList *prots;
+ GString *help;
+ char *dir;
+
+ if( B_EV_IO_READ != PURPLE_INPUT_READ ||
+ B_EV_IO_WRITE != PURPLE_INPUT_WRITE )
+ {
+ /* FIXME FIXME FIXME FIXME FIXME :-) */
+ exit( 1 );
+ }
+
+ dir = g_strdup_printf( "%s/purple", global.conf->configdir );
+ purple_util_set_user_dir( dir );
+ g_free( dir );
+
+ purple_debug_set_enabled( FALSE );
+ purple_core_set_ui_ops( &bee_core_uiops );
+ purple_eventloop_set_ui_ops( &glib_eventloops );
+ if( !purple_core_init( "BitlBee") )
+ {
+ /* Initializing the core failed. Terminate. */
+ fprintf( stderr, "libpurple initialization failed.\n" );
+ abort();
+ }
+
+ if( proxytype != PROXY_NONE )
+ {
+ PurpleProxyInfo *pi = purple_global_proxy_get_info();
+ switch( proxytype )
+ {
+ case PROXY_SOCKS4:
+ purple_proxy_info_set_type( pi, PURPLE_PROXY_SOCKS4 );
+ break;
+ case PROXY_SOCKS5:
+ purple_proxy_info_set_type( pi, PURPLE_PROXY_SOCKS5 );
+ break;
+ case PROXY_HTTP:
+ purple_proxy_info_set_type( pi, PURPLE_PROXY_HTTP );
+ break;
+ }
+ purple_proxy_info_set_host( pi, proxyhost );
+ purple_proxy_info_set_port( pi, proxyport );
+ purple_proxy_info_set_username( pi, proxyuser );
+ purple_proxy_info_set_password( pi, proxypass );
+ }
+
+ purple_set_blist( purple_blist_new() );
+
+ /* No, really. So far there were ui_ops for everything, but now suddenly
+ one needs to use signals for typing notification stuff. :-( */
+ purple_signal_connect( purple_conversations_get_handle(), "buddy-typing",
+ &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL );
+ purple_signal_connect( purple_conversations_get_handle(), "buddy-typed",
+ &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL );
+ purple_signal_connect( purple_conversations_get_handle(), "buddy-typing-stopped",
+ &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL );
+
+ memset( &funcs, 0, sizeof( funcs ) );
+ funcs.login = purple_login;
+ funcs.init = purple_init;
+ funcs.logout = purple_logout;
+ funcs.buddy_msg = purple_buddy_msg;
+ funcs.away_states = purple_away_states;
+ funcs.set_away = purple_set_away;
+ funcs.add_buddy = purple_add_buddy;
+ funcs.remove_buddy = purple_remove_buddy;
+ funcs.add_permit = purple_add_permit;
+ funcs.add_deny = purple_add_deny;
+ funcs.rem_permit = purple_rem_permit;
+ funcs.rem_deny = purple_rem_deny;
+ funcs.get_info = purple_get_info;
+ funcs.keepalive = purple_keepalive;
+ funcs.send_typing = purple_send_typing;
+ funcs.handle_cmp = g_strcasecmp;
+ /* TODO(wilmer): Set these only for protocols that support them? */
+ funcs.chat_msg = purple_chat_msg;
+ funcs.chat_with = purple_chat_with;
+ funcs.chat_invite = purple_chat_invite;
+ funcs.chat_leave = purple_chat_leave;
+ funcs.chat_join = purple_chat_join;
+ funcs.transfer_request = purple_transfer_request;
+
+ help = g_string_new( "BitlBee libpurple module supports the following IM protocols:\n" );
+
+ /* Add a protocol entry to BitlBee's structures for every protocol
+ supported by this libpurple instance. */
+ for( prots = purple_plugins_get_protocols(); prots; prots = prots->next )
+ {
+ PurplePlugin *prot = prots->data;
+ struct prpl *ret;
+
+ /* If we already have this one (as a native module), don't
+ add a libpurple duplicate. */
+ if( find_protocol( prot->info->id ) )
+ continue;
+
+ ret = g_memdup( &funcs, sizeof( funcs ) );
+ ret->name = ret->data = prot->info->id;
+ if( strncmp( ret->name, "prpl-", 5 ) == 0 )
+ ret->name += 5;
+ register_protocol( ret );
+
+ g_string_append_printf( help, "\n* %s (%s)", ret->name, prot->info->name );
+
+ /* libpurple doesn't define a protocol called OSCAR, but we
+ need it to be compatible with normal BitlBee. */
+ if( g_strcasecmp( prot->info->id, "prpl-aim" ) == 0 )
+ {
+ ret = g_memdup( &funcs, sizeof( funcs ) );
+ ret->name = "oscar";
+ ret->data = prot->info->id;
+ register_protocol( ret );
+ }
+ }
+
+ g_string_append( help, "\n\nFor used protocols, more information about available "
+ "settings can be found using \x02help purple <protocol name>\x02 "
+ "(create an account using that protocol first!)" );
+
+ /* Add a simple dynamically-generated help item listing all
+ the supported protocols. */
+ help_add_mem( &global.help, "purple", help->str );
+ g_string_free( help, TRUE );
+}
diff --git a/protocols/skype/.gitignore b/protocols/skype/.gitignore
new file mode 100644
index 00000000..e90a033b
--- /dev/null
+++ b/protocols/skype/.gitignore
@@ -0,0 +1,19 @@
+Changelog
+HEADER.html
+*.gz
+*.asc
+.htaccess
+shot
+*.swp
+aclocal.m4
+autom4te.cache
+config.log
+config.mak
+config.status
+configure
+etc
+install-sh
+skype.so
+skyped.conf
+skyped.conf.dist
+skype.dylib*
diff --git a/protocols/skype/.mailmap b/protocols/skype/.mailmap
new file mode 100644
index 00000000..cc8d43f9
--- /dev/null
+++ b/protocols/skype/.mailmap
@@ -0,0 +1 @@
+Miklos Vajna <vmiklos@frugalware.org>
diff --git a/protocols/skype/HACKING b/protocols/skype/HACKING
new file mode 100644
index 00000000..f5516832
--- /dev/null
+++ b/protocols/skype/HACKING
@@ -0,0 +1,26 @@
+== Tabs
+
+I use the following tabs during the development:
+
+1) bitlbee-skype:
+
+vim, make, etc.
+
+2) bitlbee:
+
+gdb --args ./bitlbee -v -n -D
+run
+
+3) skyped:
+
+python skyped.py -n -d
+
+4) irssi
+
+== Get the code from git
+
+To get the code directly from git, you need:
+
+git clone git://vmiklos.hu/bitlbee-skype
+cd bitlbee-skype
+make autogen
diff --git a/protocols/skype/Makefile b/protocols/skype/Makefile
new file mode 100644
index 00000000..4f7a036d
--- /dev/null
+++ b/protocols/skype/Makefile
@@ -0,0 +1,97 @@
+-include config.mak
+
+VERSION = 0.9.0
+DATE := $(shell date +%Y-%m-%d)
+# latest stable
+BITLBEE_VERSION = 3.0.1
+
+ifeq ($(ASCIIDOC),yes)
+MANPAGES = skyped.1
+else
+MANPAGES =
+endif
+
+ifeq ($(BITLBEE),yes)
+LIBS = skype.$(SHARED_EXT)
+else
+LIBS =
+endif
+
+all: $(LIBS) $(MANPAGES)
+
+skype.$(SHARED_EXT): skype.c config.mak
+ifeq ($(BITLBEE),yes)
+ $(CC) $(CFLAGS) $(SHARED_FLAGS) -o skype.$(SHARED_EXT) skype.c $(LDFLAGS)
+endif
+
+install: all
+ifeq ($(ASCIIDOC),yes)
+ $(INSTALL) -d $(DESTDIR)$(mandir)/man1
+ $(INSTALL) -m644 $(MANPAGES) $(DESTDIR)$(mandir)/man1
+endif
+ifeq ($(BITLBEE),yes)
+ $(INSTALL) -d $(DESTDIR)$(plugindir)
+ $(INSTALL) skype.$(SHARED_EXT) $(DESTDIR)$(plugindir)
+endif
+ifeq ($(SKYPE4PY),yes)
+ $(INSTALL) -d $(DESTDIR)$(bindir)
+ $(INSTALL) -d $(DESTDIR)$(sysconfdir)
+ $(INSTALL) skyped.py $(DESTDIR)$(bindir)/skyped
+ perl -p -i -e 's|/usr/local/etc/skyped|$(sysconfdir)|' $(DESTDIR)$(bindir)/skyped
+ $(INSTALL) -m644 skyped.conf.dist $(DESTDIR)$(sysconfdir)/skyped.conf
+ perl -p -i -e 's|\$${prefix}|$(prefix)|' $(DESTDIR)$(sysconfdir)/skyped.conf
+ $(INSTALL) -m644 skyped.cnf $(DESTDIR)$(sysconfdir)
+endif
+
+client: client.c
+
+autogen: configure.ac
+ cp $(shell ls /usr/share/automake-*/install-sh | tail -n1) ./
+ autoconf
+
+clean:
+ rm -f $(LIBS) $(MANPAGES)
+
+distclean: clean
+ rm -f config.log config.mak config.status
+
+autoclean: distclean
+ rm -rf aclocal.m4 autom4te.cache configure install-sh
+
+# take this from the kernel
+check:
+ perl checkpatch.pl --no-tree --file skype.c
+
+test: all
+ $(MAKE) -C t/ all
+
+dist:
+ git archive --format=tar --prefix=bitlbee-skype-$(VERSION)/ HEAD | tar xf -
+ mkdir -p bitlbee-skype-$(VERSION)
+ git log --no-merges |git name-rev --tags --stdin > bitlbee-skype-$(VERSION)/Changelog
+ make -C bitlbee-skype-$(VERSION) autogen
+ tar czf bitlbee-skype-$(VERSION).tar.gz bitlbee-skype-$(VERSION)
+ rm -rf bitlbee-skype-$(VERSION)
+
+release:
+ git tag $(VERSION)
+ $(MAKE) dist
+ gpg --comment "See http://vmiklos.hu/gpg/ for info" \
+ -ba bitlbee-skype-$(VERSION).tar.gz
+
+doc: HEADER.html Changelog AUTHORS
+
+HEADER.html: README Makefile
+ asciidoc -a toc -a numbered -a sectids -o HEADER.html -a icons -a data-uri --attribute iconsdir=./images/icons README
+ sed -i 's|@VERSION@|$(VERSION)|g' HEADER.html
+ sed -i 's|@BITLBEE_VERSION@|$(BITLBEE_VERSION)|g' HEADER.html
+
+Changelog: .git/refs/heads/master
+ git log --no-merges |git name-rev --tags --stdin >Changelog
+
+AUTHORS: .git/refs/heads/master
+ git shortlog -s -n |sed 's/.*\t//'> AUTHORS
+
+%.1: %.txt asciidoc.conf
+ a2x --asciidoc-opts="-f asciidoc.conf" \
+ -a bs_version=$(VERSION) -a bs_date=$(DATE) -f manpage $<
diff --git a/protocols/skype/NEWS b/protocols/skype/NEWS
new file mode 100644
index 00000000..b55b34c6
--- /dev/null
+++ b/protocols/skype/NEWS
@@ -0,0 +1,131 @@
+VERSION DESCRIPTION
+-----------------------------------------------------------------------------
+0.9.0 - merge support for building the plugin on OpenBSD
+ - merge support for running skyped without gobject and
+ pygnutls/pyopenssl - as a side effect this adds Windows support
+ - add /ctcp call|hangup support (you need BitlBee from bzr to use
+ this)
+ - add group support (see http://wiki.bitlbee.org/UiFix)
+0.8.4 - now using python2.7 directly in case python would point to python3k
+ - merge patch to avoid a crash when failing to connect to skyped
+ - merge support for building the plugin on NetBSD
+ - merge Debian patches
+0.8.3 - support for BitlBee 1.3dev
+ - fixed --debug switch (-d was fine)
+ - documentation fixes
+0.8.2 - building documentation is now optional
+ - new settings: test_join and show_moods
+ - '~' in skyped.conf is now expanded to the user's home directory
+ - groupchat channel names are now persistent (requires
+ BitlBee-1.2.6)
+0.8.1 - support for BitlBee 1.2.5
+ - support for Skype 2.1.0.81 and Skype4Py 1.0.32.0
+ - the plugin part now supports FreeBSD
+ - fix for edited messages, the prefix can now be configured
+0.8.0 - fix build on x86_64 (-fPIC usage)
+ - debug messages now have a timestamp
+ - more work on having the default config under ~/.skyped
+ - added a manual page for skyped
+0.7.2 - add --log option to skyped to allow logging while it the
+ daemon is in the background.
+ - prefer config files from ~/.skyped over /etc/skyped
+ - handle the case when LANG and LC_ALL env vars are empty
+0.7.1 - mostly internal changes, the monster read callback is
+ now replaced by tiny parser functions
+0.7.0 - made 'make config' more portable
+ - add 'skypeconsole' buddy for debugging purposes
+ - support autojoin for bookmarked groupchats
+ - skyped: make hardwired '/dev/null' portable and fix
+ Python-2.6 warnings
+0.6.3 - various osx-specific improvements (see the new screenshot!)
+ - added python-gnutls install instructions
+ - bitlbee.pc is now searched under
+ /usr/local/lib/pkgconfig by default to help LFS monkeys ;)
+0.6.2 - bugfix: make install required the plugin even in case
+ its build was disabled
+0.6.1 - added keepalive traffic to avoid disconnects in bitlbee
+ when there is no traffic for a long time
+ - now the plugin or skyped is automatically disabled if
+ the dependencies are not available; useful in case the
+ plugin is to be installed on a public server, or the
+ skyped is to be used with a public server only
+0.6.0 - works with BitlBee 1.2.1
+0.5.1 - configure now automatically detects the right prefix to
+ match witl BitlBee's one
+ - minor documentation improvements (public chats, bug
+ reporting address)
+0.5.0 - skyped now uses gnutls if possible, which seem to be
+ more stable, compared to openssl.
+ - skyped now tries to handle all read/write errors from/to
+ clients, and always just warn about failures, never exit.
+ - installation for Debian users should be more simple
+ - improved documentation
+ - this .0 release should be quite stable, only about 100
+ lines of new code
+0.4.2 - skyped should be now more responsive
+ - new skypeout_offline setting for hiding/showing SkypeOut
+ contacts
+ - support for SkypeOut calls
+ - support for querying the balance from Skype
+ - all setting should be documented now
+0.4.1 - support for building the plugin on Mac OSX
+ - tested with BitlBee 1.2 and Skype 2.0.0.63
+ - avoid ${prefix} (by autoconf) in the config file as we
+ don't handle such a variable
+ - now you can call echo123 (patch by Riskó Gergely)
+0.4.0 - support for starting, accepting and rejecting calls
+ - also updated documentation (the key is the account set
+ skype/call command)
+ - as usual with the .0 releases, be careful, ~200 lines of
+ new code
+0.3.2 - support for Skype 2.0.0.43
+ - skyped now automatically starts/shuts down skype
+ - improved 'make prepare' to handle more automake versions
+ - documentation improvements
+0.3.1 - beautify output when skyped is interrupted using ^C
+ - 'nick skype foo' now really sets display name, not the mood
+ text
+ - documentation fixups
+ - this version should be again as stable as 0.2.6 was
+0.3.0 - authentication support in skyped via ssl
+ - ~200 lines of new code, so be careful :)
+ - upgraders: please read the documentation about how to set up
+ your config the ssl certificate, this was no necessary till now
+0.2.6 - the server setting has a default value, 'localhost' so in most
+ cases you no longer have to set it explicitly
+ - support for receiving emoted messages, ie. when the user types
+ '/me foo'
+ - support for setting the display name (nick 0 "foo bar") - it
+ sets the mood text
+0.2.5 - now bitlbee's info command is supported (it displays full name,
+ birthday, homepage, age, etc.)
+0.2.4 - improve documentation based on feedback from people on #bitlbee
+ - fixed for Skype4Py >= 0.9.28.4
+ - tested with latest Skype beta, too (the one which supports
+ video)
+0.2.3 - fixed that annoying "creating groupchat failed" warning
+0.2.2 - don't change the topic if skype does not report a successful
+ topic change
+ - fixed for the recent bitlbee API changes
+0.2.1 - topic support in group chats
+ - bugfixes for multiline messages
+ - this version should be again as stable as 0.1.4 was
+0.2.0 - group chat support
+ - ~300 lines of new code, so be careful :)
+ - the version number mentions that this is not a minor change
+0.1.4 - documentation: mention the version of all deps (requirements
+ section)
+ - fix sending / sending accents
+ - don't use internal functions of skype4py
+ - skyped no longer dies when skype is killed
+0.1.3 - support for edited messages
+ - ignore empty messages (skype does the same)
+ - support for multiline messages
+ - switch to the x11 api instead of dbus (it's much more stable)
+0.1.2 - notification when a new call arrives in
+ - more documentation (vnc)
+ - first release which works with unpatched bitlbee
+0.1.1 - skyped now runs as daemon in the background by default
+ - skyped now automatically reconnects on Skype restarts
+0.1.0 - initial release
+ - see README for major features
diff --git a/protocols/skype/README b/protocols/skype/README
new file mode 100644
index 00000000..2c962d54
--- /dev/null
+++ b/protocols/skype/README
@@ -0,0 +1,488 @@
+= Skype plugin for BitlBee
+Miklos Vajna <vmiklos-at-vmiklos-dot-hu>
+
+== Status
+
+[quote, Wilmer van der Gaast (author of BitlBee)]
+____
+Okay, this exists now, with lots of thanks to vmiklos for his *excellent*
+work!!
+
+It's not in the main BitlBee and it'll never be for various reasons, but
+because it's a plugin that shouldn't be a problem.
+____
+
+One day I browsed the BitlBee bugtracker and found
+http://bugs.bitlbee.org/bitlbee/ticket/82[this] ticket. Then after a while I
+returned and saw that it was still open. So I wrote it.
+
+It's pretty stable (one day I wanted to restart it because of an upgrade
+and just noticed it was running for 2+ months without crashing), I use
+it for my daily work. Being a plug-in, no patching is required, you can
+just install it after installing BitlBee itself.
+
+NOTE: You will see that this implementation of the Skype plug-in still requires
+a Skype instance to be running. This is because I'm not motivated to reverse
+engineer Skype's
+http://en.wikipedia.org/wiki/Skype_Protocol#Obfuscation_Layer[obfuscation
+layer]. (Not mentioning that you should ask your lawyer about if it is legal or
+not..)
+
+== Requirements
+
+* Skype >= 1.4.0.99. The latest version I've tested is 2.1.0.81.
+* BitlBee >= 3.0. The latest version I've tested is @BITLBEE_VERSION@. Use
+ old versions (see the NEWS file about which one) if you have older BitlBee
+ installed.
+* Skype4Py >= 0.9.28.7. Previous versions won't work due to API changes.
+ The latest version I've tested is 1.0.32.0.
+
+* Python >= 2.5. Skype4Py does not work with 2.4.
+
+* OS: `bitlbee-skype` has been tested under Linux and Mac OS X. The plugin part
+ has been tested under Free/Open/NetBSD as well. The daemon part has been
+ tested on Windows, too.
+
+== How to set it up
+
+Before you start. The setup is the following: BitlBee can't connect directly to
+Skype servers (the company's ones). It needs a running Skype client to do so.
+In fact BitlBee will connect to `skyped` (a tcp server, provided in this
+package) and `skyped` will connect to to your Skype client.
+
+The benefit of this architecture is that you can run Skype and `skyped`
+on a machine different to the one where you run BitlBee (it can be even
+a public server) and/or your IRC client.
+
+NOTE: The order is important. First `skyped` starts Skype. Then `skyped`
+connects to Skype, finally BitlBee can connect to `skyped`.
+
+=== Installing under Frugalware or Debian
+
+- Install the necessary packages:
+
+----
+# pacman-g2 -S bitlbee-skype
+----
+
+or
+
+----
+# apt-get install skyped bitlbee-plugin-skype
+----
+
+(the later from the unstable repo)
+
+and you don't have to compile anything manually.
+
+=== Installing under OS X
+
+- Install the necessary packages from ports:
+
+NOTE: You have to edit the Portfile manually to include the install-dev target,
+just append install-dev after install-etc.
+
+----
+# port -v install bitlbee
+----
+
+and you have to install `bitlbee-skype` and `skype4py` from
+source.
+
+=== Installing from source
+
+NOTE: bitlbee-skype by default builds and installs skyped and the
+plugin. In case you just want to install the plugin for a public server
+or you want to use skyped with a public server (like
+`bitlbee1.asnetinc.net`), you don't need both.
+
+- You need the latest stable BitlBee release (unless you want to use a
+ public server):
+
+----
+$ wget http://get.bitlbee.org/src/bitlbee-@BITLBEE_VERSION@.tar.gz
+$ tar xf bitlbee-@BITLBEE_VERSION@.tar.gz
+$ cd bitlbee-@BITLBEE_VERSION@
+----
+
+- Now compile and install it:
+
+----
+$ ./configure
+$ make
+# make install install-dev
+----
+
+- To install http://skype4py.sourceforge.net/[Skype4Py] from source
+ (unless you want to install the plugin for a public server):
+
+----
+$ tar -zxvf Skype4Py-x.x.x.x.tar.gz
+$ cd Skype4Py-x.x.x.x
+# python setup.py install
+----
+
+- Get the plugin code (in an empty dir, or whereever you want, it does
+ not matter):
+
+----
+$ wget http://vmiklos.hu/project/bitlbee-skype/bitlbee-skype-@VERSION@.tar.gz
+$ tar xf bitlbee-skype-@VERSION@.tar.gz
+$ cd bitlbee-skype-@VERSION@
+----
+
+- Compile and install it:
+
+----
+$ ./configure
+$ make
+# make install
+----
+
+This will install the plugin to where BitlBee expects them, which is
+`/usr/local/lib/bitlbee` if you installed BitlBee from source.
+
+=== Configuring
+
+- Set up `~/.skyped/skyped.conf`: Create the `~/.skyped` directory, copy
+ `skyped.conf` and `skyped.cnf` from
+ `/usr/local/etc/skyped/skyped.conf` to `~/.skyped`, adjust `username`
+ and `password`. The `username` should be your Skype login and the
+ `password` can be whatever you want, but you will have to specify that
+ one when adding the Skype account to BitlBee (see later).
+
+NOTE: Here, and later - `/usr/local/etc` can be different on your installation
+if you used the `--sysconfdir` switch when running bitlbee-skype's `configure`.
+
+- Generate the SSL pem files:
+
+----
+# cd ~/.skyped
+# openssl req -new -x509 -days 365 -nodes -config skyped.cnf -out skyped.cert.pem \
+ -keyout skyped.key.pem
+----
+
+NOTE: Maybe you want to adjust the permissions in the `~/.skyped`
+dir. For example make it readable by just your user.
+
+- Start `skyped` (the tcp server):
+
+----
+$ skyped
+----
+
+- Start your `IRC` client, connect to BitlBee and add your account:
+
+----
+account add skype <user> <pass>
+account skype set server localhost
+----
+
+<user> should be your Skype account name, <pass> should be the one you declared
+in `skyped.conf`. If you want to run skyped on a remote machine, replace
+`localhost` with the name of the machine.
+
+If you are running skyped on a custom port:
+
+----
+account skype set port <port>
+----
+
+If you want to set your full name (optional):
+
+----
+account skype set display_name "John Smith"
+----
+
+If you want to see your skypeout contacts online as well (they are
+offline by default):
+
+----
+account skype set skypeout_offline false
+----
+
+== Setting up Skype in a VNC server (optional)
+
+Optionally, if you want to run Skype on a server, you might want to setup up
+a `VNC` server as well. I used `tightvnc` but probably other `VNC` servers will
+work, too.
+
+First run
+
+----
+$ vncpasswd ~/.vnc/passwd
+----
+
+and create a password. You will need it at least once.
+
+Now create `~/.vnc/xstartup` with the following contents:
+
+----
+#!/bin/sh
+
+blackbox
+----
+
+Adjust the permissions:
+
+----
+$ chmod +x ~/.vnc/xstartup
+----
+
+Then start the server:
+
+----
+$ vncserver
+----
+
+Then connect to it, start an `xterm`, set up Skype (username, password,
+enable X11 API and allow the `Skype4Py` client), quit from Skype, and
+start `skyped`. If you want to watch its traffic, enable debug messages
+and foreground mode:
+
+----
+$ skyped -n -d
+----
+
+== Features
+
+- Download nicks and away statuses from Skype
+
+- Noticing joins / parts while we're connected
+
+- Sending messages
+
+- Receiving messages
+
+- Receiving away status changes
+
+- `skyped` (the tcp daemon that is a gateway between Skype and tcp)
+
+- Error handling when `skyped` is not running and when it exits
+
+- Marking received messages as seen so that Skype won't say there are unread messages
+
+- Adding / removing contacts
+
+- Set away state when you do a `/away`.
+
+- When you `account off`, Skype will set status to `Offline`
+
+- When you `account on`, Skype will set status to `Online`
+
+- Detect when somebody wants to add you and ask for confirmation
+
+- Detect when somebody wants to transfer a file
+
+- Group chat support:
+
+ * Detect if we're invited
+
+ * Send / receive group chat messages
+
+ * Invite others (using `/invite <nick>`)
+
+ * Part from group chats
+
+ * Starting a group chat (using `/j #nick`)
+
+- Topic changes in group chats:
+
+ * Show the current topic (if any) on join
+
+ * Notice when someone changes the topic
+
+ * Support changing the topic using `/topic`
+
+- Viewing the profile using the `info` command.
+
+- Handling skype actions (when the `CHATMESSAGE` has `EMOTED` type)
+
+- Setting your display name using the `nick` command.
+
+- Running Skype on a machine different to BitlBee is possible, the
+ communication is encrypted.
+
+- Managing outgoing calls (with call duration at the end):
+
+ * `/ctcp nick call`
+ * `/ctcp nick hangup`
+
+- Managing outgoing SkypeOut or conference calls:
+
+ * `account skype set call +18005551234`
+ * `account skype set call nick1 nick2`
+ * `account skype set -del call`
+
+- Managing incoming calls via questions, just like when you add / remove
+ contacts.
+
+- Querying the current SkypeOut balance:
+
+ * `account skype set balance query`
+
+- For debug purposes, it's possible to send any command to `skyped`. To
+ achieve this, you need to:
+
+ * `account skype set skypeconsole true`
+
+ * then writing `skypeconsole: <command>` will work in the control
+ channel.
+
+ * `account skype set skypeconsole_receive true` will make the
+ `skypeconsole` account dump all the recieved raw traffic for you
+
+- If you want to automatically join bookmarked groupchats right after
+ you logged in, do:
+
+ * `account skype set auto_join true`
+
+- Edited messages are shown with the `EDIT:` prefix. If you don't like
+ this, you can set your own prefix using:
+
+ * `account skype set edit_prefix "updated message:"`
+
+- The `echo123` test account is hidden by default. If you want to see it:
+
+ * `account skype set test_join true`
+
+- Mood texts are not shown by default. If you want to see it:
+
+ * `account skype set show_moods true`
+
+- Group support:
+
+ * Skype groups are told to BitlBee
+ * The usual `/invite` in a group channel adds the buddy to the group in skype
+ as well (and if necessary, it creates a new group in Skype)
+
+== What needs to be done (aka. TODO)
+
+- Notice if foo invites bar. Currently you can see only that bar joined.
+
+- Public chats. See
+ link:https://developer.skype.com/jira/browse/SCL-381[this feature
+ request], this is because it is still not possible (under Linux) to
+ `join_chat` to a public chat..
+
+- Add yasrd (Yet Another Skype-Related Daemon) to allow using a public
+ server for users who are behind NAT.
+
+== I would like to have support for ...
+
+If something does not work and it's not in the TODO section, then please
+contact me! Please also try the link:HACKING[git version] before reporting a bug, your
+problem may be already fixed there.
+
+In fact, of course, I wrote this documentation after figured out how to do this
+setup, so maybe I left out some steps. If you needed 'any' additional tricks,
+then it would be nice to include them here.
+
+== Known bugs
+
+- File transfers are view-only from BitlBee. Quoting the
+ https://developer.skype.com/Docs/ApiDoc/FILETRANSFER_object[relevant
+ documentation]: 'File transfers cannot be initiated nor accepted via
+ API commands.' So it's not something I can add support for, sadly.
+
+== Screenshots
+
+You can reach some screenshots link:shot[here].
+
+== Additional resources
+
+You can reach the Changelog link:Changelog[here], and a gitweb interface
+http://vmiklos.hu/gitweb/?p=bitlbee-skype.git[here].
+
+The Skype API documentation is
+http://developer.skype.com/resources/public_api_ref.zip[here] if you're
+interested.
+
+== Testimonials
+
+----
+00:56 < scathe> I like your skype plugin :)
+----
+
+----
+It's really working great so far.
+
+Good Job and thank you!
+Sebastian
+----
+
+----
+Big respect for your work, i really appreciate it.
+
+Martin
+----
+
+----
+Thanks for bitlbee-skype. As a blind Linux user, I cannot use the
+skype GUI client because qt apps ar not accessible yet with the
+available screen readers. bitlbee-skype allows me to make use of skype
+without having to interact much with the GUI client, which helps me a
+lot.
+
+Lukas
+----
+
+----
+02:12 < newton> i must say, i love this little bee ;)
+02:15 < newton> tried it out today with the skype plugin, good work!
+----
+
+----
+18:10 < miCSu> it works fine
+----
+
+----
+13:56 < seo> i just want to thank you :)
+13:56 < seo> for bitlbee-skype
+13:57 < seo> it's working very well, so, again, thank you for your work, and for sharing it
+----
+
+----
+22:16 < ecraven> vmiklos: thanks a lot for the skype plugin for bitlbee!
+----
+
+----
+I'm blind and so I have to use a screen reader, in my case Gnome-Orca.
+But since Skype is written in QT, while Orca uses gtk+, I have no direct
+access to the Skype interface. That's why I desided to use Skyped and
+Erc.
+The text console is fully accessible.
+Thank you very much.
+
+Hermann
+----
+
+----
+i love that bitlbeeplugin. big thx for that.
+
+michael
+----
+
+----
+23:47 < krisfremen> thanks for creating this fabulous piece of software vmiklos :)
+----
+
+== Thanks
+
+to the following people:
+
+* people in link:AUTHORS[AUTHORS] for their contributions
+
+* Arkadiusz Wahlig, author of skype4py, for making suggestions to skyped
+
+* Gabor Adam Toth (tg), for noticing extra code is needed to handle multiline
+ messages
+
+* Cristobal Palmer (tarheelcoxn), for helping to testing the plugin in a
+ timezone different to mine
+
+* people on `#bitlbee` for feedback
+
+Back to my link:/projects[projects page].
+
+// vim: ft=asciidoc
diff --git a/protocols/skype/asciidoc.conf b/protocols/skype/asciidoc.conf
new file mode 100644
index 00000000..24a649c1
--- /dev/null
+++ b/protocols/skype/asciidoc.conf
@@ -0,0 +1,21 @@
+ifdef::doctype-manpage[]
+ifdef::backend-docbook[]
+[header]
+template::[header-declarations]
+<refentry>
+ <refentryinfo>
+ <date>{bs_date}</date>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>{mantitle}</refentrytitle>
+ <manvolnum>{manvolnum}</manvolnum>
+ <refmiscinfo class="source">bitlbee-skype</refmiscinfo>
+ <refmiscinfo class="version">{bs_version}</refmiscinfo>
+ <refmiscinfo class="manual">bitlbee-skype manual</refmiscinfo>
+ </refmeta>
+ <refnamediv>
+ <refname>{manname}</refname>
+ <refpurpose>{manpurpose}</refpurpose>
+ </refnamediv>
+endif::backend-docbook[]
+endif::doctype-manpage[]
diff --git a/protocols/skype/client.sh b/protocols/skype/client.sh
new file mode 100644
index 00000000..7d7689a8
--- /dev/null
+++ b/protocols/skype/client.sh
@@ -0,0 +1 @@
+openssl s_client -host localhost -port 2727 -verify 0
diff --git a/protocols/skype/config.mak.in b/protocols/skype/config.mak.in
new file mode 100644
index 00000000..06698dad
--- /dev/null
+++ b/protocols/skype/config.mak.in
@@ -0,0 +1,16 @@
+CFLAGS = @CFLAGS@
+LDFLAGS = @LDFLAGS@
+SHARED_FLAGS = @SHARED_FLAGS@
+SHARED_EXT = @SHARED_EXT@
+SKYPE4PY = @SKYPE4PY@
+BITLBEE = @BITLBEE@
+ASCIIDOC = @ASCIIDOC@
+INSTALL = @INSTALL@
+prefix = @prefix@
+sysconfdir = @sysconfdir@/skyped
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+libdir = @libdir@
+plugindir = ${libdir}/bitlbee
+datarootdir = @datarootdir@
+mandir = @mandir@
diff --git a/protocols/skype/configure.ac b/protocols/skype/configure.ac
new file mode 100644
index 00000000..a616758c
--- /dev/null
+++ b/protocols/skype/configure.ac
@@ -0,0 +1,96 @@
+AC_INIT([Skype plugin for BitlBee], 1.0, [vmiklos@vmiklos.hu], bitlbee-skype)
+AC_PROG_CC
+AC_PROG_INSTALL
+
+AC_ARG_ENABLE([debug], AS_HELP_STRING([--enable-debug], [Enable debug support (default: disabled)]), debug=yes)
+AC_MSG_CHECKING(for debug mode request)
+if test "x$debug" = "xyes" ; then
+ CFLAGS="-g -Wall -Werror"
+ AC_MSG_RESULT(yes)
+else
+ AC_MSG_RESULT(no)
+fi
+
+case "`$CC -dumpmachine`" in
+ *linux*|*freebsd*|*netbsd*|*openbsd*)
+ SHARED_FLAGS="-fPIC -shared"
+ SHARED_EXT="so"
+ ;;
+ *apple-darwin*)
+ SHARED_FLAGS="-dynamiclib -undefined dynamic_lookup"
+ SHARED_EXT="dylib"
+ ;;
+ *)
+ AC_MSG_ERROR([Your machine is not yet supported])
+ ;;
+esac
+AC_SUBST(SHARED_FLAGS)
+AC_SUBST(SHARED_EXT)
+
+dnl Check for bitlbee
+AC_MSG_CHECKING(for BitlBee)
+if test -d /usr/local/lib/pkgconfig; then
+ PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
+ export PKG_CONFIG_PATH
+fi
+pkg-config --exists bitlbee
+if test "$?" != "0"; then
+ AC_MSG_RESULT(no)
+ BITLBEE="no"
+else
+ AC_MSG_RESULT(yes)
+ BITLBEE="yes"
+ if test -z "$CFLAGS"; then
+ CFLAGS="`pkg-config --cflags bitlbee`"
+ else
+ CFLAGS="$CFLAGS `pkg-config --cflags bitlbee`"
+ fi
+ if test -z "$LDFLAGS"; then
+ LDFLAGS="`pkg-config --libs bitlbee`"
+ else
+ LDFLAGS="$LDFLAGS `pkg-config --libs bitlbee`"
+ fi
+ prefix=`pkg-config --variable=prefix bitlbee`
+fi
+AC_SUBST(BITLBEE)
+
+dnl Check for a2x
+AC_ARG_ENABLE([asciidoc], AS_HELP_STRING([--disable-asciidoc], [Disable asciidoc support (default: test)]))
+if test "$enable_asciidoc" != "no"; then
+ AC_CHECK_PROG(ASCIIDOC, asciidoc, yes, [])
+fi
+
+dnl Check for Skype4Py
+AC_MSG_CHECKING(for Python module Skype4Py)
+python2.7 -c "import Skype4Py" 2>&AS_MESSAGE_LOG_FD
+if test "$?" != "0"; then
+ AC_MSG_RESULT(no)
+ SKYPE4PY="no"
+else
+ AC_MSG_RESULT(yes)
+ SKYPE4PY="yes"
+fi
+AC_SUBST(SKYPE4PY)
+
+if test "$BITLBEE" = "no" -a "$SKYPE4PY" = "no"; then
+ AC_ERROR([In order to use bitlbee-skype you need at least BitlBee or Skype4Py installed.])
+fi
+
+AC_OUTPUT(config.mak)
+AC_OUTPUT(skyped.conf.dist)
+
+echo "
+ BitlBee plugin: $BITLBEE
+ skyped: $SKYPE4PY
+ prefix: $prefix
+ install program: $INSTALL"
+if test "$BITLBEE" = "yes"; then
+ echo " compiler flags: $CFLAGS
+ linker flags: $LDFLAGS
+ shared object flags: $SHARED_FLAGS
+ shared object extension: $SHARED_EXT"
+fi
+if test "$SKYPE4PY" = "yes"; then
+ echo " sysconfig dir: $sysconfdir/skyped"
+fi
+echo
diff --git a/protocols/skype/skype.c b/protocols/skype/skype.c
new file mode 100644
index 00000000..80279c54
--- /dev/null
+++ b/protocols/skype/skype.c
@@ -0,0 +1,1562 @@
+/*
+ * skype.c - Skype plugin for BitlBee
+ *
+ * Copyright (c) 2007, 2008, 2009, 2010, 2011 by Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#define _XOPEN_SOURCE
+#define _BSD_SOURCE
+#include <poll.h>
+#include <bitlbee.h>
+#include <ssl_client.h>
+
+#define SKYPE_DEFAULT_SERVER "localhost"
+#define SKYPE_DEFAULT_PORT "2727"
+#define IRC_LINE_SIZE 1024
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+
+/*
+ * Enumerations
+ */
+
+enum {
+ SKYPE_CALL_RINGING = 1,
+ SKYPE_CALL_MISSED,
+ SKYPE_CALL_CANCELLED,
+ SKYPE_CALL_FINISHED,
+ SKYPE_CALL_REFUSED
+};
+
+enum {
+ SKYPE_FILETRANSFER_NEW = 1,
+ SKYPE_FILETRANSFER_FAILED
+};
+
+/*
+ * Structures
+ */
+
+struct skype_data {
+ struct im_connection *ic;
+ char *username;
+ /* The effective file descriptor. We store it here so any function can
+ * write() to it. */
+ int fd;
+ /* File descriptor returned by bitlbee. we store it so we know when
+ * we're connected and when we aren't. */
+ int bfd;
+ /* ssl_getfd() uses this to get the file desciptor. */
+ void *ssl;
+ /* When we receive a new message id, we query the properties, finally
+ * the chatname. Store the properties here so that we can use
+ * imcb_buddy_msg() when we got the chatname. */
+ char *handle;
+ /* List, because of multiline messages. */
+ GList *body;
+ char *type;
+ /* This is necessary because we send a notification when we get the
+ * handle. So we store the state here and then we can send a
+ * notification about the handle is in a given status. */
+ int call_status;
+ char *call_id;
+ char *call_duration;
+ /* If the call is outgoing or not */
+ int call_out;
+ /* Same for file transfers. */
+ int filetransfer_status;
+ /* Using /j #nick we want to have a groupchat with two people. Usually
+ * not (default). */
+ char *groupchat_with;
+ /* The user who invited us to the chat. */
+ char *adder;
+ /* If we are waiting for a confirmation about we changed the topic. */
+ int topic_wait;
+ /* These are used by the info command. */
+ char *info_fullname;
+ char *info_phonehome;
+ char *info_phoneoffice;
+ char *info_phonemobile;
+ char *info_nrbuddies;
+ char *info_tz;
+ char *info_seen;
+ char *info_birthday;
+ char *info_sex;
+ char *info_language;
+ char *info_country;
+ char *info_province;
+ char *info_city;
+ char *info_homepage;
+ char *info_about;
+ /* When a call fails, we get the reason and later we get the failure
+ * event, so store the failure code here till then */
+ int failurereason;
+ /* If this is just an update of an already received message. */
+ int is_edit;
+ /* List of struct skype_group* */
+ GList *groups;
+ /* Pending user which has to be added to the next group which is
+ * created. */
+ char *pending_user;
+};
+
+struct skype_away_state {
+ char *code;
+ char *full_name;
+};
+
+struct skype_buddy_ask_data {
+ struct im_connection *ic;
+ /* This is also used for call IDs for simplicity */
+ char *handle;
+};
+
+struct skype_group {
+ int id;
+ char *name;
+ GList *users;
+};
+
+/*
+ * Tables
+ */
+
+const struct skype_away_state skype_away_state_list[] = {
+ { "AWAY", "Away" },
+ { "NA", "Not available" },
+ { "DND", "Do Not Disturb" },
+ { "INVISIBLE", "Invisible" },
+ { "OFFLINE", "Offline" },
+ { "SKYPEME", "Skype Me" },
+ { "ONLINE", "Online" },
+ { NULL, NULL}
+};
+
+/*
+ * Functions
+ */
+
+int skype_write(struct im_connection *ic, char *buf, int len)
+{
+ struct skype_data *sd = ic->proto_data;
+ struct pollfd pfd[1];
+
+ if (!sd->ssl)
+ return FALSE;
+
+ pfd[0].fd = sd->fd;
+ pfd[0].events = POLLOUT;
+
+ /* This poll is necessary or we'll get a SIGPIPE when we write() to
+ * sd->fd. */
+ poll(pfd, 1, 1000);
+ if (pfd[0].revents & POLLHUP) {
+ imc_logout(ic, TRUE);
+ return FALSE;
+ }
+ ssl_write(sd->ssl, buf, len);
+
+ return TRUE;
+}
+
+int skype_printf(struct im_connection *ic, char *fmt, ...)
+{
+ va_list args;
+ char str[IRC_LINE_SIZE];
+
+ va_start(args, fmt);
+ vsnprintf(str, IRC_LINE_SIZE, fmt, args);
+ va_end(args);
+
+ return skype_write(ic, str, strlen(str));
+}
+
+static void skype_buddy_ask_yes(void *data)
+{
+ struct skype_buddy_ask_data *bla = data;
+ skype_printf(bla->ic, "SET USER %s ISAUTHORIZED TRUE",
+ bla->handle);
+ g_free(bla->handle);
+ g_free(bla);
+}
+
+static void skype_buddy_ask_no(void *data)
+{
+ struct skype_buddy_ask_data *bla = data;
+ skype_printf(bla->ic, "SET USER %s ISAUTHORIZED FALSE",
+ bla->handle);
+ g_free(bla->handle);
+ g_free(bla);
+}
+
+void skype_buddy_ask(struct im_connection *ic, char *handle, char *message)
+{
+ struct skype_buddy_ask_data *bla = g_new0(struct skype_buddy_ask_data,
+ 1);
+ char *buf;
+
+ bla->ic = ic;
+ bla->handle = g_strdup(handle);
+
+ buf = g_strdup_printf("The user %s wants to add you to "
+ "his/her buddy list, saying: '%s'.", handle, message);
+ imcb_ask(ic, buf, bla, skype_buddy_ask_yes, skype_buddy_ask_no);
+ g_free(buf);
+}
+
+static void skype_call_ask_yes(void *data)
+{
+ struct skype_buddy_ask_data *bla = data;
+ skype_printf(bla->ic, "SET CALL %s STATUS INPROGRESS",
+ bla->handle);
+ g_free(bla->handle);
+ g_free(bla);
+}
+
+static void skype_call_ask_no(void *data)
+{
+ struct skype_buddy_ask_data *bla = data;
+ skype_printf(bla->ic, "SET CALL %s STATUS FINISHED",
+ bla->handle);
+ g_free(bla->handle);
+ g_free(bla);
+}
+
+void skype_call_ask(struct im_connection *ic, char *call_id, char *message)
+{
+ struct skype_buddy_ask_data *bla = g_new0(struct skype_buddy_ask_data,
+ 1);
+
+ bla->ic = ic;
+ bla->handle = g_strdup(call_id);
+
+ imcb_ask(ic, message, bla, skype_call_ask_yes, skype_call_ask_no);
+}
+
+static char *skype_call_strerror(int err)
+{
+ switch (err) {
+ case 1:
+ return "Miscellaneous error";
+ case 2:
+ return "User or phone number does not exist.";
+ case 3:
+ return "User is offline";
+ case 4:
+ return "No proxy found";
+ case 5:
+ return "Session terminated.";
+ case 6:
+ return "No common codec found.";
+ case 7:
+ return "Sound I/O error.";
+ case 8:
+ return "Problem with remote sound device.";
+ case 9:
+ return "Call blocked by recipient.";
+ case 10:
+ return "Recipient not a friend.";
+ case 11:
+ return "Current user not authorized by recipient.";
+ case 12:
+ return "Sound recording error.";
+ default:
+ return "Unknown error";
+ }
+}
+
+static char *skype_group_by_username(struct im_connection *ic, char *username)
+{
+ struct skype_data *sd = ic->proto_data;
+ int i, j;
+
+ /* NEEDSWORK: we just search for the first group of the user, multiple
+ * groups / user is not yet supported by BitlBee. */
+
+ for (i = 0; i < g_list_length(sd->groups); i++) {
+ struct skype_group *sg = g_list_nth_data(sd->groups, i);
+ for (j = 0; j < g_list_length(sg->users); j++) {
+ if (!strcmp(g_list_nth_data(sg->users, j), username))
+ return sg->name;
+ }
+ }
+ return NULL;
+}
+
+static struct skype_group *skype_group_by_name(struct im_connection *ic, char *name)
+{
+ struct skype_data *sd = ic->proto_data;
+ int i;
+
+ for (i = 0; i < g_list_length(sd->groups); i++) {
+ struct skype_group *sg = g_list_nth_data(sd->groups, i);
+ if (!strcmp(sg->name, name))
+ return sg;
+ }
+ return NULL;
+}
+
+static void skype_parse_users(struct im_connection *ic, char *line)
+{
+ char **i, **nicks;
+
+ nicks = g_strsplit(line + 6, ", ", 0);
+ for (i = nicks; *i; i++)
+ skype_printf(ic, "GET USER %s ONLINESTATUS\n", *i);
+ g_strfreev(nicks);
+}
+
+static void skype_parse_user(struct im_connection *ic, char *line)
+{
+ int flags = 0;
+ char *ptr;
+ struct skype_data *sd = ic->proto_data;
+ char *user = strchr(line, ' ');
+ char *status = strrchr(line, ' ');
+
+ status++;
+ ptr = strchr(++user, ' ');
+ if (!ptr)
+ return;
+ *ptr = '\0';
+ ptr++;
+ if (!strncmp(ptr, "ONLINESTATUS ", 13)) {
+ if (!strcmp(user, sd->username))
+ return;
+ if (!set_getbool(&ic->acc->set, "test_join")
+ && !strcmp(user, "echo123"))
+ return;
+ ptr = g_strdup_printf("%s@skype.com", user);
+ imcb_add_buddy(ic, ptr, skype_group_by_username(ic, user));
+ if (strcmp(status, "OFFLINE") && (strcmp(status, "SKYPEOUT") ||
+ !set_getbool(&ic->acc->set, "skypeout_offline")))
+ flags |= OPT_LOGGED_IN;
+ if (strcmp(status, "ONLINE") && strcmp(status, "SKYPEME"))
+ flags |= OPT_AWAY;
+ imcb_buddy_status(ic, ptr, flags, NULL, NULL);
+ g_free(ptr);
+ } else if (!strncmp(ptr, "RECEIVEDAUTHREQUEST ", 20)) {
+ char *message = ptr + 20;
+ if (strlen(message))
+ skype_buddy_ask(ic, user, message);
+ } else if (!strncmp(ptr, "BUDDYSTATUS ", 12)) {
+ char *st = ptr + 12;
+ if (!strcmp(st, "3")) {
+ char *buf = g_strdup_printf("%s@skype.com", user);
+ imcb_add_buddy(ic, buf, skype_group_by_username(ic, user));
+ g_free(buf);
+ }
+ } else if (!strncmp(ptr, "MOOD_TEXT ", 10)) {
+ char *buf = g_strdup_printf("%s@skype.com", user);
+ bee_user_t *bu = bee_user_by_handle(ic->bee, ic, buf);
+ g_free(buf);
+ buf = ptr + 10;
+ if (bu)
+ imcb_buddy_status(ic, bu->handle, bu->flags, NULL,
+ *buf ? buf : NULL);
+ if (set_getbool(&ic->acc->set, "show_moods"))
+ imcb_log(ic, "User `%s' changed mood text to `%s'", user, buf);
+ } else if (!strncmp(ptr, "FULLNAME ", 9))
+ sd->info_fullname = g_strdup(ptr + 9);
+ else if (!strncmp(ptr, "PHONE_HOME ", 11))
+ sd->info_phonehome = g_strdup(ptr + 11);
+ else if (!strncmp(ptr, "PHONE_OFFICE ", 13))
+ sd->info_phoneoffice = g_strdup(ptr + 13);
+ else if (!strncmp(ptr, "PHONE_MOBILE ", 13))
+ sd->info_phonemobile = g_strdup(ptr + 13);
+ else if (!strncmp(ptr, "NROF_AUTHED_BUDDIES ", 20))
+ sd->info_nrbuddies = g_strdup(ptr + 20);
+ else if (!strncmp(ptr, "TIMEZONE ", 9))
+ sd->info_tz = g_strdup(ptr + 9);
+ else if (!strncmp(ptr, "LASTONLINETIMESTAMP ", 20))
+ sd->info_seen = g_strdup(ptr + 20);
+ else if (!strncmp(ptr, "BIRTHDAY ", 9))
+ sd->info_birthday = g_strdup(ptr + 9);
+ else if (!strncmp(ptr, "SEX ", 4))
+ sd->info_sex = g_strdup(ptr + 4);
+ else if (!strncmp(ptr, "LANGUAGE ", 9))
+ sd->info_language = g_strdup(ptr + 9);
+ else if (!strncmp(ptr, "COUNTRY ", 8))
+ sd->info_country = g_strdup(ptr + 8);
+ else if (!strncmp(ptr, "PROVINCE ", 9))
+ sd->info_province = g_strdup(ptr + 9);
+ else if (!strncmp(ptr, "CITY ", 5))
+ sd->info_city = g_strdup(ptr + 5);
+ else if (!strncmp(ptr, "HOMEPAGE ", 9))
+ sd->info_homepage = g_strdup(ptr + 9);
+ else if (!strncmp(ptr, "ABOUT ", 6)) {
+ sd->info_about = g_strdup(ptr + 6);
+
+ GString *st = g_string_new("Contact Information\n");
+ g_string_append_printf(st, "Skype Name: %s\n", user);
+ if (sd->info_fullname) {
+ if (strlen(sd->info_fullname))
+ g_string_append_printf(st, "Full Name: %s\n",
+ sd->info_fullname);
+ g_free(sd->info_fullname);
+ }
+ if (sd->info_phonehome) {
+ if (strlen(sd->info_phonehome))
+ g_string_append_printf(st, "Home Phone: %s\n",
+ sd->info_phonehome);
+ g_free(sd->info_phonehome);
+ }
+ if (sd->info_phoneoffice) {
+ if (strlen(sd->info_phoneoffice))
+ g_string_append_printf(st, "Office Phone: %s\n",
+ sd->info_phoneoffice);
+ g_free(sd->info_phoneoffice);
+ }
+ if (sd->info_phonemobile) {
+ if (strlen(sd->info_phonemobile))
+ g_string_append_printf(st, "Mobile Phone: %s\n",
+ sd->info_phonemobile);
+ g_free(sd->info_phonemobile);
+ }
+ g_string_append_printf(st, "Personal Information\n");
+ if (sd->info_nrbuddies) {
+ if (strlen(sd->info_nrbuddies))
+ g_string_append_printf(st,
+ "Contacts: %s\n", sd->info_nrbuddies);
+ g_free(sd->info_nrbuddies);
+ }
+ if (sd->info_tz) {
+ if (strlen(sd->info_tz)) {
+ char ib[256];
+ time_t t = time(NULL);
+ t += atoi(sd->info_tz)-(60*60*24);
+ struct tm *gt = gmtime(&t);
+ strftime(ib, 256, "%H:%M:%S", gt);
+ g_string_append_printf(st,
+ "Local Time: %s\n", ib);
+ }
+ g_free(sd->info_tz);
+ }
+ if (sd->info_seen) {
+ if (strlen(sd->info_seen)) {
+ char ib[256];
+ time_t it = atoi(sd->info_seen);
+ struct tm *tm = localtime(&it);
+ strftime(ib, 256, ("%Y. %m. %d. %H:%M"), tm);
+ g_string_append_printf(st,
+ "Last Seen: %s\n", ib);
+ }
+ g_free(sd->info_seen);
+ }
+ if (sd->info_birthday) {
+ if (strlen(sd->info_birthday) &&
+ strcmp(sd->info_birthday, "0")) {
+ char ib[256];
+ struct tm tm;
+ strptime(sd->info_birthday, "%Y%m%d", &tm);
+ strftime(ib, 256, "%B %d, %Y", &tm);
+ g_string_append_printf(st,
+ "Birthday: %s\n", ib);
+
+ strftime(ib, 256, "%Y", &tm);
+ int year = atoi(ib);
+ time_t t = time(NULL);
+ struct tm *lt = localtime(&t);
+ g_string_append_printf(st,
+ "Age: %d\n", lt->tm_year+1900-year);
+ }
+ g_free(sd->info_birthday);
+ }
+ if (sd->info_sex) {
+ if (strlen(sd->info_sex)) {
+ char *iptr = sd->info_sex;
+ while (*iptr++)
+ *iptr = tolower(*iptr);
+ g_string_append_printf(st,
+ "Gender: %s\n", sd->info_sex);
+ }
+ g_free(sd->info_sex);
+ }
+ if (sd->info_language) {
+ if (strlen(sd->info_language)) {
+ char *iptr = strchr(sd->info_language, ' ');
+ if (iptr)
+ iptr++;
+ else
+ iptr = sd->info_language;
+ g_string_append_printf(st,
+ "Language: %s\n", iptr);
+ }
+ g_free(sd->info_language);
+ }
+ if (sd->info_country) {
+ if (strlen(sd->info_country)) {
+ char *iptr = strchr(sd->info_country, ' ');
+ if (iptr)
+ iptr++;
+ else
+ iptr = sd->info_country;
+ g_string_append_printf(st,
+ "Country: %s\n", iptr);
+ }
+ g_free(sd->info_country);
+ }
+ if (sd->info_province) {
+ if (strlen(sd->info_province))
+ g_string_append_printf(st,
+ "Region: %s\n", sd->info_province);
+ g_free(sd->info_province);
+ }
+ if (sd->info_city) {
+ if (strlen(sd->info_city))
+ g_string_append_printf(st,
+ "City: %s\n", sd->info_city);
+ g_free(sd->info_city);
+ }
+ if (sd->info_homepage) {
+ if (strlen(sd->info_homepage))
+ g_string_append_printf(st,
+ "Homepage: %s\n", sd->info_homepage);
+ g_free(sd->info_homepage);
+ }
+ if (sd->info_about) {
+ if (strlen(sd->info_about))
+ g_string_append_printf(st, "%s\n",
+ sd->info_about);
+ g_free(sd->info_about);
+ }
+ imcb_log(ic, "%s", st->str);
+ g_string_free(st, TRUE);
+ }
+}
+
+static void skype_parse_chatmessage(struct im_connection *ic, char *line)
+{
+ struct skype_data *sd = ic->proto_data;
+ char buf[IRC_LINE_SIZE];
+ char *id = strchr(line, ' ');
+
+ if (!++id)
+ return;
+ char *info = strchr(id, ' ');
+
+ if (!info)
+ return;
+ *info = '\0';
+ info++;
+ if (!strcmp(info, "STATUS RECEIVED") || !strncmp(info, "EDITED_TIMESTAMP", 16)) {
+ /* New message ID:
+ * (1) Request its from field
+ * (2) Request its body
+ * (3) Request its type
+ * (4) Query chatname
+ */
+ skype_printf(ic, "GET CHATMESSAGE %s FROM_HANDLE\n", id);
+ if (!strcmp(info, "STATUS RECEIVED"))
+ skype_printf(ic, "GET CHATMESSAGE %s BODY\n", id);
+ else
+ sd->is_edit = 1;
+ skype_printf(ic, "GET CHATMESSAGE %s TYPE\n", id);
+ skype_printf(ic, "GET CHATMESSAGE %s CHATNAME\n", id);
+ } else if (!strncmp(info, "FROM_HANDLE ", 12)) {
+ info += 12;
+ /* New from field value. Store
+ * it, then we can later use it
+ * when we got the message's
+ * body. */
+ g_free(sd->handle);
+ sd->handle = g_strdup_printf("%s@skype.com", info);
+ } else if (!strncmp(info, "EDITED_BY ", 10)) {
+ info += 10;
+ /* This is the same as
+ * FROM_HANDLE, except that we
+ * never request these lines
+ * from Skype, we just get
+ * them. */
+ g_free(sd->handle);
+ sd->handle = g_strdup_printf("%s@skype.com", info);
+ } else if (!strncmp(info, "BODY ", 5)) {
+ info += 5;
+ sd->body = g_list_append(sd->body, g_strdup(info));
+ } else if (!strncmp(info, "TYPE ", 5)) {
+ info += 5;
+ g_free(sd->type);
+ sd->type = g_strdup(info);
+ } else if (!strncmp(info, "CHATNAME ", 9)) {
+ info += 9;
+ if (sd->handle && sd->body && sd->type) {
+ struct groupchat *gc = bee_chat_by_title(ic->bee, ic, info);
+ int i;
+ for (i = 0; i < g_list_length(sd->body); i++) {
+ char *body = g_list_nth_data(sd->body, i);
+ if (!strcmp(sd->type, "SAID") ||
+ !strcmp(sd->type, "EMOTED")) {
+ if (!strcmp(sd->type, "SAID")) {
+ if (!sd->is_edit)
+ g_snprintf(buf, IRC_LINE_SIZE, "%s",
+ body);
+ else {
+ g_snprintf(buf, IRC_LINE_SIZE, "%s %s",
+ set_getstr(&ic->acc->set, "edit_prefix"),
+ body);
+ sd->is_edit = 0;
+ }
+ } else
+ g_snprintf(buf, IRC_LINE_SIZE, "/me %s",
+ body);
+ if (!gc)
+ /* Private message */
+ imcb_buddy_msg(ic,
+ sd->handle, buf, 0, 0);
+ else
+ /* Groupchat message */
+ imcb_chat_msg(gc,
+ sd->handle, buf, 0, 0);
+ } else if (!strcmp(sd->type, "SETTOPIC") && gc)
+ imcb_chat_topic(gc,
+ sd->handle, body, 0);
+ else if (!strcmp(sd->type, "LEFT") && gc)
+ imcb_chat_remove_buddy(gc,
+ sd->handle, NULL);
+ }
+ g_list_free(sd->body);
+ sd->body = NULL;
+ }
+ }
+}
+
+static void skype_parse_call(struct im_connection *ic, char *line)
+{
+ struct skype_data *sd = ic->proto_data;
+ char *id = strchr(line, ' ');
+ char buf[IRC_LINE_SIZE];
+
+ if (!++id)
+ return;
+ char *info = strchr(id, ' ');
+
+ if (!info)
+ return;
+ *info = '\0';
+ info++;
+ if (!strncmp(info, "FAILUREREASON ", 14))
+ sd->failurereason = atoi(strchr(info, ' '));
+ else if (!strcmp(info, "STATUS RINGING")) {
+ if (sd->call_id)
+ g_free(sd->call_id);
+ sd->call_id = g_strdup(id);
+ skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id);
+ sd->call_status = SKYPE_CALL_RINGING;
+ } else if (!strcmp(info, "STATUS MISSED")) {
+ skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id);
+ sd->call_status = SKYPE_CALL_MISSED;
+ } else if (!strcmp(info, "STATUS CANCELLED")) {
+ skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id);
+ sd->call_status = SKYPE_CALL_CANCELLED;
+ } else if (!strcmp(info, "STATUS FINISHED")) {
+ skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id);
+ sd->call_status = SKYPE_CALL_FINISHED;
+ } else if (!strcmp(info, "STATUS REFUSED")) {
+ skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id);
+ sd->call_status = SKYPE_CALL_REFUSED;
+ } else if (!strcmp(info, "STATUS UNPLACED")) {
+ if (sd->call_id)
+ g_free(sd->call_id);
+ /* Save the ID for later usage (Cancel/Finish). */
+ sd->call_id = g_strdup(id);
+ sd->call_out = TRUE;
+ } else if (!strcmp(info, "STATUS FAILED")) {
+ imcb_error(ic, "Call failed: %s",
+ skype_call_strerror(sd->failurereason));
+ sd->call_id = NULL;
+ } else if (!strncmp(info, "DURATION ", 9)) {
+ if (sd->call_duration)
+ g_free(sd->call_duration);
+ sd->call_duration = g_strdup(info+9);
+ } else if (!strncmp(info, "PARTNER_HANDLE ", 15)) {
+ info += 15;
+ if (!sd->call_status)
+ return;
+ switch (sd->call_status) {
+ case SKYPE_CALL_RINGING:
+ if (sd->call_out)
+ imcb_log(ic, "You are currently ringing "
+ "the user %s.", info);
+ else {
+ g_snprintf(buf, IRC_LINE_SIZE,
+ "The user %s is currently ringing you.",
+ info);
+ skype_call_ask(ic, sd->call_id, buf);
+ }
+ break;
+ case SKYPE_CALL_MISSED:
+ imcb_log(ic, "You have missed a call from user %s.",
+ info);
+ break;
+ case SKYPE_CALL_CANCELLED:
+ imcb_log(ic, "You cancelled the call to the user %s.",
+ info);
+ sd->call_status = 0;
+ sd->call_out = FALSE;
+ break;
+ case SKYPE_CALL_REFUSED:
+ if (sd->call_out)
+ imcb_log(ic, "The user %s refused the call.",
+ info);
+ else
+ imcb_log(ic,
+ "You refused the call from user %s.",
+ info);
+ sd->call_out = FALSE;
+ break;
+ case SKYPE_CALL_FINISHED:
+ if (sd->call_duration)
+ imcb_log(ic,
+ "You finished the call to the user %s "
+ "(duration: %s seconds).",
+ info, sd->call_duration);
+ else
+ imcb_log(ic,
+ "You finished the call to the user %s.",
+ info);
+ sd->call_out = FALSE;
+ break;
+ default:
+ /* Don't be noisy, ignore other statuses for now. */
+ break;
+ }
+ sd->call_status = 0;
+ }
+}
+
+static void skype_parse_filetransfer(struct im_connection *ic, char *line)
+{
+ struct skype_data *sd = ic->proto_data;
+ char *id = strchr(line, ' ');
+
+ if (!++id)
+ return;
+ char *info = strchr(id, ' ');
+
+ if (!info)
+ return;
+ *info = '\0';
+ info++;
+ if (!strcmp(info, "STATUS NEW")) {
+ skype_printf(ic, "GET FILETRANSFER %s PARTNER_HANDLE\n",
+ id);
+ sd->filetransfer_status = SKYPE_FILETRANSFER_NEW;
+ } else if (!strcmp(info, "STATUS FAILED")) {
+ skype_printf(ic, "GET FILETRANSFER %s PARTNER_HANDLE\n",
+ id);
+ sd->filetransfer_status = SKYPE_FILETRANSFER_FAILED;
+ } else if (!strncmp(info, "PARTNER_HANDLE ", 15)) {
+ info += 15;
+ if (!sd->filetransfer_status)
+ return;
+ switch (sd->filetransfer_status) {
+ case SKYPE_FILETRANSFER_NEW:
+ imcb_log(ic, "The user %s offered a new file for you.",
+ info);
+ break;
+ case SKYPE_FILETRANSFER_FAILED:
+ imcb_log(ic, "Failed to transfer file from user %s.",
+ info);
+ break;
+ }
+ sd->filetransfer_status = 0;
+ }
+}
+
+static struct skype_group *skype_group_by_id(struct im_connection *ic, int id)
+{
+ struct skype_data *sd = ic->proto_data;
+ int i;
+
+ for (i = 0; i < g_list_length(sd->groups); i++) {
+ struct skype_group *sg = (struct skype_group *)g_list_nth_data(sd->groups, i);
+
+ if (sg->id == id)
+ return sg;
+ }
+ return NULL;
+}
+
+static void skype_group_free(struct skype_group *sg, gboolean usersonly)
+{
+ int i;
+
+ for (i = 0; i < g_list_length(sg->users); i++) {
+ char *user = g_list_nth_data(sg->users, i);
+ g_free(user);
+ }
+ sg->users = NULL;
+ if (usersonly)
+ return;
+ g_free(sg->name);
+ g_free(sg);
+}
+
+/* Update the group of each user in this group */
+static void skype_group_users(struct im_connection *ic, struct skype_group *sg)
+{
+ int i;
+
+ for (i = 0; i < g_list_length(sg->users); i++) {
+ char *user = g_list_nth_data(sg->users, i);
+ char *buf = g_strdup_printf("%s@skype.com", user);
+ imcb_add_buddy(ic, buf, sg->name);
+ g_free(buf);
+ }
+}
+
+static void skype_parse_group(struct im_connection *ic, char *line)
+{
+ struct skype_data *sd = ic->proto_data;
+ char *id = strchr(line, ' ');
+
+ if (!++id)
+ return;
+
+ char *info = strchr(id, ' ');
+
+ if (!info)
+ return;
+ *info = '\0';
+ info++;
+
+ if (!strncmp(info, "DISPLAYNAME ", 12)) {
+ info += 12;
+
+ /* Name given for a group ID: try to update it or insert a new
+ * one if not found */
+ struct skype_group *sg = skype_group_by_id(ic, atoi(id));
+ if (sg) {
+ g_free(sg->name);
+ sg->name = g_strdup(info);
+ } else {
+ sg = g_new0(struct skype_group, 1);
+ sg->id = atoi(id);
+ sg->name = g_strdup(info);
+ sd->groups = g_list_append(sd->groups, sg);
+ }
+ } else if (!strncmp(info, "USERS ", 6)) {
+ struct skype_group *sg = skype_group_by_id(ic, atoi(id));
+
+ if (sg) {
+ char **i;
+ char **users = g_strsplit(info + 6, ", ", 0);
+
+ skype_group_free(sg, TRUE);
+ i = users;
+ while (*i) {
+ sg->users = g_list_append(sg->users, g_strdup(*i));
+ i++;
+ }
+ g_strfreev(users);
+ skype_group_users(ic, sg);
+ } else
+ log_message(LOGLVL_ERROR,
+ "No skype group with id %s. That's probably a bug.", id);
+ } else if (!strncmp(info, "NROFUSERS ", 10)) {
+ if (!sd->pending_user) {
+ /* Number of users changed in this group, query its type to see
+ * if it's a custom one we should care about. */
+ skype_printf(ic, "GET GROUP %s TYPE", id);
+ return;
+ }
+
+ /* This is a newly created group, we have a single user
+ * to add. */
+ struct skype_group *sg = skype_group_by_id(ic, atoi(id));
+
+ if (sg) {
+ skype_printf(ic, "ALTER GROUP %d ADDUSER %s", sg->id, sd->pending_user);
+ g_free(sd->pending_user);
+ sd->pending_user = NULL;
+ } else
+ log_message(LOGLVL_ERROR,
+ "No skype group with id %s. That's probably a bug.", id);
+ } else if (!strcmp(info, "TYPE CUSTOM_GROUP"))
+ /* This one is interesting, query its users. */
+ skype_printf(ic, "GET GROUP %s USERS", id);
+}
+
+static void skype_parse_chat(struct im_connection *ic, char *line)
+{
+ struct skype_data *sd = ic->proto_data;
+ char buf[IRC_LINE_SIZE];
+ char *id = strchr(line, ' ');
+
+ if (!++id)
+ return;
+ struct groupchat *gc;
+ char *info = strchr(id, ' ');
+
+ if (!info)
+ return;
+ *info = '\0';
+ info++;
+ /* Remove fake chat if we created one in skype_chat_with() */
+ gc = bee_chat_by_title(ic->bee, ic, "");
+ if (gc)
+ imcb_chat_free(gc);
+ if (!strcmp(info, "STATUS MULTI_SUBSCRIBED")) {
+ gc = imcb_chat_new(ic, id);
+ imcb_chat_name_hint(gc, id);
+ skype_printf(ic, "GET CHAT %s ADDER\n", id);
+ skype_printf(ic, "GET CHAT %s TOPIC\n", id);
+ } else if (!strcmp(info, "STATUS DIALOG") && sd->groupchat_with) {
+ gc = imcb_chat_new(ic, id);
+ imcb_chat_name_hint(gc, id);
+ /* According to the docs this
+ * is necessary. However it
+ * does not seem the situation
+ * and it would open an extra
+ * window on our client, so
+ * just leave it out. */
+ /*skype_printf(ic, "OPEN CHAT %s\n", id);*/
+ g_snprintf(buf, IRC_LINE_SIZE, "%s@skype.com",
+ sd->groupchat_with);
+ imcb_chat_add_buddy(gc, buf);
+ imcb_chat_add_buddy(gc, sd->username);
+ g_free(sd->groupchat_with);
+ sd->groupchat_with = NULL;
+ skype_printf(ic, "GET CHAT %s ADDER\n", id);
+ skype_printf(ic, "GET CHAT %s TOPIC\n", id);
+ } else if (!strcmp(info, "STATUS UNSUBSCRIBED")) {
+ gc = bee_chat_by_title(ic->bee, ic, id);
+ if (gc)
+ gc->data = (void *)FALSE;
+ } else if (!strncmp(info, "ADDER ", 6)) {
+ info += 6;
+ g_free(sd->adder);
+ sd->adder = g_strdup_printf("%s@skype.com", info);
+ } else if (!strncmp(info, "TOPIC ", 6)) {
+ info += 6;
+ gc = bee_chat_by_title(ic->bee, ic, id);
+ if (gc && (sd->adder || sd->topic_wait)) {
+ if (sd->topic_wait) {
+ sd->adder = g_strdup(sd->username);
+ sd->topic_wait = 0;
+ }
+ imcb_chat_topic(gc, sd->adder, info, 0);
+ g_free(sd->adder);
+ sd->adder = NULL;
+ }
+ } else if (!strncmp(info, "ACTIVEMEMBERS ", 14)) {
+ info += 14;
+ gc = bee_chat_by_title(ic->bee, ic, id);
+ /* Hack! We set ->data to TRUE
+ * while we're on the channel
+ * so that we won't rejoin
+ * after a /part. */
+ if (!gc || gc->data)
+ return;
+ char **members = g_strsplit(info, " ", 0);
+ int i;
+ for (i = 0; members[i]; i++) {
+ if (!strcmp(members[i], sd->username))
+ continue;
+ g_snprintf(buf, IRC_LINE_SIZE, "%s@skype.com",
+ members[i]);
+ if (!g_list_find_custom(gc->in_room, buf,
+ (GCompareFunc)strcmp))
+ imcb_chat_add_buddy(gc, buf);
+ }
+ imcb_chat_add_buddy(gc, sd->username);
+ g_strfreev(members);
+ }
+}
+
+static void skype_parse_password(struct im_connection *ic, char *line)
+{
+ if (!strncmp(line+9, "OK", 2))
+ imcb_connected(ic);
+ else {
+ imcb_error(ic, "Authentication Failed");
+ imc_logout(ic, TRUE);
+ }
+}
+
+static void skype_parse_profile(struct im_connection *ic, char *line)
+{
+ imcb_log(ic, "SkypeOut balance value is '%s'.", line+21);
+}
+
+static void skype_parse_ping(struct im_connection *ic, char *line)
+{
+ /* Unused parameter */
+ line = line;
+ skype_printf(ic, "PONG\n");
+}
+
+static void skype_parse_chats(struct im_connection *ic, char *line)
+{
+ char **i;
+ char **chats = g_strsplit(line + 6, ", ", 0);
+
+ i = chats;
+ while (*i) {
+ skype_printf(ic, "GET CHAT %s STATUS\n", *i);
+ skype_printf(ic, "GET CHAT %s ACTIVEMEMBERS\n", *i);
+ i++;
+ }
+ g_strfreev(chats);
+}
+
+static void skype_parse_groups(struct im_connection *ic, char *line)
+{
+ char **i;
+ char **groups = g_strsplit(line + 7, ", ", 0);
+
+ i = groups;
+ while (*i) {
+ skype_printf(ic, "GET GROUP %s DISPLAYNAME\n", *i);
+ skype_printf(ic, "GET GROUP %s USERS\n", *i);
+ i++;
+ }
+ g_strfreev(groups);
+}
+
+static void skype_parse_alter_group(struct im_connection *ic, char *line)
+{
+ char *id = line + strlen("ALTER GROUP");
+
+ if (!++id)
+ return;
+
+ char *info = strchr(id, ' ');
+
+ if (!info)
+ return;
+ *info = '\0';
+ info++;
+
+ if (!strncmp(info, "ADDUSER ", 8)) {
+ struct skype_group *sg = skype_group_by_id(ic, atoi(id));
+
+ info += 8;
+ if (sg) {
+ char *buf = g_strdup_printf("%s@skype.com", info);
+ sg->users = g_list_append(sg->users, g_strdup(info));
+ imcb_add_buddy(ic, buf, sg->name);
+ g_free(buf);
+ } else
+ log_message(LOGLVL_ERROR,
+ "No skype group with id %s. That's probably a bug.", id);
+ }
+}
+
+typedef void (*skype_parser)(struct im_connection *ic, char *line);
+
+static gboolean skype_read_callback(gpointer data, gint fd,
+ b_input_condition cond)
+{
+ struct im_connection *ic = data;
+ struct skype_data *sd = ic->proto_data;
+ char buf[IRC_LINE_SIZE];
+ int st, i;
+ char **lines, **lineptr, *line;
+ static struct parse_map {
+ char *k;
+ skype_parser v;
+ } parsers[] = {
+ { "USERS ", skype_parse_users },
+ { "USER ", skype_parse_user },
+ { "CHATMESSAGE ", skype_parse_chatmessage },
+ { "CALL ", skype_parse_call },
+ { "FILETRANSFER ", skype_parse_filetransfer },
+ { "CHAT ", skype_parse_chat },
+ { "GROUP ", skype_parse_group },
+ { "PASSWORD ", skype_parse_password },
+ { "PROFILE PSTN_BALANCE ", skype_parse_profile },
+ { "PING", skype_parse_ping },
+ { "CHATS ", skype_parse_chats },
+ { "GROUPS ", skype_parse_groups },
+ { "ALTER GROUP ", skype_parse_alter_group },
+ };
+
+ /* Unused parameters */
+ fd = fd;
+ cond = cond;
+
+ if (!sd || sd->fd == -1)
+ return FALSE;
+ /* Read the whole data. */
+ st = ssl_read(sd->ssl, buf, sizeof(buf));
+ if (st > 0) {
+ buf[st] = '\0';
+ /* Then split it up to lines. */
+ lines = g_strsplit(buf, "\n", 0);
+ lineptr = lines;
+ while ((line = *lineptr)) {
+ if (!strlen(line))
+ break;
+ if (set_getbool(&ic->acc->set, "skypeconsole_receive"))
+ imcb_buddy_msg(ic, "skypeconsole", line, 0, 0);
+ for (i = 0; i < ARRAY_SIZE(parsers); i++)
+ if (!strncmp(line, parsers[i].k,
+ strlen(parsers[i].k))) {
+ parsers[i].v(ic, line);
+ break;
+ }
+ lineptr++;
+ }
+ g_strfreev(lines);
+ } else if (st == 0 || (st < 0 && !sockerr_again())) {
+ closesocket(sd->fd);
+ sd->fd = -1;
+
+ imcb_error(ic, "Error while reading from server");
+ imc_logout(ic, TRUE);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+gboolean skype_start_stream(struct im_connection *ic)
+{
+ struct skype_data *sd = ic->proto_data;
+ int st;
+
+ if (!sd)
+ return FALSE;
+
+ if (sd->bfd <= 0)
+ sd->bfd = b_input_add(sd->fd, B_EV_IO_READ,
+ skype_read_callback, ic);
+
+ /* Log in */
+ skype_printf(ic, "USERNAME %s\n", ic->acc->user);
+ skype_printf(ic, "PASSWORD %s\n", ic->acc->pass);
+
+ /* This will download all buddies and groups. */
+ st = skype_printf(ic, "SEARCH GROUPS CUSTOM\n");
+ skype_printf(ic, "SEARCH FRIENDS\n");
+
+ skype_printf(ic, "SET USERSTATUS ONLINE\n");
+
+ /* Auto join to bookmarked chats if requested.*/
+ if (set_getbool(&ic->acc->set, "auto_join"))
+ skype_printf(ic, "SEARCH BOOKMARKEDCHATS\n");
+ return st;
+}
+
+gboolean skype_connected(gpointer data, void *source, b_input_condition cond)
+{
+ struct im_connection *ic = data;
+ struct skype_data *sd = ic->proto_data;
+
+ /* Unused parameter */
+ cond = cond;
+
+ if (!source) {
+ sd->ssl = NULL;
+ imcb_error(ic, "Could not connect to server");
+ imc_logout(ic, TRUE);
+ return FALSE;
+ }
+ imcb_log(ic, "Connected to server, logging in");
+
+ return skype_start_stream(ic);
+}
+
+static void skype_login(account_t *acc)
+{
+ struct im_connection *ic = imcb_new(acc);
+ struct skype_data *sd = g_new0(struct skype_data, 1);
+
+ ic->proto_data = sd;
+
+ imcb_log(ic, "Connecting");
+ sd->ssl = ssl_connect(set_getstr(&acc->set, "server"),
+ set_getint(&acc->set, "port"), skype_connected, ic);
+ sd->fd = sd->ssl ? ssl_getfd(sd->ssl) : -1;
+ sd->username = g_strdup(acc->user);
+
+ sd->ic = ic;
+
+ if (set_getbool(&acc->set, "skypeconsole"))
+ imcb_add_buddy(ic, "skypeconsole", NULL);
+}
+
+static void skype_logout(struct im_connection *ic)
+{
+ struct skype_data *sd = ic->proto_data;
+ int i;
+
+ skype_printf(ic, "SET USERSTATUS OFFLINE\n");
+
+ for (i = 0; i < g_list_length(sd->groups); i++) {
+ struct skype_group *sg = (struct skype_group *)g_list_nth_data(sd->groups, i);
+ skype_group_free(sg, FALSE);
+ }
+ g_free(sd->username);
+ g_free(sd->handle);
+ g_free(sd);
+ ic->proto_data = NULL;
+}
+
+static int skype_buddy_msg(struct im_connection *ic, char *who, char *message,
+ int flags)
+{
+ char *ptr, *nick;
+ int st;
+
+ /* Unused parameter */
+ flags = flags;
+
+ nick = g_strdup(who);
+ ptr = strchr(nick, '@');
+ if (ptr)
+ *ptr = '\0';
+
+ if (!strncmp(who, "skypeconsole", 12))
+ st = skype_printf(ic, "%s\n", message);
+ else
+ st = skype_printf(ic, "MESSAGE %s %s\n", nick, message);
+ g_free(nick);
+
+ return st;
+}
+
+const struct skype_away_state *skype_away_state_by_name(char *name)
+{
+ int i;
+
+ for (i = 0; skype_away_state_list[i].full_name; i++)
+ if (g_strcasecmp(skype_away_state_list[i].full_name, name) == 0)
+ return skype_away_state_list + i;
+
+ return NULL;
+}
+
+static void skype_set_away(struct im_connection *ic, char *state_txt,
+ char *message)
+{
+ const struct skype_away_state *state;
+
+ /* Unused parameter */
+ message = message;
+
+ if (state_txt == NULL)
+ state = skype_away_state_by_name("Online");
+ else
+ state = skype_away_state_by_name(state_txt);
+ skype_printf(ic, "SET USERSTATUS %s\n", state->code);
+}
+
+static GList *skype_away_states(struct im_connection *ic)
+{
+ static GList *l;
+ int i;
+
+ /* Unused parameter */
+ ic = ic;
+
+ if (l == NULL)
+ for (i = 0; skype_away_state_list[i].full_name; i++)
+ l = g_list_append(l,
+ (void *)skype_away_state_list[i].full_name);
+
+ return l;
+}
+
+static char *skype_set_display_name(set_t *set, char *value)
+{
+ account_t *acc = set->data;
+ struct im_connection *ic = acc->ic;
+
+ skype_printf(ic, "SET PROFILE FULLNAME %s", value);
+ return value;
+}
+
+static char *skype_set_balance(set_t *set, char *value)
+{
+ account_t *acc = set->data;
+ struct im_connection *ic = acc->ic;
+
+ skype_printf(ic, "GET PROFILE PSTN_BALANCE");
+ return value;
+}
+
+static void skype_call(struct im_connection *ic, char *value)
+{
+ char *nick = g_strdup(value);
+ char *ptr = strchr(nick, '@');
+
+ if (ptr)
+ *ptr = '\0';
+ skype_printf(ic, "CALL %s", nick);
+ g_free(nick);
+}
+
+static void skype_hangup(struct im_connection *ic)
+{
+ struct skype_data *sd = ic->proto_data;
+
+ if (sd->call_id) {
+ skype_printf(ic, "SET CALL %s STATUS FINISHED",
+ sd->call_id);
+ g_free(sd->call_id);
+ sd->call_id = 0;
+ } else
+ imcb_error(ic, "There are no active calls currently.");
+}
+
+static char *skype_set_call(set_t *set, char *value)
+{
+ account_t *acc = set->data;
+ struct im_connection *ic = acc->ic;
+
+ if (value)
+ skype_call(ic, value);
+ else
+ skype_hangup(ic);
+ return value;
+}
+
+static void skype_add_buddy(struct im_connection *ic, char *who, char *group)
+{
+ struct skype_data *sd = ic->proto_data;
+ char *nick, *ptr;
+
+ nick = g_strdup(who);
+ ptr = strchr(nick, '@');
+ if (ptr)
+ *ptr = '\0';
+
+ if (!group) {
+ skype_printf(ic, "SET USER %s BUDDYSTATUS 2 Please authorize me\n",
+ nick);
+ g_free(nick);
+ } else {
+ struct skype_group *sg = skype_group_by_name(ic, group);
+
+ if (!sg) {
+ /* No such group, we need to create it, then have to
+ * add the user once it's created. */
+ skype_printf(ic, "CREATE GROUP %s", group);
+ sd->pending_user = g_strdup(nick);
+ } else {
+ skype_printf(ic, "ALTER GROUP %d ADDUSER %s", sg->id, nick);
+ }
+ }
+}
+
+static void skype_remove_buddy(struct im_connection *ic, char *who, char *group)
+{
+ char *nick, *ptr;
+
+ /* Unused parameter */
+ group = group;
+
+ nick = g_strdup(who);
+ ptr = strchr(nick, '@');
+ if (ptr)
+ *ptr = '\0';
+ skype_printf(ic, "SET USER %s BUDDYSTATUS 1\n", nick);
+ g_free(nick);
+}
+
+void skype_chat_msg(struct groupchat *gc, char *message, int flags)
+{
+ struct im_connection *ic = gc->ic;
+
+ /* Unused parameter */
+ flags = flags;
+
+ skype_printf(ic, "CHATMESSAGE %s %s\n", gc->title, message);
+}
+
+void skype_chat_leave(struct groupchat *gc)
+{
+ struct im_connection *ic = gc->ic;
+ skype_printf(ic, "ALTER CHAT %s LEAVE\n", gc->title);
+ gc->data = (void *)TRUE;
+}
+
+void skype_chat_invite(struct groupchat *gc, char *who, char *message)
+{
+ struct im_connection *ic = gc->ic;
+ char *ptr, *nick;
+
+ /* Unused parameter */
+ who = who;
+
+ nick = g_strdup(message);
+ ptr = strchr(nick, '@');
+ if (ptr)
+ *ptr = '\0';
+ skype_printf(ic, "ALTER CHAT %s ADDMEMBERS %s\n", gc->title, nick);
+ g_free(nick);
+}
+
+void skype_chat_topic(struct groupchat *gc, char *message)
+{
+ struct im_connection *ic = gc->ic;
+ struct skype_data *sd = ic->proto_data;
+ skype_printf(ic, "ALTER CHAT %s SETTOPIC %s\n",
+ gc->title, message);
+ sd->topic_wait = 1;
+}
+
+struct groupchat *skype_chat_with(struct im_connection *ic, char *who)
+{
+ struct skype_data *sd = ic->proto_data;
+ char *ptr, *nick;
+ nick = g_strdup(who);
+ ptr = strchr(nick, '@');
+ if (ptr)
+ *ptr = '\0';
+ skype_printf(ic, "CHAT CREATE %s\n", nick);
+ sd->groupchat_with = g_strdup(nick);
+ g_free(nick);
+ /* We create a fake chat for now. We will replace it with a real one in
+ * the real callback. */
+ return imcb_chat_new(ic, "");
+}
+
+static void skype_get_info(struct im_connection *ic, char *who)
+{
+ char *ptr, *nick;
+ nick = g_strdup(who);
+ ptr = strchr(nick, '@');
+ if (ptr)
+ *ptr = '\0';
+ skype_printf(ic, "GET USER %s FULLNAME\n", nick);
+ skype_printf(ic, "GET USER %s PHONE_HOME\n", nick);
+ skype_printf(ic, "GET USER %s PHONE_OFFICE\n", nick);
+ skype_printf(ic, "GET USER %s PHONE_MOBILE\n", nick);
+ skype_printf(ic, "GET USER %s NROF_AUTHED_BUDDIES\n", nick);
+ skype_printf(ic, "GET USER %s TIMEZONE\n", nick);
+ skype_printf(ic, "GET USER %s LASTONLINETIMESTAMP\n", nick);
+ skype_printf(ic, "GET USER %s BIRTHDAY\n", nick);
+ skype_printf(ic, "GET USER %s SEX\n", nick);
+ skype_printf(ic, "GET USER %s LANGUAGE\n", nick);
+ skype_printf(ic, "GET USER %s COUNTRY\n", nick);
+ skype_printf(ic, "GET USER %s PROVINCE\n", nick);
+ skype_printf(ic, "GET USER %s CITY\n", nick);
+ skype_printf(ic, "GET USER %s HOMEPAGE\n", nick);
+ skype_printf(ic, "GET USER %s ABOUT\n", nick);
+}
+
+static void skype_set_my_name(struct im_connection *ic, char *info)
+{
+ skype_set_display_name(set_find(&ic->acc->set, "display_name"), info);
+}
+
+static void skype_init(account_t *acc)
+{
+ set_t *s;
+
+ s = set_add(&acc->set, "server", SKYPE_DEFAULT_SERVER, set_eval_account,
+ acc);
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add(&acc->set, "port", SKYPE_DEFAULT_PORT, set_eval_int, acc);
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add(&acc->set, "display_name", NULL, skype_set_display_name,
+ acc);
+ s->flags |= ACC_SET_NOSAVE | ACC_SET_ONLINE_ONLY;
+
+ s = set_add(&acc->set, "call", NULL, skype_set_call, acc);
+ s->flags |= ACC_SET_NOSAVE | ACC_SET_ONLINE_ONLY;
+
+ s = set_add(&acc->set, "balance", NULL, skype_set_balance, acc);
+ s->flags |= ACC_SET_NOSAVE | ACC_SET_ONLINE_ONLY;
+
+ s = set_add(&acc->set, "skypeout_offline", "true", set_eval_bool, acc);
+
+ s = set_add(&acc->set, "skypeconsole", "false", set_eval_bool, acc);
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add(&acc->set, "skypeconsole_receive", "false", set_eval_bool,
+ acc);
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add(&acc->set, "auto_join", "false", set_eval_bool, acc);
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add(&acc->set, "test_join", "false", set_eval_bool, acc);
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add(&acc->set, "show_moods", "false", set_eval_bool, acc);
+
+ s = set_add(&acc->set, "edit_prefix", "EDIT:",
+ NULL, acc);
+}
+
+#if BITLBEE_VERSION_CODE > BITLBEE_VER(3, 0, 1)
+GList *skype_buddy_action_list(bee_user_t *bu)
+{
+ static GList *ret;
+
+ /* Unused parameter */
+ bu = bu;
+
+ if (ret == NULL) {
+ static const struct buddy_action ba[3] = {
+ {"CALL", "Initiate a call" },
+ {"HANGUP", "Hang up a call" },
+ };
+
+ ret = g_list_prepend(ret, (void *) ba + 0);
+ }
+
+ return ret;
+}
+
+void *skype_buddy_action(struct bee_user *bu, const char *action, char * const args[], void *data)
+{
+ /* Unused parameters */
+ args = args;
+ data = data;
+
+ if (!g_strcasecmp(action, "CALL"))
+ skype_call(bu->ic, bu->handle);
+ else if (!g_strcasecmp(action, "HANGUP"))
+ skype_hangup(bu->ic);
+
+ return NULL;
+}
+#endif
+
+void init_plugin(void)
+{
+ struct prpl *ret = g_new0(struct prpl, 1);
+
+ ret->name = "skype";
+ ret->login = skype_login;
+ ret->init = skype_init;
+ ret->logout = skype_logout;
+ ret->buddy_msg = skype_buddy_msg;
+ ret->get_info = skype_get_info;
+ ret->set_my_name = skype_set_my_name;
+ ret->away_states = skype_away_states;
+ ret->set_away = skype_set_away;
+ ret->add_buddy = skype_add_buddy;
+ ret->remove_buddy = skype_remove_buddy;
+ ret->chat_msg = skype_chat_msg;
+ ret->chat_leave = skype_chat_leave;
+ ret->chat_invite = skype_chat_invite;
+ ret->chat_with = skype_chat_with;
+ ret->handle_cmp = g_strcasecmp;
+ ret->chat_topic = skype_chat_topic;
+#if BITLBEE_VERSION_CODE > BITLBEE_VER(3, 0, 1)
+ ret->buddy_action_list = skype_buddy_action_list;
+ ret->buddy_action = skype_buddy_action;
+#endif
+ register_protocol(ret);
+}
diff --git a/protocols/skype/skyped.cnf b/protocols/skype/skyped.cnf
new file mode 100644
index 00000000..c7dc9098
--- /dev/null
+++ b/protocols/skype/skyped.cnf
@@ -0,0 +1,40 @@
+# create RSA certs - Server
+
+RANDFILE = skyped.rnd
+
+[ req ]
+default_bits = 1024
+encrypt_key = yes
+distinguished_name = req_dn
+x509_extensions = cert_type
+
+[ req_dn ]
+countryName = Country Name (2 letter code)
+countryName_default = HU
+countryName_min = 2
+countryName_max = 2
+
+stateOrProvinceName = State or Province Name (full name)
+stateOrProvinceName_default = Some-State
+
+localityName = Locality Name (eg, city)
+
+0.organizationName = Organization Name (eg, company)
+0.organizationName_default = Stunnel Developers Ltd
+
+organizationalUnitName = Organizational Unit Name (eg, section)
+#organizationalUnitName_default =
+
+0.commonName = Common Name (FQDN of your server)
+0.commonName_default = localhost
+
+# To create a certificate for more than one name uncomment:
+# 1.commonName = DNS alias of your server
+# 2.commonName = DNS alias of your server
+# ...
+# See http://home.netscape.com/eng/security/ssl_2.0_certificate.html
+# to see how Netscape understands commonName.
+
+[ cert_type ]
+nsCertType = server
+
diff --git a/protocols/skype/skyped.conf.dist.in b/protocols/skype/skyped.conf.dist.in
new file mode 100644
index 00000000..21e07796
--- /dev/null
+++ b/protocols/skype/skyped.conf.dist.in
@@ -0,0 +1,10 @@
+[skyped]
+# change to your skype username
+username = john
+# use `echo -n foo|sha1sum` to generate this hash for your password
+password = 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33
+
+# you have to change the following paths to your home directory:
+cert = /home/YOUR_USER/.skyped/skyped.cert.pem
+key = /home/YOUR_USER/.skyped/skyped.key.pem
+port = 2727
diff --git a/protocols/skype/skyped.py b/protocols/skype/skyped.py
new file mode 100644
index 00000000..023c1f81
--- /dev/null
+++ b/protocols/skype/skyped.py
@@ -0,0 +1,488 @@
+#!/usr/bin/env python2.7
+#
+# skyped.py
+#
+# Copyright (c) 2007, 2008, 2009, 2010, 2011 by Miklos Vajna <vmiklos@frugalware.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+# USA.
+#
+
+import sys
+import os
+import signal
+import locale
+import time
+import socket
+import getopt
+import Skype4Py
+import hashlib
+from ConfigParser import ConfigParser, NoOptionError
+from traceback import print_exception
+import ssl
+
+__version__ = "0.1.1"
+
+try:
+ import gobject
+ hasgobject = True
+except ImportError:
+ import select
+ import threading
+ hasgobject = False
+
+def eh(type, value, tb):
+ global options
+
+ if type != KeyboardInterrupt:
+ print_exception(type, value, tb)
+ if hasgobject:
+ gobject.MainLoop().quit()
+ if options.conn:
+ options.conn.close()
+ # shut down client if it's running
+ try:
+ skype.skype.Client.Shutdown()
+ except NameError:
+ pass
+ sys.exit("Exiting.")
+
+sys.excepthook = eh
+
+def wait_for_lock(lock, timeout_to_print, timeout, msg):
+ start = time.time()
+ locked = lock.acquire(0)
+ while not(locked):
+ time.sleep(0.5)
+ if timeout_to_print and (time.time() - timeout_to_print > start):
+ dprint("%s: Waited %f seconds" % \
+ (msg, time.time() - start))
+ timeout_to_print = False
+ if timeout and (time.time() - timeout > start):
+ dprint("%s: Waited %f seconds, giving up" % \
+ (msg, time.time() - start))
+ return False
+ locked = lock.acquire(0)
+ return True
+
+def input_handler(fd, io_condition = None):
+ global options
+ global skype
+ if options.buf:
+ for i in options.buf:
+ skype.send(i.strip())
+ options.buf = None
+ if not hasgobject:
+ return True
+ else:
+ if not hasgobject:
+ close_socket = False
+ if wait_for_lock(options.lock, 3, 10, "input_handler"):
+ try:
+ input = fd.recv(1024)
+ options.lock.release()
+ except Exception, s:
+ dprint("Warning, receiving 1024 bytes failed (%s)." % s)
+ fd.close()
+ options.conn = False
+ options.lock.release()
+ return False
+ for i in input.split("\n"):
+ if i.strip() == "SET USERSTATUS OFFLINE":
+ close_socket = True
+ skype.send(i.strip())
+ return not(close_socket)
+ try:
+ input = fd.recv(1024)
+ except Exception, s:
+ dprint("Warning, receiving 1024 bytes failed (%s)." % s)
+ fd.close()
+ return False
+ for i in input.split("\n"):
+ skype.send(i.strip())
+ return True
+
+def skype_idle_handler(skype):
+ try:
+ c = skype.skype.Command("PING", Block=True)
+ skype.skype.SendCommand(c)
+ except Skype4Py.SkypeAPIError, s:
+ dprint("Warning, pinging Skype failed (%s)." % (s))
+ return True
+
+def send(sock, txt):
+ global options
+ from time import sleep
+ count = 1
+ done = False
+ if hasgobject:
+ while (not done) and (count < 10):
+ try:
+ sock.send(txt)
+ done = True
+ except Exception, s:
+ count += 1
+ dprint("Warning, sending '%s' failed (%s). count=%d" % (txt, s, count))
+ sleep(1)
+ if not done:
+ options.conn.close()
+ else:
+ while (not done) and (count < 10) and options.conn:
+ if wait_for_lock(options.lock, 3, 10, "socket send"):
+ try:
+ if options.conn: sock.send(txt)
+ options.lock.release()
+ done = True
+ except Exception, s:
+ options.lock.release()
+ count += 1
+ dprint("Warning, sending '%s' failed (%s). count=%d" % (txt, s, count))
+ sleep(1)
+ if not done:
+ if options.conn:
+ options.conn.close()
+ options.conn = False
+ return done
+
+def bitlbee_idle_handler(skype):
+ global options
+ done = False
+ if options.conn:
+ try:
+ e = "PING"
+ done = send(options.conn, "%s\n" % e)
+ except Exception, s:
+ dprint("Warning, sending '%s' failed (%s)." % (e, s))
+ if hasgobject:
+ options.conn.close()
+ else:
+ if options.conn: options.conn.close()
+ options.conn = False
+ done = False
+ if hasgobject:
+ return True
+ else:
+ return done
+ return True
+
+def server(host, port, skype = None):
+ global options
+ sock = socket.socket()
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.bind((host, port))
+ sock.listen(1)
+ if hasgobject:
+ gobject.io_add_watch(sock, gobject.IO_IN, listener)
+ else:
+ dprint("Waiting for connection...")
+ listener(sock, skype)
+
+def listener(sock, skype):
+ global options
+ if not hasgobject:
+ if not(wait_for_lock(options.lock, 3, 10, "listener")): return False
+ rawsock, addr = sock.accept()
+ options.conn = ssl.wrap_socket(rawsock,
+ server_side=True,
+ certfile=options.config.sslcert,
+ keyfile=options.config.sslkey,
+ ssl_version=ssl.PROTOCOL_TLSv1)
+ if hasattr(options.conn, 'handshake'):
+ try:
+ options.conn.handshake()
+ except Exception:
+ if not hasgobject:
+ options.lock.release()
+ dprint("Warning, handshake failed, closing connection.")
+ return False
+ ret = 0
+ try:
+ line = options.conn.recv(1024)
+ if line.startswith("USERNAME") and line.split(' ')[1].strip() == options.config.username:
+ ret += 1
+ line = options.conn.recv(1024)
+ if line.startswith("PASSWORD") and hashlib.sha1(line.split(' ')[1].strip()).hexdigest() == options.config.password:
+ ret += 1
+ except Exception, s:
+ dprint("Warning, receiving 1024 bytes failed (%s)." % s)
+ options.conn.close()
+ if not hasgobject:
+ options.conn = False
+ options.lock.release()
+ return False
+ if ret == 2:
+ dprint("Username and password OK.")
+ options.conn.send("PASSWORD OK\n")
+ if hasgobject:
+ gobject.io_add_watch(options.conn, gobject.IO_IN, input_handler)
+ else:
+ options.lock.release()
+ serverloop(options, skype)
+ return True
+ else:
+ dprint("Username and/or password WRONG.")
+ options.conn.send("PASSWORD KO\n")
+ if not hasgobject:
+ options.conn.close()
+ options.conn = False
+ options.lock.release()
+ return False
+
+def dprint(msg):
+ from time import strftime
+ global options
+
+ now = strftime("%Y-%m-%d %H:%M:%S")
+
+ if options.debug:
+ try:
+ print now + ": " + msg
+ except Exception, s:
+ try:
+ sanitized = msg.encode("ascii", "backslashreplace")
+ except Error, s:
+ try:
+ sanitized = "hex [" + msg.encode("hex") + "]"
+ except Error, s:
+ sanitized = "[unable to print debug message]"
+ print now + "~=" + sanitized
+ sys.stdout.flush()
+ if options.log:
+ sock = open(options.log, "a")
+ sock.write("%s: %s\n" % (now, msg))
+ sock.close()
+
+class SkypeApi:
+ def __init__(self):
+ self.skype = Skype4Py.Skype()
+ self.skype.OnNotify = self.recv
+ self.skype.Client.Start()
+
+ def recv(self, msg_text):
+ global options
+ if msg_text == "PONG":
+ return
+ if "\n" in msg_text:
+ # crappy skype prefixes only the first line for
+ # multiline messages so we need to do so for the other
+ # lines, too. this is something like:
+ # 'CHATMESSAGE id BODY first line\nsecond line' ->
+ # 'CHATMESSAGE id BODY first line\nCHATMESSAGE id BODY second line'
+ prefix = " ".join(msg_text.split(" ")[:3])
+ msg_text = ["%s %s" % (prefix, i) for i in " ".join(msg_text.split(" ")[3:]).split("\n")]
+ else:
+ msg_text = [msg_text]
+ for i in msg_text:
+ try:
+ # Internally, BitlBee always uses UTF-8 and encodes/decodes as
+ # necessary to communicate with the IRC client; thus send the
+ # UTF-8 it expects
+ e = i.encode('UTF-8')
+ except:
+ # Should never happen, but it's better to send difficult to
+ # read data than crash because some message couldn't be encoded
+ e = i.encode('ascii', 'backslashreplace')
+ if options.conn:
+ dprint('<< ' + e)
+ try:
+ send(options.conn, e + "\n")
+ except Exception, s:
+ dprint("Warning, sending '%s' failed (%s)." % (e, s))
+ if options.conn: options.conn.close()
+ options.conn = False
+ else:
+ dprint('-- ' + e)
+
+ def send(self, msg_text):
+ if not len(msg_text) or msg_text == "PONG":
+ if msg_text == "PONG":
+ options.last_bitlbee_pong = time.time()
+ return
+ try:
+ # Internally, BitlBee always uses UTF-8 and encodes/decodes as
+ # necessary to communicate with the IRC client; thus decode the
+ # UTF-8 it sent us
+ e = msg_text.decode('UTF-8')
+ except:
+ # Should never happen, but it's better to send difficult to read
+ # data to Skype than to crash
+ e = msg_text.decode('ascii', 'backslashreplace')
+ dprint('>> ' + e)
+ try:
+ c = self.skype.Command(e, Block=True)
+ self.skype.SendCommand(c)
+ self.recv(c.Reply)
+ except Skype4Py.SkypeError:
+ pass
+ except Skype4Py.SkypeAPIError, s:
+ dprint("Warning, sending '%s' failed (%s)." % (e, s))
+
+class Options:
+ def __init__(self):
+ self.cfgpath = os.path.join(os.environ['HOME'], ".skyped", "skyped.conf")
+ # for backwards compatibility
+ self.syscfgpath = "/usr/local/etc/skyped/skyped.conf"
+ if os.path.exists(self.syscfgpath):
+ self.cfgpath = self.syscfgpath
+ self.daemon = True
+ self.debug = False
+ self.help = False
+ self.host = "0.0.0.0"
+ self.log = None
+ self.port = None
+ self.version = False
+ # well, this is a bit hackish. we store the socket of the last connected client
+ # here and notify it. maybe later notify all connected clients?
+ self.conn = None
+ # this will be read first by the input handler
+ self.buf = None
+
+
+ def usage(self, ret):
+ print """Usage: skyped [OPTION]...
+
+skyped is a daemon that acts as a tcp server on top of a Skype instance.
+
+Options:
+ -c --config path to configuration file (default: %s)
+ -d --debug enable debug messages
+ -h --help this help
+ -H --host set the tcp host (default: %s)
+ -l --log set the log file in background mode (default: none)
+ -n --nofork don't run as daemon in the background
+ -p --port set the tcp port (default: %s)
+ -v --version display version information""" % (self.cfgpath, self.host, self.port)
+ sys.exit(ret)
+
+def serverloop(options, skype):
+ timeout = 1; # in seconds
+ skype_ping_period = 5
+ bitlbee_ping_period = 10
+ bitlbee_pong_timeout = 30
+ now = time.time()
+ skype_ping_start_time = now
+ bitlbee_ping_start_time = now
+ options.last_bitlbee_pong = now
+ in_error = []
+ handler_ok = True
+ while (len(in_error) == 0) and handler_ok and options.conn:
+ ready_to_read, ready_to_write, in_error = \
+ select.select([options.conn], [], [options.conn], \
+ timeout)
+ now = time.time()
+ handler_ok = len(in_error) == 0
+ if (len(ready_to_read) == 1) and handler_ok:
+ handler_ok = input_handler(ready_to_read.pop())
+ # don't ping bitlbee/skype if they already received data
+ now = time.time() # allow for the input_handler to take some time
+ bitlbee_ping_start_time = now
+ skype_ping_start_time = now
+ options.last_bitlbee_pong = now
+ if (now - skype_ping_period > skype_ping_start_time) and handler_ok:
+ handler_ok = skype_idle_handler(skype)
+ skype_ping_start_time = now
+ if now - bitlbee_ping_period > bitlbee_ping_start_time:
+ handler_ok = bitlbee_idle_handler(skype)
+ bitlbee_ping_start_time = now
+ if options.last_bitlbee_pong:
+ if (now - options.last_bitlbee_pong) > bitlbee_pong_timeout:
+ dprint("Bitlbee pong timeout")
+ # TODO is following line necessary? Should there be a options.conn.unwrap() somewhere?
+ # options.conn.shutdown()
+ if options.conn:
+ options.conn.close()
+ options.conn = False
+ else:
+ options.last_bitlbee_pong = now
+
+if __name__=='__main__':
+ options = Options()
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "c:dhH:l:np:v", ["config=", "debug", "help", "host=", "log=", "nofork", "port=", "version"])
+ except getopt.GetoptError:
+ options.usage(1)
+ for opt, arg in opts:
+ if opt in ("-c", "--config"):
+ options.cfgpath = arg
+ elif opt in ("-d", "--debug"):
+ options.debug = True
+ elif opt in ("-h", "--help"):
+ options.help = True
+ elif opt in ("-H", "--host"):
+ options.host = arg
+ elif opt in ("-l", "--log"):
+ options.log = arg
+ elif opt in ("-n", "--nofork"):
+ options.daemon = False
+ elif opt in ("-p", "--port"):
+ options.port = int(arg)
+ elif opt in ("-v", "--version"):
+ options.version = True
+ if options.help:
+ options.usage(0)
+ elif options.version:
+ print "skyped %s" % __version__
+ sys.exit(0)
+ # parse our config
+ if not os.path.exists(options.cfgpath):
+ print "Can't find configuration file at '%s'." % options.cfgpath
+ print "Use the -c option to specify an alternate one."
+ sys.exit(1)
+ options.config = ConfigParser()
+ options.config.read(options.cfgpath)
+ options.config.username = options.config.get('skyped', 'username').split('#')[0]
+ options.config.password = options.config.get('skyped', 'password').split('#')[0]
+ options.config.sslkey = os.path.expanduser(options.config.get('skyped', 'key').split('#')[0])
+ options.config.sslcert = os.path.expanduser(options.config.get('skyped', 'cert').split('#')[0])
+ # hack: we have to parse the parameters first to locate the
+ # config file but the -p option should overwrite the value from
+ # the config file
+ try:
+ options.config.port = int(options.config.get('skyped', 'port').split('#')[0])
+ if not options.port:
+ options.port = options.config.port
+ except NoOptionError:
+ pass
+ if not options.port:
+ options.port = 2727
+ dprint("Parsing config file '%s' done, username is '%s'." % (options.cfgpath, options.config.username))
+ if options.daemon:
+ pid = os.fork()
+ if pid == 0:
+ nullin = file(os.devnull, 'r')
+ nullout = file(os.devnull, 'w')
+ os.dup2(nullin.fileno(), sys.stdin.fileno())
+ os.dup2(nullout.fileno(), sys.stdout.fileno())
+ os.dup2(nullout.fileno(), sys.stderr.fileno())
+ else:
+ print 'skyped is started on port %s, pid: %d' % (options.port, pid)
+ sys.exit(0)
+ else:
+ dprint('skyped is started on port %s' % options.port)
+ if hasgobject:
+ server(options.host, options.port)
+ try:
+ skype = SkypeApi()
+ except Skype4Py.SkypeAPIError, s:
+ sys.exit("%s. Are you sure you have started Skype?" % s)
+ if hasgobject:
+ gobject.timeout_add(2000, skype_idle_handler, skype)
+ gobject.timeout_add(60000, bitlbee_idle_handler, skype)
+ gobject.MainLoop().run()
+ else:
+ while 1:
+ options.conn = False
+ options.lock = threading.Lock()
+ server(options.host, options.port, skype)
diff --git a/protocols/skype/skyped.txt b/protocols/skype/skyped.txt
new file mode 100644
index 00000000..53f2626d
--- /dev/null
+++ b/protocols/skype/skyped.txt
@@ -0,0 +1,52 @@
+= skyped(1)
+
+== NAME
+
+skyped - allows remote control of the Skype GUI client
+
+== SYNOPSIS
+
+skyped [<options>]
+
+== DESCRIPTION
+
+Skype supports remote control of the GUI client only via X11 or DBus
+messages. This is hard in care you want remote control. This daemon
+listens on a TCP port and runs on the same machine where the GUI client
+runs. It passes all the input it gets to Skype directly, except for a
+few commands which is related to authentication. The whole communication
+is done via SSL.
+
+== CONFIGURATION
+
+See the README for information about how to configure this daemon.
+
+== OPTIONS
+
+-c, --config::
+ Path to configuration file (default: $HOME/.skyped/skyped.conf)
+
+-d, --debug::
+ Enable debug messages
+
+-h, --help::
+ Show short summary of options
+
+-H, --host::
+ Set the tcp host (default: 0.0.0.0)
+
+-l, --log::
+ Set the log file in background mode (default: none)
+
+-n, --nofork::
+ Don't run as daemon in the background
+
+-p, --port::
+ Set the tcp port (default: 2727)
+
+-v, --version::
+ Display version information
+
+== AUTHOR
+
+Written by Miklos Vajna <vmiklos@frugalware.org>
diff --git a/protocols/skype/t/Makefile b/protocols/skype/t/Makefile
new file mode 100644
index 00000000..9c5e95f9
--- /dev/null
+++ b/protocols/skype/t/Makefile
@@ -0,0 +1,33 @@
+PORT=9876
+BITLBEE=/usr/sbin/bitlbee
+
+export TEST_SKYPE_ID=user
+export TEST_SKYPE_PASSWORD=pass
+
+testfiles := $(wildcard irssi/*.test)
+tests := $(patsubst %.test,%,$(testfiles))
+
+.PHONY: $(tests)
+
+all: $(tests)
+ @echo "passed $$(echo $(testfiles)|wc -w) tests."
+
+$(tests): % : %.test
+ @echo "--- Running test $@ ---"; \
+ if [ -r "$(BITLBEE)" -a -x "$(BITLBEE)" ]; then \
+ bitlbee_binary="$(BITLBEE)"; \
+ else \
+ bitlbee_basename=`basename $(BITLBEE)`; \
+ bitlbee_binary=`which $$bitlbee_basename`; \
+ fi; \
+ if ! ./livetest-bitlbee.sh "$$bitlbee_binary" $(PORT) irssi/livetest-irssi.sh $< >$@.log; then \
+ echo Test failed, log: ;\
+ cat $@.log;\
+ exit 1;\
+ fi;\
+ echo "--- OK ---" ;\
+ sleep 1
+clean:
+ rm -r irssi/*.log bitlbeetest.pid dotirssi livetest
+
+
diff --git a/protocols/skype/t/bitlbee.conf b/protocols/skype/t/bitlbee.conf
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/protocols/skype/t/bitlbee.conf
diff --git a/protocols/skype/t/irssi/livetest-irssi.sh b/protocols/skype/t/irssi/livetest-irssi.sh
new file mode 100755
index 00000000..a8e136cf
--- /dev/null
+++ b/protocols/skype/t/irssi/livetest-irssi.sh
@@ -0,0 +1,109 @@
+#!/usr/bin/env bash
+ISCRIPT=$1
+OPT=$2
+
+[ -n "$ISCRIPT" ] || { echo Syntax: `basename "$0"` irssi-test-script; exit 1; }
+
+# Load variables from test
+eval `sed -e '1,/^###/!d;/^###/d' "$ISCRIPT"`
+
+#if [ "$OPT" == "checkvars" ]; then echo $TESTNEEDEDVARS; fi
+RET=0
+
+# Check if we have the neccessary environment variables for this test
+for var in $TESTNEEDEDVARS; do
+ if [ -z `eval echo \$\{$var\}` ]; then
+ if [ "$OPT" != "checkvars" ]; then
+ echo Need environment variable "$var" for this test.
+ exit 66
+ else
+ echo $var
+ RET=66
+ fi
+ fi
+done
+
+# if we got this far we're OK
+if [ "$OPT" == "checkvars" ]; then exit $RET; fi
+
+[ -n "$PORT" ] || { echo 'Need the bitlbee listening port as environment variable PORT'; exit 1; }
+
+# Setup the irssi dir
+(
+ rm -r dotirssi
+ mkdir -p dotirssi/scripts dotirssi/logs
+ cp "`dirname $0`"/trigger.pl dotirssi/scripts &&
+ echo 'script load trigger.pl' >dotirssi/startup
+) &>/dev/null || { echo Failed to setup irssi testdir; exit 1; }
+
+# write irssi config
+
+echo '
+
+aliases = {
+ runtest = "'`sed -e "1,/^###/d;s/@LOGIN@/$TESTLOGIN/;s/@PASSWORD@/$TESTPASSWORD/" "$ISCRIPT" | tr '\n' ';'`'";
+ expectbee = "/trigger add -publics -channels &bitlbee -regexp";
+ expectjoin = "/trigger add -joins -masks *!$0@* $1-";
+ expectmsg = "/trigger add -privmsgs -masks *!$0@* $1-";
+};
+
+servers = ( { address = "localhost"; chatnet = "local"; port = "'$PORT'"; autoconnect="yes";});
+
+settings = {
+ settings_autosave = "no";
+ core = { real_name = "bitlbee-test"; user_name = "bitlbee-test"; nick = "bitlbeetest"; };
+ "fe-text" = { actlist_sort = "refnum"; };
+};
+
+chatnets = { local = { type = "IRC"; autosendcmd = "/runtest"; }; };
+
+logs = {
+"dotirssi/logs/status.log" = { auto_open = "yes"; level = "ALL"; items = ( { type = "window"; name = "1"; } ); };
+"dotirssi/logs/control.log" = { auto_open = "yes"; level = "ALL"; items = ( { type = "target"; name = "&bitlbee"; } ); };
+' >dotirssi/config
+
+for nick in $TESTLOGNICKS; do
+ echo '
+ "dotirssi/logs/'$nick'.log" = { auto_open = "yes"; level = "ALL"; items = ( { type = "target"; name = "'$nick'"; } ); };
+ ' >>dotirssi/config
+done
+
+echo '};' >>dotirssi/config
+
+# Go!
+
+echo Running irssi...
+screen -D -m irssi --config=dotirssi/config --home=dotirssi/ &
+
+# output logs
+
+submitlogs() {
+ perl -p -i -e "s/$TESTLOGIN/---TESTLOGIN---/;s/$TESTPASSWORD/---TESTPASSWORD---/" dotirssi/logs/*.log
+
+ if [ "$OPT" == "tgz" ]; then
+ tar czf "`dirname $0`"/"`basename "$ISCRIPT"`".logs.tgz dotirssi/logs/*.log
+ elif [ "$OPT" == "ctest" ]; then
+ echo CTEST_FULL_OUTPUT
+ for log in dotirssi/logs/*.log; do
+ echo -n '<DartMeasurement name="'$log'" type="text/string"><![CDATA['
+ cat "$log"
+ echo "]]></DartMeasurement>"
+ done
+ else
+ echo Test logs: dotirssi/logs/*.log
+ fi
+}
+
+# timeout stuff
+
+t=$TESTDURATION
+intval=1
+while (( t >= intval )); do
+ sleep $intval
+ kill -0 $! &>/dev/null || { echo screen/irssi terminated.; submitlogs; bash -c "cd dotirssi/logs && $TESTCHECKRESULT" >/dev/null; exit $?; }
+ t=$(( t - $intval ))
+done
+echo Killing screen/irssi...
+kill $!
+submitlogs
+exit 22
diff --git a/protocols/skype/t/irssi/skype-call.test b/protocols/skype/t/irssi/skype-call.test
new file mode 100644
index 00000000..8f502a59
--- /dev/null
+++ b/protocols/skype/t/irssi/skype-call.test
@@ -0,0 +1,13 @@
+TESTNEEDEDVARS="TEST_SKYPE_ID TEST_SKYPE_PASSWORD"
+TESTDURATION=60
+TESTCHECKRESULT="grep '\[Test Passed\]' status.log"
+TESTLOGIN="$TEST_SKYPE_ID"
+TESTPASSWORD="$TEST_SKYPE_PASSWORD"
+### Test receiving call output
+/expectbee 'Welcome to the BitlBee' -command 'msg $$C register testing'
+/expectbee 'Account successfully created' -command 'msg $$C account add skype @LOGIN@ @PASSWORD@'
+/expectbee 'Account successfully added' -command 'msg $$C account 0 set test_join true'
+/expectbee 'test_join' -command 'msg $$C account 0 on'
+/expectjoin echo123 -command 'ctcp echo123 call'
+/expectbee 'You are currently ringing the user' -command 'ctcp echo123 hangup'
+/expectbee '(You cancelled the call|You finished the call)' -command 'quit Test Passed'
diff --git a/protocols/skype/t/irssi/skype-info.test b/protocols/skype/t/irssi/skype-info.test
new file mode 100644
index 00000000..e8507321
--- /dev/null
+++ b/protocols/skype/t/irssi/skype-info.test
@@ -0,0 +1,12 @@
+TESTNEEDEDVARS="TEST_SKYPE_ID TEST_SKYPE_PASSWORD"
+TESTDURATION=60
+TESTCHECKRESULT="grep '\[Test Passed\]' status.log"
+TESTLOGIN="$TEST_SKYPE_ID"
+TESTPASSWORD="$TEST_SKYPE_PASSWORD"
+### Test receiving info output
+/expectbee 'Welcome to the BitlBee' -command 'msg $$C register testing'
+/expectbee 'Account successfully created' -command 'msg $$C account add skype @LOGIN@ @PASSWORD@'
+/expectbee 'Account successfully added' -command 'msg $$C account 0 set test_join true'
+/expectbee 'test_join' -command 'msg $$C account 0 on'
+/expectjoin echo123 -command 'msg $$C info echo123'
+/expectbee 'Full Name: Echo / Sound Test Service' -command 'quit Test Passed'
diff --git a/protocols/skype/t/irssi/skype-login.test b/protocols/skype/t/irssi/skype-login.test
new file mode 100644
index 00000000..ca627002
--- /dev/null
+++ b/protocols/skype/t/irssi/skype-login.test
@@ -0,0 +1,10 @@
+TESTNEEDEDVARS="TEST_SKYPE_ID TEST_SKYPE_PASSWORD"
+TESTDURATION=10
+TESTCHECKRESULT="grep '\[Test Passed\]' status.log"
+TESTLOGIN="$TEST_SKYPE_ID"
+TESTPASSWORD="$TEST_SKYPE_PASSWORD"
+### Test login
+/expectbee 'Welcome to the BitlBee' -command 'msg $$C register testing'
+/expectbee 'Account successfully created' -command 'msg $$C account add skype @LOGIN@ @PASSWORD@'
+/expectbee 'Account successfully added' -command 'msg $$C account 0 on'
+/expectbee 'Logged in' -command 'quit Test Passed'
diff --git a/protocols/skype/t/irssi/skype-msg.test b/protocols/skype/t/irssi/skype-msg.test
new file mode 100644
index 00000000..d35615cd
--- /dev/null
+++ b/protocols/skype/t/irssi/skype-msg.test
@@ -0,0 +1,17 @@
+TESTNEEDEDVARS="TEST_SKYPE_ID TEST_SKYPE_PASSWORD"
+TESTDURATION=60
+TESTCHECKRESULT="grep '\[Test Passed\]' status.log"
+TESTLOGIN="$TEST_SKYPE_ID"
+TESTPASSWORD="$TEST_SKYPE_PASSWORD"
+### Test sending and receiving messages
+/expectbee 'Welcome to the BitlBee' -command 'msg $$C register testing'
+/expectbee 'Account successfully created' -command 'msg $$C account add skype @LOGIN@ @PASSWORD@'
+/expectbee 'Account successfully added' -command 'msg $$C account 0 set test_join true'
+/expectbee 'test_join' -command 'msg $$C account 0 on'
+# use builtin test service
+/expectjoin echo123 -command 'msg $$C echo123: ping, say pong'
+/expectbee 'pong' -command 'quit Test Passed'
+# use a public bot as well, just in case the above one would fail
+/expectjoin echo123 -command 'msg $$C add skype pam_bot'
+/expectjoin pam_bot -command 'msg $$C pam_bot: pambot help'
+/expectbee 'PamBot, thanks for chatting with me' -command 'quit Test Passed'
diff --git a/protocols/skype/t/irssi/trigger.pl b/protocols/skype/t/irssi/trigger.pl
new file mode 100644
index 00000000..02f8951f
--- /dev/null
+++ b/protocols/skype/t/irssi/trigger.pl
@@ -0,0 +1,1225 @@
+# trigger.pl - execute a command or replace text, triggered by an event in irssi
+# Do /TRIGGER HELP or look at http://wouter.coekaerts.be/irssi/ for help
+
+# Copyright (C) 2002-2006 Wouter Coekaerts <wouter@coekaerts.be>
+#
+# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+
+use strict;
+use Irssi 20020324 qw(command_bind command_runsub command signal_add_first signal_continue signal_stop signal_remove);
+use Text::ParseWords;
+use IO::File;
+use vars qw($VERSION %IRSSI);
+
+$VERSION = '1.0';
+%IRSSI = (
+ authors => 'Wouter Coekaerts',
+ contact => 'wouter@coekaerts.be',
+ name => 'trigger',
+ description => 'execute a command or replace text, triggered by an event in irssi',
+ license => 'GPLv2 or later',
+ url => 'http://wouter.coekaerts.be/irssi/',
+ changed => '$LastChangedDate: 2006-01-23 13:10:19 +0100 (Mon, 23 Jan 2006) $',
+);
+
+sub cmd_help {
+ Irssi::print (<<'SCRIPTHELP_EOF', MSGLEVEL_CLIENTCRAP);
+
+TRIGGER LIST
+TRIGGER SAVE
+TRIGGER RELOAD
+TRIGGER MOVE <number> <number>
+TRIGGER DELETE <number>
+TRIGGER CHANGE <number> ...
+TRIGGER ADD ...
+
+When to match:
+On which types of event to trigger:
+ These are simply specified by -name_of_the_type
+ The normal IRC event types are:
+ publics, %|privmsgs, pubactions, privactions, pubnotices, privnotices, joins, parts, quits, kicks, topics, invites, nick_changes, dcc_msgs, dcc_actions, dcc_ctcps
+ mode_channel: %|a mode on the (whole) channel (like +t, +i, +b)
+ mode_nick: %|a mode on someone in the channel (like +o, +v)
+ -all is an alias for all of those.
+ Additionally, there is:
+ rawin: %|raw text incoming from the server
+ send_command: %|commands you give to irssi
+ send_text: %|lines you type that aren't commands
+ beep: %|when irssi beeps
+ notify_join: %|someone in you notify list comes online
+ notify_part: %|someone in your notify list goes offline
+ notify_away: %|someone in your notify list goes away
+ notify_unaway: %|someone in your notify list goes unaway
+ notify_unidle: %|someone in your notify list stops idling
+
+Filters (conditions) the event has to satisfy. They all take one parameter.
+If you can give a list, seperate elements by space and use quotes around the list.
+ -pattern: %|The message must match the given pattern. ? and * can be used as wildcards
+ -regexp: %|The message must match the given regexp. (see man perlre)
+ %|if -nocase is given as an option, the regexp or pattern is matched case insensitive
+ -tags: %|The servertag must be in the given list of tags
+ -channels: %|The event must be in one of the given list of channels.
+ Examples: %|-channels '#chan1 #chan2' or -channels 'IRCNet/#channel'
+ %|-channels 'EFNet/' means every channel on EFNet and is the same as -tags 'EFNet'
+ -masks: %|The person who triggers it must match one of the given list of masks
+ -hasmode: %|The person who triggers it must have the give mode
+ Examples: %|'-o' means not opped, '+ov' means opped OR voiced, '-o&-v' means not opped AND not voiced
+ -hasflag: %|Only trigger if if friends.pl (friends_shasta.pl) or people.pl is loaded and the person who triggers it has the given flag in the script (same syntax as -hasmode)
+ -other_masks
+ -other_hasmode
+ -other_hasflag: %|Same as above but for the victim for kicks or mode_nick.
+
+What to do when it matches:
+ -command: Execute the given Irssi-command
+ %|You are able to use $1, $2 and so on generated by your regexp pattern.
+ %|For multiple commands ; (or $;) can be used as seperator
+ %|The following variables are also expanded:
+ $T: %|Server tag
+ $C: %|Channel name
+ $N: %|Nickname of the person who triggered this command
+ $A: %|His address (foo@bar.com),
+ $I: %|His ident (foo)
+ $H: %|His hostname (bar.com)
+ $M: %|The complete message
+ ${other}: %|The victim for kicks or mode_nick
+ ${mode_type}: %|The type ('+' or '-') for a mode_channel or mode_nick
+ ${mode_char}: %|The mode char ('o' for ops, 'b' for ban,...)
+ ${mode_arg} : %|The argument to the mode (if there is one)
+ %|$\X, with X being one of the above expands (e.g. $\M), escapes all non-alphanumeric characters, so it can be used with /eval or /exec. Don't use /eval or /exec without this, it's not safe.
+
+ -replace: %|replaces the matching part with the given replacement in the event (requires a -regexp or -pattern)
+ -once: %|remove the trigger if it is triggered, so it only executes once and then is forgotten.
+ -stop: %|stops the signal. It won't get displayed by Irssi. Like /IGNORE
+ -debug: %|print some debugging info
+
+Other options:
+ -disabled: %|Same as removing it, but keeps it in case you might need it later
+ -name: %|Give the trigger a name. You can refer to the trigger with this name in add/del/change commands
+
+Examples:
+ Knockout people who do a !list:
+ /TRIGGER ADD %|-publics -channels "#channel1 #channel2" -nocase -regexp ^!list -command "KN $N This is not a warez channel!"
+ React to !echo commands from people who are +o in your friends-script:
+ /TRIGGER ADD %|-publics -regexp '^!echo (.*)' -hasflag '+o' -command 'say echo: $1'
+ Ignore all non-ops on #channel:
+ /TRIGGER ADD %|-publics -actions -channels "#channel" -hasmode '-o' -stop
+ Send a mail to yourself every time a topic is changed:
+ /TRIGGER ADD %|-topics -command 'exec echo $\N changed topic of $\C to: $\M | mail you@somewhere.com -s topic'
+
+
+Examples with -replace:
+ %|Replace every occurence of shit with sh*t, case insensitive:
+ /TRIGGER ADD %|-all -nocase -regexp shit -replace sh*t
+ %|Strip all colorcodes from *!lamer@*:
+ /TRIGGER ADD %|-all -masks *!lamer@* -regexp '\x03\d?\d?(,\d\d?)?|\x02|\x1f|\x16|\x06' -replace ''
+ %|Never let *!bot1@foo.bar or *!bot2@foo.bar hilight you
+ %|(this works by cutting your nick in 2 different parts, 'myn' and 'ick' here)
+ %|you don't need to understand the -replace argument, just trust that it works if the 2 parts separately don't hilight:
+ /TRIGGER ADD %|-all masks '*!bot1@foo.bar *!bot2@foo.bar' -regexp '(myn)(ick)' -nocase -replace '$1\x02\x02$2'
+ %|Avoid being hilighted by !top10 in eggdrops with stats.mod (but show your nick in bold):
+ /TRIGGER ADD %|-publics -regexp '(Top.0\(.*\): 1.*)(my)(nick)' -replace '$1\x02$2\x02\x02$3\x02'
+ %|Convert a Windows-1252 Euro to an ISO-8859-15 Euro (same effect as euro.pl):
+ /TRIGGER ADD %|-regexp '\x80' -replace '\xA4'
+ %|Show tabs as spaces, not the inverted I (same effect as tab_stop.pl):
+ /TRIGGER ADD %|-all -regexp '\t' -replace ' '
+SCRIPTHELP_EOF
+} # /
+
+my @triggers; # array of all triggers
+my %triggers_by_type; # hash mapping types on triggers of that type
+my $recursion_depth = 0;
+my $changed_since_last_save = 0;
+
+###############
+### formats ###
+###############
+
+Irssi::theme_register([
+ 'trigger_header' => 'Triggers:',
+ 'trigger_line' => '%#$[-4]0 $1',
+ 'trigger_added' => 'Trigger $0 added: $1',
+ 'trigger_not_found' => 'Trigger {hilight $0} not found',
+ 'trigger_saved' => 'Triggers saved to $0',
+ 'trigger_loaded' => 'Triggers loaded from $0'
+]);
+
+#########################################
+### catch the signals & do your thing ###
+#########################################
+
+# trigger types with a message and a channel
+my @allchanmsg_types = qw(publics pubactions pubnotices pubctcps pubctcpreplies parts quits kicks topics);
+# trigger types with a message
+my @allmsg_types = (@allchanmsg_types, qw(privmsgs privactions privnotices privctcps privctcpreplies dcc_msgs dcc_actions dcc_ctcps));
+# trigger types with a channel
+my @allchan_types = (@allchanmsg_types, qw(mode_channel mode_nick joins invites));
+# trigger types in -all
+my @all_types = (@allmsg_types, qw(mode_channel mode_nick joins invites nick_changes));
+# trigger types with a server
+my @all_server_types = (@all_types, qw(rawin notify_join notify_part notify_away notify_unaway notify_unidle));
+# all trigger types
+my @trigger_types = (@all_server_types, qw(send_command send_text beep));
+#trigger types that are not in -all
+#my @notall_types = grep {my $a=$_; return (!grep {$_ eq $a} @all_types);} @trigger_types;
+my @notall_types = qw(rawin notify_join notify_part notify_away notify_unaway notify_unidle send_command send_text beep);
+
+my @signals = (
+# "message public", SERVER_REC, char *msg, char *nick, char *address, char *target
+{
+ 'types' => ['publics'],
+ 'signal' => 'message public',
+ 'sub' => sub {check_signal_message(\@_,1,$_[0],$_[4],$_[2],$_[3],'publics');},
+},
+# "message private", SERVER_REC, char *msg, char *nick, char *address
+{
+ 'types' => ['privmsgs'],
+ 'signal' => 'message private',
+ 'sub' => sub {check_signal_message(\@_,1,$_[0],undef,$_[2],$_[3],'privmsgs');},
+},
+# "message irc action", SERVER_REC, char *msg, char *nick, char *address, char *target
+{
+ 'types' => ['privactions','pubactions'],
+ 'signal' => 'message irc action',
+ 'sub' => sub {
+ if ($_[4] eq $_[0]->{nick}) {
+ check_signal_message(\@_,1,$_[0],undef,$_[2],$_[3],'privactions');
+ } else {
+ check_signal_message(\@_,1,$_[0],$_[4],$_[2],$_[3],'pubactions');
+ }
+ },
+},
+# "message irc notice", SERVER_REC, char *msg, char *nick, char *address, char *target
+{
+ 'types' => ['privnotices','pubnotices'],
+ 'signal' => 'message irc notice',
+ 'sub' => sub {
+ if ($_[4] eq $_[0]->{nick}) {
+ check_signal_message(\@_,1,$_[0],undef,$_[2],$_[3],'privnotices');
+ } else {
+ check_signal_message(\@_,1,$_[0],$_[4],$_[2],$_[3],'pubnotices');
+ }
+ }
+},
+# "message join", SERVER_REC, char *channel, char *nick, char *address
+{
+ 'types' => ['joins'],
+ 'signal' => 'message join',
+ 'sub' => sub {check_signal_message(\@_,-1,$_[0],$_[1],$_[2],$_[3],'joins');}
+},
+# "message part", SERVER_REC, char *channel, char *nick, char *address, char *reason
+{
+ 'types' => ['parts'],
+ 'signal' => 'message part',
+ 'sub' => sub {check_signal_message(\@_,4,$_[0],$_[1],$_[2],$_[3],'parts');}
+},
+# "message quit", SERVER_REC, char *nick, char *address, char *reason
+{
+ 'types' => ['quits'],
+ 'signal' => 'message quit',
+ 'sub' => sub {check_signal_message(\@_,3,$_[0],undef,$_[1],$_[2],'quits');}
+},
+# "message kick", SERVER_REC, char *channel, char *nick, char *kicker, char *address, char *reason
+{
+ 'types' => ['kicks'],
+ 'signal' => 'message kick',
+ 'sub' => sub {check_signal_message(\@_,5,$_[0],$_[1],$_[3],$_[4],'kicks',{'other'=>$_[2]});}
+},
+# "message topic", SERVER_REC, char *channel, char *topic, char *nick, char *address
+{
+ 'types' => ['topics'],
+ 'signal' => 'message topic',
+ 'sub' => sub {check_signal_message(\@_,2,$_[0],$_[1],$_[3],$_[4],'topics');}
+},
+# "message invite", SERVER_REC, char *channel, char *nick, char *address
+{
+ 'types' => ['invites'],
+ 'signal' => 'message invite',
+ 'sub' => sub {check_signal_message(\@_,-1,$_[0],$_[1],$_[2],$_[3],'invites');}
+},
+# "message nick", SERVER_REC, char *newnick, char *oldnick, char *address
+{
+ 'types' => ['nick_changes'],
+ 'signal' => 'message nick',
+ 'sub' => sub {check_signal_message(\@_,-1,$_[0],undef,$_[1],$_[3],'nick_changes');}
+},
+# "message dcc", DCC_REC *dcc, char *msg
+{
+ 'types' => ['dcc_msgs'],
+ 'signal' => 'message dcc',
+ 'sub' => sub {check_signal_message(\@_,1,$_[0]->{'server'},undef,$_[0]->{'nick'},undef,'dcc_msgs');
+ }
+},
+# "message dcc action", DCC_REC *dcc, char *msg
+{
+ 'types' => ['dcc_actions'],
+ 'signal' => 'message dcc action',
+ 'sub' => sub {check_signal_message(\@_,1,$_[0]->{'server'},undef,$_[0]->{'nick'},undef,'dcc_actions');}
+},
+# "message dcc ctcp", DCC_REC *dcc, char *cmd, char *data
+{
+ 'types' => ['dcc_ctcps'],
+ 'signal' => 'message dcc ctcp',
+ 'sub' => sub {check_signal_message(\@_,1,$_[0]->{'server'},undef,$_[0]->{'nick'},undef,'dcc_ctcps');}
+},
+# "server incoming", SERVER_REC, char *data
+{
+ 'types' => ['rawin'],
+ 'signal' => 'server incoming',
+ 'sub' => sub {check_signal_message(\@_,1,$_[0],undef,undef,undef,'rawin');}
+},
+# "send command", char *args, SERVER_REC, WI_ITEM_REC
+{
+ 'types' => ['send_command'],
+ 'signal' => 'send command',
+ 'sub' => sub {
+ sig_send_text_or_command(\@_,1);
+ }
+},
+# "send text", char *line, SERVER_REC, WI_ITEM_REC
+{
+ 'types' => ['send_text'],
+ 'signal' => 'send text',
+ 'sub' => sub {
+ sig_send_text_or_command(\@_,0);
+ }
+},
+# "beep"
+{
+ 'types' => ['beep'],
+ 'signal' => 'beep',
+ 'sub' => sub {check_signal_message(\@_,-1,undef,undef,undef,undef,'beep');}
+},
+# "event "<cmd>, SERVER_REC, char *args, char *sender_nick, char *sender_address
+{
+ 'types' => ['mode_channel', 'mode_nick'],
+ 'signal' => 'event mode',
+ 'sub' => sub {
+ my ($server, $event_args, $nickname, $address) = @_;
+ my ($target, $modes, $modeargs) = split(/ /, $event_args, 3);
+ return if (!$server->ischannel($target));
+ my (@modeargs) = split(/ /,$modeargs);
+ my ($pos, $type, $event_type, $arg) = (0, '+');
+ foreach my $char (split(//,$modes)) {
+ if ($char eq "+" || $char eq "-") {
+ $type = $char;
+ } else {
+ if ($char =~ /[Oovh]/) { # mode_nick
+ $event_type = 'mode_nick';
+ $arg = $modeargs[$pos++];
+ } elsif ($char =~ /[beIqdk]/ || ( $char =~ /[lfJ]/ && $type eq '+')) { # chan_mode with arg
+ $event_type = 'mode_channel';
+ $arg = $modeargs[$pos++];
+ } else { # chan_mode without arg
+ $event_type = 'mode_channel';
+ $arg = undef;
+ }
+ check_signal_message(\@_,-1,$server,$target,$nickname,$address,$event_type,{
+ 'mode_type' => $type,
+ 'mode_char' => $char,
+ 'mode_arg' => $arg,
+ 'other' => ($event_type eq 'mode_nick') ? $arg : undef
+ });
+ }
+ }
+ }
+},
+# "notifylist joined", SERVER_REC, char *nick, char *user, char *host, char *realname, char *awaymsg
+{
+ 'types' => ['notify_join'],
+ 'signal' => 'notifylist joined',
+ 'sub' => sub {check_signal_message(\@_, 5, $_[0], undef, $_[1], $_[2].'@'.$_[3], 'notify_join', {'realname' => $_[4]});}
+},
+{
+ 'types' => ['notify_part'],
+ 'signal' => 'notifylist left',
+ 'sub' => sub {check_signal_message(\@_, 5, $_[0], undef, $_[1], $_[2].'@'.$_[3], 'notify_left', {'realname' => $_[4]});}
+},
+{
+ 'types' => ['notify_unidle'],
+ 'signal' => 'notifylist unidle',
+ 'sub' => sub {check_signal_message(\@_, 5, $_[0], undef, $_[1], $_[2].'@'.$_[3], 'notify_unidle', {'realname' => $_[4]});}
+},
+{
+ 'types' => ['notify_away', 'notify_unaway'],
+ 'signal' => 'notifylist away changed',
+ 'sub' => sub {check_signal_message(\@_, 5, $_[0], undef, $_[1], $_[2].'@'.$_[3], ($_[5] ? 'notify_away' : 'notify_unaway'), {'realname' => $_[4]});}
+},
+# "ctcp msg", SERVER_REC, char *args, char *nick, char *addr, char *target
+{
+ 'types' => ['pubctcps', 'privctcps'],
+ 'signal' => 'ctcp msg',
+ 'sub' => sub {
+ my ($server, $args, $nick, $addr, $target) = @_;
+ if ($target eq $server->{'nick'}) {
+ check_signal_message(\@_, 1, $server, undef, $nick, $addr, 'privctcps');
+ } else {
+ check_signal_message(\@_, 1, $server, $target, $nick, $addr, 'pubctcps');
+ }
+ }
+},
+# "ctcp reply", SERVER_REC, char *args, char *nick, char *addr, char *target
+{
+ 'types' => ['pubctcpreplies', 'privctcpreplies'],
+ 'signal' => 'ctcp reply',
+ 'sub' => sub {
+ my ($server, $args, $nick, $addr, $target) = @_;
+ if ($target eq $server->{'nick'}) {
+ check_signal_message(\@_, 1, $server, undef, $nick, $addr, 'privctcps');
+ } else {
+ check_signal_message(\@_, 1, $server, $target, $nick, $addr, 'pubctcps');
+ }
+ }
+}
+);
+
+sub sig_send_text_or_command {
+ my ($signal, $iscommand) = @_;
+ my ($line, $server, $item) = @$signal;
+ my ($channelname,$nickname,$address) = (undef,undef,undef);
+ if ($item && (ref($item) eq 'Irssi::Irc::Channel' || ref($item) eq 'Irssi::Silc::Channel')) {
+ $channelname = $item->{'name'};
+ } elsif ($item && ref($item) eq 'Irssi::Irc::Query') { # TODO Silc query ?
+ $nickname = $item->{'name'};
+ $address = $item->{'address'}
+ }
+ # TODO pass context also for non-channels (queries and other stuff)
+ check_signal_message($signal,0,$server,$channelname,$nickname,$address,$iscommand ? 'send_command' : 'send_text');
+
+}
+
+my %filters = (
+'tags' => {
+ 'types' => \@all_server_types,
+ 'sub' => sub {
+ my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+
+ if (!defined($server)) {
+ return 0;
+ }
+ my $matches = 0;
+ foreach my $tag (split(/ /,$param)) {
+ if (lc($server->{'tag'}) eq lc($tag)) {
+ $matches = 1;
+ last;
+ }
+ }
+ return $matches;
+ }
+},
+'channels' => {
+ 'types' => \@allchan_types,
+ 'sub' => sub {
+ my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+
+ if (!defined($channelname) || !defined($server)) {
+ return 0;
+ }
+ my $matches = 0;
+ foreach my $trigger_channel (split(/ /,$param)) {
+ if (lc($channelname) eq lc($trigger_channel)
+ || lc($server->{'tag'}.'/'.$channelname) eq lc($trigger_channel)
+ || lc($server->{'tag'}.'/') eq lc($trigger_channel)) {
+ $matches = 1;
+ last; # this channel matches, stop checking channels
+ }
+ }
+ return $matches;
+ }
+},
+'masks' => {
+ 'types' => \@all_types,
+ 'sub' => sub {
+ my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+ return (defined($nickname) && defined($address) && defined($server) && $server->masks_match($param, $nickname, $address));
+ }
+},
+'other_masks' => {
+ 'types' => ['kicks', 'mode_nick'],
+ 'sub' => sub {
+ my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+ return 0 unless defined($extra->{'other'});
+ my $other_address = get_address($extra->{'other'}, $server, $channelname);
+ return defined($other_address) && $server->masks_match($param, $extra->{'other'}, $other_address);
+ }
+},
+'hasmode' => {
+ 'types' => \@all_types,
+ 'sub' => sub {
+ my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+ return hasmode($param, $nickname, $server, $channelname);
+ }
+},
+'other_hasmode' => {
+ 'types' => ['kicks', 'mode_nick'],
+ 'sub' => sub {
+ my ($param,$signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+ return defined($extra->{'other'}) && hasmode($param, $extra->{'other'}, $server, $channelname);
+ }
+},
+'hasflag' => {
+ 'types' => \@all_types,
+ 'sub' => sub {
+ my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+ return 0 unless defined($nickname) && defined($address) && defined($server);
+ my $flags = get_flags ($server->{'chatnet'},$channelname,$nickname,$address);
+ return defined($flags) && check_modes($flags,$param);
+ }
+},
+'other_hasflag' => {
+ 'types' => ['kicks', 'mode_nick'],
+ 'sub' => sub {
+ my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+ return 0 unless defined($extra->{'other'});
+ my $other_address = get_address($extra->{'other'}, $server, $channelname);
+ return 0 unless defined($other_address);
+ my $flags = get_flags ($server->{'chatnet'},$channelname,$extra->{'other'},$other_address);
+ return defined($flags) && check_modes($flags,$param);
+ }
+},
+'mode_type' => {
+ 'types' => ['mode_channel', 'mode_nick'],
+ 'sub' => sub {
+ my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+ return (($param) eq $extra->{'mode_type'});
+ }
+},
+'mode_char' => {
+ 'types' => ['mode_channel', 'mode_nick'],
+ 'sub' => sub {
+ my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+ return (($param) eq $extra->{'mode_char'});
+ }
+},
+'mode_arg' => {
+ 'types' => ['mode_channel', 'mode_nick'],
+ 'sub' => sub {
+ my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+ return (($param) eq $extra->{'mode_arg'});
+ }
+}
+);
+
+sub get_address {
+ my ($nick, $server, $channel) = @_;
+ my $nickrec = get_nickrec($nick, $server, $channel);
+ return $nickrec ? $nickrec->{'host'} : undef;
+}
+sub get_nickrec {
+ my ($nick, $server, $channel) = @_;
+ return unless defined($server) && defined($channel) && defined($nick);
+ my $chanrec = $server->channel_find($channel);
+ return $chanrec ? $chanrec->nick_find($nick) : undef;
+}
+
+sub hasmode {
+ my ($param, $nickname, $server, $channelname) = @_;
+ my $nickrec = get_nickrec($nickname, $server, $channelname);
+ return 0 unless defined $nickrec;
+ my $modes =
+ ($nickrec->{'op'} ? 'o' : '')
+ . ($nickrec->{'voice'} ? 'v' : '')
+ . ($nickrec->{'halfop'} ? 'h' : '')
+ ;
+ return check_modes($modes, $param);
+}
+
+# list of all switches
+my @trigger_switches = (@trigger_types, qw(all nocase stop once debug disabled));
+# parameters (with an argument)
+my @trigger_params = qw(pattern regexp command replace name);
+# list of all options (including switches) for /TRIGGER ADD
+my @trigger_add_options = (@trigger_switches, @trigger_params, keys(%filters));
+# same for /TRIGGER CHANGE, this includes the -no<option>'s
+my @trigger_options = map(($_,'no'.$_) ,@trigger_add_options);
+
+# check the triggers on $signal's $parammessage parameter, for triggers with $condition set
+# on $server in $channelname, for $nickname!$address
+# set $parammessage to -1 if the signal doesn't have a message
+# for signal without channel, nick or address, set to undef
+sub check_signal_message {
+ my ($signal, $parammessage, $server, $channelname, $nickname, $address, $condition, $extra) = @_;
+ my ($changed, $stopped, $context, $need_rebuild);
+ my $message = ($parammessage == -1) ? '' : $signal->[$parammessage];
+
+ return if (!$triggers_by_type{$condition});
+
+ if ($recursion_depth > 10) {
+ Irssi::print("Trigger error: Maximum recursion depth reached, aborting trigger.", MSGLEVEL_CLIENTERROR);
+ return;
+ }
+ $recursion_depth++;
+
+TRIGGER:
+ foreach my $trigger (@{$triggers_by_type{$condition}}) {
+ # check filters
+ foreach my $trigfilter (@{$trigger->{'filters'}}) {
+ if (! ($trigfilter->[2]($trigfilter->[1], $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra))) {
+
+ next TRIGGER;
+ }
+ }
+
+ # check regexp (and keep matches in @- and @+, so don't make a this a {block})
+ next if ($trigger->{'compregexp'} && ($parammessage == -1 || $message !~ m/$trigger->{'compregexp'}/));
+
+ # if we got this far, it fully matched, and we need to do the replace/command/stop/once
+ my $expands = $extra;
+ $expands->{'M'} = $message,;
+ $expands->{'T'} = (defined($server)) ? $server->{'tag'} : '';
+ $expands->{'C'} = $channelname;
+ $expands->{'N'} = $nickname;
+ $expands->{'A'} = $address;
+ $expands->{'I'} = ((!defined($address)) ? '' : substr($address,0,index($address,'@')));
+ $expands->{'H'} = ((!defined($address)) ? '' : substr($address,index($address,'@')+1));
+ $expands->{'$'} = '$';
+ $expands->{';'} = ';';
+
+ if (defined($trigger->{'replace'})) { # it's a -replace
+ $message =~ s/$trigger->{'compregexp'}/do_expands($trigger->{'compreplace'},$expands,$message)/ge;
+ $changed = 1;
+ }
+
+ if ($trigger->{'command'}) { # it's a (nonempty) -command
+ my $command = $trigger->{'command'};
+ # $1 = the stuff behind the $ we want to expand: a number, or a character from %expands
+ $command = do_expands($command, $expands, $message);
+
+ if (defined($server)) {
+ if (defined($channelname) && $server->channel_find($channelname)) {
+ $context = $server->channel_find($channelname);
+ } else {
+ $context = $server;
+ }
+ } else {
+ $context = undef;
+ }
+
+ if (defined($context)) {
+ $context->command("eval $command");
+ } else {
+ Irssi::command("eval $command");
+ }
+ }
+
+ if ($trigger->{'debug'}) {
+ print("DEBUG: trigger $condition pmesg=$parammessage message=$message server=$server->{tag} channel=$channelname nick=$nickname address=$address " . join(' ',map {$_ . '=' . $extra->{$_}} keys(%$extra)));
+ }
+
+ if ($trigger->{'stop'}) {
+ $stopped = 1;
+ }
+
+ if ($trigger->{'once'}) {
+ # find this trigger in the real trigger list, and remove it
+ for (my $realindex=0; $realindex < scalar(@triggers); $realindex++) {
+ if ($triggers[$realindex] == $trigger) {
+ splice (@triggers,$realindex,1);
+ last;
+ }
+ }
+ $need_rebuild = 1;
+ }
+ }
+
+ if ($need_rebuild) {
+ rebuild();
+ $changed_since_last_save = 1;
+ }
+ if ($stopped) { # stopped with -stop
+ signal_stop();
+ } elsif ($changed) { # changed with -replace
+ $signal->[$parammessage] = $message;
+ signal_continue(@$signal);
+ }
+ $recursion_depth--;
+}
+
+# used in check_signal_message to expand $'s
+# $inthis is a string that can contain $ stuff (like 'foo$1bar$N')
+sub do_expands {
+ my ($inthis, $expands, $from) = @_;
+ # @+ and @- are copied because there are two s/// nested, and the inner needs the $1 and $2,... of the outer one
+ my @plus = @+;
+ my @min = @-;
+ my $p = \@plus; my $m = \@min;
+ $inthis =~ s/\$(\\*(\d+|[^0-9x{]|x[0-9a-fA-F][0-9a-fA-F]|{.*?}))/expand_and_escape($1,$expands,$m,$p,$from)/ge;
+ return $inthis;
+}
+
+# \ $ and ; need extra escaping because we use eval
+sub expand_and_escape {
+ my $retval = expand(@_);
+ $retval =~ s/([\\\$;])/\\\1/g;
+ return $retval;
+}
+
+# used in do_expands (via expand_and_escape), to_expand is the part after the $
+sub expand {
+ my ($to_expand, $expands, $min, $plus, $from) = @_;
+ if ($to_expand =~ /^\d+$/) { # a number => look up in $vars
+ # from man perlvar:
+ # $3 is the same as "substr $var, $-[3], $+[3] - $-[3])"
+ return ($to_expand > @{$min} ? '' : substr($from,$min->[$to_expand],$plus->[$to_expand]-$min->[$to_expand]));
+ } elsif ($to_expand =~ s/^\\//) { # begins with \, so strip that from to_expand
+ my $exp = expand($to_expand,$expands,$min,$plus,$from); # first expand without \
+ $exp =~ s/([^a-zA-Z0-9])/\\\1/g; # escape non-word chars
+ return $exp;
+ } elsif ($to_expand =~ /^x([0-9a-fA-F]{2})/) { # $xAA
+ return chr(hex($1));
+ } elsif ($to_expand =~ /^{(.*?)}$/) { # ${foo}
+ return expand($1, $expands, $min, $plus, $from);
+ } else { # look up in $expands
+ return $expands->{$to_expand};
+ }
+}
+
+sub check_modes {
+ my ($has_modes, $need_modes) = @_;
+ my $matches;
+ my $switch = 1; # if a '-' if found, will be 0 (meaning the modes should not be set)
+ foreach my $need_mode (split /&/, $need_modes) {
+ $matches = 0;
+ foreach my $char (split //, $need_mode) {
+ if ($char eq '-') {
+ $switch = 0;
+ } elsif ($char eq '+') {
+ $switch = 1;
+ } elsif ((index($has_modes, $char) != -1) == $switch) {
+ $matches = 1;
+ last;
+ }
+ }
+ if (!$matches) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+# get someones flags from people.pl or friends(_shasta).pl
+sub get_flags {
+ my ($chatnet, $channel, $nick, $address) = @_;
+ my $flags;
+ no strict 'refs';
+ if (defined %{ 'Irssi::Script::people::' }) {
+ if (defined ($channel)) {
+ $flags = (&{ 'Irssi::Script::people::find_local_flags' }($chatnet,$channel,$nick,$address));
+ } else {
+ $flags = (&{ 'Irssi::Script::people::find_global_flags' }($chatnet,$nick,$address));
+ }
+ $flags = join('',keys(%{$flags}));
+ } else {
+ my $shasta;
+ if (defined %{ 'Irssi::Script::friends_shasta::' }) {
+ $shasta = 'friends_shasta';
+ } elsif (defined &{ 'Irssi::Script::friends::get_idx' }) {
+ $shasta = 'friends';
+ } else {
+ return undef;
+ }
+ my $idx = (&{ 'Irssi::Script::'.$shasta.'::get_idx' }($nick, $address));
+ if ($idx == -1) {
+ return '';
+ }
+ $flags = (&{ 'Irssi::Script::'.$shasta.'::get_friends_flags' }($idx,undef));
+ if ($channel) {
+ $flags .= (&{ 'Irssi::Script::'.$shasta.'::get_friends_flags' }($idx,$channel));
+ }
+ }
+ return $flags;
+}
+
+########################################################
+### internal stuff called by manage, needed by above ###
+########################################################
+
+my %mask_to_regexp = ();
+foreach my $i (0..255) {
+ my $ch = chr $i;
+ $mask_to_regexp{$ch} = "\Q$ch\E";
+}
+$mask_to_regexp{'?'} = '(.)';
+$mask_to_regexp{'*'} = '(.*)';
+
+sub compile_trigger {
+ my ($trigger) = @_;
+ my $regexp;
+
+ if ($trigger->{'regexp'}) {
+ $regexp = $trigger->{'regexp'};
+ } elsif ($trigger->{'pattern'}) {
+ $regexp = $trigger->{'pattern'};
+ $regexp =~ s/(.)/$mask_to_regexp{$1}/g;
+ } else {
+ delete $trigger->{'compregexp'};
+ return;
+ }
+
+ if ($trigger->{'nocase'}) {
+ $regexp = '(?i)' . $regexp;
+ }
+
+ $trigger->{'compregexp'} = qr/$regexp/;
+
+ if(defined($trigger->{'replace'})) {
+ (my $replace = $trigger->{'replace'}) =~ s/\$/\$\$/g;
+ $trigger->{'compreplace'} = Irssi::parse_special($replace);
+ }
+}
+
+# rebuilds triggers_by_type and updates signal binds
+sub rebuild {
+ %triggers_by_type = ();
+ foreach my $trigger (@triggers) {
+ if (!$trigger->{'disabled'}) {
+ if ($trigger->{'all'}) {
+ # -all is an alias for all types in @all_types for which the filters can apply
+ALLTYPES:
+ foreach my $type (@all_types) {
+ # check if all filters can apply to $type
+ foreach my $filter (@{$trigger->{'filters'}}) {
+ if (! grep {$_ eq $type} $filters{$filter->[0]}->{'types'}) {
+ next ALLTYPES;
+ }
+ }
+ push @{$triggers_by_type{$type}}, ($trigger);
+ }
+ }
+
+ foreach my $type ($trigger->{'all'} ? @notall_types : @trigger_types) {
+ if ($trigger->{$type}) {
+ push @{$triggers_by_type{$type}}, ($trigger);
+ }
+ }
+ }
+ }
+
+ foreach my $signal (@signals) {
+ my $should_bind = 0;
+ foreach my $type (@{$signal->{'types'}}) {
+ if (defined($triggers_by_type{$type})) {
+ $should_bind = 1;
+ }
+ }
+ if ($should_bind && !$signal->{'bind'}) {
+ signal_add_first($signal->{'signal'}, $signal->{'sub'});
+ $signal->{'bind'} = 1;
+ } elsif (!$should_bind && $signal->{'bind'}) {
+ signal_remove($signal->{'signal'}, $signal->{'sub'});
+ $signal->{'bind'} = 0;
+ }
+ }
+}
+
+################################
+### manage the triggers-list ###
+################################
+
+my $trigger_file; # cached setting
+
+sub sig_setup_changed {
+ $trigger_file = Irssi::settings_get_str('trigger_file');
+}
+
+sub autosave {
+ cmd_save() if ($changed_since_last_save);
+}
+
+# TRIGGER SAVE
+sub cmd_save {
+ my $io = new IO::File $trigger_file, "w";
+ if (defined $io) {
+ $io->print("#Triggers file version $VERSION\n");
+ foreach my $trigger (@triggers) {
+ $io->print(to_string($trigger) . "\n");
+ }
+ $io->close;
+ }
+ Irssi::printformat(MSGLEVEL_CLIENTNOTICE, 'trigger_saved', $trigger_file);
+ $changed_since_last_save = 0;
+}
+
+# save on unload
+sub UNLOAD {
+ cmd_save();
+}
+
+# TRIGGER LOAD
+sub cmd_load {
+ sig_setup_changed(); # make sure we've read the trigger_file setting
+ my $converted = 0;
+ my $io = new IO::File $trigger_file, "r";
+ if (not defined $io) {
+ if (-e $trigger_file) {
+ Irssi::print("Error opening triggers file", MSGLEVEL_CLIENTERROR);
+ }
+ return;
+ }
+ if (defined $io) {
+ @triggers = ();
+ my $text;
+ $text = $io->getline;
+ my $file_version = '';
+ if ($text =~ /^#Triggers file version (.*)\n/) {
+ $file_version = $1;
+ }
+ if ($file_version lt '0.6.1+2') {
+ no strict 'vars';
+ $text .= $_ foreach ($io->getlines);
+ my $rep = eval "$text";
+ if (! ref $rep) {
+ Irssi::print("Error in triggers file");
+ return;
+ }
+ my @old_triggers = @$rep;
+
+ for (my $index=0;$index < scalar(@old_triggers);$index++) {
+ my $trigger = $old_triggers[$index];
+
+ if ($file_version lt '0.6.1') {
+ # convert old names: notices => pubnotices, actions => pubactions
+ foreach $oldname ('notices','actions') {
+ if ($trigger->{$oldname}) {
+ delete $trigger->{$oldname};
+ $trigger->{'pub'.$oldname} = 1;
+ $converted = 1;
+ }
+ }
+ }
+ if ($file_version lt '0.6.1+1' && $trigger->{'modifiers'}) {
+ if ($trigger->{'modifiers'} =~ /i/) {
+ $trigger->{'nocase'} = 1;
+ Irssi::print("Trigger: trigger ".($index+1)." had 'i' in it's modifiers, it has been converted to -nocase");
+ }
+ if ($trigger->{'modifiers'} !~ /^[ig]*$/) {
+ Irssi::print("Trigger: trigger ".($index+1)." had unrecognised modifier '". $trigger->{'modifiers'} ."', which couldn't be converted.");
+ }
+ delete $trigger->{'modifiers'};
+ $converted = 1;
+ }
+
+ if (defined($trigger->{'replace'}) && ! $trigger->{'regexp'}) {
+ Irssi::print("Trigger: trigger ".($index+1)." had -replace but no -regexp, removed it");
+ splice (@old_triggers,$index,1);
+ $index--; # nr of next trigger now is the same as this one was
+ }
+
+ # convert to text with compat, and then to new trigger hash
+ $text = to_string($trigger,1);
+ my @args = &shellwords($text . ' a');
+ my $trigger = parse_options({},@args);
+ if ($trigger) {
+ push @triggers, $trigger;
+ }
+ }
+ } else { # new format
+ while ( $text = $io->getline ) {
+ chop($text);
+ my @args = &shellwords($text . ' a');
+ my $trigger = parse_options({},@args);
+ if ($trigger) {
+ push @triggers, $trigger;
+ }
+ }
+ }
+ }
+ Irssi::printformat(MSGLEVEL_CLIENTNOTICE, 'trigger_loaded', $trigger_file);
+ if ($converted) {
+ Irssi::print("Trigger: Triggers file will be in new format next time it's saved.");
+ }
+ rebuild();
+}
+
+# escape for printing with to_string
+# <<abcdef>> => << 'abcdef' >>
+# <<abc'def>> => << "abc'def" >>
+# <<abc'def\x02>> => << 'abc'\''def\x02' >>
+sub param_to_string {
+ my ($text) = @_;
+ # avoid ugly escaping if we can use "-quotes without other escaping (no " or \)
+ if ($text =~ /^[^"\\]*'[^"\\]$/) {
+ return ' "' . $text . '" ';
+ }
+ # "'" signs without a (odd number of) \ in front of them, need be to escaped as '\''
+ # this is ugly :(
+ $text =~ s/(^|[^\\](\\\\)*)'/$1'\\''/g;
+ return " '$text' ";
+}
+
+# converts a trigger back to "-switch -options 'foo'" form
+# if $compat, $trigger is in the old format (used to convert)
+sub to_string {
+ my ($trigger, $compat) = @_;
+ my $string;
+
+ foreach my $switch (@trigger_switches) {
+ if ($trigger->{$switch}) {
+ $string .= '-'.$switch.' ';
+ }
+ }
+
+ if ($compat) {
+ foreach my $filter (keys(%filters)) {
+ if ($trigger->{$filter}) {
+ $string .= '-' . $filter . param_to_string($trigger->{$filter});
+ }
+ }
+ } else {
+ foreach my $trigfilter (@{$trigger->{'filters'}}) {
+ $string .= '-' . $trigfilter->[0] . param_to_string($trigfilter->[1]);
+ }
+ }
+
+ foreach my $param (@trigger_params) {
+ if ($trigger->{$param} || ($param eq 'replace' && defined($trigger->{'replace'}))) {
+ $string .= '-' . $param . param_to_string($trigger->{$param});
+ }
+ }
+ return $string;
+}
+
+# find a trigger (for REPLACE and DELETE), returns index of trigger, or -1 if not found
+sub find_trigger {
+ my ($data) = @_;
+ if ($data =~ /^[0-9]*$/ and defined($triggers[$data-1])) {
+ return $data-1;
+ } else {
+ for (my $i=0; $i < scalar(@triggers); $i++) {
+ if ($triggers[$i]->{'name'} eq $data) {
+ return $i;
+ }
+ }
+ }
+ Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trigger_not_found', $data);
+ return -1; # not found
+}
+
+
+# TRIGGER ADD <options>
+sub cmd_add {
+ my ($data, $server, $item) = @_;
+ my @args = shellwords($data . ' a');
+
+ my $trigger = parse_options({}, @args);
+ if ($trigger) {
+ push @triggers, $trigger;
+ Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trigger_added', scalar(@triggers), to_string($trigger));
+ rebuild();
+ $changed_since_last_save = 1;
+ }
+}
+
+# TRIGGER CHANGE <nr> <options>
+sub cmd_change {
+ my ($data, $server, $item) = @_;
+ my @args = shellwords($data . ' a');
+ my $index = find_trigger(shift @args);
+ if ($index != -1) {
+ if(parse_options($triggers[$index], @args)) {
+ Irssi::print("Trigger " . ($index+1) ." changed to: ". to_string($triggers[$index]));
+ }
+ rebuild();
+ $changed_since_last_save = 1;
+ }
+}
+
+# parses options for TRIGGER ADD and TRIGGER CHANGE
+# if invalid args returns undef, else changes $thetrigger and returns it
+sub parse_options {
+ my ($thetrigger,@args) = @_;
+ my ($trigger, $option);
+
+ if (pop(@args) ne 'a') {
+ Irssi::print("Syntax error, probably missing a closing quote", MSGLEVEL_CLIENTERROR);
+ return undef;
+ }
+
+ %$trigger = %$thetrigger; # make a copy to prevent changing the given trigger if args doesn't parse
+ARGS: for (my $arg = shift @args; $arg; $arg = shift @args) {
+ # expand abbreviated options, put in $option
+ $arg =~ s/^-//;
+ $option = undef;
+ foreach my $ioption (@trigger_options) {
+ if (index($ioption, $arg) == 0) { # -$opt starts with $arg
+ if ($option) { # another already matched
+ Irssi::print("Ambiguous option: $arg", MSGLEVEL_CLIENTERROR);
+ return undef;
+ }
+ $option = $ioption;
+ last if ($arg eq $ioption); # exact match is unambiguous
+ }
+ }
+ if (!$option) {
+ Irssi::print("Unknown option: $arg", MSGLEVEL_CLIENTERROR);
+ return undef;
+ }
+
+ # -<param> <value> or -no<param>
+ foreach my $param (@trigger_params) {
+ if ($option eq $param) {
+ $trigger->{$param} = shift @args;
+ next ARGS;
+ }
+ if ($option eq 'no'.$param) {
+ $trigger->{$param} = undef;
+ next ARGS;
+ }
+ }
+
+ # -[no]<switch>
+ foreach my $switch (@trigger_switches) {
+ # -<switch>
+ if ($option eq $switch) {
+ $trigger->{$switch} = 1;
+ next ARGS;
+ }
+ # -no<switch>
+ elsif ($option eq 'no'.$switch) {
+ $trigger->{$switch} = undef;
+ next ARGS;
+ }
+ }
+
+ # -<filter> <value>
+ if ($filters{$option}) {
+ push @{$trigger->{'filters'}}, [$option, shift @args, $filters{$option}->{'sub'}];
+ next ARGS;
+ }
+
+ # -<nofilter>
+ if ($option =~ /^no(.*)$/ && $filters{$1}) {
+ my $filter = $1;
+ # the new filters are the old grepped for everything except ones with name $filter
+ @{$trigger->{'filters'}} = grep( $_->[0] ne $filter, @{$trigger->{'filters'}} );
+ }
+ }
+
+ if (defined($trigger->{'replace'}) && ! $trigger->{'regexp'} && !$trigger->{'pattern'}) {
+ Irssi::print("Trigger error: Can't have -replace without -regexp", MSGLEVEL_CLIENTERROR);
+ return undef;
+ }
+
+ if ($trigger->{'pattern'} && $trigger->{'regexp'}) {
+ Irssi::print("Trigger error: Can't have -pattern and -regexp in same trigger", MSGLEVEL_CLIENTERROR);
+ return undef;
+ }
+
+ # remove types that are implied by -all
+ if ($trigger->{'all'}) {
+ foreach my $type (@all_types) {
+ delete $trigger->{$type};
+ }
+ }
+
+ # remove types for which the filters don't apply
+ foreach my $type (@trigger_types) {
+ if ($trigger->{$type}) {
+ foreach my $filter (@{$trigger->{'filters'}}) {
+ if (!grep {$_ eq $type} @{$filters{$filter->[0]}->{'types'}}) {
+ Irssi::print("Warning: the filter -" . $filter->[0] . " can't apply to an event of type -$type, so I'm removing that type from this trigger.");
+ delete $trigger->{$type};
+ }
+ }
+ }
+ }
+
+ # check if it has at least one type
+ my $has_a_type;
+ foreach my $type (@trigger_types) {
+ if ($trigger->{$type}) {
+ $has_a_type = 1;
+ last;
+ }
+ }
+ if (!$has_a_type && !$trigger->{'all'}) {
+ Irssi::print("Warning: this trigger doesn't trigger on any type of message. you probably want to add -publics or -all");
+ }
+
+ compile_trigger($trigger);
+ %$thetrigger = %$trigger; # copy changes to real trigger
+ return $thetrigger;
+}
+
+# TRIGGER DELETE <num>
+sub cmd_del {
+ my ($data, $server, $item) = @_;
+ my @args = shellwords($data);
+ my $index = find_trigger(shift @args);
+ if ($index != -1) {
+ Irssi::print("Deleted ". ($index+1) .": ". to_string($triggers[$index]));
+ splice (@triggers,$index,1);
+ rebuild();
+ $changed_since_last_save = 1;
+ }
+}
+
+# TRIGGER MOVE <num> <num>
+sub cmd_move {
+ my ($data, $server, $item) = @_;
+ my @args = &shellwords($data);
+ my $index = find_trigger(shift @args);
+ if ($index != -1) {
+ my $newindex = shift @args;
+ if ($newindex < 1 || $newindex > scalar(@triggers)) {
+ Irssi::print("$newindex is not a valid trigger number");
+ return;
+ }
+ Irssi::print("Moved from ". ($index+1) ." to $newindex: ". to_string($triggers[$index]));
+ $newindex -= 1; # array starts counting from 0
+ my $trigger = splice (@triggers,$index,1); # remove from old place
+ splice (@triggers,$newindex,0,($trigger)); # insert at new place
+ rebuild();
+ $changed_since_last_save = 1;
+ }
+}
+
+# TRIGGER LIST
+sub cmd_list {
+ Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trigger_header');
+ my $i=1;
+ foreach my $trigger (@triggers) {
+ Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trigger_line', $i++, to_string($trigger));
+ }
+}
+
+######################
+### initialisation ###
+######################
+
+command_bind('trigger help',\&cmd_help);
+command_bind('help trigger',\&cmd_help);
+command_bind('trigger add',\&cmd_add);
+command_bind('trigger change',\&cmd_change);
+command_bind('trigger move',\&cmd_move);
+command_bind('trigger list',\&cmd_list);
+command_bind('trigger delete',\&cmd_del);
+command_bind('trigger save',\&cmd_save);
+command_bind('trigger reload',\&cmd_load);
+command_bind 'trigger' => sub {
+ my ( $data, $server, $item ) = @_;
+ $data =~ s/\s+$//g;
+ command_runsub('trigger', $data, $server, $item);
+};
+
+Irssi::signal_add('setup saved', \&autosave);
+Irssi::signal_add('setup changed', \&sig_setup_changed);
+
+# This makes tab completion work
+Irssi::command_set_options('trigger add',join(' ',@trigger_add_options));
+Irssi::command_set_options('trigger change',join(' ',@trigger_options));
+
+Irssi::settings_add_str($IRSSI{'name'}, 'trigger_file', Irssi::get_irssi_dir()."/triggers");
+
+cmd_load();
diff --git a/protocols/skype/t/livetest-bitlbee.sh b/protocols/skype/t/livetest-bitlbee.sh
new file mode 100755
index 00000000..7cbfbf6e
--- /dev/null
+++ b/protocols/skype/t/livetest-bitlbee.sh
@@ -0,0 +1,116 @@
+#!/usr/bin/env bash
+
+start_skyped()
+{
+ python ../skyped.py "$@" > skypedtest.pid
+ while true
+ do
+ [ -e skypedtest.pid ] || break
+ pid=$(sed 's/.*: //' skypedtest.pid)
+ if [ -n "$(ps -p $pid -o pid=)" ]; then
+ sleep 5
+ else
+ start_skyped "$@"
+ break
+ fi
+ done
+}
+
+BITLBEE=$1
+typeset -ix PORT=`echo $2 | egrep '^[0-9]{1,5}$'`
+SCRIPT=$3
+shift 3
+
+[ -n "$SCRIPT" -a -n "$BITLBEE" -a -e "$SCRIPT" -a "$PORT" -ne 0 ] || { echo Syntax: `basename "$0"` bitlbee-executable listening-port test-script test-script-args; exit 1; }
+
+# Create or empty test dir
+mkdir livetest 2>/dev/null || rm livetest/bitlbeetest*.xml bitlbeetest.pid 2>/dev/null
+
+# Run the bee
+echo Running bitlbee...
+$VALGRIND $BITLBEE -n -c bitlbee.conf -d livetest/ -D -P bitlbeetest.pid -p $PORT 2>bitlbee.log &
+sleep 2
+
+# Check if it's really running
+kill -0 `cat bitlbeetest.pid 2>/dev/null ` 2>/dev/null || { echo Failed to run bitlbee daemon on port $PORT; exit 1; }
+
+if [ -z "$TUNNELED_MODE" ]; then
+ # Set up skyped
+
+ rm -rf etc
+ mkdir etc
+ cd etc
+ cp ../../skyped.cnf .
+ cp ~/.skyped/skyped.cert.pem .
+ cp ~/.skyped/skyped.key.pem .
+ cd ..
+ echo "[skyped]" > skyped.conf
+ echo "username = $TEST_SKYPE_ID" >> skyped.conf
+ SHA1=`which sha1sum`
+ if [ -z "$SHA1" ]; then
+ SHA1=`which sha1`
+ fi
+ if [ -z "$SHA1" ]; then
+ echo Test failed
+ echo "(Can't compute password for skyped.conf)"
+ exit 77
+ fi
+ echo "password = $(echo -n $TEST_SKYPE_PASSWORD|$SHA1|sed 's/ *-$//')" >> skyped.conf
+ # we use ~ here to test that resolve that syntax works
+ echo "cert = $(pwd|sed "s|$HOME|~|")/etc/skyped.cert.pem" >> skyped.conf
+ echo "key = $(pwd|sed "s|$HOME|~|")/etc/skyped.key.pem" >> skyped.conf
+ echo "port = 2727" >> skyped.conf
+
+ # Run skyped
+ start_skyped -c skyped.conf -l skypedtest.log &
+ sleep 2
+fi
+
+if [ "$TUNNELED_MODE" = "yes" ]; then
+ rm -f tunnel.pid
+ if [ -n "$TUNNEL_SCRIPT" ]; then
+ $TUNNEL_SCRIPT &
+ echo $! > tunnel.pid
+ sleep 5
+ fi
+fi
+
+# Run the test
+echo Running test script...
+"$SCRIPT" $*
+RET=$?
+
+if [ -z "$TUNNELED_MODE" ]; then
+ # skyped runs on another host: no means to kill it
+ # Kill skyped
+ killall -TERM skype
+ if [ -f skypedtest.pid ]; then
+ pid=$(sed 's/.*: //' skypedtest.pid)
+ rm skypedtest.pid
+ [ -n "$(ps -p $pid -o pid=)" ] && kill -TERM $pid
+ fi
+fi
+
+if [ "$TUNNELED_MODE" = "yes" ]; then
+ if [ -n "$TUNNEL_SCRIPT" ]; then
+ cat tunnel.pid >> /tmp/tunnel.pid
+ kill `cat tunnel.pid`
+ rm -f tunnel.pid
+ fi
+fi
+
+# Kill bee
+echo Killing bitlbee...
+kill `cat bitlbeetest.pid`
+
+if [ "$TUNNELED_MODE" = "yes" ]; then
+ # give the skyped a chance to timeout
+ sleep 30
+fi
+
+# Return test result
+[ $RET -eq 0 ] && echo Test passed
+[ $RET -ne 0 ] && echo Test failed
+[ $RET -eq 22 ] && echo '(timed out)'
+[ $RET -eq 66 ] && echo '(environment variables missing)'
+exit $RET
diff --git a/protocols/twitter/Makefile b/protocols/twitter/Makefile
new file mode 100644
index 00000000..74f0ea11
--- /dev/null
+++ b/protocols/twitter/Makefile
@@ -0,0 +1,46 @@
+###########################
+## Makefile for BitlBee ##
+## ##
+## Copyright 2002 Lintux ##
+###########################
+
+### DEFINITIONS
+
+-include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/twitter/
+endif
+
+# [SH] Program variables
+objects = twitter.o twitter_http.o twitter_lib.o
+
+LFLAGS += -r
+
+# [SH] Phony targets
+all: twitter_mod.o
+check: all
+lcov: check
+gcov:
+ gcov *.c
+
+.PHONY: all clean distclean
+
+clean:
+ rm -f *.o core
+
+distclean: clean
+ rm -rf .depend
+
+### MAIN PROGRAM
+
+$(objects): ../../Makefile.settings Makefile
+
+$(objects): %.o: $(SRCDIR)%.c
+ @echo '*' Compiling $<
+ @$(CC) -c $(CFLAGS) $< -o $@
+
+twitter_mod.o: $(objects)
+ @echo '*' Linking twitter_mod.o
+ @$(LD) $(LFLAGS) $(objects) -o twitter_mod.o
+
+-include .depend/*.d
diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c
new file mode 100644
index 00000000..50bf6cd2
--- /dev/null
+++ b/protocols/twitter/twitter.c
@@ -0,0 +1,633 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Simple module to facilitate twitter functionality. *
+* *
+* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> *
+* *
+* This library is free software; you can redistribute it and/or *
+* modify it under the terms of the GNU Lesser General Public *
+* License as published by the Free Software Foundation, version *
+* 2.1. *
+* *
+* This library is distributed in the hope that it will be useful, *
+* but WITHOUT ANY WARRANTY; without even the implied warranty of *
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+* Lesser General Public License for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this library; if not, write to the Free Software Foundation, *
+* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
+* *
+****************************************************************************/
+
+#include "nogaim.h"
+#include "oauth.h"
+#include "twitter.h"
+#include "twitter_http.h"
+#include "twitter_lib.h"
+#include "url.h"
+
+#define twitter_msg( ic, fmt... ) \
+ do { \
+ struct twitter_data *td = ic->proto_data; \
+ if( td->home_timeline_gc ) \
+ imcb_chat_log( td->home_timeline_gc, fmt ); \
+ else \
+ imcb_log( ic, fmt ); \
+ } while( 0 );
+
+GSList *twitter_connections = NULL;
+
+/**
+ * Main loop function
+ */
+gboolean twitter_main_loop(gpointer data, gint fd, b_input_condition cond)
+{
+ struct im_connection *ic = data;
+
+ // Check if we are still logged in...
+ if (!g_slist_find( twitter_connections, ic ))
+ return 0;
+
+ // Do stuff..
+ twitter_get_home_timeline(ic, -1);
+
+ // If we are still logged in run this function again after timeout.
+ return (ic->flags & OPT_LOGGED_IN) == OPT_LOGGED_IN;
+}
+
+static void twitter_main_loop_start( struct im_connection *ic )
+{
+ struct twitter_data *td = ic->proto_data;
+
+ imcb_log( ic, "Getting initial statuses" );
+
+ // Run this once. After this queue the main loop function.
+ twitter_main_loop(ic, -1, 0);
+
+ // Queue the main_loop
+ // Save the return value, so we can remove the timeout on logout.
+ td->main_loop_id = b_timeout_add(60000, twitter_main_loop, ic);
+}
+
+static void twitter_oauth_start( struct im_connection *ic );
+
+void twitter_login_finish( struct im_connection *ic )
+{
+ struct twitter_data *td = ic->proto_data;
+
+ if( set_getbool( &ic->acc->set, "oauth" ) && !td->oauth_info )
+ twitter_oauth_start( ic );
+ else if( g_strcasecmp( set_getstr( &ic->acc->set, "mode" ), "one" ) != 0 &&
+ !( td->flags & TWITTER_HAVE_FRIENDS ) )
+ {
+ imcb_log( ic, "Getting contact list" );
+ twitter_get_statuses_friends( ic, -1 );
+ }
+ else
+ twitter_main_loop_start( ic );
+}
+
+static const struct oauth_service twitter_oauth =
+{
+ "http://api.twitter.com/oauth/request_token",
+ "http://api.twitter.com/oauth/access_token",
+ "https://api.twitter.com/oauth/authorize",
+ .consumer_key = "xsDNKJuNZYkZyMcu914uEA",
+ .consumer_secret = "FCxqcr0pXKzsF9ajmP57S3VQ8V6Drk4o2QYtqMcOszo",
+};
+
+static const struct oauth_service identica_oauth =
+{
+ "http://identi.ca/api/oauth/request_token",
+ "http://identi.ca/api/oauth/access_token",
+ "https://identi.ca/api/oauth/authorize",
+ .consumer_key = "e147ff789fcbd8a5a07963afbb43f9da",
+ .consumer_secret = "c596267f277457ec0ce1ab7bb788d828",
+};
+
+static gboolean twitter_oauth_callback( struct oauth_info *info );
+
+static const struct oauth_service *get_oauth_service( struct im_connection *ic )
+{
+ struct twitter_data *td = ic->proto_data;
+
+ if( strstr( td->url_host, "identi.ca" ) )
+ return &identica_oauth;
+ else
+ return &twitter_oauth;
+
+ /* Could add more services, or allow configuring your own base URL +
+ API keys. */
+}
+
+static void twitter_oauth_start( struct im_connection *ic )
+{
+ struct twitter_data *td = ic->proto_data;
+
+ imcb_log( ic, "Requesting OAuth request token" );
+
+ td->oauth_info = oauth_request_token( get_oauth_service( ic ), twitter_oauth_callback, ic );
+
+ /* We need help from the user to complete OAuth login, so don't time
+ out on this login. */
+ ic->flags |= OPT_SLOW_LOGIN;
+}
+
+static gboolean twitter_oauth_callback( struct oauth_info *info )
+{
+ struct im_connection *ic = info->data;
+ struct twitter_data *td;
+
+ if( !g_slist_find( twitter_connections, ic ) )
+ return FALSE;
+
+ td = ic->proto_data;
+ if( info->stage == OAUTH_REQUEST_TOKEN )
+ {
+ char name[strlen(ic->acc->user)+9], *msg;
+
+ if( info->request_token == NULL )
+ {
+ imcb_error( ic, "OAuth error: %s", info->http->status_string );
+ imc_logout( ic, TRUE );
+ return FALSE;
+ }
+
+ sprintf( name, "%s_%s", td->prefix, ic->acc->user );
+ msg = g_strdup_printf( "To finish OAuth authentication, please visit "
+ "%s and respond with the resulting PIN code.",
+ info->auth_url );
+ imcb_buddy_msg( ic, name, msg, 0, 0 );
+ g_free( msg );
+ }
+ else if( info->stage == OAUTH_ACCESS_TOKEN )
+ {
+ if( info->token == NULL || info->token_secret == NULL )
+ {
+ imcb_error( ic, "OAuth error: %s", info->http->status_string );
+ imc_logout( ic, TRUE );
+ return FALSE;
+ }
+ else
+ {
+ const char *sn = oauth_params_get( &info->params, "screen_name" );
+
+ if( sn != NULL && ic->acc->prpl->handle_cmp( sn, ic->acc->user ) != 0 )
+ {
+ imcb_log( ic, "Warning: You logged in via OAuth as %s "
+ "instead of %s.", sn, ic->acc->user );
+ }
+ }
+
+ /* IM mods didn't do this so far and it's ugly but I should
+ be able to get away with it... */
+ g_free( ic->acc->pass );
+ ic->acc->pass = oauth_to_string( info );
+
+ twitter_login_finish( ic );
+ }
+
+ return TRUE;
+}
+
+
+static char *set_eval_mode( set_t *set, char *value )
+{
+ if( g_strcasecmp( value, "one" ) == 0 ||
+ g_strcasecmp( value, "many" ) == 0 ||
+ g_strcasecmp( value, "chat" ) == 0 )
+ return value;
+ else
+ return NULL;
+}
+
+static gboolean twitter_length_check( struct im_connection *ic, gchar *msg )
+{
+ int max = set_getint( &ic->acc->set, "message_length" ), len;
+
+ if( max == 0 || ( len = g_utf8_strlen( msg, -1 ) ) <= max )
+ return TRUE;
+
+ imcb_error( ic, "Maximum message length exceeded: %d > %d", len, max );
+
+ return FALSE;
+}
+
+static void twitter_init( account_t *acc )
+{
+ set_t *s;
+ char *def_url;
+ char *def_oauth;
+
+ if( strcmp( acc->prpl->name, "twitter" ) == 0 )
+ {
+ def_url = TWITTER_API_URL;
+ def_oauth = "true";
+ }
+ else /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */
+ {
+ def_url = IDENTICA_API_URL;
+ def_oauth = "false";
+ }
+
+ s = set_add( &acc->set, "auto_reply_timeout", "10800", set_eval_int, acc );
+
+ s = set_add( &acc->set, "base_url", def_url, NULL, acc );
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add( &acc->set, "commands", "true", set_eval_bool, acc );
+
+ s = set_add( &acc->set, "message_length", "140", set_eval_int, acc );
+
+ s = set_add( &acc->set, "mode", "chat", set_eval_mode, acc );
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add( &acc->set, "show_ids", "false", set_eval_bool, acc );
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add( &acc->set, "oauth", def_oauth, set_eval_bool, acc );
+}
+
+/**
+ * Login method. Since the twitter API works with seperate HTTP request we
+ * only save the user and pass to the twitter_data object.
+ */
+static void twitter_login( account_t *acc )
+{
+ struct im_connection *ic = imcb_new( acc );
+ struct twitter_data *td;
+ char name[strlen(acc->user)+9];
+ url_t url;
+
+ if( !url_set( &url, set_getstr( &ic->acc->set, "base_url" ) ) ||
+ ( url.proto != PROTO_HTTP && url.proto != PROTO_HTTPS ) )
+ {
+ imcb_error( ic, "Incorrect API base URL: %s", set_getstr( &ic->acc->set, "base_url" ) );
+ imc_logout( ic, FALSE );
+ return;
+ }
+
+ twitter_connections = g_slist_append( twitter_connections, ic );
+ td = g_new0( struct twitter_data, 1 );
+ ic->proto_data = td;
+
+ td->url_ssl = url.proto == PROTO_HTTPS;
+ td->url_port = url.port;
+ td->url_host = g_strdup( url.host );
+ if( strcmp( url.file, "/" ) != 0 )
+ td->url_path = g_strdup( url.file );
+ else
+ td->url_path = g_strdup( "" );
+ if( g_str_has_suffix( url.host, ".com" ) )
+ td->prefix = g_strndup( url.host, strlen( url.host ) - 4 );
+ else
+ td->prefix = g_strdup( url.host );
+
+ td->user = acc->user;
+ if( strstr( acc->pass, "oauth_token=" ) )
+ td->oauth_info = oauth_from_string( acc->pass, get_oauth_service( ic ) );
+
+ sprintf( name, "%s_%s", td->prefix, acc->user );
+ imcb_add_buddy( ic, name, NULL );
+ imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL );
+
+ if( set_getbool( &acc->set, "show_ids" ) )
+ td->log = g_new0( struct twitter_log_data, TWITTER_LOG_LENGTH );
+
+ imcb_log( ic, "Connecting" );
+
+ twitter_login_finish( ic );
+}
+
+/**
+ * Logout method. Just free the twitter_data.
+ */
+static void twitter_logout( struct im_connection *ic )
+{
+ struct twitter_data *td = ic->proto_data;
+
+ // Set the status to logged out.
+ ic->flags &= ~ OPT_LOGGED_IN;
+
+ // Remove the main_loop function from the function queue.
+ b_event_remove(td->main_loop_id);
+
+ if(td->home_timeline_gc)
+ imcb_chat_free(td->home_timeline_gc);
+
+ if( td )
+ {
+ oauth_info_free( td->oauth_info );
+ g_free( td->prefix );
+ g_free( td->url_host );
+ g_free( td->url_path );
+ g_free( td->pass );
+ g_free( td->log );
+ g_free( td );
+ }
+
+ twitter_connections = g_slist_remove( twitter_connections, ic );
+}
+
+static void twitter_handle_command( struct im_connection *ic, char *message );
+
+/**
+ *
+ */
+static int twitter_buddy_msg( struct im_connection *ic, char *who, char *message, int away )
+{
+ struct twitter_data *td = ic->proto_data;
+ int plen = strlen( td->prefix );
+
+ if (g_strncasecmp(who, td->prefix, plen) == 0 && who[plen] == '_' &&
+ g_strcasecmp(who + plen + 1, ic->acc->user) == 0)
+ {
+ if( set_getbool( &ic->acc->set, "oauth" ) &&
+ td->oauth_info && td->oauth_info->token == NULL )
+ {
+ char pin[strlen(message)+1], *s;
+
+ strcpy( pin, message );
+ for( s = pin + sizeof( pin ) - 2; s > pin && isspace( *s ); s -- )
+ *s = '\0';
+ for( s = pin; *s && isspace( *s ); s ++ ) {}
+
+ if( !oauth_access_token( s, td->oauth_info ) )
+ {
+ imcb_error( ic, "OAuth error: %s", "Failed to send access token request" );
+ imc_logout( ic, TRUE );
+ return FALSE;
+ }
+ }
+ else
+ twitter_handle_command(ic, message);
+ }
+ else
+ {
+ twitter_direct_messages_new(ic, who, message);
+ }
+ return( 0 );
+}
+
+/**
+ *
+ */
+static void twitter_set_my_name( struct im_connection *ic, char *info )
+{
+}
+
+static void twitter_get_info(struct im_connection *ic, char *who)
+{
+}
+
+static void twitter_add_buddy( struct im_connection *ic, char *who, char *group )
+{
+ twitter_friendships_create_destroy(ic, who, 1);
+}
+
+static void twitter_remove_buddy( struct im_connection *ic, char *who, char *group )
+{
+ twitter_friendships_create_destroy(ic, who, 0);
+}
+
+static void twitter_chat_msg( struct groupchat *c, char *message, int flags )
+{
+ if( c && message )
+ twitter_handle_command( c->ic, message );
+}
+
+static void twitter_chat_invite( struct groupchat *c, char *who, char *message )
+{
+}
+
+static void twitter_chat_leave( struct groupchat *c )
+{
+ struct twitter_data *td = c->ic->proto_data;
+
+ if( c != td->home_timeline_gc )
+ return; /* WTF? */
+
+ /* If the user leaves the channel: Fine. Rejoin him/her once new
+ tweets come in. */
+ imcb_chat_free(td->home_timeline_gc);
+ td->home_timeline_gc = NULL;
+}
+
+static void twitter_keepalive( struct im_connection *ic )
+{
+}
+
+static void twitter_add_permit( struct im_connection *ic, char *who )
+{
+}
+
+static void twitter_rem_permit( struct im_connection *ic, char *who )
+{
+}
+
+static void twitter_add_deny( struct im_connection *ic, char *who )
+{
+}
+
+static void twitter_rem_deny( struct im_connection *ic, char *who )
+{
+}
+
+//static char *twitter_set_display_name( set_t *set, char *value )
+//{
+// return value;
+//}
+
+static void twitter_buddy_data_add( struct bee_user *bu )
+{
+ bu->data = g_new0( struct twitter_user_data, 1 );
+}
+
+static void twitter_buddy_data_free( struct bee_user *bu )
+{
+ g_free( bu->data );
+}
+
+static void twitter_handle_command( struct im_connection *ic, char *message )
+{
+ struct twitter_data *td = ic->proto_data;
+ char *cmds, **cmd, *new = NULL;
+ guint64 in_reply_to = 0;
+
+ cmds = g_strdup( message );
+ cmd = split_command_parts( cmds );
+
+ if( cmd[0] == NULL )
+ {
+ g_free( cmds );
+ return;
+ }
+ else if( !set_getbool( &ic->acc->set, "commands" ) )
+ {
+ /* Not supporting commands. */
+ }
+ else if( g_strcasecmp( cmd[0], "undo" ) == 0 )
+ {
+ guint64 id;
+
+ if( cmd[1] )
+ id = g_ascii_strtoull( cmd[1], NULL, 10 );
+ else
+ id = td->last_status_id;
+
+ /* TODO: User feedback. */
+ if( id )
+ twitter_status_destroy( ic, id );
+ else
+ twitter_msg( ic, "Could not undo last action" );
+
+ g_free( cmds );
+ return;
+ }
+ else if( g_strcasecmp( cmd[0], "follow" ) == 0 && cmd[1] )
+ {
+ twitter_add_buddy( ic, cmd[1], NULL );
+ g_free( cmds );
+ return;
+ }
+ else if( g_strcasecmp( cmd[0], "unfollow" ) == 0 && cmd[1] )
+ {
+ twitter_remove_buddy( ic, cmd[1], NULL );
+ g_free( cmds );
+ return;
+ }
+ else if( g_strcasecmp( cmd[0], "rt" ) == 0 && cmd[1] )
+ {
+ struct twitter_user_data *tud;
+ bee_user_t *bu;
+ guint64 id;
+
+ if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[1] ) ) &&
+ ( tud = bu->data ) && tud->last_id )
+ id = tud->last_id;
+ else
+ {
+ id = g_ascii_strtoull( cmd[1], NULL, 10 );
+ if( id < TWITTER_LOG_LENGTH && td->log )
+ id = td->log[id].id;
+ }
+
+ td->last_status_id = 0;
+ if( id )
+ twitter_status_retweet( ic, id );
+ else
+ twitter_msg( ic, "User `%s' does not exist or didn't "
+ "post any statuses recently", cmd[1] );
+
+ g_free( cmds );
+ return;
+ }
+ else if( g_strcasecmp( cmd[0], "reply" ) == 0 && cmd[1] && cmd[2] )
+ {
+ struct twitter_user_data *tud;
+ bee_user_t *bu = NULL;
+ guint64 id = 0;
+
+ if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[1] ) ) &&
+ ( tud = bu->data ) && tud->last_id )
+ {
+ id = tud->last_id;
+ }
+ else if( ( id = g_ascii_strtoull( cmd[1], NULL, 10 ) ) &&
+ ( id < TWITTER_LOG_LENGTH ) && td->log )
+ {
+ bu = td->log[id].bu;
+ if( g_slist_find( ic->bee->users, bu ) )
+ id = td->log[id].id;
+ else
+ bu = NULL;
+ }
+ if( !id || !bu )
+ {
+ twitter_msg( ic, "User `%s' does not exist or didn't "
+ "post any statuses recently", cmd[1] );
+ return;
+ }
+ message = new = g_strdup_printf( "@%s %s", bu->handle,
+ message + ( cmd[2] - cmd[0] ) );
+ in_reply_to = id;
+ }
+ else if( g_strcasecmp( cmd[0], "post" ) == 0 )
+ {
+ message += 5;
+ }
+
+ {
+ char *s;
+ bee_user_t *bu;
+
+ if( !twitter_length_check( ic, message ) )
+ {
+ g_free( new );
+ g_free( cmds );
+ return;
+ }
+
+ s = cmd[0] + strlen( cmd[0] ) - 1;
+ if( !new && s > cmd[0] && ( *s == ':' || *s == ',' ) )
+ {
+ *s = '\0';
+
+ if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[0] ) ) )
+ {
+ struct twitter_user_data *tud = bu->data;
+
+ new = g_strdup_printf( "@%s %s", bu->handle,
+ message + ( s - cmd[0] ) + 2 );
+ message = new;
+
+ if( time( NULL ) < tud->last_time +
+ set_getint( &ic->acc->set, "auto_reply_timeout" ) )
+ in_reply_to = tud->last_id;
+ }
+ }
+
+ /* If the user runs undo between this request and its response
+ this would delete the second-last Tweet. Prevent that. */
+ td->last_status_id = 0;
+ twitter_post_status( ic, message, in_reply_to );
+ g_free( new );
+ }
+ g_free( cmds );
+}
+
+void twitter_initmodule()
+{
+ struct prpl *ret = g_new0(struct prpl, 1);
+
+ ret->options = OPT_NOOTR;
+ ret->name = "twitter";
+ ret->login = twitter_login;
+ ret->init = twitter_init;
+ ret->logout = twitter_logout;
+ ret->buddy_msg = twitter_buddy_msg;
+ ret->get_info = twitter_get_info;
+ ret->set_my_name = twitter_set_my_name;
+ ret->add_buddy = twitter_add_buddy;
+ ret->remove_buddy = twitter_remove_buddy;
+ ret->chat_msg = twitter_chat_msg;
+ ret->chat_invite = twitter_chat_invite;
+ ret->chat_leave = twitter_chat_leave;
+ ret->keepalive = twitter_keepalive;
+ ret->add_permit = twitter_add_permit;
+ ret->rem_permit = twitter_rem_permit;
+ ret->add_deny = twitter_add_deny;
+ ret->rem_deny = twitter_rem_deny;
+ ret->buddy_data_add = twitter_buddy_data_add;
+ ret->buddy_data_free = twitter_buddy_data_free;
+ ret->handle_cmp = g_strcasecmp;
+
+ register_protocol(ret);
+
+ /* And an identi.ca variant: */
+ ret = g_memdup(ret, sizeof(struct prpl));
+ ret->name = "identica";
+ register_protocol(ret);
+}
diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h
new file mode 100644
index 00000000..5bce97d4
--- /dev/null
+++ b/protocols/twitter/twitter.h
@@ -0,0 +1,87 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Simple module to facilitate twitter functionality. *
+* *
+* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> *
+* *
+* This library is free software; you can redistribute it and/or *
+* modify it under the terms of the GNU Lesser General Public *
+* License as published by the Free Software Foundation, version *
+* 2.1. *
+* *
+* This library is distributed in the hope that it will be useful, *
+* but WITHOUT ANY WARRANTY; without even the implied warranty of *
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+* Lesser General Public License for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this library; if not, write to the Free Software Foundation, *
+* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
+* *
+****************************************************************************/
+
+#include "nogaim.h"
+
+#ifndef _TWITTER_H
+#define _TWITTER_H
+
+#ifdef DEBUG_TWITTER
+#define debug( text... ) imcb_log( ic, text );
+#else
+#define debug( text... )
+#endif
+
+typedef enum
+{
+ TWITTER_HAVE_FRIENDS = 1,
+} twitter_flags_t;
+
+struct twitter_log_data;
+
+struct twitter_data
+{
+ char* user;
+ char* pass;
+ struct oauth_info *oauth_info;
+ guint64 home_timeline_id;
+ guint64 last_status_id; /* For undo */
+ gint main_loop_id;
+ struct groupchat *home_timeline_gc;
+ gint http_fails;
+ twitter_flags_t flags;
+
+ gboolean url_ssl;
+ int url_port;
+ char *url_host;
+ char *url_path;
+
+ char *prefix; /* Used to generate contact + channel name. */
+
+ struct twitter_log_data *log;
+ int log_id;
+};
+
+struct twitter_user_data
+{
+ guint64 last_id;
+ time_t last_time;
+};
+
+#define TWITTER_LOG_LENGTH 100
+struct twitter_log_data
+{
+ guint64 id;
+ struct bee_user *bu; /* DANGER: can be a dead pointer. Check it first. */
+};
+
+/**
+ * This has the same function as the msn_connections GSList. We use this to
+ * make sure the connection is still alive in callbacks before we do anything
+ * else.
+ */
+extern GSList *twitter_connections;
+
+void twitter_login_finish( struct im_connection *ic );
+
+#endif //_TWITTER_H
diff --git a/protocols/twitter/twitter_http.c b/protocols/twitter/twitter_http.c
new file mode 100644
index 00000000..ff17f5f4
--- /dev/null
+++ b/protocols/twitter/twitter_http.c
@@ -0,0 +1,142 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Simple module to facilitate twitter functionality. *
+* *
+* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> *
+* *
+* This library is free software; you can redistribute it and/or *
+* modify it under the terms of the GNU Lesser General Public *
+* License as published by the Free Software Foundation, version *
+* 2.1. *
+* *
+* This library is distributed in the hope that it will be useful, *
+* but WITHOUT ANY WARRANTY; without even the implied warranty of *
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+* Lesser General Public License for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this library; if not, write to the Free Software Foundation, *
+* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
+* *
+****************************************************************************/
+
+/***************************************************************************\
+* *
+* Some funtions within this file have been copied from other files within *
+* BitlBee. *
+* *
+****************************************************************************/
+
+#include "twitter.h"
+#include "bitlbee.h"
+#include "url.h"
+#include "misc.h"
+#include "base64.h"
+#include "oauth.h"
+#include <ctype.h>
+#include <errno.h>
+
+#include "twitter_http.h"
+
+
+static char *twitter_url_append(char *url, char *key, char* value);
+
+/**
+ * Do a request.
+ * This is actually pretty generic function... Perhaps it should move to the lib/http_client.c
+ */
+void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, gpointer data, int is_post, char** arguments, int arguments_len)
+{
+ struct twitter_data *td = ic->proto_data;
+ char *tmp;
+ GString *request = g_string_new("");
+ void *ret;
+ char *url_arguments;
+
+ url_arguments = g_strdup("");
+
+ // Construct the url arguments.
+ if (arguments_len != 0)
+ {
+ int i;
+ for (i=0; i<arguments_len; i+=2)
+ {
+ tmp = twitter_url_append(url_arguments, arguments[i], arguments[i+1]);
+ g_free(url_arguments);
+ url_arguments = tmp;
+ }
+ }
+
+ // Make the request.
+ g_string_printf(request, "%s %s%s%s%s HTTP/1.0\r\n"
+ "Host: %s\r\n"
+ "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n",
+ is_post ? "POST" : "GET",
+ td->url_path, url_string,
+ is_post ? "" : "?", is_post ? "" : url_arguments,
+ td->url_host);
+
+ // If a pass and user are given we append them to the request.
+ if (td->oauth_info)
+ {
+ char *full_header;
+ char *full_url;
+
+ full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url" ), url_string, NULL);
+ full_header = oauth_http_header(td->oauth_info, is_post ? "POST" : "GET",
+ full_url, url_arguments);
+
+ g_string_append_printf(request, "Authorization: %s\r\n", full_header);
+ g_free(full_header);
+ g_free(full_url);
+ }
+ else
+ {
+ char userpass[strlen(ic->acc->user)+2+strlen(ic->acc->pass)];
+ char *userpass_base64;
+
+ g_snprintf(userpass, sizeof(userpass), "%s:%s", ic->acc->user, ic->acc->pass);
+ userpass_base64 = base64_encode((unsigned char*)userpass, strlen(userpass));
+ g_string_append_printf(request, "Authorization: Basic %s\r\n", userpass_base64);
+ g_free( userpass_base64 );
+ }
+
+ // Do POST stuff..
+ if (is_post)
+ {
+ // Append the Content-Type and url-encoded arguments.
+ g_string_append_printf(request,
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Content-Length: %zd\r\n\r\n%s",
+ strlen(url_arguments), url_arguments);
+ } else {
+ // Append an extra \r\n to end the request...
+ g_string_append(request, "\r\n");
+ }
+
+ ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data);
+
+ g_free( url_arguments );
+ g_string_free( request, TRUE );
+ return ret;
+}
+
+static char *twitter_url_append(char *url, char *key, char* value)
+{
+ char *key_encoded = g_strndup(key, 3 * strlen(key));
+ http_encode(key_encoded);
+ char *value_encoded = g_strndup(value, 3 * strlen(value));
+ http_encode(value_encoded);
+
+ char *retval;
+ if (strlen(url) != 0)
+ retval = g_strdup_printf("%s&%s=%s", url, key_encoded, value_encoded);
+ else
+ retval = g_strdup_printf("%s=%s", key_encoded, value_encoded);
+
+ g_free(key_encoded);
+ g_free(value_encoded);
+
+ return retval;
+}
diff --git a/protocols/twitter/twitter_http.h b/protocols/twitter/twitter_http.h
new file mode 100644
index 00000000..393a1c26
--- /dev/null
+++ b/protocols/twitter/twitter_http.h
@@ -0,0 +1,36 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Simple module to facilitate twitter functionality. *
+* *
+* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> *
+* *
+* This library is free software; you can redistribute it and/or *
+* modify it under the terms of the GNU Lesser General Public *
+* License as published by the Free Software Foundation, version *
+* 2.1. *
+* *
+* This library is distributed in the hope that it will be useful, *
+* but WITHOUT ANY WARRANTY; without even the implied warranty of *
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+* Lesser General Public License for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this library; if not, write to the Free Software Foundation, *
+* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
+* *
+****************************************************************************/
+
+#ifndef _TWITTER_HTTP_H
+#define _TWITTER_HTTP_H
+
+#include "nogaim.h"
+#include "http_client.h"
+
+struct oauth_info;
+
+void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func,
+ gpointer data, int is_post, char** arguments, int arguments_len);
+
+#endif //_TWITTER_HTTP_H
+
diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c
new file mode 100644
index 00000000..e8fb3530
--- /dev/null
+++ b/protocols/twitter/twitter_lib.c
@@ -0,0 +1,898 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Simple module to facilitate twitter functionality. *
+* *
+* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> *
+* *
+* This library is free software; you can redistribute it and/or *
+* modify it under the terms of the GNU Lesser General Public *
+* License as published by the Free Software Foundation, version *
+* 2.1. *
+* *
+* This library is distributed in the hope that it will be useful, *
+* but WITHOUT ANY WARRANTY; without even the implied warranty of *
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+* Lesser General Public License for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this library; if not, write to the Free Software Foundation, *
+* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
+* *
+****************************************************************************/
+
+/* For strptime(): */
+#if(__sun)
+#else
+#define _XOPEN_SOURCE
+#endif
+
+#include "twitter_http.h"
+#include "twitter.h"
+#include "bitlbee.h"
+#include "url.h"
+#include "misc.h"
+#include "base64.h"
+#include "xmltree.h"
+#include "twitter_lib.h"
+#include <ctype.h>
+#include <errno.h>
+
+/* GLib < 2.12.0 doesn't have g_ascii_strtoll(), work around using system strtoll(). */
+/* GLib < 2.12.4 can be buggy: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=488013 */
+#if !GLIB_CHECK_VERSION(2,12,5)
+#include <stdlib.h>
+#include <limits.h>
+#define g_ascii_strtoll strtoll
+#endif
+
+#define TXL_STATUS 1
+#define TXL_USER 2
+#define TXL_ID 3
+
+struct twitter_xml_list {
+ int type;
+ gint64 next_cursor;
+ GSList *list;
+ gpointer data;
+};
+
+struct twitter_xml_user {
+ char *name;
+ char *screen_name;
+};
+
+struct twitter_xml_status {
+ time_t created_at;
+ char *text;
+ struct twitter_xml_user *user;
+ guint64 id, reply_to;
+};
+
+static void twitter_groupchat_init(struct im_connection *ic);
+
+/**
+ * Frees a twitter_xml_user struct.
+ */
+static void txu_free(struct twitter_xml_user *txu)
+{
+ if (txu == NULL)
+ return;
+ g_free(txu->name);
+ g_free(txu->screen_name);
+ g_free(txu);
+}
+
+
+/**
+ * Frees a twitter_xml_status struct.
+ */
+static void txs_free(struct twitter_xml_status *txs)
+{
+ g_free(txs->text);
+ txu_free(txs->user);
+ g_free(txs);
+}
+
+/**
+ * Free a twitter_xml_list struct.
+ * type is the type of list the struct holds.
+ */
+static void txl_free(struct twitter_xml_list *txl)
+{
+ GSList *l;
+ if (txl == NULL)
+ return;
+ for ( l = txl->list; l ; l = g_slist_next(l) )
+ if (txl->type == TXL_STATUS)
+ txs_free((struct twitter_xml_status *)l->data);
+ else if (txl->type == TXL_ID)
+ g_free(l->data);
+ g_slist_free(txl->list);
+ g_free(txl);
+}
+
+/**
+ * Add a buddy if it is not allready added, set the status to logged in.
+ */
+static void twitter_add_buddy(struct im_connection *ic, char *name, const char *fullname)
+{
+ struct twitter_data *td = ic->proto_data;
+
+ // Check if the buddy is allready in the buddy list.
+ if (!bee_user_by_handle( ic->bee, ic, name ))
+ {
+ char *mode = set_getstr(&ic->acc->set, "mode");
+
+ // The buddy is not in the list, add the buddy and set the status to logged in.
+ imcb_add_buddy( ic, name, NULL );
+ imcb_rename_buddy( ic, name, fullname );
+ if (g_strcasecmp(mode, "chat") == 0)
+ {
+ /* Necessary so that nicks always get translated to the
+ exact Twitter username. */
+ imcb_buddy_nick_hint( ic, name, name );
+ imcb_chat_add_buddy( td->home_timeline_gc, name );
+ }
+ else if (g_strcasecmp(mode, "many") == 0)
+ imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL );
+ }
+}
+
+/* Warning: May return a malloc()ed value, which will be free()d on the next
+ call. Only for short-term use. */
+static char *twitter_parse_error(struct http_request *req)
+{
+ static char *ret = NULL;
+ struct xt_parser *xp = NULL;
+ struct xt_node *node;
+
+ g_free(ret);
+ ret = NULL;
+
+ if (req->body_size > 0)
+ {
+ xp = xt_new(NULL, NULL);
+ xt_feed(xp, req->reply_body, req->body_size);
+
+ if ((node = xt_find_node(xp->root, "hash")) &&
+ (node = xt_find_node(node->children, "error")) &&
+ node->text_len > 0)
+ {
+ ret = g_strdup_printf("%s (%s)", req->status_string, node->text);
+ xt_free(xp);
+ return ret;
+ }
+
+ xt_free(xp);
+ }
+
+ return req->status_string;
+}
+
+static void twitter_http_get_friends_ids(struct http_request *req);
+
+/**
+ * Get the friends ids.
+ */
+void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor)
+{
+ // Primitive, but hey! It works...
+ char* args[2];
+ args[0] = "cursor";
+ args[1] = g_strdup_printf ("%lld", (long long) next_cursor);
+ twitter_http(ic, TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, args, 2);
+
+ g_free(args[1]);
+}
+
+/**
+ * Function to help fill a list.
+ */
+static xt_status twitter_xt_next_cursor( struct xt_node *node, struct twitter_xml_list *txl )
+{
+ char *end = NULL;
+
+ if( node->text )
+ txl->next_cursor = g_ascii_strtoll( node->text, &end, 10 );
+ if( end == NULL )
+ txl->next_cursor = -1;
+
+ return XT_HANDLED;
+}
+
+/**
+ * Fill a list of ids.
+ */
+static xt_status twitter_xt_get_friends_id_list( struct xt_node *node, struct twitter_xml_list *txl )
+{
+ struct xt_node *child;
+
+ // Set the list type.
+ txl->type = TXL_ID;
+
+ // The root <statuses> node should hold the list of statuses <status>
+ // Walk over the nodes children.
+ for( child = node->children ; child ; child = child->next )
+ {
+ if ( g_strcasecmp( "id", child->name ) == 0)
+ {
+ // Add the item to the list.
+ txl->list = g_slist_append (txl->list, g_memdup( child->text, child->text_len + 1 ));
+ }
+ else if ( g_strcasecmp( "next_cursor", child->name ) == 0)
+ {
+ twitter_xt_next_cursor(child, txl);
+ }
+ }
+
+ return XT_HANDLED;
+}
+
+/**
+ * Callback for getting the friends ids.
+ */
+static void twitter_http_get_friends_ids(struct http_request *req)
+{
+ struct im_connection *ic;
+ struct xt_parser *parser;
+ struct twitter_xml_list *txl;
+ struct twitter_data *td;
+
+ ic = req->data;
+
+ // Check if the connection is still active.
+ if( !g_slist_find( twitter_connections, ic ) )
+ return;
+
+ td = ic->proto_data;
+
+ // Check if the HTTP request went well.
+ if (req->status_code != 200) {
+ // It didn't go well, output the error and return.
+ if (++td->http_fails >= 5)
+ imcb_error(ic, "Could not retrieve friends: %s", twitter_parse_error(req));
+
+ return;
+ } else {
+ td->http_fails = 0;
+ }
+
+ txl = g_new0(struct twitter_xml_list, 1);
+
+ // Parse the data.
+ parser = xt_new( NULL, txl );
+ xt_feed( parser, req->reply_body, req->body_size );
+ twitter_xt_get_friends_id_list(parser->root, txl);
+ xt_free( parser );
+
+ if (txl->next_cursor)
+ twitter_get_friends_ids(ic, txl->next_cursor);
+
+ txl_free(txl);
+}
+
+/**
+ * Function to fill a twitter_xml_user struct.
+ * It sets:
+ * - the name and
+ * - the screen_name.
+ */
+static xt_status twitter_xt_get_user( struct xt_node *node, struct twitter_xml_user *txu )
+{
+ struct xt_node *child;
+
+ // Walk over the nodes children.
+ for( child = node->children ; child ; child = child->next )
+ {
+ if ( g_strcasecmp( "name", child->name ) == 0)
+ {
+ txu->name = g_memdup( child->text, child->text_len + 1 );
+ }
+ else if (g_strcasecmp( "screen_name", child->name ) == 0)
+ {
+ txu->screen_name = g_memdup( child->text, child->text_len + 1 );
+ }
+ }
+ return XT_HANDLED;
+}
+
+/**
+ * Function to fill a twitter_xml_list struct.
+ * It sets:
+ * - all <user>s from the <users> element.
+ */
+static xt_status twitter_xt_get_users( struct xt_node *node, struct twitter_xml_list *txl )
+{
+ struct twitter_xml_user *txu;
+ struct xt_node *child;
+
+ // Set the type of the list.
+ txl->type = TXL_USER;
+
+ // The root <users> node should hold the list of users <user>
+ // Walk over the nodes children.
+ for( child = node->children ; child ; child = child->next )
+ {
+ if ( g_strcasecmp( "user", child->name ) == 0)
+ {
+ txu = g_new0(struct twitter_xml_user, 1);
+ twitter_xt_get_user(child, txu);
+ // Put the item in the front of the list.
+ txl->list = g_slist_prepend (txl->list, txu);
+ }
+ }
+
+ return XT_HANDLED;
+}
+
+/**
+ * Function to fill a twitter_xml_list struct.
+ * It calls twitter_xt_get_users to get the <user>s from a <users> element.
+ * It sets:
+ * - the next_cursor.
+ */
+static xt_status twitter_xt_get_user_list( struct xt_node *node, struct twitter_xml_list *txl )
+{
+ struct xt_node *child;
+
+ // Set the type of the list.
+ txl->type = TXL_USER;
+
+ // The root <user_list> node should hold a users <users> element
+ // Walk over the nodes children.
+ for( child = node->children ; child ; child = child->next )
+ {
+ if ( g_strcasecmp( "users", child->name ) == 0)
+ {
+ twitter_xt_get_users(child, txl);
+ }
+ else if ( g_strcasecmp( "next_cursor", child->name ) == 0)
+ {
+ twitter_xt_next_cursor(child, txl);
+ }
+ }
+
+ return XT_HANDLED;
+}
+
+#ifdef __GLIBC__
+#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S %z %Y"
+#else
+#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y"
+#endif
+
+/**
+ * Function to fill a twitter_xml_status struct.
+ * It sets:
+ * - the status text and
+ * - the created_at timestamp and
+ * - the status id and
+ * - the user in a twitter_xml_user struct.
+ */
+static xt_status twitter_xt_get_status( struct xt_node *node, struct twitter_xml_status *txs )
+{
+ struct xt_node *child, *rt = NULL;
+ gboolean truncated = FALSE;
+
+ // Walk over the nodes children.
+ for( child = node->children ; child ; child = child->next )
+ {
+ if ( g_strcasecmp( "text", child->name ) == 0)
+ {
+ txs->text = g_memdup( child->text, child->text_len + 1 );
+ }
+ else if (g_strcasecmp( "truncated", child->name ) == 0 && child->text)
+ {
+ truncated = bool2int(child->text);
+ }
+ else if (g_strcasecmp( "retweeted_status", child->name ) == 0)
+ {
+ rt = child;
+ }
+ else if (g_strcasecmp( "created_at", child->name ) == 0)
+ {
+ struct tm parsed;
+
+ /* Very sensitive to changes to the formatting of
+ this field. :-( Also assumes the timezone used
+ is UTC since C time handling functions suck. */
+ if( strptime( child->text, TWITTER_TIME_FORMAT, &parsed ) != NULL )
+ txs->created_at = mktime_utc( &parsed );
+ }
+ else if (g_strcasecmp( "user", child->name ) == 0)
+ {
+ txs->user = g_new0(struct twitter_xml_user, 1);
+ twitter_xt_get_user( child, txs->user );
+ }
+ else if (g_strcasecmp( "id", child->name ) == 0)
+ {
+ txs->id = g_ascii_strtoull (child->text, NULL, 10);
+ }
+ else if (g_strcasecmp( "in_reply_to_status_id", child->name ) == 0)
+ {
+ txs->reply_to = g_ascii_strtoull (child->text, NULL, 10);
+ }
+ }
+
+ /* If it's a truncated retweet, get the original because dots suck. */
+ if (truncated && rt)
+ {
+ struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1);
+ if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED)
+ {
+ txs_free(rtxs);
+ return XT_HANDLED;
+ }
+
+ g_free(txs->text);
+ txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);
+ txs_free(rtxs);
+ }
+
+ return XT_HANDLED;
+}
+
+/**
+ * Function to fill a twitter_xml_list struct.
+ * It sets:
+ * - all <status>es within the <status> element and
+ * - the next_cursor.
+ */
+static xt_status twitter_xt_get_status_list( struct im_connection *ic, struct xt_node *node, struct twitter_xml_list *txl )
+{
+ struct twitter_xml_status *txs;
+ struct xt_node *child;
+ bee_user_t *bu;
+
+ // Set the type of the list.
+ txl->type = TXL_STATUS;
+
+ // The root <statuses> node should hold the list of statuses <status>
+ // Walk over the nodes children.
+ for( child = node->children ; child ; child = child->next )
+ {
+ if ( g_strcasecmp( "status", child->name ) == 0)
+ {
+ txs = g_new0(struct twitter_xml_status, 1);
+ twitter_xt_get_status(child, txs);
+ // Put the item in the front of the list.
+ txl->list = g_slist_prepend (txl->list, txs);
+
+ if (txs->user && txs->user->screen_name &&
+ (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name)))
+ {
+ struct twitter_user_data *tud = bu->data;
+
+ if (txs->id > tud->last_id)
+ {
+ tud->last_id = txs->id;
+ tud->last_time = txs->created_at;
+ }
+ }
+ }
+ else if ( g_strcasecmp( "next_cursor", child->name ) == 0)
+ {
+ twitter_xt_next_cursor(child, txl);
+ }
+ }
+
+ return XT_HANDLED;
+}
+
+static void twitter_http_get_home_timeline(struct http_request *req);
+
+/**
+ * Get the timeline.
+ */
+void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
+{
+ struct twitter_data *td = ic->proto_data;
+
+ char* args[4];
+ args[0] = "cursor";
+ args[1] = g_strdup_printf ("%lld", (long long) next_cursor);
+ if (td->home_timeline_id) {
+ args[2] = "since_id";
+ args[3] = g_strdup_printf ("%llu", (long long unsigned int) td->home_timeline_id);
+ }
+
+ twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args, td->home_timeline_id ? 4 : 2);
+
+ g_free(args[1]);
+ if (td->home_timeline_id) {
+ g_free(args[3]);
+ }
+}
+
+static char *twitter_msg_add_id(struct im_connection *ic,
+ struct twitter_xml_status *txs, const char *prefix)
+{
+ struct twitter_data *td = ic->proto_data;
+ char *ret = NULL;
+
+ if (!set_getbool(&ic->acc->set, "show_ids"))
+ {
+ if (*prefix)
+ return g_strconcat(prefix, txs->text, NULL);
+ else
+ return NULL;
+ }
+
+ td->log[td->log_id].id = txs->id;
+ td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name);
+ if (txs->reply_to)
+ {
+ int i;
+ for (i = 0; i < TWITTER_LOG_LENGTH; i ++)
+ if (td->log[i].id == txs->reply_to)
+ {
+ ret = g_strdup_printf( "\002[\002%02d->%02d\002]\002 %s%s",
+ td->log_id, i, prefix, txs->text);
+ break;
+ }
+ }
+ if (ret == NULL)
+ ret = g_strdup_printf( "\002[\002%02d\002]\002 %s%s",
+ td->log_id, prefix, txs->text);
+ td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH;
+
+ return ret;
+}
+
+static void twitter_groupchat_init(struct im_connection *ic)
+{
+ char *name_hint;
+ struct groupchat *gc;
+ struct twitter_data *td = ic->proto_data;
+ GSList *l;
+
+ td->home_timeline_gc = gc = imcb_chat_new( ic, "home/timeline" );
+
+ name_hint = g_strdup_printf( "%s_%s", td->prefix, ic->acc->user );
+ imcb_chat_name_hint( gc, name_hint );
+ g_free( name_hint );
+
+ for( l = ic->bee->users; l; l = l->next )
+ {
+ bee_user_t *bu = l->data;
+ if( bu->ic == ic )
+ imcb_chat_add_buddy( td->home_timeline_gc, bu->handle );
+ }
+}
+
+/**
+ * Function that is called to see the statuses in a groupchat window.
+ */
+static void twitter_groupchat(struct im_connection *ic, GSList *list)
+{
+ struct twitter_data *td = ic->proto_data;
+ GSList *l = NULL;
+ struct twitter_xml_status *status;
+ struct groupchat *gc;
+
+ // Create a new groupchat if it does not exsist.
+ if (!td->home_timeline_gc)
+ twitter_groupchat_init(ic);
+
+ gc = td->home_timeline_gc;
+ if (!gc->joined)
+ imcb_chat_add_buddy( gc, ic->acc->user );
+
+ for ( l = list; l ; l = g_slist_next(l) )
+ {
+ char *msg;
+
+ status = l->data;
+ if (status->user == NULL || status->text == NULL)
+ continue;
+
+ twitter_add_buddy(ic, status->user->screen_name, status->user->name);
+
+ strip_html(status->text);
+ msg = twitter_msg_add_id(ic, status, "");
+
+ // Say it!
+ if (g_strcasecmp(td->user, status->user->screen_name) == 0)
+ imcb_chat_log(gc, "You: %s", msg ? msg : status->text);
+ else
+ imcb_chat_msg(gc, status->user->screen_name,
+ msg ? msg : status->text, 0, status->created_at );
+
+ g_free(msg);
+
+ // Update the home_timeline_id to hold the highest id, so that by the next request
+ // we won't pick up the updates already in the list.
+ td->home_timeline_id = MAX(td->home_timeline_id, status->id);
+ }
+}
+
+/**
+ * Function that is called to see statuses as private messages.
+ */
+static void twitter_private_message_chat(struct im_connection *ic, GSList *list)
+{
+ struct twitter_data *td = ic->proto_data;
+ GSList *l = NULL;
+ struct twitter_xml_status *status;
+ char from[MAX_STRING];
+ gboolean mode_one;
+
+ mode_one = g_strcasecmp( set_getstr( &ic->acc->set, "mode" ), "one" ) == 0;
+
+ if( mode_one )
+ {
+ g_snprintf( from, sizeof( from ) - 1, "%s_%s", td->prefix, ic->acc->user );
+ from[MAX_STRING-1] = '\0';
+ }
+
+ for ( l = list; l ; l = g_slist_next(l) )
+ {
+ char *prefix = NULL, *text = NULL;
+
+ status = l->data;
+
+ strip_html( status->text );
+ if( mode_one )
+ prefix = g_strdup_printf("\002<\002%s\002>\002 ",
+ status->user->screen_name);
+ else
+ twitter_add_buddy(ic, status->user->screen_name, status->user->name);
+
+ text = twitter_msg_add_id(ic, status, prefix ? prefix : "");
+
+ imcb_buddy_msg( ic,
+ mode_one ? from : status->user->screen_name,
+ text ? text : status->text,
+ 0, status->created_at );
+
+ // Update the home_timeline_id to hold the highest id, so that by the next request
+ // we won't pick up the updates already in the list.
+ td->home_timeline_id = MAX(td->home_timeline_id, status->id);
+
+ g_free( text );
+ g_free( prefix );
+ }
+}
+
+/**
+ * Callback for getting the home timeline.
+ */
+static void twitter_http_get_home_timeline(struct http_request *req)
+{
+ struct im_connection *ic = req->data;
+ struct twitter_data *td;
+ struct xt_parser *parser;
+ struct twitter_xml_list *txl;
+
+ // Check if the connection is still active.
+ if( !g_slist_find( twitter_connections, ic ) )
+ return;
+
+ td = ic->proto_data;
+
+ // Check if the HTTP request went well.
+ if (req->status_code == 200)
+ {
+ td->http_fails = 0;
+ if (!(ic->flags & OPT_LOGGED_IN))
+ imcb_connected(ic);
+ }
+ else if (req->status_code == 401)
+ {
+ imcb_error( ic, "Authentication failure" );
+ imc_logout( ic, FALSE );
+ return;
+ }
+ else
+ {
+ // It didn't go well, output the error and return.
+ if (++td->http_fails >= 5)
+ imcb_error(ic, "Could not retrieve " TWITTER_HOME_TIMELINE_URL ": %s", twitter_parse_error(req));
+
+ return;
+ }
+
+ txl = g_new0(struct twitter_xml_list, 1);
+ txl->list = NULL;
+
+ // Parse the data.
+ parser = xt_new( NULL, txl );
+ xt_feed( parser, req->reply_body, req->body_size );
+ // The root <statuses> node should hold the list of statuses <status>
+ twitter_xt_get_status_list(ic, parser->root, txl);
+ xt_free( parser );
+
+ // See if the user wants to see the messages in a groupchat window or as private messages.
+ if (txl->list == NULL)
+ ;
+ else if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
+ twitter_groupchat(ic, txl->list);
+ else
+ twitter_private_message_chat(ic, txl->list);
+
+ // Free the structure.
+ txl_free(txl);
+}
+
+/**
+ * Callback for getting (twitter)friends...
+ *
+ * Be afraid, be very afraid! This function will potentially add hundreds of "friends". "Who has
+ * hundreds of friends?" you wonder? You probably not, since you are reading the source of
+ * BitlBee... Get a life and meet new people!
+ */
+static void twitter_http_get_statuses_friends(struct http_request *req)
+{
+ struct im_connection *ic = req->data;
+ struct twitter_data *td;
+ struct xt_parser *parser;
+ struct twitter_xml_list *txl;
+ GSList *l = NULL;
+ struct twitter_xml_user *user;
+
+ // Check if the connection is still active.
+ if( !g_slist_find( twitter_connections, ic ) )
+ return;
+
+ td = ic->proto_data;
+
+ // Check if the HTTP request went well.
+ if (req->status_code == 401)
+ {
+ imcb_error( ic, "Authentication failure" );
+ imc_logout( ic, FALSE );
+ return;
+ } else if (req->status_code != 200) {
+ // It didn't go well, output the error and return.
+ imcb_error(ic, "Could not retrieve " TWITTER_SHOW_FRIENDS_URL ": %s", twitter_parse_error(req));
+ imc_logout( ic, TRUE );
+ return;
+ } else {
+ td->http_fails = 0;
+ }
+
+ if( !td->home_timeline_gc &&
+ g_strcasecmp( set_getstr( &ic->acc->set, "mode" ), "chat" ) == 0 )
+ twitter_groupchat_init( ic );
+
+ txl = g_new0(struct twitter_xml_list, 1);
+ txl->list = NULL;
+
+ // Parse the data.
+ parser = xt_new( NULL, txl );
+ xt_feed( parser, req->reply_body, req->body_size );
+
+ // Get the user list from the parsed xml feed.
+ twitter_xt_get_user_list(parser->root, txl);
+ xt_free( parser );
+
+ // Add the users as buddies.
+ for ( l = txl->list; l ; l = g_slist_next(l) )
+ {
+ user = l->data;
+ twitter_add_buddy(ic, user->screen_name, user->name);
+ }
+
+ // if the next_cursor is set to something bigger then 0 there are more friends to gather.
+ if (txl->next_cursor > 0)
+ {
+ twitter_get_statuses_friends(ic, txl->next_cursor);
+ }
+ else
+ {
+ td->flags |= TWITTER_HAVE_FRIENDS;
+ twitter_login_finish(ic);
+ }
+
+ // Free the structure.
+ txl_free(txl);
+}
+
+/**
+ * Get the friends.
+ */
+void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor)
+{
+ char* args[2];
+ args[0] = "cursor";
+ args[1] = g_strdup_printf ("%lld", (long long) next_cursor);
+
+ twitter_http(ic, TWITTER_SHOW_FRIENDS_URL, twitter_http_get_statuses_friends, ic, 0, args, 2);
+
+ g_free(args[1]);
+}
+
+/**
+ * Callback to use after sending a post request to twitter.
+ */
+static void twitter_http_post(struct http_request *req)
+{
+ struct im_connection *ic = req->data;
+ struct twitter_data *td;
+
+ // Check if the connection is still active.
+ if( !g_slist_find( twitter_connections, ic ) )
+ return;
+
+ td = ic->proto_data;
+ td->last_status_id = 0;
+
+ // Check if the HTTP request went well.
+ if (req->status_code != 200) {
+ // It didn't go well, output the error and return.
+ imcb_error(ic, "HTTP error: %s", twitter_parse_error(req));
+ return;
+ }
+
+ if (req->body_size > 0)
+ {
+ struct xt_parser *xp = NULL;
+ struct xt_node *node;
+
+ xp = xt_new(NULL, NULL);
+ xt_feed(xp, req->reply_body, req->body_size);
+
+ if ((node = xt_find_node(xp->root, "status")) &&
+ (node = xt_find_node(node->children, "id")) && node->text)
+ td->last_status_id = g_ascii_strtoull( node->text, NULL, 10 );
+
+ xt_free(xp);
+ }
+}
+
+/**
+ * Function to POST a new status to twitter.
+ */
+void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to)
+{
+ char* args[4] = {
+ "status", msg,
+ "in_reply_to_status_id",
+ g_strdup_printf("%llu", (unsigned long long) in_reply_to)
+ };
+ twitter_http(ic, TWITTER_STATUS_UPDATE_URL, twitter_http_post, ic, 1,
+ args, in_reply_to ? 4 : 2);
+ g_free(args[3]);
+}
+
+
+/**
+ * Function to POST a new message to twitter.
+ */
+void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg)
+{
+ char* args[4];
+ args[0] = "screen_name";
+ args[1] = who;
+ args[2] = "text";
+ args[3] = msg;
+ // Use the same callback as for twitter_post_status, since it does basically the same.
+ twitter_http(ic, TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post, ic, 1, args, 4);
+// g_free(args[1]);
+// g_free(args[3]);
+}
+
+void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create)
+{
+ char* args[2];
+ args[0] = "screen_name";
+ args[1] = who;
+ twitter_http(ic, create ? TWITTER_FRIENDSHIPS_CREATE_URL : TWITTER_FRIENDSHIPS_DESTROY_URL, twitter_http_post, ic, 1, args, 2);
+}
+
+void twitter_status_destroy(struct im_connection *ic, guint64 id)
+{
+ char *url;
+ url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_DESTROY_URL, (unsigned long long) id, ".xml");
+ twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
+ g_free(url);
+}
+
+void twitter_status_retweet(struct im_connection *ic, guint64 id)
+{
+ char *url;
+ url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_RETWEET_URL, (unsigned long long) id, ".xml");
+ twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
+ g_free(url);
+}
diff --git a/protocols/twitter/twitter_lib.h b/protocols/twitter/twitter_lib.h
new file mode 100644
index 00000000..24b4a089
--- /dev/null
+++ b/protocols/twitter/twitter_lib.h
@@ -0,0 +1,91 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Simple module to facilitate twitter functionality. *
+* *
+* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> *
+* *
+* This library is free software; you can redistribute it and/or *
+* modify it under the terms of the GNU Lesser General Public *
+* License as published by the Free Software Foundation, version *
+* 2.1. *
+* *
+* This library is distributed in the hope that it will be useful, *
+* but WITHOUT ANY WARRANTY; without even the implied warranty of *
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+* Lesser General Public License for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this library; if not, write to the Free Software Foundation, *
+* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
+* *
+****************************************************************************/
+
+
+#ifndef _TWITTER_LIB_H
+#define _TWITTER_LIB_H
+
+#include "nogaim.h"
+#include "twitter_http.h"
+
+#define TWITTER_API_URL "http://twitter.com"
+#define IDENTICA_API_URL "http://identi.ca/api"
+
+/* Status URLs */
+#define TWITTER_STATUS_UPDATE_URL "/statuses/update.xml"
+#define TWITTER_STATUS_SHOW_URL "/statuses/show/"
+#define TWITTER_STATUS_DESTROY_URL "/statuses/destroy/"
+#define TWITTER_STATUS_RETWEET_URL "/statuses/retweet/"
+
+/* Timeline URLs */
+#define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline.xml"
+#define TWITTER_FEATURED_USERS_URL "/statuses/featured.xml"
+#define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline.xml"
+#define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline.xml"
+#define TWITTER_MENTIONS_URL "/statuses/mentions.xml"
+#define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline.xml"
+
+/* Users URLs */
+#define TWITTER_SHOW_USERS_URL "/users/show.xml"
+#define TWITTER_SHOW_FRIENDS_URL "/statuses/friends.xml"
+#define TWITTER_SHOW_FOLLOWERS_URL "/statuses/followers.xml"
+
+/* Direct messages URLs */
+#define TWITTER_DIRECT_MESSAGES_URL "/direct_messages.xml"
+#define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new.xml"
+#define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent.xml"
+#define TWITTER_DIRECT_MESSAGES_DESTROY_URL "/direct_messages/destroy/"
+
+/* Friendships URLs */
+#define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create.xml"
+#define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy.xml"
+#define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show.xml"
+
+/* Social graphs URLs */
+#define TWITTER_FRIENDS_IDS_URL "/friends/ids.xml"
+#define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.xml"
+
+/* Account URLs */
+#define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.xml"
+
+/* Favorites URLs */
+#define TWITTER_FAVORITES_GET_URL "/favorites.xml"
+#define TWITTER_FAVORITE_CREATE_URL "/favorites/create/"
+#define TWITTER_FAVORITE_DESTROY_URL "/favorites/destroy/"
+
+/* Block URLs */
+#define TWITTER_BLOCKS_CREATE_URL "/blocks/create/"
+#define TWITTER_BLOCKS_DESTROY_URL "/blocks/destroy/"
+
+void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor);
+void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
+void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor);
+
+void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to);
+void twitter_direct_messages_new(struct im_connection *ic, char *who, char *message);
+void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create);
+void twitter_status_destroy(struct im_connection *ic, guint64 id);
+void twitter_status_retweet(struct im_connection *ic, guint64 id);
+
+#endif //_TWITTER_LIB_H
+
diff --git a/protocols/yahoo/Makefile b/protocols/yahoo/Makefile
new file mode 100644
index 00000000..7908b773
--- /dev/null
+++ b/protocols/yahoo/Makefile
@@ -0,0 +1,47 @@
+###########################
+## Makefile for BitlBee ##
+## ##
+## Copyright 2002 Lintux ##
+###########################
+
+### DEFINITIONS
+
+-include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/yahoo/
+endif
+
+# [SH] Program variables
+objects = yahoo.o crypt.o libyahoo2.o yahoo_fn.o yahoo_httplib.o yahoo_util.o
+
+CFLAGS += -DSTDC_HEADERS -DHAVE_STRING_H -DHAVE_STRCHR -DHAVE_MEMCPY -DHAVE_GLIB
+LFLAGS += -r
+
+# [SH] Phony targets
+all: yahoo_mod.o
+check: all
+lcov: check
+gcov:
+ gcov *.c
+
+.PHONY: all clean distclean
+
+clean:
+ rm -f *.o core
+
+distclean: clean
+ rm -rf .depend
+
+### MAIN PROGRAM
+
+$(objects): ../../Makefile.settings Makefile
+
+$(objects): %.o: $(SRCDIR)%.c
+ @echo '*' Compiling $<
+ @$(CC) -c $(CFLAGS) $< -o $@
+
+yahoo_mod.o: $(objects)
+ @echo '*' Linking yahoo_mod.o
+ @$(LD) $(LFLAGS) $(objects) -o yahoo_mod.o
+
+-include .depend/*.d
diff --git a/protocols/yahoo/crypt.c b/protocols/yahoo/crypt.c
new file mode 100644
index 00000000..5122e3af
--- /dev/null
+++ b/protocols/yahoo/crypt.c
@@ -0,0 +1,203 @@
+/* One way encryption based on MD5 sum.
+ Copyright (C) 1996, 1997, 1999, 2000 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ 02111-1307 USA. */
+
+/* warmenhoven took this file and made it work with the md5.[ch] we
+ * already had. isn't that lovely. people should just use linux or
+ * freebsd, crypt works properly on those systems. i hate solaris */
+
+#if HAVE_STRING_H
+# include <string.h>
+#elif HAVE_STRINGS_H
+# include <strings.h>
+#endif
+
+#include <stdlib.h>
+#include "yahoo_util.h"
+
+#include "md5.h"
+
+/* Define our magic string to mark salt for MD5 "encryption"
+ replacement. This is meant to be the same as for other MD5 based
+ encryption implementations. */
+static const char md5_salt_prefix[] = "$1$";
+
+/* Table with characters for base64 transformation. */
+static const char b64t[64] =
+"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+char *yahoo_crypt(char *key, char *salt)
+{
+ char *buffer = NULL;
+ int buflen = 0;
+ int needed = 3 + strlen (salt) + 1 + 26 + 1;
+
+ md5_byte_t alt_result[16];
+ md5_state_t ctx;
+ md5_state_t alt_ctx;
+ size_t salt_len;
+ size_t key_len;
+ size_t cnt;
+ char *cp;
+
+ if (buflen < needed) {
+ buflen = needed;
+ if ((buffer = realloc(buffer, buflen)) == NULL)
+ return NULL;
+ }
+
+ /* Find beginning of salt string. The prefix should normally always
+ be present. Just in case it is not. */
+ if (strncmp (md5_salt_prefix, salt, sizeof (md5_salt_prefix) - 1) == 0)
+ /* Skip salt prefix. */
+ salt += sizeof (md5_salt_prefix) - 1;
+
+ salt_len = MIN (strcspn (salt, "$"), 8);
+ key_len = strlen (key);
+
+ /* Prepare for the real work. */
+ md5_init(&ctx);
+
+ /* Add the key string. */
+ md5_append(&ctx, (md5_byte_t *)key, key_len);
+
+ /* Because the SALT argument need not always have the salt prefix we
+ add it separately. */
+ md5_append(&ctx, (md5_byte_t *)md5_salt_prefix, sizeof (md5_salt_prefix) - 1);
+
+ /* The last part is the salt string. This must be at most 8
+ characters and it ends at the first `$' character (for
+ compatibility which existing solutions). */
+ md5_append(&ctx, (md5_byte_t *)salt, salt_len);
+
+ /* Compute alternate MD5 sum with input KEY, SALT, and KEY. The
+ final result will be added to the first context. */
+ md5_init(&alt_ctx);
+
+ /* Add key. */
+ md5_append(&alt_ctx, (md5_byte_t *)key, key_len);
+
+ /* Add salt. */
+ md5_append(&alt_ctx, (md5_byte_t *)salt, salt_len);
+
+ /* Add key again. */
+ md5_append(&alt_ctx, (md5_byte_t *)key, key_len);
+
+ /* Now get result of this (16 bytes) and add it to the other
+ context. */
+ md5_finish(&alt_ctx, alt_result);
+
+ /* Add for any character in the key one byte of the alternate sum. */
+ for (cnt = key_len; cnt > 16; cnt -= 16)
+ md5_append(&ctx, alt_result, 16);
+ md5_append(&ctx, alt_result, cnt);
+
+ /* For the following code we need a NUL byte. */
+ alt_result[0] = '\0';
+
+ /* The original implementation now does something weird: for every 1
+ bit in the key the first 0 is added to the buffer, for every 0
+ bit the first character of the key. This does not seem to be
+ what was intended but we have to follow this to be compatible. */
+ for (cnt = key_len; cnt > 0; cnt >>= 1)
+ md5_append(&ctx, (cnt & 1) != 0 ? alt_result : (md5_byte_t *)key, 1);
+
+ /* Create intermediate result. */
+ md5_finish(&ctx, alt_result);
+
+ /* Now comes another weirdness. In fear of password crackers here
+ comes a quite long loop which just processes the output of the
+ previous round again. We cannot ignore this here. */
+ for (cnt = 0; cnt < 1000; ++cnt) {
+ /* New context. */
+ md5_init(&ctx);
+
+ /* Add key or last result. */
+ if ((cnt & 1) != 0)
+ md5_append(&ctx, (md5_byte_t *)key, key_len);
+ else
+ md5_append(&ctx, alt_result, 16);
+
+ /* Add salt for numbers not divisible by 3. */
+ if (cnt % 3 != 0)
+ md5_append(&ctx, (md5_byte_t *)salt, salt_len);
+
+ /* Add key for numbers not divisible by 7. */
+ if (cnt % 7 != 0)
+ md5_append(&ctx, (md5_byte_t *)key, key_len);
+
+ /* Add key or last result. */
+ if ((cnt & 1) != 0)
+ md5_append(&ctx, alt_result, 16);
+ else
+ md5_append(&ctx, (md5_byte_t *)key, key_len);
+
+ /* Create intermediate result. */
+ md5_finish(&ctx, alt_result);
+ }
+
+ /* Now we can construct the result string. It consists of three
+ parts. */
+
+ strncpy(buffer, md5_salt_prefix, MAX (0, buflen));
+ cp = buffer + strlen(buffer);
+ buflen -= sizeof (md5_salt_prefix);
+
+ strncpy(cp, salt, MIN ((size_t) buflen, salt_len));
+ cp = cp + strlen(cp);
+ buflen -= MIN ((size_t) buflen, salt_len);
+
+ if (buflen > 0) {
+ *cp++ = '$';
+ --buflen;
+ }
+
+#define b64_from_24bit(B2, B1, B0, N) \
+ do { \
+ unsigned int w = ((B2) << 16) | ((B1) << 8) | (B0); \
+ int n = (N); \
+ while (n-- > 0 && buflen > 0) { \
+ *cp++ = b64t[w & 0x3f]; \
+ --buflen; \
+ w >>= 6; \
+ }\
+ } while (0)
+
+ b64_from_24bit (alt_result[0], alt_result[6], alt_result[12], 4);
+ b64_from_24bit (alt_result[1], alt_result[7], alt_result[13], 4);
+ b64_from_24bit (alt_result[2], alt_result[8], alt_result[14], 4);
+ b64_from_24bit (alt_result[3], alt_result[9], alt_result[15], 4);
+ b64_from_24bit (alt_result[4], alt_result[10], alt_result[5], 4);
+ b64_from_24bit (0, 0, alt_result[11], 2);
+ if (buflen <= 0) {
+ FREE(buffer);
+ } else
+ *cp = '\0'; /* Terminate the string. */
+
+ /* Clear the buffer for the intermediate result so that people
+ attaching to processes or reading core dumps cannot get any
+ information. We do it in this way to clear correct_words[]
+ inside the MD5 implementation as well. */
+ md5_init(&ctx);
+ md5_finish(&ctx, alt_result);
+ memset (&ctx, '\0', sizeof (ctx));
+ memset (&alt_ctx, '\0', sizeof (alt_ctx));
+
+ return buffer;
+}
diff --git a/protocols/yahoo/libyahoo2.c b/protocols/yahoo/libyahoo2.c
new file mode 100644
index 00000000..07689809
--- /dev/null
+++ b/protocols/yahoo/libyahoo2.c
@@ -0,0 +1,5437 @@
+/*
+ * libyahoo2: libyahoo2.c
+ *
+ * Some code copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.net>
+ * YMSG16 code copyright (C) 2009,
+ * Siddhesh Poyarekar <siddhesh dot poyarekar at gmail dot com>
+ *
+ * Yahoo Search copyright (C) 2003, Konstantin Klyagin <konst AT konst.org.ua>
+ *
+ * Much of this code was taken and adapted from the yahoo module for
+ * gaim released under the GNU GPL. This code is also released under the
+ * GNU GPL.
+ *
+ * This code is derivitive of Gaim <http://gaim.sourceforge.net>
+ * copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
+ * 1998-1999, Adam Fritzler <afritz@marko.net>
+ * 1998-2002, Rob Flynn <rob@marko.net>
+ * 2000-2002, Eric Warmenhoven <eric@warmenhoven.org>
+ * 2001-2002, Brian Macke <macke@strangelove.net>
+ * 2001, Anand Biligiri S <abiligiri@users.sf.net>
+ * 2001, Valdis Kletnieks
+ * 2002, Sean Egan <bj91704@binghamton.edu>
+ * 2002, Toby Gray <toby.gray@ntlworld.com>
+ *
+ * This library also uses code from other libraries, namely:
+ * Portions from libfaim copyright 1998, 1999 Adam Fritzler
+ * <afritz@auk.cx>
+ * Portions of Sylpheed copyright 2000-2002 Hiroyuki Yamamoto
+ * <hiro-y@kcn.ne.jp>
+ *
+ * YMSG16 authentication code based mostly on write-up at:
+ * http://www.carbonize.co.uk/ymsg16.html
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+#include <errno.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#if STDC_HEADERS
+# include <string.h>
+#else
+# if !HAVE_STRCHR
+# define strchr index
+# define strrchr rindex
+# endif
+char *strchr (), *strrchr ();
+# if !HAVE_MEMCPY
+# define memcpy(d, s, n) bcopy ((s), (d), (n))
+# define memmove(d, s, n) bcopy ((s), (d), (n))
+# endif
+#endif
+
+#include <sys/types.h>
+
+#ifdef __MINGW32__
+# include <winsock2.h>
+#endif
+
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "sha1.h"
+#include "md5.h"
+#include "yahoo2.h"
+#include "yahoo_httplib.h"
+#include "yahoo_util.h"
+#include "yahoo_fn.h"
+
+#include "yahoo2_callbacks.h"
+#include "yahoo_debug.h"
+#if defined(__MINGW32__) && !defined(HAVE_GLIB)
+#define snprintf _snprintf
+#define vsnprintf _vsnprintf
+#endif
+
+#include "base64.h"
+#include "http_client.h"
+
+#ifdef USE_STRUCT_CALLBACKS
+struct yahoo_callbacks *yc = NULL;
+
+void yahoo_register_callbacks(struct yahoo_callbacks *tyc)
+{
+ yc = tyc;
+}
+
+#define YAHOO_CALLBACK(x) yc->x
+#else
+#define YAHOO_CALLBACK(x) x
+#endif
+
+static int yahoo_send_data(void *fd, void *data, int len);
+static void _yahoo_http_connected(int id, void *fd, int error, void *data);
+static void yahoo_connected(void *fd, int error, void *data);
+
+int yahoo_log_message(char *fmt, ...)
+{
+ char out[1024];
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(out, sizeof(out), fmt, ap);
+ va_end(ap);
+ return YAHOO_CALLBACK(ext_yahoo_log) ("%s", out);
+}
+
+int yahoo_connect(char *host, int port)
+{
+ return YAHOO_CALLBACK(ext_yahoo_connect) (host, port);
+}
+
+static enum yahoo_log_level log_level = YAHOO_LOG_NONE;
+
+enum yahoo_log_level yahoo_get_log_level()
+{
+ return log_level;
+}
+
+int yahoo_set_log_level(enum yahoo_log_level level)
+{
+ enum yahoo_log_level l = log_level;
+ log_level = level;
+ return l;
+}
+
+/* default values for servers */
+static char *default_pager_hosts[] = { "scs.msg.yahoo.com",
+ "scsa.msg.yahoo.com",
+ "scsb.msg.yahoo.com",
+ "scsc.msg.yahoo.com",
+ NULL};
+
+static int pager_port = 5050;
+static int fallback_ports[] = { 23, 25, 80, 20, 119, 8001, 8002, 5050, 0 };
+
+static char filetransfer_host[] = "filetransfer.msg.yahoo.com";
+static int filetransfer_port = 80;
+static char webcam_host[] = "webcam.yahoo.com";
+static int webcam_port = 5100;
+static char webcam_description[] = "";
+static char local_host[] = "";
+static int conn_type = Y_WCM_DSL;
+
+static char profile_url[] = "http://profiles.yahoo.com/";
+
+struct connect_callback_data {
+ struct yahoo_data *yd;
+ int tag;
+ int i;
+ int server_i;
+};
+
+struct yahoo_pair {
+ int key;
+ char *value;
+};
+
+struct yahoo_packet {
+ unsigned short int service;
+ unsigned int status;
+ unsigned int id;
+ YList *hash;
+};
+
+struct yahoo_search_state {
+ int lsearch_type;
+ char *lsearch_text;
+ int lsearch_gender;
+ int lsearch_agerange;
+ int lsearch_photo;
+ int lsearch_yahoo_only;
+ int lsearch_nstart;
+ int lsearch_nfound;
+ int lsearch_ntotal;
+};
+
+struct data_queue {
+ unsigned char *queue;
+ int len;
+};
+
+struct yahoo_input_data {
+ struct yahoo_data *yd;
+ struct yahoo_webcam *wcm;
+ struct yahoo_webcam_data *wcd;
+ struct yahoo_search_state *ys;
+
+ void *fd;
+ enum yahoo_connection_type type;
+
+ unsigned char *rxqueue;
+ int rxlen;
+ int read_tag;
+
+ YList *txqueues;
+ int write_tag;
+};
+
+struct yahoo_server_settings {
+ char *pager_host;
+ int pager_port;
+ char *filetransfer_host;
+ int filetransfer_port;
+ char *webcam_host;
+ int webcam_port;
+ char *webcam_description;
+ char *local_host;
+ int conn_type;
+ char **pager_host_list;
+};
+
+static void yahoo_process_ft_connection(struct yahoo_input_data *yid, int over);
+
+static void yahoo_process_filetransfer(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt);
+static void yahoo_process_filetransferinfo(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt);
+static void yahoo_process_filetransferaccept(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt);
+
+static void yahoo_https_auth(struct yahoo_input_data *yid, const char *seed, const char *sn);
+
+static void *_yahoo_default_server_settings()
+{
+ struct yahoo_server_settings *yss =
+ y_new0(struct yahoo_server_settings, 1);
+
+ /* Give preference to the default host list
+ * Make sure that only one of the two is set at any time
+ */
+ yss->pager_host = NULL;
+ yss->pager_host_list = default_pager_hosts;
+
+ yss->pager_port = pager_port;
+ yss->filetransfer_host = strdup(filetransfer_host);
+ yss->filetransfer_port = filetransfer_port;
+ yss->webcam_host = strdup(webcam_host);
+ yss->webcam_port = webcam_port;
+ yss->webcam_description = strdup(webcam_description);
+ yss->local_host = strdup(local_host);
+ yss->conn_type = conn_type;
+
+ return yss;
+}
+
+static void *_yahoo_assign_server_settings(va_list ap)
+{
+ struct yahoo_server_settings *yss = _yahoo_default_server_settings();
+ char *key;
+ char *svalue;
+ int nvalue;
+ char **pvalue;
+
+ while (1) {
+ key = va_arg(ap, char *);
+ if (key == NULL)
+ break;
+
+ if (!strcmp(key, "pager_host")) {
+ svalue = va_arg(ap, char *);
+ free(yss->pager_host);
+ yss->pager_host = strdup(svalue);
+ yss->pager_host_list = NULL;
+ } else if (!strcmp(key, "pager_host_list")) {
+ pvalue = va_arg(ap, char **);
+ yss->pager_host_list = pvalue;
+ free(yss->pager_host);
+ yss->pager_host = NULL;
+ } else if (!strcmp(key, "pager_port")) {
+ nvalue = va_arg(ap, int);
+ yss->pager_port = nvalue;
+ } else if (!strcmp(key, "filetransfer_host")) {
+ svalue = va_arg(ap, char *);
+ free(yss->filetransfer_host);
+ yss->filetransfer_host = strdup(svalue);
+ } else if (!strcmp(key, "filetransfer_port")) {
+ nvalue = va_arg(ap, int);
+ yss->filetransfer_port = nvalue;
+ } else if (!strcmp(key, "webcam_host")) {
+ svalue = va_arg(ap, char *);
+ free(yss->webcam_host);
+ yss->webcam_host = strdup(svalue);
+ } else if (!strcmp(key, "webcam_port")) {
+ nvalue = va_arg(ap, int);
+ yss->webcam_port = nvalue;
+ } else if (!strcmp(key, "webcam_description")) {
+ svalue = va_arg(ap, char *);
+ free(yss->webcam_description);
+ yss->webcam_description = strdup(svalue);
+ } else if (!strcmp(key, "local_host")) {
+ svalue = va_arg(ap, char *);
+ free(yss->local_host);
+ yss->local_host = strdup(svalue);
+ } else if (!strcmp(key, "conn_type")) {
+ nvalue = va_arg(ap, int);
+ yss->conn_type = nvalue;
+ } else {
+ WARNING(("Unknown key passed to yahoo_init, "
+ "perhaps you didn't terminate the list "
+ "with NULL"));
+ }
+ }
+
+ return yss;
+}
+
+static void yahoo_free_server_settings(struct yahoo_server_settings *yss)
+{
+ if (!yss)
+ return;
+
+ free(yss->pager_host);
+ free(yss->filetransfer_host);
+ free(yss->webcam_host);
+ free(yss->webcam_description);
+ free(yss->local_host);
+
+ free(yss);
+}
+
+static YList *conns = NULL;
+static YList *inputs = NULL;
+static int last_id = 0;
+
+static void add_to_list(struct yahoo_data *yd)
+{
+ conns = y_list_prepend(conns, yd);
+}
+
+static struct yahoo_data *find_conn_by_id(int id)
+{
+ YList *l;
+ for (l = conns; l; l = y_list_next(l)) {
+ struct yahoo_data *yd = l->data;
+ if (yd->client_id == id)
+ return yd;
+ }
+ return NULL;
+}
+
+static void del_from_list(struct yahoo_data *yd)
+{
+ conns = y_list_remove(conns, yd);
+}
+
+/* call repeatedly to get the next one */
+/*
+static struct yahoo_input_data * find_input_by_id(int id)
+{
+ YList *l;
+ for(l = inputs; l; l = y_list_next(l)) {
+ struct yahoo_input_data *yid = l->data;
+ if(yid->yd->client_id == id)
+ return yid;
+ }
+ return NULL;
+}
+*/
+
+static struct yahoo_input_data *find_input_by_id_and_webcam_user(int id,
+ const char *who)
+{
+ YList *l;
+ LOG(("find_input_by_id_and_webcam_user"));
+ for (l = inputs; l; l = y_list_next(l)) {
+ struct yahoo_input_data *yid = l->data;
+ if (yid->type == YAHOO_CONNECTION_WEBCAM
+ && yid->yd->client_id == id && yid->wcm && ((who
+ && yid->wcm->user
+ && !strcmp(who, yid->wcm->user))
+ || !(yid->wcm->user && !who)))
+ return yid;
+ }
+ return NULL;
+}
+
+static struct yahoo_input_data *find_input_by_id_and_type(int id,
+ enum yahoo_connection_type type)
+{
+ YList *l;
+ LOG(("find_input_by_id_and_type"));
+ for (l = inputs; l; l = y_list_next(l)) {
+ struct yahoo_input_data *yid = l->data;
+ if (yid->type == type && yid->yd->client_id == id)
+ return yid;
+ }
+ return NULL;
+}
+
+static struct yahoo_input_data *find_input_by_id_and_fd(int id, void *fd)
+{
+ YList *l;
+ LOG(("find_input_by_id_and_fd"));
+ for (l = inputs; l; l = y_list_next(l)) {
+ struct yahoo_input_data *yid = l->data;
+ if (yid->fd == fd && yid->yd->client_id == id)
+ return yid;
+ }
+ return NULL;
+}
+
+static int count_inputs_with_id(int id)
+{
+ int c = 0;
+ YList *l;
+ LOG(("counting %d", id));
+ for (l = inputs; l; l = y_list_next(l)) {
+ struct yahoo_input_data *yid = l->data;
+ if (yid->yd->client_id == id)
+ c++;
+ }
+ LOG(("%d", c));
+ return c;
+}
+
+extern char *yahoo_crypt(char *, char *);
+
+/* Free a buddy list */
+static void yahoo_free_buddies(YList *list)
+{
+ YList *l;
+
+ for (l = list; l; l = l->next) {
+ struct yahoo_buddy *bud = l->data;
+ if (!bud)
+ continue;
+
+ FREE(bud->group);
+ FREE(bud->id);
+ FREE(bud->real_name);
+ if (bud->yab_entry) {
+ FREE(bud->yab_entry->fname);
+ FREE(bud->yab_entry->lname);
+ FREE(bud->yab_entry->nname);
+ FREE(bud->yab_entry->id);
+ FREE(bud->yab_entry->email);
+ FREE(bud->yab_entry->hphone);
+ FREE(bud->yab_entry->wphone);
+ FREE(bud->yab_entry->mphone);
+ FREE(bud->yab_entry);
+ }
+ FREE(bud);
+ l->data = bud = NULL;
+ }
+
+ y_list_free(list);
+}
+
+/* Free an identities list */
+static void yahoo_free_identities(YList *list)
+{
+ while (list) {
+ YList *n = list;
+ FREE(list->data);
+ list = y_list_remove_link(list, list);
+ y_list_free_1(n);
+ }
+}
+
+/* Free webcam data */
+static void yahoo_free_webcam(struct yahoo_webcam *wcm)
+{
+ if (wcm) {
+ FREE(wcm->user);
+ FREE(wcm->server);
+ FREE(wcm->key);
+ FREE(wcm->description);
+ FREE(wcm->my_ip);
+ }
+ FREE(wcm);
+}
+
+static void yahoo_free_data(struct yahoo_data *yd)
+{
+ FREE(yd->user);
+ FREE(yd->password);
+ FREE(yd->cookie_y);
+ FREE(yd->cookie_t);
+ FREE(yd->cookie_b);
+ FREE(yd->cookie_c);
+ FREE(yd->login_cookie);
+ FREE(yd->login_id);
+
+ yahoo_free_buddies(yd->buddies);
+ yahoo_free_buddies(yd->ignore);
+ yahoo_free_identities(yd->identities);
+
+ yahoo_free_server_settings(yd->server_settings);
+
+ FREE(yd);
+}
+
+#define YAHOO_PACKET_HDRLEN (4 + 2 + 2 + 2 + 2 + 4 + 4)
+
+static struct yahoo_packet *yahoo_packet_new(enum yahoo_service service,
+ enum ypacket_status status, int id)
+{
+ struct yahoo_packet *pkt = y_new0(struct yahoo_packet, 1);
+
+ pkt->service = service;
+ pkt->status = status;
+ pkt->id = id;
+
+ return pkt;
+}
+
+static void yahoo_packet_hash(struct yahoo_packet *pkt, int key,
+ const char *value)
+{
+ struct yahoo_pair *pair = y_new0(struct yahoo_pair, 1);
+ pair->key = key;
+ pair->value = strdup(value);
+ pkt->hash = y_list_append(pkt->hash, pair);
+}
+
+static int yahoo_packet_length(struct yahoo_packet *pkt)
+{
+ YList *l;
+
+ int len = 0;
+
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ int tmp = pair->key;
+ do {
+ tmp /= 10;
+ len++;
+ } while (tmp);
+ len += 2;
+ len += strlen(pair->value);
+ len += 2;
+ }
+
+ return len;
+}
+
+#define yahoo_put16(buf, data) ( \
+ (*(buf) = (unsigned char)((data)>>8)&0xff), \
+ (*((buf)+1) = (unsigned char)(data)&0xff), \
+ 2)
+#define yahoo_get16(buf) ((((*(buf))&0xff)<<8) + ((*((buf)+1)) & 0xff))
+#define yahoo_put32(buf, data) ( \
+ (*((buf)) = (unsigned char)((data)>>24)&0xff), \
+ (*((buf)+1) = (unsigned char)((data)>>16)&0xff), \
+ (*((buf)+2) = (unsigned char)((data)>>8)&0xff), \
+ (*((buf)+3) = (unsigned char)(data)&0xff), \
+ 4)
+#define yahoo_get32(buf) ((((*(buf) )&0xff)<<24) + \
+ (((*((buf)+1))&0xff)<<16) + \
+ (((*((buf)+2))&0xff)<< 8) + \
+ (((*((buf)+3))&0xff)))
+
+static void yahoo_packet_read(struct yahoo_packet *pkt, unsigned char *data,
+ int len)
+{
+ int pos = 0;
+
+ while (pos + 1 < len) {
+ char *key, *value = NULL;
+ int accept;
+ int x;
+
+ struct yahoo_pair *pair = y_new0(struct yahoo_pair, 1);
+
+ key = malloc(len + 1);
+ x = 0;
+ while (pos + 1 < len) {
+ if (data[pos] == 0xc0 && data[pos + 1] == 0x80)
+ break;
+ key[x++] = data[pos++];
+ }
+ key[x] = 0;
+ pos += 2;
+ pair->key = strtol(key, NULL, 10);
+ free(key);
+
+ /* Libyahoo2 developer(s) don't seem to have the time to fix
+ this problem, so for now try to work around it:
+
+ Sometimes we receive an invalid packet with not any more
+ data at this point. I don't know how to handle this in a
+ clean way, but let's hope this is clean enough: */
+
+ if (pos + 1 < len) {
+ accept = x;
+ /* if x is 0 there was no key, so don't accept it */
+ if (accept)
+ value = malloc(len - pos + 1);
+ x = 0;
+ while (pos + 1 < len) {
+ if (data[pos] == 0xc0 && data[pos + 1] == 0x80)
+ break;
+ if (accept)
+ value[x++] = data[pos++];
+ }
+ if (accept)
+ value[x] = 0;
+ pos += 2;
+ } else {
+ accept = 0;
+ }
+
+ if (accept) {
+ pair->value = strdup(value);
+ FREE(value);
+ pkt->hash = y_list_append(pkt->hash, pair);
+ DEBUG_MSG(("Key: %d \tValue: %s", pair->key,
+ pair->value));
+ } else {
+ FREE(pair);
+ }
+ }
+}
+
+static void yahoo_packet_write(struct yahoo_packet *pkt, unsigned char *data)
+{
+ YList *l;
+ int pos = 0;
+
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ unsigned char buf[100];
+
+ snprintf((char *)buf, sizeof(buf), "%d", pair->key);
+ strcpy((char *)data + pos, (char *)buf);
+ pos += strlen((char *)buf);
+ data[pos++] = 0xc0;
+ data[pos++] = 0x80;
+
+ strcpy((char *)data + pos, pair->value);
+ pos += strlen(pair->value);
+ data[pos++] = 0xc0;
+ data[pos++] = 0x80;
+ }
+}
+
+static void yahoo_dump_unhandled(struct yahoo_packet *pkt)
+{
+ YList *l;
+
+ NOTICE(("Service: 0x%02x\tStatus: %d", pkt->service, pkt->status));
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ NOTICE(("\t%d => %s", pair->key, pair->value));
+ }
+}
+
+static void yahoo_packet_dump(unsigned char *data, int len)
+{
+ if (yahoo_get_log_level() >= YAHOO_LOG_DEBUG) {
+ int i;
+ for (i = 0; i < len; i++) {
+ if ((i % 8 == 0) && i)
+ YAHOO_CALLBACK(ext_yahoo_log) (" ");
+ if ((i % 16 == 0) && i)
+ YAHOO_CALLBACK(ext_yahoo_log) ("\n");
+ YAHOO_CALLBACK(ext_yahoo_log) ("%02x ", data[i]);
+ }
+ YAHOO_CALLBACK(ext_yahoo_log) ("\n");
+ for (i = 0; i < len; i++) {
+ if ((i % 8 == 0) && i)
+ YAHOO_CALLBACK(ext_yahoo_log) (" ");
+ if ((i % 16 == 0) && i)
+ YAHOO_CALLBACK(ext_yahoo_log) ("\n");
+ if (isprint(data[i]))
+ YAHOO_CALLBACK(ext_yahoo_log) (" %c ", data[i]);
+ else
+ YAHOO_CALLBACK(ext_yahoo_log) (" . ");
+ }
+ YAHOO_CALLBACK(ext_yahoo_log) ("\n");
+ }
+}
+
+/* raw bytes in quasi-big-endian order to base 64 string (NUL-terminated) */
+static void to_y64(unsigned char *out, const unsigned char *in, int inlen)
+{
+ base64_encode_real(in, inlen, out, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-");
+}
+
+static void yahoo_add_to_send_queue(struct yahoo_input_data *yid, void *data,
+ int length)
+{
+ struct data_queue *tx = y_new0(struct data_queue, 1);
+ tx->queue = y_new0(unsigned char, length);
+ tx->len = length;
+ memcpy(tx->queue, data, length);
+
+ yid->txqueues = y_list_append(yid->txqueues, tx);
+
+ if (!yid->write_tag)
+ yid->write_tag =
+ YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->
+ client_id, yid->fd, YAHOO_INPUT_WRITE, yid);
+}
+
+static void yahoo_send_packet(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt, int extra_pad)
+{
+ int pktlen = yahoo_packet_length(pkt);
+ int len = YAHOO_PACKET_HDRLEN + pktlen;
+ unsigned char *data;
+ int pos = 0;
+
+ if (yid->fd < 0)
+ return;
+
+ data = y_new0(unsigned char, len + 1);
+
+ memcpy(data + pos, "YMSG", 4);
+ pos += 4;
+ pos += yahoo_put16(data + pos, YAHOO_PROTO_VER); /* version [latest 12 0x000c] */
+ pos += yahoo_put16(data + pos, 0x0000); /* HIWORD pkt length??? */
+ pos += yahoo_put16(data + pos, pktlen + extra_pad); /* LOWORD pkt length? */
+ pos += yahoo_put16(data + pos, pkt->service); /* service */
+ pos += yahoo_put32(data + pos, pkt->status); /* status [4bytes] */
+ pos += yahoo_put32(data + pos, pkt->id); /* session [4bytes] */
+
+ yahoo_packet_write(pkt, data + pos);
+
+ yahoo_packet_dump(data, len);
+
+ if (yid->type == YAHOO_CONNECTION_FT)
+ yahoo_send_data(yid->fd, data, len);
+ else
+ yahoo_add_to_send_queue(yid, data, len);
+ FREE(data);
+}
+
+static void yahoo_packet_free(struct yahoo_packet *pkt)
+{
+ while (pkt->hash) {
+ struct yahoo_pair *pair = pkt->hash->data;
+ YList *tmp;
+ FREE(pair->value);
+ FREE(pair);
+ tmp = pkt->hash;
+ pkt->hash = y_list_remove_link(pkt->hash, pkt->hash);
+ y_list_free_1(tmp);
+ }
+ FREE(pkt);
+}
+
+static int yahoo_send_data(void *fd, void *data, int len)
+{
+ int ret;
+ int e;
+
+ if (fd == NULL)
+ return -1;
+
+ yahoo_packet_dump(data, len);
+
+ do {
+ ret = YAHOO_CALLBACK(ext_yahoo_write) (fd, data, len);
+ } while (ret == -1 && errno == EINTR);
+ e = errno;
+
+ if (ret == -1) {
+ LOG(("wrote data: ERR %s", strerror(errno)));
+ } else {
+ LOG(("wrote data: OK"));
+ }
+
+ errno = e;
+ return ret;
+}
+
+void yahoo_close(int id)
+{
+ struct yahoo_data *yd = find_conn_by_id(id);
+ if (!yd)
+ return;
+
+ del_from_list(yd);
+
+ yahoo_free_data(yd);
+ if (id == last_id)
+ last_id--;
+}
+
+static void yahoo_input_close(struct yahoo_input_data *yid)
+{
+ inputs = y_list_remove(inputs, yid);
+
+ LOG(("yahoo_input_close(read)"));
+ YAHOO_CALLBACK(ext_yahoo_remove_handler) (yid->yd->client_id,
+ yid->read_tag);
+ LOG(("yahoo_input_close(write)"));
+ YAHOO_CALLBACK(ext_yahoo_remove_handler) (yid->yd->client_id,
+ yid->write_tag);
+ yid->read_tag = yid->write_tag = 0;
+ if (yid->fd)
+ YAHOO_CALLBACK(ext_yahoo_close) (yid->fd);
+ yid->fd = 0;
+ FREE(yid->rxqueue);
+ if (count_inputs_with_id(yid->yd->client_id) == 0) {
+ LOG(("closing %d", yid->yd->client_id));
+ yahoo_close(yid->yd->client_id);
+ }
+ yahoo_free_webcam(yid->wcm);
+ if (yid->wcd)
+ FREE(yid->wcd);
+ if (yid->ys) {
+ FREE(yid->ys->lsearch_text);
+ FREE(yid->ys);
+ }
+ FREE(yid);
+}
+
+static int is_same_bud(const void *a, const void *b)
+{
+ const struct yahoo_buddy *subject = a;
+ const struct yahoo_buddy *object = b;
+
+ return strcmp(subject->id, object->id);
+}
+
+static char *getcookie(char *rawcookie)
+{
+ char *cookie = NULL;
+ char *tmpcookie;
+ char *cookieend;
+
+ if (strlen(rawcookie) < 2)
+ return NULL;
+
+ tmpcookie = strdup(rawcookie + 2);
+ cookieend = strchr(tmpcookie, ';');
+
+ if (cookieend)
+ *cookieend = '\0';
+
+ cookie = strdup(tmpcookie);
+ FREE(tmpcookie);
+ /* cookieend=NULL; not sure why this was there since the value is not preserved in the stack -dd */
+
+ return cookie;
+}
+
+static char *getlcookie(char *cookie)
+{
+ char *tmp;
+ char *tmpend;
+ char *login_cookie = NULL;
+
+ tmpend = strstr(cookie, "n=");
+ if (tmpend) {
+ tmp = strdup(tmpend + 2);
+ tmpend = strchr(tmp, '&');
+ if (tmpend)
+ *tmpend = '\0';
+ login_cookie = strdup(tmp);
+ FREE(tmp);
+ }
+
+ return login_cookie;
+}
+
+static void yahoo_process_notify(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ struct yahoo_data *yd = yid->yd;
+ char *msg = NULL;
+ char *from = NULL;
+ char *to = NULL;
+ int stat = 0;
+ int accept = 0;
+ char *ind = NULL;
+ YList *l;
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 4)
+ from = pair->value;
+ if (pair->key == 5)
+ to = pair->value;
+ if (pair->key == 49)
+ msg = pair->value;
+ if (pair->key == 13)
+ stat = atoi(pair->value);
+ if (pair->key == 14)
+ ind = pair->value;
+ if (pair->key == 16) { /* status == -1 */
+ NOTICE((pair->value));
+ return;
+ }
+
+ }
+
+ if (!msg)
+ return;
+
+ if (!strncasecmp(msg, "TYPING", strlen("TYPING")))
+ YAHOO_CALLBACK(ext_yahoo_typing_notify) (yd->client_id, to,
+ from, stat);
+ else if (!strncasecmp(msg, "GAME", strlen("GAME")))
+ YAHOO_CALLBACK(ext_yahoo_game_notify) (yd->client_id, to, from,
+ stat, ind);
+ else if (!strncasecmp(msg, "WEBCAMINVITE", strlen("WEBCAMINVITE"))) {
+ if (!strcmp(ind, " ")) {
+ YAHOO_CALLBACK(ext_yahoo_webcam_invite) (yd->client_id,
+ to, from);
+ } else {
+ accept = atoi(ind);
+ /* accept the invitation (-1 = deny 1 = accept) */
+ if (accept < 0)
+ accept = 0;
+ YAHOO_CALLBACK(ext_yahoo_webcam_invite_reply) (yd->
+ client_id, to, from, accept);
+ }
+ } else
+ LOG(("Got unknown notification: %s", msg));
+}
+
+static void yahoo_process_conference(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ struct yahoo_data *yd = yid->yd;
+ char *msg = NULL;
+ char *host = NULL;
+ char *who = NULL;
+ char *room = NULL;
+ char *id = NULL;
+ int utf8 = 0;
+ YList *members = NULL;
+ YList *l;
+
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 50)
+ host = pair->value;
+
+ if (pair->key == 52) { /* invite */
+ members = y_list_append(members, strdup(pair->value));
+ }
+ if (pair->key == 53) /* logon */
+ who = pair->value;
+ if (pair->key == 54) /* decline */
+ who = pair->value;
+ if (pair->key == 55) /* unavailable (status == 2) */
+ who = pair->value;
+ if (pair->key == 56) /* logoff */
+ who = pair->value;
+
+ if (pair->key == 57)
+ room = pair->value;
+
+ if (pair->key == 58) /* join message */
+ msg = pair->value;
+ if (pair->key == 14) /* decline/conf message */
+ msg = pair->value;
+
+ if (pair->key == 13) ;
+ if (pair->key == 16) /* error */
+ msg = pair->value;
+
+ if (pair->key == 1) /* my id */
+ id = pair->value;
+ if (pair->key == 3) /* message sender */
+ who = pair->value;
+
+ if (pair->key == 97)
+ utf8 = atoi(pair->value);
+ }
+
+ if (!room)
+ return;
+
+ if (host) {
+ for (l = members; l; l = l->next) {
+ char *w = l->data;
+ if (!strcmp(w, host))
+ break;
+ }
+ if (!l)
+ members = y_list_append(members, strdup(host));
+ }
+ /* invite, decline, join, left, message -> status == 1 */
+
+ switch (pkt->service) {
+ case YAHOO_SERVICE_CONFINVITE:
+ if (pkt->status == 2) ;
+ else if (members)
+ YAHOO_CALLBACK(ext_yahoo_got_conf_invite) (yd->
+ client_id, id, host, room, msg, members);
+ else if (msg)
+ YAHOO_CALLBACK(ext_yahoo_error) (yd->client_id, msg, 0,
+ E_CONFNOTAVAIL);
+ break;
+ case YAHOO_SERVICE_CONFADDINVITE:
+ if (pkt->status == 1)
+ YAHOO_CALLBACK(ext_yahoo_got_conf_invite) (yd->
+ client_id, id, host, room, msg, members);
+ break;
+ case YAHOO_SERVICE_CONFDECLINE:
+ if (who)
+ YAHOO_CALLBACK(ext_yahoo_conf_userdecline) (yd->
+ client_id, id, who, room, msg);
+ break;
+ case YAHOO_SERVICE_CONFLOGON:
+ if (who)
+ YAHOO_CALLBACK(ext_yahoo_conf_userjoin) (yd->client_id,
+ id, who, room);
+ break;
+ case YAHOO_SERVICE_CONFLOGOFF:
+ if (who)
+ YAHOO_CALLBACK(ext_yahoo_conf_userleave) (yd->client_id,
+ id, who, room);
+ break;
+ case YAHOO_SERVICE_CONFMSG:
+ if (who)
+ YAHOO_CALLBACK(ext_yahoo_conf_message) (yd->client_id,
+ id, who, room, msg, utf8);
+ break;
+ }
+}
+
+static void yahoo_process_chat(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ char *msg = NULL;
+ char *id = NULL;
+ char *who = NULL;
+ char *room = NULL;
+ char *topic = NULL;
+ YList *members = NULL;
+ struct yahoo_chat_member *currentmember = NULL;
+ int msgtype = 1;
+ int utf8 = 0;
+ int firstjoin = 0;
+ int membercount = 0;
+ int chaterr = 0;
+ YList *l;
+
+ yahoo_dump_unhandled(pkt);
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+
+ if (pair->key == 1) {
+ /* My identity */
+ id = pair->value;
+ }
+
+ if (pair->key == 104) {
+ /* Room name */
+ room = pair->value;
+ }
+
+ if (pair->key == 105) {
+ /* Room topic */
+ topic = pair->value;
+ }
+
+ if (pair->key == 108) {
+ /* Number of members in this packet */
+ membercount = atoi(pair->value);
+ }
+
+ if (pair->key == 109) {
+ /* message sender */
+ who = pair->value;
+
+ if (pkt->service == YAHOO_SERVICE_CHATJOIN) {
+ currentmember =
+ y_new0(struct yahoo_chat_member, 1);
+ currentmember->id = strdup(pair->value);
+ members = y_list_append(members, currentmember);
+ }
+ }
+
+ if (pair->key == 110) {
+ /* age */
+ if (pkt->service == YAHOO_SERVICE_CHATJOIN)
+ currentmember->age = atoi(pair->value);
+ }
+
+ if (pair->key == 113) {
+ /* attribs */
+ if (pkt->service == YAHOO_SERVICE_CHATJOIN)
+ currentmember->attribs = atoi(pair->value);
+ }
+
+ if (pair->key == 141) {
+ /* alias */
+ if (pkt->service == YAHOO_SERVICE_CHATJOIN)
+ currentmember->alias = strdup(pair->value);
+ }
+
+ if (pair->key == 142) {
+ /* location */
+ if (pkt->service == YAHOO_SERVICE_CHATJOIN)
+ currentmember->location = strdup(pair->value);
+ }
+
+ if (pair->key == 130) {
+ /* first join */
+ firstjoin = 1;
+ }
+
+ if (pair->key == 117) {
+ /* message */
+ msg = pair->value;
+ }
+
+ if (pair->key == 124) {
+ /* Message type */
+ msgtype = atoi(pair->value);
+ }
+ if (pair->key == 114) {
+ /* message error not sure what all the pair values mean */
+ /* but -1 means no session in room */
+ chaterr = atoi(pair->value);
+ }
+ }
+
+ if (!room) {
+ if (pkt->service == YAHOO_SERVICE_CHATLOGOUT) { /* yahoo originated chat logout */
+ YAHOO_CALLBACK(ext_yahoo_chat_yahoologout) (yid->yd->
+ client_id, id);
+ return;
+ }
+ if (pkt->service == YAHOO_SERVICE_COMMENT && chaterr) {
+ YAHOO_CALLBACK(ext_yahoo_chat_yahooerror) (yid->yd->
+ client_id, id);
+ return;
+ }
+
+ WARNING(("We didn't get a room name, ignoring packet"));
+ return;
+ }
+
+ switch (pkt->service) {
+ case YAHOO_SERVICE_CHATJOIN:
+ if (y_list_length(members) != membercount) {
+ WARNING(("Count of members doesn't match No. of members we got"));
+ }
+ if (firstjoin && members) {
+ YAHOO_CALLBACK(ext_yahoo_chat_join) (yid->yd->client_id,
+ id, room, topic, members, yid->fd);
+ } else if (who) {
+ if (y_list_length(members) != 1) {
+ WARNING(("Got more than 1 member on a normal join"));
+ }
+ /* this should only ever have one, but just in case */
+ while (members) {
+ YList *n = members->next;
+ currentmember = members->data;
+ YAHOO_CALLBACK(ext_yahoo_chat_userjoin) (yid->
+ yd->client_id, id, room, currentmember);
+ y_list_free_1(members);
+ members = n;
+ }
+ }
+ break;
+ case YAHOO_SERVICE_CHATEXIT:
+ if (who) {
+ YAHOO_CALLBACK(ext_yahoo_chat_userleave) (yid->yd->
+ client_id, id, room, who);
+ }
+ break;
+ case YAHOO_SERVICE_COMMENT:
+ if (who) {
+ YAHOO_CALLBACK(ext_yahoo_chat_message) (yid->yd->
+ client_id, id, who, room, msg, msgtype, utf8);
+ }
+ break;
+ }
+}
+
+static void yahoo_process_message(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ struct yahoo_data *yd = yid->yd;
+ YList *l;
+ YList *messages = NULL;
+
+ struct m {
+ int i_31;
+ int i_32;
+ char *to;
+ char *from;
+ long tm;
+ char *msg;
+ int utf8;
+ char *gunk;
+ } *message = y_new0(struct m, 1);
+
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 1 || pair->key == 4) {
+ if (!message->from)
+ message->from = pair->value;
+ } else if (pair->key == 5)
+ message->to = pair->value;
+ else if (pair->key == 15)
+ message->tm = strtol(pair->value, NULL, 10);
+ else if (pair->key == 97)
+ message->utf8 = atoi(pair->value);
+ /* This comes when the official client sends us a message */
+ else if (pair->key == 429)
+ message->gunk = pair->value;
+ /* user message *//* sys message */
+ else if (pair->key == 14 || pair->key == 16)
+ message->msg = pair->value;
+ else if (pair->key == 31) {
+ if (message->i_31) {
+ messages = y_list_append(messages, message);
+ message = y_new0(struct m, 1);
+ }
+ message->i_31 = atoi(pair->value);
+ } else if (pair->key == 32)
+ message->i_32 = atoi(pair->value);
+ else
+ LOG(("yahoo_process_message: status: %d, key: %d, value: %s", pkt->status, pair->key, pair->value));
+ }
+
+ messages = y_list_append(messages, message);
+
+ for (l = messages; l; l = l->next) {
+ message = l->data;
+ if (pkt->service == YAHOO_SERVICE_SYSMESSAGE) {
+ YAHOO_CALLBACK(ext_yahoo_system_message) (yd->client_id,
+ message->to, message->from, message->msg);
+ } else if (pkt->status <= 2 || pkt->status == 5) {
+ /* Confirm message receipt if we got the gunk */
+ if(message->gunk) {
+ struct yahoo_packet *outpkt;
+
+ outpkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE_CONFIRM,
+ YPACKET_STATUS_DEFAULT, 0);
+ yahoo_packet_hash(outpkt, 1, yd->user);
+ yahoo_packet_hash(outpkt, 5, message->from);
+ yahoo_packet_hash(outpkt, 302, "430");
+ yahoo_packet_hash(outpkt, 430, message->gunk);
+ yahoo_packet_hash(outpkt, 303, "430");
+ yahoo_packet_hash(outpkt, 450, "0");
+ yahoo_send_packet(yid, outpkt, 0);
+
+ yahoo_packet_free(outpkt);
+ }
+
+ if (!strcmp(message->msg, "<ding>"))
+ YAHOO_CALLBACK(ext_yahoo_got_buzz) (yd->client_id,
+ message->to, message->from, message->tm);
+ else
+ YAHOO_CALLBACK(ext_yahoo_got_im) (yd->client_id,
+ message->to, message->from, message->msg,
+ message->tm, pkt->status, message->utf8);
+ } else if (pkt->status == 0xffffffff) {
+ YAHOO_CALLBACK(ext_yahoo_error) (yd->client_id,
+ message->msg, 0, E_SYSTEM);
+ }
+ FREE(message);
+ }
+
+ y_list_free(messages);
+}
+
+/*
+ * Here's what multi-level packets look like. Data in brackets is the value.
+ *
+ * 3 level:
+ * =======
+ *
+ * 302 (318) - Beginning level 1
+ * 300 (318) - Begin level 2
+ * 302 (319) - End level 2 header
+ * 300 (319) - Begin level 3
+ * 301 (319) - End level 3
+ * 303 (319) - End level 2
+ * 303 (318) - End level 1
+ *
+ * 2 level:
+ * =======
+ *
+ * 302 (315) - Beginning level 1
+ * 300 (315) - Begin level 2
+ * 301 (315) - End level 2
+ * 303 (315) - End level 1
+ *
+ */
+static void yahoo_process_status(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ YList *l;
+ struct yahoo_data *yd = yid->yd;
+
+ struct yahoo_process_status_entry *u;
+
+ YList *users = 0;
+
+ if (pkt->service == YAHOO_SERVICE_LOGOFF && pkt->status == -1) {
+ YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
+ YAHOO_LOGIN_DUPL, NULL);
+ return;
+ }
+
+ /*
+ * Status updates may be spread accross multiple packets and not
+ * even on buddy boundaries, so keeping some state is important.
+ * So, continue where we left off, and only add a user entry to
+ * the list once it's complete (301-315 End buddy).
+ */
+ u = yd->half_user;
+
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+
+ switch (pair->key) {
+ case 300: /* Begin buddy */
+ if (!strcmp(pair->value, "315") && !u) {
+ u = yd->half_user = y_new0(struct yahoo_process_status_entry, 1);
+ }
+ break;
+ case 301: /* End buddy */
+ if (!strcmp(pair->value, "315") && u) {
+ /* Sometimes user info comes in an odd format with no
+ "begin buddy" but *with* an "end buddy". Don't add
+ it twice. */
+ if (!y_list_find(users, u))
+ users = y_list_prepend(users, u);
+ u = yd->half_user = NULL;
+ }
+ break;
+ case 0: /* we won't actually do anything with this */
+ NOTICE(("key %d:%s", pair->key, pair->value));
+ break;
+ case 1: /* we don't get the full buddy list here. */
+ if (!yd->logged_in) {
+ yd->logged_in = 1;
+ if (yd->current_status < 0)
+ yd->current_status = yd->initial_status;
+ YAHOO_CALLBACK(ext_yahoo_login_response) (yd->
+ client_id, YAHOO_LOGIN_OK, NULL);
+ }
+ break;
+ case 8: /* how many online buddies we have */
+ NOTICE(("key %d:%s", pair->key, pair->value));
+ break;
+ case 7: /* the current buddy */
+ if (!u) {
+ /* This will only happen in case of a single level message */
+ u = y_new0(struct yahoo_process_status_entry, 1);
+ users = y_list_prepend(users, u);
+ }
+ u->name = pair->value;
+ break;
+ case 10: /* state */
+ u->state = strtol(pair->value, NULL, 10);
+ break;
+ case 19: /* custom status message */
+ u->msg = pair->value;
+ break;
+ case 47: /* is it an away message or not. Not applicable for YMSG16 anymore */
+ u->away = atoi(pair->value);
+ break;
+ case 137: /* seconds idle */
+ u->idle = atoi(pair->value);
+ break;
+ case 11: /* this is the buddy's session id */
+ u->buddy_session = atoi(pair->value);
+ break;
+ case 17: /* in chat? */
+ u->f17 = atoi(pair->value);
+ break;
+ case 13: /* bitmask, bit 0 = pager, bit 1 = chat, bit 2 = game */
+ u->flags = atoi(pair->value);
+ break;
+ case 60: /* SMS -> 1 MOBILE USER */
+ /* sometimes going offline makes this 2, but invisible never sends it */
+ u->mobile = atoi(pair->value);
+ break;
+ case 138:
+ u->f138 = atoi(pair->value);
+ break;
+ case 184:
+ u->f184 = pair->value;
+ break;
+ case 192:
+ u->f192 = atoi(pair->value);
+ break;
+ case 10001:
+ u->f10001 = atoi(pair->value);
+ break;
+ case 10002:
+ u->f10002 = atoi(pair->value);
+ break;
+ case 198:
+ u->f198 = atoi(pair->value);
+ break;
+ case 197:
+ u->f197 = pair->value;
+ break;
+ case 205:
+ u->f205 = pair->value;
+ break;
+ case 213:
+ u->f213 = atoi(pair->value);
+ break;
+ case 16: /* Custom error message */
+ YAHOO_CALLBACK(ext_yahoo_error) (yd->client_id,
+ pair->value, 0, E_CUSTOM);
+ break;
+ default:
+ WARNING(("unknown status key %d:%s", pair->key,
+ pair->value));
+ break;
+ }
+ }
+
+ while (users) {
+ YList *t = users;
+ struct yahoo_process_status_entry *u = users->data;
+
+ if (u->name != NULL) {
+ if (pkt->service ==
+ YAHOO_SERVICE_LOGOFF
+ /*|| u->flags == 0 No flags for YMSG16 */ ) {
+ YAHOO_CALLBACK(ext_yahoo_status_changed) (yd->
+ client_id, u->name,
+ YAHOO_STATUS_OFFLINE, NULL, 1, 0, 0);
+ } else {
+ /* Key 47 always seems to be 1 for YMSG16 */
+ if (!u->state)
+ u->away = 0;
+ else
+ u->away = 1;
+
+ YAHOO_CALLBACK(ext_yahoo_status_changed) (yd->
+ client_id, u->name, u->state, u->msg,
+ u->away, u->idle, u->mobile);
+ }
+ }
+
+ users = y_list_remove_link(users, users);
+ y_list_free_1(t);
+ FREE(u);
+ }
+}
+
+static void yahoo_process_buddy_list(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ struct yahoo_data *yd = yid->yd;
+ YList *l;
+ int last_packet = 0;
+ char *cur_group = NULL;
+ struct yahoo_buddy *newbud = NULL;
+
+ /* we could be getting multiple packets here */
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+
+ switch (pair->key) {
+ case 300:
+ case 301:
+ case 302:
+ break; /* Separators. Our logic does not need them */
+ case 303:
+ if (318 == atoi(pair->value))
+ last_packet = 1;
+ break;
+ case 65:
+ cur_group = strdup(pair->value);
+ break;
+ case 7:
+ newbud = y_new0(struct yahoo_buddy, 1);
+ newbud->id = strdup(pair->value);
+ if (cur_group)
+ newbud->group = strdup(cur_group);
+ else if (yd->buddies) {
+ struct yahoo_buddy *lastbud =
+ (struct yahoo_buddy *)y_list_nth(yd->
+ buddies,
+ y_list_length(yd->buddies) - 1)->data;
+ newbud->group = strdup(lastbud->group);
+ } else
+ newbud->group = strdup("Buddies");
+
+ yd->buddies = y_list_append(yd->buddies, newbud);
+
+ break;
+ }
+ }
+
+ /* we could be getting multiple packets here */
+ if (pkt->hash && !last_packet)
+ return;
+
+ YAHOO_CALLBACK(ext_yahoo_got_buddies) (yd->client_id, yd->buddies);
+
+ /* Logged in */
+ if (!yd->logged_in) {
+ yd->logged_in = 1;
+ if (yd->current_status < 0)
+ yd->current_status = yd->initial_status;
+ YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
+ YAHOO_LOGIN_OK, NULL);
+
+ /*
+ yahoo_set_away(yd->client_id, yd->initial_status, NULL,
+ (yd->initial_status == YAHOO_STATUS_AVAILABLE) ? 0 : 1);
+
+ yahoo_get_yab(yd->client_id);
+ */
+ }
+
+}
+
+static void yahoo_process_list(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ struct yahoo_data *yd = yid->yd;
+ YList *l;
+
+ /* we could be getting multiple packets here */
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+
+ switch (pair->key) {
+ case 89: /* identities */
+ {
+ char **identities =
+ y_strsplit(pair->value, ",", -1);
+ int i;
+ for (i = 0; identities[i]; i++)
+ yd->identities =
+ y_list_append(yd->identities,
+ strdup(identities[i]));
+ y_strfreev(identities);
+ }
+ YAHOO_CALLBACK(ext_yahoo_got_identities) (yd->client_id,
+ yd->identities);
+ break;
+ case 59: /* cookies */
+ if (pair->value[0] == 'Y') {
+ FREE(yd->cookie_y);
+ FREE(yd->login_cookie);
+
+ yd->cookie_y = getcookie(pair->value);
+ yd->login_cookie = getlcookie(yd->cookie_y);
+
+ } else if (pair->value[0] == 'T') {
+ FREE(yd->cookie_t);
+ yd->cookie_t = getcookie(pair->value);
+
+ } else if (pair->value[0] == 'C') {
+ FREE(yd->cookie_c);
+ yd->cookie_c = getcookie(pair->value);
+ }
+
+ break;
+ case 3: /* my id */
+ case 90: /* 1 */
+ case 100: /* 0 */
+ case 101: /* NULL */
+ case 102: /* NULL */
+ case 93: /* 86400/1440 */
+ break;
+ }
+ }
+
+ if (yd->cookie_y && yd->cookie_t) /* We don't get cookie_c anymore */
+ YAHOO_CALLBACK(ext_yahoo_got_cookies) (yd->client_id);
+}
+
+static void yahoo_process_verify(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ struct yahoo_data *yd = yid->yd;
+
+ if (pkt->status != 0x01) {
+ DEBUG_MSG(("expected status: 0x01, got: %d", pkt->status));
+ YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
+ YAHOO_LOGIN_LOCK, "");
+ return;
+ }
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YPACKET_STATUS_DEFAULT,
+ yd->session_id);
+
+ yahoo_packet_hash(pkt, 1, yd->user);
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+
+}
+
+static void yahoo_process_picture_checksum(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ struct yahoo_data *yd = yid->yd;
+ char *from = NULL;
+ char *to = NULL;
+ int checksum = 0;
+ YList *l;
+
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+
+ switch (pair->key) {
+ case 1:
+ case 4:
+ from = pair->value;
+ case 5:
+ to = pair->value;
+ break;
+ case 212:
+ break;
+ case 192:
+ checksum = atoi(pair->value);
+ break;
+ }
+ }
+
+ YAHOO_CALLBACK(ext_yahoo_got_buddyicon_checksum) (yd->client_id, to,
+ from, checksum);
+}
+
+static void yahoo_process_picture(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ struct yahoo_data *yd = yid->yd;
+ char *url = NULL;
+ char *from = NULL;
+ char *to = NULL;
+ int status = 0;
+ int checksum = 0;
+ YList *l;
+
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+
+ switch (pair->key) {
+ case 1:
+ case 4: /* sender */
+ from = pair->value;
+ break;
+ case 5: /* we */
+ to = pair->value;
+ break;
+ case 13: /* request / sending */
+ status = atoi(pair->value);
+ break;
+ case 20: /* url */
+ url = pair->value;
+ break;
+ case 192: /*checksum */
+ checksum = atoi(pair->value);
+ break;
+ }
+ }
+
+ switch (status) {
+ case 1: /* this is a request, ignore for now */
+ YAHOO_CALLBACK(ext_yahoo_got_buddyicon_request) (yd->client_id,
+ to, from);
+ break;
+ case 2: /* this is cool - we get a picture :) */
+ YAHOO_CALLBACK(ext_yahoo_got_buddyicon) (yd->client_id, to,
+ from, url, checksum);
+ break;
+ }
+}
+
+static void yahoo_process_picture_upload(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ struct yahoo_data *yd = yid->yd;
+ YList *l;
+ char *url = NULL;
+
+ if (pkt->status != 1)
+ return; /* something went wrong */
+
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+
+ switch (pair->key) {
+ case 5: /* we */
+ break;
+ case 20: /* url */
+ url = pair->value;
+ break;
+ case 27: /* local filename */
+ break;
+ case 38: /* time */
+ break;
+ }
+ }
+
+ YAHOO_CALLBACK(ext_yahoo_buddyicon_uploaded) (yd->client_id, url);
+}
+
+void yahoo_login(int id, int initial)
+{
+ struct yahoo_data *yd = find_conn_by_id(id);
+ struct connect_callback_data *ccd;
+ struct yahoo_server_settings *yss;
+ int tag;
+
+ char *host;
+
+ struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1);
+ yid->yd = yd;
+ yid->type = YAHOO_CONNECTION_PAGER;
+ inputs = y_list_prepend(inputs, yid);
+
+ yd->initial_status = initial;
+ yss = yd->server_settings;
+
+ ccd = y_new0(struct connect_callback_data, 1);
+ ccd->yd = yd;
+
+ host = yss->pager_host;
+
+ if (!host)
+ host = yss->pager_host_list[0];
+
+ tag = YAHOO_CALLBACK(ext_yahoo_connect_async) (yd->client_id,
+ host, yss->pager_port, yahoo_connected, ccd, 0);
+
+ /*
+ * if tag <= 0, then callback has already been called
+ * so ccd will have been freed
+ */
+ if (tag > 0)
+ ccd->tag = tag;
+ else if (tag < 0)
+ YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
+ YAHOO_LOGIN_SOCK, NULL);
+}
+
+struct yahoo_https_auth_data
+{
+ struct yahoo_input_data *yid;
+ char *token;
+ char *chal;
+};
+
+static void yahoo_https_auth_token_init(struct yahoo_https_auth_data *had);
+static void yahoo_https_auth_token_finish(struct http_request *req);
+static void yahoo_https_auth_init(struct yahoo_https_auth_data *had);
+static void yahoo_https_auth_finish(struct http_request *req);
+
+/* Extract a value from a login.yahoo.com response. Assume CRLF-linebreaks
+ and FAIL miserably if they're not there... */
+static char *yahoo_ha_find_key(char *response, char *key)
+{
+ char *s, *end;
+ int len = strlen(key);
+
+ s = response;
+ do {
+ if (strncmp(s, key, len) == 0 && s[len] == '=') {
+ s += len + 1;
+ if ((end = strchr(s, '\r')))
+ return g_strndup(s, end - s);
+ else
+ return g_strdup(s);
+ }
+
+ if ((s = strchr(s, '\n')))
+ s ++;
+ } while (s && *s);
+
+ return NULL;
+}
+
+static enum yahoo_status yahoo_https_status_parse(int code)
+{
+ switch (code)
+ {
+ case 1212: return YAHOO_LOGIN_PASSWD;
+ case 1213: return YAHOO_LOGIN_LOCK;
+ case 1235: return YAHOO_LOGIN_UNAME;
+ default: return (enum yahoo_status) code;
+ }
+}
+
+static void yahoo_https_auth(struct yahoo_input_data *yid, const char *seed, const char *sn)
+{
+ struct yahoo_https_auth_data *had = g_new0(struct yahoo_https_auth_data, 1);
+
+ had->yid = yid;
+ had->chal = g_strdup(seed);
+
+ yahoo_https_auth_token_init(had);
+}
+
+static void yahoo_https_auth_token_init(struct yahoo_https_auth_data *had)
+{
+ struct yahoo_input_data *yid = had->yid;
+ struct yahoo_data *yd = yid->yd;
+ struct http_request *req;
+ char *login, *passwd, *chal;
+ char *url;
+
+ login = g_strndup(yd->user, 3 * strlen(yd->user));
+ http_encode(login);
+ passwd = g_strndup(yd->password, 3 * strlen(yd->password));
+ http_encode(passwd);
+ chal = g_strndup(had->chal, 3 * strlen(had->chal));
+ http_encode(chal);
+
+ url = g_strdup_printf("https://login.yahoo.com/config/pwtoken_get?src=ymsgr&ts=%d&login=%s&passwd=%s&chal=%s",
+ (int) time(NULL), login, passwd, chal);
+
+ req = http_dorequest_url(url, yahoo_https_auth_token_finish, had);
+
+ g_free(url);
+ g_free(chal);
+ g_free(passwd);
+ g_free(login);
+}
+
+static void yahoo_https_auth_token_finish(struct http_request *req)
+{
+ struct yahoo_https_auth_data *had = req->data;
+ struct yahoo_input_data *yid;
+ struct yahoo_data *yd;
+ int st;
+
+ if (y_list_find(inputs, had->yid) == NULL)
+ return;
+
+ yid = had->yid;
+ yd = yid->yd;
+
+ if (req->status_code != 200) {
+ YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 2000 + req->status_code, NULL);
+ goto fail;
+ }
+
+ if (sscanf(req->reply_body, "%d", &st) != 1 || st != 0) {
+ YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, yahoo_https_status_parse(st), NULL);
+ goto fail;
+ }
+
+ if ((had->token = yahoo_ha_find_key(req->reply_body, "ymsgr")) == NULL) {
+ YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 3001, NULL);
+ goto fail;
+ }
+
+ yahoo_https_auth_init(had);
+ return;
+
+fail:
+ g_free(had->token);
+ g_free(had->chal);
+ g_free(had);
+}
+
+static void yahoo_https_auth_init(struct yahoo_https_auth_data *had)
+{
+ struct http_request *req;
+ char *url;
+
+ url = g_strdup_printf("https://login.yahoo.com/config/pwtoken_login?src=ymsgr&ts=%d&token=%s",
+ (int) time(NULL), had->token);
+
+ req = http_dorequest_url(url, yahoo_https_auth_finish, had);
+
+ g_free(url);
+}
+
+static void yahoo_https_auth_finish(struct http_request *req)
+{
+ struct yahoo_https_auth_data *had = req->data;
+ struct yahoo_input_data *yid;
+ struct yahoo_data *yd;
+ struct yahoo_packet *pack;
+ char *crumb = NULL;
+ int st;
+
+ if (y_list_find(inputs, had->yid) == NULL)
+ return;
+
+ yid = had->yid;
+ yd = yid->yd;
+
+ md5_byte_t result[16];
+ md5_state_t ctx;
+
+ unsigned char yhash[32];
+
+ if (req->status_code != 200) {
+ YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 2000 + req->status_code, NULL);
+ goto fail;
+ }
+
+ if (sscanf(req->reply_body, "%d", &st) != 1 || st != 0) {
+ YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, yahoo_https_status_parse(st), NULL);
+ goto fail;
+ }
+
+ if ((yd->cookie_y = yahoo_ha_find_key(req->reply_body, "Y")) == NULL ||
+ (yd->cookie_t = yahoo_ha_find_key(req->reply_body, "T")) == NULL ||
+ (crumb = yahoo_ha_find_key(req->reply_body, "crumb")) == NULL) {
+ YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 3002, NULL);
+ goto fail;
+ }
+
+ md5_init(&ctx);
+ md5_append(&ctx, (unsigned char*) crumb, 11);
+ md5_append(&ctx, (unsigned char*) had->chal, strlen(had->chal));
+ md5_finish(&ctx, result);
+ to_y64(yhash, result, 16);
+
+ pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, yd->initial_status, yd->session_id);
+ yahoo_packet_hash(pack, 1, yd->user);
+ yahoo_packet_hash(pack, 0, yd->user);
+ yahoo_packet_hash(pack, 277, yd->cookie_y);
+ yahoo_packet_hash(pack, 278, yd->cookie_t);
+ yahoo_packet_hash(pack, 307, (char*) yhash);
+ yahoo_packet_hash(pack, 244, "524223");
+ yahoo_packet_hash(pack, 2, yd->user);
+ yahoo_packet_hash(pack, 2, "1");
+ yahoo_packet_hash(pack, 98, "us");
+ yahoo_packet_hash(pack, 135, "7.5.0.647");
+
+ yahoo_send_packet(yid, pack, 0);
+
+ yahoo_packet_free(pack);
+
+fail:
+ g_free(crumb);
+ g_free(had->token);
+ g_free(had->chal);
+ g_free(had);
+}
+
+static void yahoo_process_auth(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ char *seed = NULL;
+ char *sn = NULL;
+ YList *l = pkt->hash;
+ int m = 0;
+ struct yahoo_data *yd = yid->yd;
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+
+ switch (pair->key) {
+ case 94:
+ seed = pair->value;
+ break;
+ case 1:
+ sn = pair->value;
+ break;
+ case 13:
+ m = atoi(pair->value);
+ break;
+ }
+ l = l->next;
+ }
+
+ if (!seed)
+ return;
+
+ if (m==2)
+ yahoo_https_auth(yid, seed, sn);
+ else {
+ /* call error */
+ WARNING(("unknown auth type %d", m));
+ YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
+ YAHOO_LOGIN_UNKNOWN, NULL);
+ }
+}
+
+static void yahoo_process_auth_resp(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ struct yahoo_data *yd = yid->yd;
+ char *login_id;
+ char *handle;
+ char *url = NULL;
+ int login_status = -1;
+
+ YList *l;
+
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 0)
+ login_id = pair->value;
+ else if (pair->key == 1)
+ handle = pair->value;
+ else if (pair->key == 20)
+ url = pair->value;
+ else if (pair->key == 66)
+ login_status = atoi(pair->value);
+ }
+
+ if (pkt->status == YPACKET_STATUS_DISCONNECTED) {
+ YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
+ login_status, url);
+ /* yahoo_logoff(yd->client_id); */
+ }
+}
+
+static void yahoo_process_mail(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ struct yahoo_data *yd = yid->yd;
+ char *who = NULL;
+ char *email = NULL;
+ char *subj = NULL;
+ int count = 0;
+ YList *l;
+
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 9)
+ count = strtol(pair->value, NULL, 10);
+ else if (pair->key == 43)
+ who = pair->value;
+ else if (pair->key == 42)
+ email = pair->value;
+ else if (pair->key == 18)
+ subj = pair->value;
+ else
+ LOG(("key: %d => value: %s", pair->key, pair->value));
+ }
+
+ if (who && email && subj) {
+ char from[1024];
+ snprintf(from, sizeof(from), "%s (%s)", who, email);
+ YAHOO_CALLBACK(ext_yahoo_mail_notify) (yd->client_id, from,
+ subj, count);
+ } else if (count > 0)
+ YAHOO_CALLBACK(ext_yahoo_mail_notify) (yd->client_id, NULL,
+ NULL, count);
+}
+
+static void yahoo_process_new_contact(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ struct yahoo_data *yd = yid->yd;
+ char *me = NULL;
+ char *who = NULL;
+ char *msg = NULL;
+ int online = -1;
+
+ YList *l;
+
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 4)
+ who = pair->value;
+ else if (pair->key == 5)
+ me = pair->value;
+ else if (pair->key == 14)
+ msg = pair->value;
+ else if (pair->key == 13)
+ online = strtol(pair->value, NULL, 10);
+ }
+
+ if (who && online < 0)
+ YAHOO_CALLBACK(ext_yahoo_contact_added) (yd->client_id, me, who,
+ msg);
+ else if (online == 2)
+ YAHOO_CALLBACK(ext_yahoo_rejected) (yd->client_id, who, msg);
+}
+
+/* UNUSED? */
+static void yahoo_process_contact(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ struct yahoo_data *yd = yid->yd;
+ char *id = NULL;
+ char *who = NULL;
+ char *msg = NULL;
+ char *name = NULL;
+ long tm = 0L;
+ int state = YAHOO_STATUS_AVAILABLE;
+ int online = 0;
+ int away = 0;
+ int idle = 0;
+ int mobile = 0;
+
+ YList *l;
+
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 1)
+ id = pair->value;
+ else if (pair->key == 3)
+ who = pair->value;
+ else if (pair->key == 14)
+ msg = pair->value;
+ else if (pair->key == 7)
+ name = pair->value;
+ else if (pair->key == 10)
+ state = strtol(pair->value, NULL, 10);
+ else if (pair->key == 15)
+ tm = strtol(pair->value, NULL, 10);
+ else if (pair->key == 13)
+ online = strtol(pair->value, NULL, 10);
+ else if (pair->key == 47)
+ away = strtol(pair->value, NULL, 10);
+ else if (pair->key == 137)
+ idle = strtol(pair->value, NULL, 10);
+ else if (pair->key == 60)
+ mobile = strtol(pair->value, NULL, 10);
+
+ }
+
+ if (id)
+ YAHOO_CALLBACK(ext_yahoo_contact_added) (yd->client_id, id, who,
+ msg);
+ else if (name)
+ YAHOO_CALLBACK(ext_yahoo_status_changed) (yd->client_id, name,
+ state, msg, away, idle, mobile);
+ else if (pkt->status == 0x07)
+ YAHOO_CALLBACK(ext_yahoo_rejected) (yd->client_id, who, msg);
+}
+
+static void yahoo_process_buddyadd(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ struct yahoo_data *yd = yid->yd;
+ char *who = NULL;
+ char *where = NULL;
+ int status = 0;
+ char *me = NULL;
+
+ struct yahoo_buddy *bud = NULL;
+
+ YList *l;
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 1)
+ me = pair->value;
+ if (pair->key == 7)
+ who = pair->value;
+ if (pair->key == 65)
+ where = pair->value;
+ if (pair->key == 66)
+ status = strtol(pair->value, NULL, 10);
+ }
+
+ if (!who)
+ return;
+ if (!where)
+ where = "Unknown";
+
+ bud = y_new0(struct yahoo_buddy, 1);
+ bud->id = strdup(who);
+ bud->group = strdup(where);
+ bud->real_name = NULL;
+
+ yd->buddies = y_list_append(yd->buddies, bud);
+
+#if 0
+ /* BitlBee: This seems to be wrong in my experience. I think:
+ status = 0: Success
+ status = 2: Already on list
+ status = 3: Doesn't exist
+ status = 42: Invalid handle (possibly banned/reserved, I get it for
+ handles like joe or jjjjjj)
+ Haven't seen others yet. But whenever the add is successful, there
+ will be a separate "went online" packet when the auth. request is
+ accepted. Couldn't find any test account that doesn't require auth.
+ unfortunately (if there is even such a thing?) */
+
+ /* A non-zero status (i've seen 2) seems to mean the buddy is already
+ * added and is online */
+ if (status) {
+ LOG(("Setting online see packet for info"));
+ yahoo_dump_unhandled(pkt);
+ YAHOO_CALLBACK(ext_yahoo_status_changed) (yd->client_id, who,
+ YAHOO_STATUS_AVAILABLE, NULL, 0, 0, 0);
+ }
+#endif
+ /* BitlBee: Need ACK of added buddy, if it was successful. */
+ if (status == 0) {
+ YList *tmp = y_list_append(NULL, bud);
+ YAHOO_CALLBACK(ext_yahoo_got_buddies) (yd->client_id, tmp);
+ y_list_free(tmp);
+ }
+}
+
+static void yahoo_process_buddydel(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ struct yahoo_data *yd = yid->yd;
+ char *who = NULL;
+ char *where = NULL;
+ int unk_66 = 0;
+ char *me = NULL;
+ struct yahoo_buddy *bud;
+
+ YList *buddy;
+
+ YList *l;
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 1)
+ me = pair->value;
+ else if (pair->key == 7)
+ who = pair->value;
+ else if (pair->key == 65)
+ where = pair->value;
+ else if (pair->key == 66)
+ unk_66 = strtol(pair->value, NULL, 10);
+ else
+ DEBUG_MSG(("unknown key: %d = %s", pair->key,
+ pair->value));
+ }
+
+ if (!who || !where)
+ return;
+
+ bud = y_new0(struct yahoo_buddy, 1);
+ bud->id = strdup(who);
+ bud->group = strdup(where);
+
+ buddy = y_list_find_custom(yd->buddies, bud, is_same_bud);
+
+ FREE(bud->id);
+ FREE(bud->group);
+ FREE(bud);
+
+ if (buddy) {
+ bud = buddy->data;
+ yd->buddies = y_list_remove_link(yd->buddies, buddy);
+ y_list_free_1(buddy);
+
+ FREE(bud->id);
+ FREE(bud->group);
+ FREE(bud->real_name);
+ FREE(bud);
+
+ bud = NULL;
+ }
+}
+
+static void yahoo_process_ignore(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ char *who = NULL;
+ int status = 0;
+ char *me = NULL;
+ int un_ignore = 0;
+
+ YList *l;
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 0)
+ who = pair->value;
+ if (pair->key == 1)
+ me = pair->value;
+ if (pair->key == 13) /* 1 == ignore, 2 == unignore */
+ un_ignore = strtol(pair->value, NULL, 10);
+ if (pair->key == 66)
+ status = strtol(pair->value, NULL, 10);
+ }
+
+ /*
+ * status
+ * 0 - ok
+ * 2 - already in ignore list, could not add
+ * 3 - not in ignore list, could not delete
+ * 12 - is a buddy, could not add
+ */
+
+/* if(status)
+ YAHOO_CALLBACK(ext_yahoo_error)(yd->client_id, who, 0, status);
+*/
+}
+
+static void yahoo_process_voicechat(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ char *who = NULL;
+ char *me = NULL;
+ char *room = NULL;
+ char *voice_room = NULL;
+
+ YList *l;
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 4)
+ who = pair->value;
+ if (pair->key == 5)
+ me = pair->value;
+ if (pair->key == 13)
+ voice_room = pair->value;
+ if (pair->key == 57)
+ room = pair->value;
+ }
+
+ NOTICE(("got voice chat invite from %s in %s to identity %s", who, room,
+ me));
+ /*
+ * send: s:0 1:me 5:who 57:room 13:1
+ * ???? s:4 5:who 10:99 19:-1615114531
+ * gotr: s:4 5:who 10:99 19:-1615114615
+ * ???? s:1 5:me 4:who 57:room 13:3room
+ * got: s:1 5:me 4:who 57:room 13:1room
+ * rej: s:0 1:me 5:who 57:room 13:3
+ * rejr: s:4 5:who 10:99 19:-1617114599
+ */
+}
+
+static void yahoo_process_ping(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ char *errormsg = NULL;
+
+ YList *l;
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 16)
+ errormsg = pair->value;
+ }
+
+ NOTICE(("got ping packet"));
+ YAHOO_CALLBACK(ext_yahoo_got_ping) (yid->yd->client_id, errormsg);
+}
+
+static void yahoo_process_buddy_change_group(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ YList *l;
+ char *me = NULL;
+ char *who = NULL;
+ char *old_group = NULL;
+ char *new_group = NULL;
+
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 1)
+ me = pair->value;
+ if (pair->key == 7)
+ who = pair->value;
+ if (pair->key == 224)
+ old_group = pair->value;
+ if (pair->key == 264)
+ new_group = pair->value;
+ }
+
+ YAHOO_CALLBACK(ext_yahoo_got_buddy_change_group) (yid->yd->client_id,
+ me, who, old_group, new_group);
+}
+
+static void _yahoo_webcam_get_server_connected(void *fd, int error, void *d)
+{
+ struct yahoo_input_data *yid = d;
+ char *who = yid->wcm->user;
+ char *data = NULL;
+ char *packet = NULL;
+ unsigned char magic_nr[] = { 0, 1, 0 };
+ unsigned char header_len = 8;
+ unsigned int len = 0;
+ unsigned int pos = 0;
+
+ if (error || !fd) {
+ FREE(who);
+ FREE(yid);
+ return;
+ }
+
+ yid->fd = fd;
+ inputs = y_list_prepend(inputs, yid);
+
+ /* send initial packet */
+ if (who)
+ data = strdup("<RVWCFG>");
+ else
+ data = strdup("<RUPCFG>");
+ yahoo_add_to_send_queue(yid, data, strlen(data));
+ FREE(data);
+
+ /* send data */
+ if (who) {
+ data = strdup("g=");
+ data = y_string_append(data, who);
+ data = y_string_append(data, "\r\n");
+ } else {
+ data = strdup("f=1\r\n");
+ }
+ len = strlen(data);
+ packet = y_new0(char, header_len + len);
+ packet[pos++] = header_len;
+ memcpy(packet + pos, magic_nr, sizeof(magic_nr));
+ pos += sizeof(magic_nr);
+ pos += yahoo_put32(packet + pos, len);
+ memcpy(packet + pos, data, len);
+ pos += len;
+ yahoo_add_to_send_queue(yid, packet, pos);
+ FREE(packet);
+ FREE(data);
+
+ yid->read_tag =
+ YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd,
+ YAHOO_INPUT_READ, yid);
+}
+
+static void yahoo_webcam_get_server(struct yahoo_input_data *y, char *who,
+ char *key)
+{
+ struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1);
+ struct yahoo_server_settings *yss = y->yd->server_settings;
+
+ yid->type = YAHOO_CONNECTION_WEBCAM_MASTER;
+ yid->yd = y->yd;
+ yid->wcm = y_new0(struct yahoo_webcam, 1);
+ yid->wcm->user = who ? strdup(who) : NULL;
+ yid->wcm->direction = who ? YAHOO_WEBCAM_DOWNLOAD : YAHOO_WEBCAM_UPLOAD;
+ yid->wcm->key = strdup(key);
+
+ YAHOO_CALLBACK(ext_yahoo_connect_async) (yid->yd->client_id,
+ yss->webcam_host, yss->webcam_port,
+ _yahoo_webcam_get_server_connected, yid, 0);
+
+}
+
+static YList *webcam_queue = NULL;
+static void yahoo_process_webcam_key(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ char *me = NULL;
+ char *key = NULL;
+ char *who = NULL;
+
+ YList *l;
+ yahoo_dump_unhandled(pkt);
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 5)
+ me = pair->value;
+ if (pair->key == 61)
+ key = pair->value;
+ }
+
+ l = webcam_queue;
+ if (!l)
+ return;
+ who = l->data;
+ webcam_queue = y_list_remove_link(webcam_queue, webcam_queue);
+ y_list_free_1(l);
+ yahoo_webcam_get_server(yid, who, key);
+ FREE(who);
+}
+
+static void yahoo_packet_process(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ DEBUG_MSG(("yahoo_packet_process: 0x%02x", pkt->service));
+ switch (pkt->service) {
+ case YAHOO_SERVICE_USERSTAT:
+ case YAHOO_SERVICE_LOGON:
+ case YAHOO_SERVICE_LOGOFF:
+ case YAHOO_SERVICE_ISAWAY:
+ case YAHOO_SERVICE_ISBACK:
+ case YAHOO_SERVICE_GAMELOGON:
+ case YAHOO_SERVICE_GAMELOGOFF:
+ case YAHOO_SERVICE_IDACT:
+ case YAHOO_SERVICE_IDDEACT:
+ case YAHOO_SERVICE_Y6_STATUS_UPDATE:
+ case YAHOO_SERVICE_Y8_STATUS:
+ yahoo_process_status(yid, pkt);
+ break;
+ case YAHOO_SERVICE_NOTIFY:
+ yahoo_process_notify(yid, pkt);
+ break;
+ case YAHOO_SERVICE_MESSAGE:
+ case YAHOO_SERVICE_GAMEMSG:
+ case YAHOO_SERVICE_SYSMESSAGE:
+ yahoo_process_message(yid, pkt);
+ break;
+ case YAHOO_SERVICE_NEWMAIL:
+ yahoo_process_mail(yid, pkt);
+ break;
+ case YAHOO_SERVICE_Y7_AUTHORIZATION:
+ yahoo_process_new_contact(yid, pkt);
+ break;
+ case YAHOO_SERVICE_NEWCONTACT:
+ yahoo_process_contact(yid, pkt);
+ break;
+ case YAHOO_SERVICE_LIST:
+ yahoo_process_list(yid, pkt);
+ break;
+ case YAHOO_SERVICE_VERIFY:
+ yahoo_process_verify(yid, pkt);
+ break;
+ case YAHOO_SERVICE_AUTH:
+ yahoo_process_auth(yid, pkt);
+ break;
+ case YAHOO_SERVICE_AUTHRESP:
+ yahoo_process_auth_resp(yid, pkt);
+ break;
+ case YAHOO_SERVICE_CONFINVITE:
+ case YAHOO_SERVICE_CONFADDINVITE:
+ case YAHOO_SERVICE_CONFDECLINE:
+ case YAHOO_SERVICE_CONFLOGON:
+ case YAHOO_SERVICE_CONFLOGOFF:
+ case YAHOO_SERVICE_CONFMSG:
+ yahoo_process_conference(yid, pkt);
+ break;
+ case YAHOO_SERVICE_CHATONLINE:
+ case YAHOO_SERVICE_CHATGOTO:
+ case YAHOO_SERVICE_CHATJOIN:
+ case YAHOO_SERVICE_CHATLEAVE:
+ case YAHOO_SERVICE_CHATEXIT:
+ case YAHOO_SERVICE_CHATLOGOUT:
+ case YAHOO_SERVICE_CHATPING:
+ case YAHOO_SERVICE_COMMENT:
+ yahoo_process_chat(yid, pkt);
+ break;
+ case YAHOO_SERVICE_P2PFILEXFER:
+ case YAHOO_SERVICE_Y7_FILETRANSFER:
+ yahoo_process_filetransfer(yid, pkt);
+ break;
+ case YAHOO_SERVICE_Y7_FILETRANSFERINFO:
+ yahoo_process_filetransferinfo(yid, pkt);
+ break;
+ case YAHOO_SERVICE_Y7_FILETRANSFERACCEPT:
+ yahoo_process_filetransferaccept(yid, pkt);
+ break;
+ case YAHOO_SERVICE_ADDBUDDY:
+ yahoo_process_buddyadd(yid, pkt);
+ break;
+ case YAHOO_SERVICE_REMBUDDY:
+ yahoo_process_buddydel(yid, pkt);
+ break;
+ case YAHOO_SERVICE_IGNORECONTACT:
+ yahoo_process_ignore(yid, pkt);
+ break;
+ case YAHOO_SERVICE_VOICECHAT:
+ yahoo_process_voicechat(yid, pkt);
+ break;
+ case YAHOO_SERVICE_WEBCAM:
+ yahoo_process_webcam_key(yid, pkt);
+ break;
+ case YAHOO_SERVICE_PING:
+ yahoo_process_ping(yid, pkt);
+ break;
+ case YAHOO_SERVICE_Y7_CHANGE_GROUP:
+ yahoo_process_buddy_change_group(yid, pkt);
+ break;
+ case YAHOO_SERVICE_IDLE:
+ case YAHOO_SERVICE_MAILSTAT:
+ case YAHOO_SERVICE_CHATINVITE:
+ case YAHOO_SERVICE_CALENDAR:
+ case YAHOO_SERVICE_NEWPERSONALMAIL:
+ case YAHOO_SERVICE_ADDIDENT:
+ case YAHOO_SERVICE_ADDIGNORE:
+ case YAHOO_SERVICE_GOTGROUPRENAME:
+ case YAHOO_SERVICE_GROUPRENAME:
+ case YAHOO_SERVICE_PASSTHROUGH2:
+ case YAHOO_SERVICE_CHATLOGON:
+ case YAHOO_SERVICE_CHATLOGOFF:
+ case YAHOO_SERVICE_CHATMSG:
+ case YAHOO_SERVICE_REJECTCONTACT:
+ case YAHOO_SERVICE_PEERTOPEER:
+ WARNING(("unhandled service 0x%02x", pkt->service));
+ yahoo_dump_unhandled(pkt);
+ break;
+ case YAHOO_SERVICE_PICTURE:
+ yahoo_process_picture(yid, pkt);
+ break;
+ case YAHOO_SERVICE_PICTURE_CHECKSUM:
+ yahoo_process_picture_checksum(yid, pkt);
+ break;
+ case YAHOO_SERVICE_PICTURE_UPLOAD:
+ yahoo_process_picture_upload(yid, pkt);
+ break;
+ case YAHOO_SERVICE_Y8_LIST: /* Buddy List */
+ yahoo_process_buddy_list(yid, pkt);
+ break;
+ default:
+ WARNING(("unknown service 0x%02x", pkt->service));
+ yahoo_dump_unhandled(pkt);
+ break;
+ }
+}
+
+static struct yahoo_packet *yahoo_getdata(struct yahoo_input_data *yid)
+{
+ struct yahoo_packet *pkt;
+ struct yahoo_data *yd = yid->yd;
+ int pos = 0;
+ int pktlen;
+
+ if (!yd)
+ return NULL;
+
+ DEBUG_MSG(("rxlen is %d", yid->rxlen));
+ if (yid->rxlen < YAHOO_PACKET_HDRLEN) {
+ DEBUG_MSG(("len < YAHOO_PACKET_HDRLEN"));
+ return NULL;
+ }
+
+ pos += 4; /* YMSG */
+ pos += 2;
+ pos += 2;
+
+ pktlen = yahoo_get16(yid->rxqueue + pos);
+ pos += 2;
+ DEBUG_MSG(("%d bytes to read, rxlen is %d", pktlen, yid->rxlen));
+
+ if (yid->rxlen < (YAHOO_PACKET_HDRLEN + pktlen)) {
+ DEBUG_MSG(("len < YAHOO_PACKET_HDRLEN + pktlen"));
+ return NULL;
+ }
+
+ LOG(("reading packet"));
+ yahoo_packet_dump(yid->rxqueue, YAHOO_PACKET_HDRLEN + pktlen);
+
+ pkt = yahoo_packet_new(0, 0, 0);
+
+ pkt->service = yahoo_get16(yid->rxqueue + pos);
+ pos += 2;
+ pkt->status = yahoo_get32(yid->rxqueue + pos);
+ pos += 4;
+ DEBUG_MSG(("Yahoo Service: 0x%02x Status: %d", pkt->service,
+ pkt->status));
+ pkt->id = yahoo_get32(yid->rxqueue + pos);
+ pos += 4;
+
+ yd->session_id = pkt->id;
+
+ yahoo_packet_read(pkt, yid->rxqueue + pos, pktlen);
+
+ yid->rxlen -= YAHOO_PACKET_HDRLEN + pktlen;
+ DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue));
+ if (yid->rxlen > 0) {
+ unsigned char *tmp = y_memdup(yid->rxqueue + YAHOO_PACKET_HDRLEN
+ + pktlen, yid->rxlen);
+ FREE(yid->rxqueue);
+ yid->rxqueue = tmp;
+ DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen,
+ yid->rxqueue));
+ } else {
+ DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue));
+ FREE(yid->rxqueue);
+ }
+
+ return pkt;
+}
+
+static struct yab *yahoo_yab_read(unsigned char *d, int len)
+{
+ char *st, *en;
+ char *data = (char *)d;
+ struct yab *yab = NULL;
+
+ data[len] = '\0';
+
+ DEBUG_MSG(("Got yab: %s", data));
+ st = en = strstr(data, "e0=\"");
+ if (st) {
+ yab = y_new0(struct yab, 1);
+
+ st += strlen("e0=\"");
+ en = strchr(st, '"');
+ *en++ = '\0';
+ yab->email = yahoo_xmldecode(st);
+ }
+
+ if (!en)
+ return NULL;
+
+ st = strstr(en, "id=\"");
+ if (st) {
+ st += strlen("id=\"");
+ en = strchr(st, '"');
+ *en++ = '\0';
+ yab->yid = atoi(yahoo_xmldecode(st));
+ }
+
+ st = strstr(en, "fn=\"");
+ if (st) {
+ st += strlen("fn=\"");
+ en = strchr(st, '"');
+ *en++ = '\0';
+ yab->fname = yahoo_xmldecode(st);
+ }
+
+ st = strstr(en, "ln=\"");
+ if (st) {
+ st += strlen("ln=\"");
+ en = strchr(st, '"');
+ *en++ = '\0';
+ yab->lname = yahoo_xmldecode(st);
+ }
+
+ st = strstr(en, "nn=\"");
+ if (st) {
+ st += strlen("nn=\"");
+ en = strchr(st, '"');
+ *en++ = '\0';
+ yab->nname = yahoo_xmldecode(st);
+ }
+
+ st = strstr(en, "yi=\"");
+ if (st) {
+ st += strlen("yi=\"");
+ en = strchr(st, '"');
+ *en++ = '\0';
+ yab->id = yahoo_xmldecode(st);
+ }
+
+ st = strstr(en, "hphone=\"");
+ if (st) {
+ st += strlen("hphone=\"");
+ en = strchr(st, '"');
+ *en++ = '\0';
+ yab->hphone = yahoo_xmldecode(st);
+ }
+
+ st = strstr(en, "wphone=\"");
+ if (st) {
+ st += strlen("wphone=\"");
+ en = strchr(st, '"');
+ *en++ = '\0';
+ yab->wphone = yahoo_xmldecode(st);
+ }
+
+ st = strstr(en, "mphone=\"");
+ if (st) {
+ st += strlen("mphone=\"");
+ en = strchr(st, '"');
+ *en++ = '\0';
+ yab->mphone = yahoo_xmldecode(st);
+ }
+
+ st = strstr(en, "dbid=\"");
+ if (st) {
+ st += strlen("dbid=\"");
+ en = strchr(st, '"');
+ *en++ = '\0';
+ yab->dbid = atoi(st);
+ }
+
+ return yab;
+}
+
+static struct yab *yahoo_getyab(struct yahoo_input_data *yid)
+{
+ struct yab *yab = NULL;
+ int pos = 0, end = 0;
+ struct yahoo_data *yd = yid->yd;
+
+ if (!yd)
+ return NULL;
+
+ do {
+ DEBUG_MSG(("rxlen is %d", yid->rxlen));
+
+ if (yid->rxlen <= strlen("<ct"))
+ return NULL;
+
+ /* start with <ct */
+ while (pos < yid->rxlen - strlen("<ct") + 1
+ && memcmp(yid->rxqueue + pos, "<ct", strlen("<ct")))
+ pos++;
+
+ if (pos >= yid->rxlen - 1)
+ return NULL;
+
+ end = pos + 2;
+ /* end with > */
+ while (end < yid->rxlen - strlen(">")
+ && memcmp(yid->rxqueue + end, ">", strlen(">")))
+ end++;
+
+ if (end >= yid->rxlen - 1)
+ return NULL;
+
+ yab = yahoo_yab_read(yid->rxqueue + pos, end + 2 - pos);
+
+ yid->rxlen -= end + 1;
+ DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen,
+ yid->rxqueue));
+ if (yid->rxlen > 0) {
+ unsigned char *tmp =
+ y_memdup(yid->rxqueue + end + 1, yid->rxlen);
+ FREE(yid->rxqueue);
+ yid->rxqueue = tmp;
+ DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen,
+ yid->rxqueue));
+ } else {
+ DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue));
+ FREE(yid->rxqueue);
+ }
+
+ } while (!yab && end < yid->rxlen - 1);
+
+ return yab;
+}
+
+static char *yahoo_getwebcam_master(struct yahoo_input_data *yid)
+{
+ unsigned int pos = 0;
+ unsigned int len = 0;
+ unsigned int status = 0;
+ char *server = NULL;
+ struct yahoo_data *yd = yid->yd;
+
+ if (!yid || !yd)
+ return NULL;
+
+ DEBUG_MSG(("rxlen is %d", yid->rxlen));
+
+ len = yid->rxqueue[pos++];
+ if (yid->rxlen < len)
+ return NULL;
+
+ /* extract status (0 = ok, 6 = webcam not online) */
+ status = yid->rxqueue[pos++];
+
+ if (status == 0) {
+ pos += 2; /* skip next 2 bytes */
+ server = y_memdup(yid->rxqueue + pos, 16);
+ pos += 16;
+ } else if (status == 6) {
+ YAHOO_CALLBACK(ext_yahoo_webcam_closed)
+ (yd->client_id, yid->wcm->user, 4);
+ }
+
+ /* skip rest of the data */
+
+ yid->rxlen -= len;
+ DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue));
+ if (yid->rxlen > 0) {
+ unsigned char *tmp = y_memdup(yid->rxqueue + pos, yid->rxlen);
+ FREE(yid->rxqueue);
+ yid->rxqueue = tmp;
+ DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen,
+ yid->rxqueue));
+ } else {
+ DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue));
+ FREE(yid->rxqueue);
+ }
+
+ return server;
+}
+
+static int yahoo_get_webcam_data(struct yahoo_input_data *yid)
+{
+ unsigned char reason = 0;
+ unsigned int pos = 0;
+ unsigned int begin = 0;
+ unsigned int end = 0;
+ unsigned int closed = 0;
+ unsigned char header_len = 0;
+ char *who;
+ int connect = 0;
+ struct yahoo_data *yd = yid->yd;
+
+ if (!yd)
+ return -1;
+
+ if (!yid->wcm || !yid->wcd || !yid->rxlen)
+ return -1;
+
+ DEBUG_MSG(("rxlen is %d", yid->rxlen));
+
+ /* if we are not reading part of image then read header */
+ if (!yid->wcd->to_read) {
+ header_len = yid->rxqueue[pos++];
+ yid->wcd->packet_type = 0;
+
+ if (yid->rxlen < header_len)
+ return 0;
+
+ if (header_len >= 8) {
+ reason = yid->rxqueue[pos++];
+ /* next 2 bytes should always be 05 00 */
+ pos += 2;
+ yid->wcd->data_size = yahoo_get32(yid->rxqueue + pos);
+ pos += 4;
+ yid->wcd->to_read = yid->wcd->data_size;
+ }
+ if (header_len >= 13) {
+ yid->wcd->packet_type = yid->rxqueue[pos++];
+ yid->wcd->timestamp = yahoo_get32(yid->rxqueue + pos);
+ pos += 4;
+ }
+
+ /* skip rest of header */
+ pos = header_len;
+ }
+
+ begin = pos;
+ pos += yid->wcd->to_read;
+ if (pos > yid->rxlen)
+ pos = yid->rxlen;
+
+ /* if it is not an image then make sure we have the whole packet */
+ if (yid->wcd->packet_type != 0x02) {
+ if ((pos - begin) != yid->wcd->data_size) {
+ yid->wcd->to_read = 0;
+ return 0;
+ } else {
+ yahoo_packet_dump(yid->rxqueue + begin, pos - begin);
+ }
+ }
+
+ DEBUG_MSG(("packet type %.2X, data length %d", yid->wcd->packet_type,
+ yid->wcd->data_size));
+
+ /* find out what kind of packet we got */
+ switch (yid->wcd->packet_type) {
+ case 0x00:
+ /* user requests to view webcam (uploading) */
+ if (yid->wcd->data_size &&
+ yid->wcm->direction == YAHOO_WEBCAM_UPLOAD) {
+ end = begin;
+ while (end <= yid->rxlen && yid->rxqueue[end++] != 13) ;
+ if (end > begin) {
+ who = y_memdup(yid->rxqueue + begin,
+ end - begin);
+ who[end - begin - 1] = 0;
+ YAHOO_CALLBACK(ext_yahoo_webcam_viewer) (yd->
+ client_id, who + 2, 2);
+ FREE(who);
+ }
+ }
+
+ if (yid->wcm->direction == YAHOO_WEBCAM_DOWNLOAD) {
+ /* timestamp/status field */
+ /* 0 = declined viewing permission */
+ /* 1 = accepted viewing permission */
+ if (yid->wcd->timestamp == 0) {
+ YAHOO_CALLBACK(ext_yahoo_webcam_closed) (yd->
+ client_id, yid->wcm->user, 3);
+ }
+ }
+ break;
+ case 0x01: /* status packets?? */
+ /* timestamp contains status info */
+ /* 00 00 00 01 = we have data?? */
+ break;
+ case 0x02: /* image data */
+ YAHOO_CALLBACK(ext_yahoo_got_webcam_image) (yd->client_id,
+ yid->wcm->user, yid->rxqueue + begin,
+ yid->wcd->data_size, pos - begin, yid->wcd->timestamp);
+ break;
+ case 0x05: /* response packets when uploading */
+ if (!yid->wcd->data_size) {
+ YAHOO_CALLBACK(ext_yahoo_webcam_data_request) (yd->
+ client_id, yid->wcd->timestamp);
+ }
+ break;
+ case 0x07: /* connection is closing */
+ switch (reason) {
+ case 0x01: /* user closed connection */
+ closed = 1;
+ break;
+ case 0x0F: /* user cancelled permission */
+ closed = 2;
+ break;
+ }
+ YAHOO_CALLBACK(ext_yahoo_webcam_closed) (yd->client_id,
+ yid->wcm->user, closed);
+ break;
+ case 0x0C: /* user connected */
+ case 0x0D: /* user disconnected */
+ if (yid->wcd->data_size) {
+ who = y_memdup(yid->rxqueue + begin, pos - begin + 1);
+ who[pos - begin] = 0;
+ if (yid->wcd->packet_type == 0x0C)
+ connect = 1;
+ else
+ connect = 0;
+ YAHOO_CALLBACK(ext_yahoo_webcam_viewer) (yd->client_id,
+ who, connect);
+ FREE(who);
+ }
+ break;
+ case 0x13: /* user data */
+ /* i=user_ip (ip of the user we are viewing) */
+ /* j=user_ext_ip (external ip of the user we */
+ /* are viewing) */
+ break;
+ case 0x17: /* ?? */
+ break;
+ }
+ yid->wcd->to_read -= pos - begin;
+
+ yid->rxlen -= pos;
+ DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue));
+ if (yid->rxlen > 0) {
+ unsigned char *tmp = y_memdup(yid->rxqueue + pos, yid->rxlen);
+ FREE(yid->rxqueue);
+ yid->rxqueue = tmp;
+ DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen,
+ yid->rxqueue));
+ } else {
+ DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue));
+ FREE(yid->rxqueue);
+ }
+
+ /* If we read a complete packet return success */
+ if (!yid->wcd->to_read)
+ return 1;
+
+ return 0;
+}
+
+int yahoo_write_ready(int id, void *fd, void *data)
+{
+ struct yahoo_input_data *yid = data;
+ int len;
+ struct data_queue *tx;
+
+ LOG(("write callback: id=%d fd=%p data=%p", id, fd, data));
+ if (!yid || !yid->txqueues)
+ return -2;
+
+ tx = yid->txqueues->data;
+ LOG(("writing %d bytes", tx->len));
+ len = yahoo_send_data(fd, tx->queue, MIN(1024, tx->len));
+
+ if (len == -1 && errno == EAGAIN)
+ return 1;
+
+ if (len <= 0) {
+ int e = errno;
+ DEBUG_MSG(("len == %d (<= 0)", len));
+ while (yid->txqueues) {
+ YList *l = yid->txqueues;
+ tx = l->data;
+ free(tx->queue);
+ free(tx);
+ yid->txqueues =
+ y_list_remove_link(yid->txqueues,
+ yid->txqueues);
+ y_list_free_1(l);
+ }
+ LOG(("yahoo_write_ready(%d, %p) len < 0", id, fd));
+ YAHOO_CALLBACK(ext_yahoo_remove_handler) (id, yid->write_tag);
+ yid->write_tag = 0;
+ errno = e;
+ return 0;
+ }
+
+
+ tx->len -= len;
+ if (tx->len > 0) {
+ unsigned char *tmp = y_memdup(tx->queue + len, tx->len);
+ FREE(tx->queue);
+ tx->queue = tmp;
+ } else {
+ YList *l = yid->txqueues;
+ free(tx->queue);
+ free(tx);
+ yid->txqueues =
+ y_list_remove_link(yid->txqueues, yid->txqueues);
+ y_list_free_1(l);
+ /*
+ if(!yid->txqueues)
+ LOG(("yahoo_write_ready(%d, %d) !yxqueues", id, fd));
+ */
+ if (!yid->txqueues) {
+ LOG(("yahoo_write_ready(%d, %p) !txqueues", id, fd));
+ YAHOO_CALLBACK(ext_yahoo_remove_handler) (id,
+ yid->write_tag);
+ yid->write_tag = 0;
+ }
+ }
+
+ return 1;
+}
+
+static void yahoo_process_pager_connection(struct yahoo_input_data *yid,
+ int over)
+{
+ struct yahoo_packet *pkt;
+ struct yahoo_data *yd = yid->yd;
+ int id = yd->client_id;
+
+ if (over)
+ return;
+
+ while (find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER)
+ && (pkt = yahoo_getdata(yid)) != NULL) {
+
+ yahoo_packet_process(yid, pkt);
+
+ yahoo_packet_free(pkt);
+ }
+}
+
+static void yahoo_process_chatcat_connection(struct yahoo_input_data *yid,
+ int over)
+{
+ if (over)
+ return;
+
+ if (strstr((char *)yid->rxqueue + (yid->rxlen - 20), "</content>")) {
+ YAHOO_CALLBACK(ext_yahoo_chat_cat_xml) (yid->yd->client_id,
+ (char *)yid->rxqueue);
+ }
+}
+
+static void yahoo_process_yab_connection(struct yahoo_input_data *yid, int over)
+{
+ struct yahoo_data *yd = yid->yd;
+ struct yab *yab;
+ YList *buds;
+ int changed = 0;
+ int id = yd->client_id;
+ int yab_used = 0;
+
+ LOG(("Got data for YAB"));
+
+ if (over)
+ return;
+
+ while (find_input_by_id_and_type(id, YAHOO_CONNECTION_YAB)
+ && (yab = yahoo_getyab(yid)) != NULL) {
+ if (!yab->id)
+ continue;
+
+ changed = 1;
+ yab_used = 0;
+ for (buds = yd->buddies; buds; buds = buds->next) {
+ struct yahoo_buddy *bud = buds->data;
+ if (!strcmp(bud->id, yab->id)) {
+ yab_used = 1;
+ bud->yab_entry = yab;
+ if (yab->nname) {
+ bud->real_name = strdup(yab->nname);
+ } else if (yab->fname && yab->lname) {
+ bud->real_name = y_new0(char,
+ strlen(yab->fname) +
+ strlen(yab->lname) + 2);
+ sprintf(bud->real_name, "%s %s",
+ yab->fname, yab->lname);
+ } else if (yab->fname) {
+ bud->real_name = strdup(yab->fname);
+ }
+ break; /* for */
+ }
+ }
+
+ if (!yab_used) {
+ FREE(yab->fname);
+ FREE(yab->lname);
+ FREE(yab->nname);
+ FREE(yab->id);
+ FREE(yab->email);
+ FREE(yab->hphone);
+ FREE(yab->wphone);
+ FREE(yab->mphone);
+ FREE(yab);
+ }
+
+ }
+
+ if (changed)
+ YAHOO_CALLBACK(ext_yahoo_got_buddies) (yd->client_id,
+ yd->buddies);
+}
+
+static void yahoo_process_search_connection(struct yahoo_input_data *yid,
+ int over)
+{
+ struct yahoo_found_contact *yct = NULL;
+ char *p = (char *)yid->rxqueue, *np, *cp;
+ int k, n;
+ int start = 0, found = 0, total = 0;
+ YList *contacts = NULL;
+ struct yahoo_input_data *pyid =
+ find_input_by_id_and_type(yid->yd->client_id,
+ YAHOO_CONNECTION_PAGER);
+
+ if (!over || !pyid)
+ return;
+
+ if (p && (p = strstr(p, "\r\n\r\n"))) {
+ p += 4;
+
+ for (k = 0; (p = strchr(p, 4)) && (k < 4); k++) {
+ p++;
+ n = atoi(p);
+ switch (k) {
+ case 0:
+ found = pyid->ys->lsearch_nfound = n;
+ break;
+ case 2:
+ start = pyid->ys->lsearch_nstart = n;
+ break;
+ case 3:
+ total = pyid->ys->lsearch_ntotal = n;
+ break;
+ }
+ }
+
+ if (p)
+ p++;
+
+ k = 0;
+ while (p && *p) {
+ cp = p;
+ np = strchr(p, 4);
+
+ if (!np)
+ break;
+ *np = 0;
+ p = np + 1;
+
+ switch (k++) {
+ case 1:
+ if (strlen(cp) > 2
+ && y_list_length(contacts) < total) {
+ yct = y_new0(struct yahoo_found_contact,
+ 1);
+ contacts = y_list_append(contacts, yct);
+ yct->id = cp + 2;
+ } else {
+ *p = 0;
+ }
+ break;
+ case 2:
+ yct->online = !strcmp(cp, "2") ? 1 : 0;
+ break;
+ case 3:
+ yct->gender = cp;
+ break;
+ case 4:
+ yct->age = atoi(cp);
+ break;
+ case 5:
+ /* not worth the context switch for strcmp */
+ if (cp[0] != '\005' || cp[1] != '\000')
+ yct->location = cp;
+ k = 0;
+ break;
+ }
+ }
+ }
+
+ YAHOO_CALLBACK(ext_yahoo_got_search_result) (yid->yd->client_id, found,
+ start, total, contacts);
+
+ while (contacts) {
+ YList *node = contacts;
+ contacts = y_list_remove_link(contacts, node);
+ free(node->data);
+ y_list_free_1(node);
+ }
+}
+
+static void _yahoo_webcam_connected(void *fd, int error, void *d)
+{
+ struct yahoo_input_data *yid = d;
+ struct yahoo_webcam *wcm = yid->wcm;
+ struct yahoo_data *yd = yid->yd;
+ char conn_type[100];
+ char *data = NULL;
+ char *packet = NULL;
+ unsigned char magic_nr[] = { 1, 0, 0, 0, 1 };
+ unsigned header_len = 0;
+ unsigned int len = 0;
+ unsigned int pos = 0;
+
+ if (error || !fd) {
+ FREE(yid);
+ return;
+ }
+
+ yid->fd = fd;
+ inputs = y_list_prepend(inputs, yid);
+
+ LOG(("Connected"));
+ /* send initial packet */
+ switch (wcm->direction) {
+ case YAHOO_WEBCAM_DOWNLOAD:
+ data = strdup("<REQIMG>");
+ break;
+ case YAHOO_WEBCAM_UPLOAD:
+ data = strdup("<SNDIMG>");
+ break;
+ default:
+ return;
+ }
+ yahoo_add_to_send_queue(yid, data, strlen(data));
+ FREE(data);
+
+ /* send data */
+ switch (wcm->direction) {
+ case YAHOO_WEBCAM_DOWNLOAD:
+ header_len = 8;
+ data = strdup("a=2\r\nc=us\r\ne=21\r\nu=");
+ data = y_string_append(data, yd->user);
+ data = y_string_append(data, "\r\nt=");
+ data = y_string_append(data, wcm->key);
+ data = y_string_append(data, "\r\ni=");
+ data = y_string_append(data, wcm->my_ip);
+ data = y_string_append(data, "\r\ng=");
+ data = y_string_append(data, wcm->user);
+ data = y_string_append(data, "\r\no=w-2-5-1\r\np=");
+ snprintf(conn_type, sizeof(conn_type), "%d", wcm->conn_type);
+ data = y_string_append(data, conn_type);
+ data = y_string_append(data, "\r\n");
+ break;
+ case YAHOO_WEBCAM_UPLOAD:
+ header_len = 13;
+ data = strdup("a=2\r\nc=us\r\nu=");
+ data = y_string_append(data, yd->user);
+ data = y_string_append(data, "\r\nt=");
+ data = y_string_append(data, wcm->key);
+ data = y_string_append(data, "\r\ni=");
+ data = y_string_append(data, wcm->my_ip);
+ data = y_string_append(data, "\r\no=w-2-5-1\r\np=");
+ snprintf(conn_type, sizeof(conn_type), "%d", wcm->conn_type);
+ data = y_string_append(data, conn_type);
+ data = y_string_append(data, "\r\nb=");
+ data = y_string_append(data, wcm->description);
+ data = y_string_append(data, "\r\n");
+ break;
+ }
+
+ len = strlen(data);
+ packet = y_new0(char, header_len + len);
+ packet[pos++] = header_len;
+ packet[pos++] = 0;
+ switch (wcm->direction) {
+ case YAHOO_WEBCAM_DOWNLOAD:
+ packet[pos++] = 1;
+ packet[pos++] = 0;
+ break;
+ case YAHOO_WEBCAM_UPLOAD:
+ packet[pos++] = 5;
+ packet[pos++] = 0;
+ break;
+ }
+
+ pos += yahoo_put32(packet + pos, len);
+ if (wcm->direction == YAHOO_WEBCAM_UPLOAD) {
+ memcpy(packet + pos, magic_nr, sizeof(magic_nr));
+ pos += sizeof(magic_nr);
+ }
+ memcpy(packet + pos, data, len);
+ yahoo_add_to_send_queue(yid, packet, header_len + len);
+ FREE(packet);
+ FREE(data);
+
+ yid->read_tag =
+ YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id,
+ yid->fd, YAHOO_INPUT_READ, yid);
+}
+
+static void yahoo_webcam_connect(struct yahoo_input_data *y)
+{
+ struct yahoo_webcam *wcm = y->wcm;
+ struct yahoo_input_data *yid;
+ struct yahoo_server_settings *yss;
+
+ if (!wcm || !wcm->server || !wcm->key)
+ return;
+
+ yid = y_new0(struct yahoo_input_data, 1);
+ yid->type = YAHOO_CONNECTION_WEBCAM;
+ yid->yd = y->yd;
+
+ /* copy webcam data to new connection */
+ yid->wcm = y->wcm;
+ y->wcm = NULL;
+
+ yss = y->yd->server_settings;
+
+ yid->wcd = y_new0(struct yahoo_webcam_data, 1);
+
+ LOG(("Connecting to: %s:%d", wcm->server, wcm->port));
+ YAHOO_CALLBACK(ext_yahoo_connect_async) (y->yd->client_id, wcm->server,
+ wcm->port, _yahoo_webcam_connected, yid, 0);
+
+}
+
+static void yahoo_process_webcam_master_connection(struct yahoo_input_data *yid,
+ int over)
+{
+ char *server;
+ struct yahoo_server_settings *yss;
+
+ if (over)
+ return;
+
+ server = yahoo_getwebcam_master(yid);
+
+ if (server) {
+ yss = yid->yd->server_settings;
+ yid->wcm->server = strdup(server);
+ yid->wcm->port = yss->webcam_port;
+ yid->wcm->conn_type = yss->conn_type;
+ yid->wcm->my_ip = strdup(yss->local_host);
+ if (yid->wcm->direction == YAHOO_WEBCAM_UPLOAD)
+ yid->wcm->description = strdup(yss->webcam_description);
+ yahoo_webcam_connect(yid);
+ FREE(server);
+ }
+}
+
+static void yahoo_process_webcam_connection(struct yahoo_input_data *yid,
+ int over)
+{
+ int id = yid->yd->client_id;
+ void *fd = yid->fd;
+
+ if (over)
+ return;
+
+ /* as long as we still have packets available keep processing them */
+ while (find_input_by_id_and_fd(id, fd)
+ && yahoo_get_webcam_data(yid) == 1) ;
+}
+
+static void (*yahoo_process_connection[]) (struct yahoo_input_data *,
+ int over) = {
+yahoo_process_pager_connection, yahoo_process_ft_connection,
+ yahoo_process_yab_connection,
+ yahoo_process_webcam_master_connection,
+ yahoo_process_webcam_connection,
+ yahoo_process_chatcat_connection,
+ yahoo_process_search_connection};
+
+int yahoo_read_ready(int id, void *fd, void *data)
+{
+ struct yahoo_input_data *yid = data;
+ char buf[1024];
+ int len;
+
+ LOG(("read callback: id=%d fd=%p data=%p", id, fd, data));
+ if (!yid)
+ return -2;
+
+ do {
+ len = YAHOO_CALLBACK(ext_yahoo_read) (fd, buf, sizeof(buf));
+ } while (len == -1 && errno == EINTR);
+
+ if (len == -1 && (errno == EAGAIN || errno == EINTR)) /* we'll try again later */
+ return 1;
+
+ if (len <= 0) {
+ int e = errno;
+ DEBUG_MSG(("len == %d (<= 0)", len));
+
+ if (yid->type == YAHOO_CONNECTION_PAGER) {
+ YAHOO_CALLBACK(ext_yahoo_login_response) (yid->yd->
+ client_id, YAHOO_LOGIN_SOCK, NULL);
+ }
+
+ yahoo_process_connection[yid->type] (yid, 1);
+ yahoo_input_close(yid);
+
+ /* no need to return an error, because we've already fixed it */
+ if (len == 0)
+ return 1;
+
+ errno = e;
+ LOG(("read error: %s", strerror(errno)));
+ return -1;
+ }
+
+ yid->rxqueue =
+ y_renew(unsigned char, yid->rxqueue, len + yid->rxlen + 1);
+ memcpy(yid->rxqueue + yid->rxlen, buf, len);
+ yid->rxlen += len;
+ yid->rxqueue[yid->rxlen] = 0;
+
+ yahoo_process_connection[yid->type] (yid, 0);
+
+ return len;
+}
+
+int yahoo_init_with_attributes(const char *username, const char *password, ...)
+{
+ va_list ap;
+ struct yahoo_data *yd;
+
+ yd = y_new0(struct yahoo_data, 1);
+
+ if (!yd)
+ return 0;
+
+ yd->user = strdup(username);
+ yd->password = strdup(password);
+
+ yd->initial_status = -1;
+ yd->current_status = -1;
+
+ yd->client_id = ++last_id;
+
+ add_to_list(yd);
+
+ va_start(ap, password);
+ yd->server_settings = _yahoo_assign_server_settings(ap);
+ va_end(ap);
+
+ return yd->client_id;
+}
+
+int yahoo_init(const char *username, const char *password)
+{
+ return yahoo_init_with_attributes(username, password, NULL);
+}
+
+static void yahoo_connected(void *fd, int error, void *data)
+{
+ struct connect_callback_data *ccd = data;
+ struct yahoo_data *yd = ccd->yd;
+ struct yahoo_packet *pkt;
+ struct yahoo_input_data *yid;
+ struct yahoo_server_settings *yss = yd->server_settings;
+
+ if (error) {
+ int tag;
+ if (fallback_ports[ccd->i]) {
+ char *host = yss->pager_host;
+
+ if (!host)
+ host = yss->pager_host_list[ccd->server_i];
+
+ yss->pager_port = fallback_ports[ccd->i++];
+ tag = YAHOO_CALLBACK(ext_yahoo_connect_async) (yd->
+ client_id, host, yss->pager_port,
+ yahoo_connected, ccd, 0);
+
+ if (tag > 0)
+ ccd->tag = tag;
+ } else if (yss->pager_host_list
+ && yss->pager_host_list[ccd->server_i]) {
+
+ /* Get back to the default port */
+ yss->pager_port = pager_port;
+ ccd->server_i++;
+ LOG(("Fallback: Connecting to %s:%d", yss->pager_host_list[ccd->server_i], yss->pager_port));
+
+ ccd->i = 0;
+ tag = YAHOO_CALLBACK(ext_yahoo_connect_async) (yd->client_id,
+ yss->pager_host_list[ccd->server_i], yss->pager_port,
+ yahoo_connected, ccd, 0);
+ } else {
+ FREE(ccd);
+ YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
+ YAHOO_LOGIN_SOCK, NULL);
+ }
+ return;
+ }
+
+ FREE(ccd);
+
+ /* fd == NULL && error == 0 means connect was cancelled */
+ if (!fd)
+ return;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YPACKET_STATUS_DEFAULT,
+ yd->session_id);
+ NOTICE(("Sending initial packet"));
+
+ yahoo_packet_hash(pkt, 1, yd->user);
+
+ yid = find_input_by_id_and_type(yd->client_id, YAHOO_CONNECTION_PAGER);
+ yid->fd = fd;
+
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+
+ yid->read_tag =
+ YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id,
+ yid->fd, YAHOO_INPUT_READ, yid);
+}
+
+void *yahoo_get_fd(int id)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ if (!yid)
+ return 0;
+ else
+ return yid->fd;
+}
+
+void yahoo_send_buzz(int id, const char *from, const char *who)
+{
+ yahoo_send_im(id, from, who, "<ding>", 1, 0);
+}
+
+void yahoo_send_im(int id, const char *from, const char *who, const char *what,
+ int utf8, int picture)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_packet *pkt = NULL;
+ struct yahoo_data *yd;
+ char pic_str[10];
+
+ if (!yid)
+ return;
+
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE, YAHOO_STATUS_OFFLINE,
+ yd->session_id);
+
+ snprintf(pic_str, sizeof(pic_str), "%d", picture);
+
+ if (from && strcmp(from, yd->user))
+ yahoo_packet_hash(pkt, 0, yd->user);
+ yahoo_packet_hash(pkt, 1, from ? from : yd->user);
+ yahoo_packet_hash(pkt, 5, who);
+ yahoo_packet_hash(pkt, 14, what);
+
+ if (utf8)
+ yahoo_packet_hash(pkt, 97, "1");
+
+ yahoo_packet_hash(pkt, 63, ";0"); /* imvironment name; or ;0 */
+ yahoo_packet_hash(pkt, 64, "0");
+ yahoo_packet_hash(pkt, 206, pic_str);
+
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_send_typing(int id, const char *from, const char *who, int typ)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt = NULL;
+ if (!yid)
+ return;
+
+ yd = yid->yd;
+ pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YPACKET_STATUS_NOTIFY,
+ yd->session_id);
+
+ yahoo_packet_hash(pkt, 5, who);
+ yahoo_packet_hash(pkt, 1, from ? from : yd->user);
+ yahoo_packet_hash(pkt, 14, " ");
+ yahoo_packet_hash(pkt, 13, typ ? "1" : "0");
+ yahoo_packet_hash(pkt, 49, "TYPING");
+
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_set_away(int id, enum yahoo_status state, const char *msg, int away)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt = NULL;
+ int old_status;
+ char s[4];
+
+ if (!yid)
+ return;
+
+ yd = yid->yd;
+
+ old_status = yd->current_status;
+ yd->current_status = state;
+
+ /* Thank you libpurple :) */
+ if (yd->current_status == YAHOO_STATUS_INVISIBLE) {
+ pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBLE_TOGGLE,
+ YAHOO_STATUS_AVAILABLE, 0);
+ yahoo_packet_hash(pkt, 13, "2");
+ yahoo_send_packet(yid, pkt, 0);
+ yahoo_packet_free(pkt);
+
+ return;
+ }
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_STATUS_UPDATE,
+ yd->current_status, yd->session_id);
+ snprintf(s, sizeof(s), "%d", yd->current_status);
+ yahoo_packet_hash(pkt, 10, s);
+ yahoo_packet_hash(pkt, 19, msg && state == YAHOO_STATUS_CUSTOM ? msg : "");
+ yahoo_packet_hash(pkt, 47, (away == 2)? "2": (away) ?"1":"0");
+ yahoo_send_packet(yid, pkt, 0);
+ yahoo_packet_free(pkt);
+
+ if (old_status == YAHOO_STATUS_INVISIBLE) {
+ pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBLE_TOGGLE,
+ YAHOO_STATUS_AVAILABLE, 0);
+ yahoo_packet_hash(pkt, 13, "1");
+ yahoo_send_packet(yid, pkt, 0);
+ yahoo_packet_free(pkt);
+ }
+}
+
+void yahoo_logoff(int id)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt = NULL;
+
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ LOG(("yahoo_logoff: current status: %d", yd->current_status));
+
+ if (yd->current_status != -1 && 0) {
+ /* Meh. Don't send this. The event handlers are not going to
+ get to do this so it'll just leak memory. And the TCP
+ connection reset will hopefully be clear enough. */
+ pkt = yahoo_packet_new(YAHOO_SERVICE_LOGOFF,
+ YPACKET_STATUS_DEFAULT, yd->session_id);
+ yd->current_status = -1;
+
+ if (pkt) {
+ yahoo_send_packet(yid, pkt, 0);
+ yahoo_packet_free(pkt);
+ }
+ }
+
+/* do {
+ yahoo_input_close(yid);
+ } while((yid = find_input_by_id(id)));*/
+
+}
+
+void yahoo_get_list(int id)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt = NULL;
+
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_LIST, YPACKET_STATUS_DEFAULT,
+ yd->session_id);
+ yahoo_packet_hash(pkt, 1, yd->user);
+ if (pkt) {
+ yahoo_send_packet(yid, pkt, 0);
+ yahoo_packet_free(pkt);
+ }
+}
+
+static void _yahoo_http_connected(int id, void *fd, int error, void *data)
+{
+ struct yahoo_input_data *yid = data;
+ if (fd == NULL || error) {
+ inputs = y_list_remove(inputs, yid);
+ FREE(yid);
+ return;
+ }
+
+ yid->fd = fd;
+ yid->read_tag =
+ YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd,
+ YAHOO_INPUT_READ, yid);
+}
+
+/* FIXME Get address book from address.yahoo.com instead */
+void yahoo_get_yab(int id)
+{
+ struct yahoo_data *yd = find_conn_by_id(id);
+ struct yahoo_input_data *yid;
+ char url[1024];
+ char buff[2048];
+
+ if (!yd)
+ return;
+
+ yid = y_new0(struct yahoo_input_data, 1);
+ yid->yd = yd;
+ yid->type = YAHOO_CONNECTION_YAB;
+
+ LOG(("Sending request for Address Book"));
+
+ snprintf(url, 1024,
+ "http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us"
+ "&diffs=1&t=0&tags=short&rt=0&prog-ver=8.1.0.249&useutf8=1&legenc=codepage-1252");
+
+ snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t);
+
+ inputs = y_list_prepend(inputs, yid);
+
+ yahoo_http_get(yid->yd->client_id, url, buff, 0, 0,
+ _yahoo_http_connected, yid);
+}
+
+struct yahoo_post_data {
+ struct yahoo_input_data *yid;
+ char *data;
+};
+
+static void _yahoo_http_post_connected(int id, void *fd, int error, void *data)
+{
+ struct yahoo_post_data *yad = data;
+ struct yahoo_input_data *yid = yad->yid;
+ char *buff = yad->data;
+
+ if (!fd) {
+ inputs = y_list_remove(inputs, yid);
+ FREE(yid);
+ return;
+ }
+
+ YAHOO_CALLBACK(ext_yahoo_write) (fd, buff, strlen(buff));
+
+ yid->fd = fd;
+ yid->read_tag =
+ YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd,
+ YAHOO_INPUT_READ, yid);
+
+ FREE(buff);
+ FREE(yad);
+}
+
+/* FIXME This is also likely affected */
+void yahoo_set_yab(int id, struct yab *yab)
+{
+ struct yahoo_post_data *yad = y_new0(struct yahoo_post_data, 1);
+ struct yahoo_data *yd = find_conn_by_id(id);
+ struct yahoo_input_data *yid;
+ char url[1024];
+ char buff[1024];
+ char post[1024];
+ int size = 0;
+
+ if (!yd)
+ return;
+
+ yid = y_new0(struct yahoo_input_data, 1);
+ yid->type = YAHOO_CONNECTION_YAB;
+ yid->yd = yd;
+
+ if(yab->yid)
+ size = snprintf(post, sizeof(post), "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<ab k=\"%s\" cc=\"%d\">"
+ "<ct id=\"%d\" e=\"1\" yi=\"%s\" nn=\"%s\" />"
+ "</ab>", yd->user, 9, yab->yid, /* Don't know why */
+ yab->id, yab->nname?yab->nname:"");
+ else
+ size = snprintf(post, sizeof(post), "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<ab k=\"%s\" cc=\"%d\">"
+ "<ct a=\"1\" yi=\"%s\" nn=\"%s\" />"
+ "</ab>", yd->user, 1, /* Don't know why */
+ yab->id, yab->nname?yab->nname:"");
+
+ yad->yid = yid;
+ yad->data = strdup(post);
+
+ strcpy(url, "http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us"
+ "&sync=1&tags=short&noclear=1&useutf8=1&legenc=codepage-1252");
+
+ snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t);
+
+ inputs = y_list_prepend(inputs, yid);
+
+ yahoo_http_post(yid->yd->client_id, url, buff, size,
+ _yahoo_http_post_connected, yad);
+}
+
+void yahoo_set_identity_status(int id, const char *identity, int active)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt = NULL;
+
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(active ? YAHOO_SERVICE_IDACT :
+ YAHOO_SERVICE_IDDEACT, YPACKET_STATUS_DEFAULT, yd->session_id);
+ yahoo_packet_hash(pkt, 3, identity);
+ if (pkt) {
+ yahoo_send_packet(yid, pkt, 0);
+ yahoo_packet_free(pkt);
+ }
+}
+
+void yahoo_refresh(int id)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt = NULL;
+
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_USERSTAT, YPACKET_STATUS_DEFAULT,
+ yd->session_id);
+ if (pkt) {
+ yahoo_send_packet(yid, pkt, 0);
+ yahoo_packet_free(pkt);
+ }
+}
+
+void yahoo_keepalive(int id)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt = NULL;
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_PING, YPACKET_STATUS_DEFAULT,
+ yd->session_id);
+ yahoo_send_packet(yid, pkt, 0);
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_chat_keepalive(int id)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt = NULL;
+
+ if (!yid)
+ return;
+
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_CHATPING, YPACKET_STATUS_DEFAULT,
+ yd->session_id);
+ yahoo_send_packet(yid, pkt, 0);
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_add_buddy(int id, const char *who, const char *group,
+ const char *msg)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ if (!yd->logged_in)
+ return;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YPACKET_STATUS_DEFAULT,
+ yd->session_id);
+ if (msg != NULL) /* add message/request "it's me add me" */
+ yahoo_packet_hash(pkt, 14, msg);
+ else
+ yahoo_packet_hash(pkt, 14, "");
+ yahoo_packet_hash(pkt, 65, group);
+ yahoo_packet_hash(pkt, 97, "1");
+ yahoo_packet_hash(pkt, 1, yd->user);
+ yahoo_packet_hash(pkt, 302, "319");
+ yahoo_packet_hash(pkt, 300, "319");
+ yahoo_packet_hash(pkt, 7, who);
+ yahoo_packet_hash(pkt, 334, "0");
+ yahoo_packet_hash(pkt, 301, "319");
+ yahoo_packet_hash(pkt, 303, "319");
+ yahoo_send_packet(yid, pkt, 0);
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_remove_buddy(int id, const char *who, const char *group)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt = NULL;
+
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YPACKET_STATUS_DEFAULT,
+ yd->session_id);
+
+ yahoo_packet_hash(pkt, 1, yd->user);
+ yahoo_packet_hash(pkt, 7, who);
+ yahoo_packet_hash(pkt, 65, group);
+ yahoo_send_packet(yid, pkt, 0);
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_confirm_buddy(int id, const char *who, int reject, const char *msg)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ if (!yd->logged_in)
+ return;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_AUTHORIZATION,
+ YPACKET_STATUS_DEFAULT, yd->session_id);
+ yahoo_packet_hash(pkt, 1, yd->user);
+ yahoo_packet_hash(pkt, 5, who);
+ if (reject)
+ yahoo_packet_hash(pkt, 13, "2");
+ else {
+ yahoo_packet_hash(pkt, 241, "0");
+ yahoo_packet_hash(pkt, 13, "1");
+ }
+
+ yahoo_packet_hash(pkt, 334, "0");
+
+ if (reject) {
+ yahoo_packet_hash(pkt, 14, msg ? msg : "");
+ yahoo_packet_hash(pkt, 97, "1");
+ }
+
+ yahoo_send_packet(yid, pkt, 0);
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_ignore_buddy(int id, const char *who, int unignore)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ if (!yd->logged_in)
+ return;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT,
+ YPACKET_STATUS_DEFAULT, yd->session_id);
+ yahoo_packet_hash(pkt, 1, yd->user);
+ yahoo_packet_hash(pkt, 7, who);
+ yahoo_packet_hash(pkt, 13, unignore ? "2" : "1");
+ yahoo_send_packet(yid, pkt, 0);
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_stealth_buddy(int id, const char *who, int unstealth)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ if (!yd->logged_in)
+ return;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_STEALTH_PERM,
+ YPACKET_STATUS_DEFAULT, yd->session_id);
+ yahoo_packet_hash(pkt, 1, yd->user);
+ yahoo_packet_hash(pkt, 7, who);
+ yahoo_packet_hash(pkt, 31, unstealth ? "2" : "1");
+ yahoo_packet_hash(pkt, 13, "2");
+ yahoo_send_packet(yid, pkt, 0);
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_change_buddy_group(int id, const char *who, const char *old_group,
+ const char *new_group)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt = NULL;
+
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_CHANGE_GROUP,
+ YPACKET_STATUS_DEFAULT, yd->session_id);
+ yahoo_packet_hash(pkt, 1, yd->user);
+ yahoo_packet_hash(pkt, 302, "240");
+ yahoo_packet_hash(pkt, 300, "240");
+ yahoo_packet_hash(pkt, 7, who);
+ yahoo_packet_hash(pkt, 224, old_group);
+ yahoo_packet_hash(pkt, 264, new_group);
+ yahoo_packet_hash(pkt, 301, "240");
+ yahoo_packet_hash(pkt, 303, "240");
+
+ yahoo_send_packet(yid, pkt, 0);
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_group_rename(int id, const char *old_group, const char *new_group)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt = NULL;
+
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_GROUPRENAME,
+ YPACKET_STATUS_DEFAULT, yd->session_id);
+ yahoo_packet_hash(pkt, 1, yd->user);
+ yahoo_packet_hash(pkt, 65, old_group);
+ yahoo_packet_hash(pkt, 67, new_group);
+
+ yahoo_send_packet(yid, pkt, 0);
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_conference_addinvite(int id, const char *from, const char *who,
+ const char *room, const YList *members, const char *msg)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_CONFADDINVITE,
+ YPACKET_STATUS_DEFAULT, yd->session_id);
+
+ yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
+ yahoo_packet_hash(pkt, 51, who);
+ yahoo_packet_hash(pkt, 57, room);
+ yahoo_packet_hash(pkt, 58, msg);
+ yahoo_packet_hash(pkt, 13, "0");
+ for (; members; members = members->next) {
+ yahoo_packet_hash(pkt, 52, (char *)members->data);
+ yahoo_packet_hash(pkt, 53, (char *)members->data);
+ }
+ /* 52, 53 -> other members? */
+
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_conference_invite(int id, const char *from, YList *who,
+ const char *room, const char *msg)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_CONFINVITE, YPACKET_STATUS_DEFAULT,
+ yd->session_id);
+
+ yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
+ yahoo_packet_hash(pkt, 50, yd->user);
+ for (; who; who = who->next) {
+ yahoo_packet_hash(pkt, 52, (char *)who->data);
+ }
+ yahoo_packet_hash(pkt, 57, room);
+ yahoo_packet_hash(pkt, 58, msg);
+ yahoo_packet_hash(pkt, 13, "0");
+
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_conference_logon(int id, const char *from, YList *who,
+ const char *room)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGON, YPACKET_STATUS_DEFAULT,
+ yd->session_id);
+
+ yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
+ yahoo_packet_hash(pkt, 3, (from ? from : yd->user));
+ yahoo_packet_hash(pkt, 57, room);
+ for (; who; who = who->next)
+ yahoo_packet_hash(pkt, 3, (char *)who->data);
+
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_conference_decline(int id, const char *from, YList *who,
+ const char *room, const char *msg)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_CONFDECLINE,
+ YPACKET_STATUS_DEFAULT, yd->session_id);
+
+ yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
+ yahoo_packet_hash(pkt, 3, (from ? from : yd->user));
+ for (; who; who = who->next)
+ yahoo_packet_hash(pkt, 3, (char *)who->data);
+ yahoo_packet_hash(pkt, 57, room);
+ yahoo_packet_hash(pkt, 14, msg);
+
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_conference_logoff(int id, const char *from, YList *who,
+ const char *room)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGOFF, YPACKET_STATUS_DEFAULT,
+ yd->session_id);
+
+ yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
+ yahoo_packet_hash(pkt, 3, (from ? from : yd->user));
+ for (; who; who = who->next)
+ yahoo_packet_hash(pkt, 3, (char *)who->data);
+
+ yahoo_packet_hash(pkt, 57, room);
+
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_conference_message(int id, const char *from, YList *who,
+ const char *room, const char *msg, int utf8)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ if (!yid)
+ return;
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_CONFMSG, YPACKET_STATUS_DEFAULT,
+ yd->session_id);
+
+ yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
+ yahoo_packet_hash(pkt, 53, (from ? from : yd->user));
+ for (; who; who = who->next)
+ yahoo_packet_hash(pkt, 53, (char *)who->data);
+
+ yahoo_packet_hash(pkt, 57, room);
+ yahoo_packet_hash(pkt, 14, msg);
+
+ if (utf8)
+ yahoo_packet_hash(pkt, 97, "1");
+
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_get_chatrooms(int id, int chatroomid)
+{
+ struct yahoo_data *yd = find_conn_by_id(id);
+ struct yahoo_input_data *yid;
+ char url[1024];
+ char buff[1024];
+
+ if (!yd)
+ return;
+
+ yid = y_new0(struct yahoo_input_data, 1);
+ yid->yd = yd;
+ yid->type = YAHOO_CONNECTION_CHATCAT;
+
+ if (chatroomid == 0) {
+ snprintf(url, 1024,
+ "http://insider.msg.yahoo.com/ycontent/?chatcat=0");
+ } else {
+ snprintf(url, 1024,
+ "http://insider.msg.yahoo.com/ycontent/?chatroom_%d=0",
+ chatroomid);
+ }
+
+ snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t);
+
+ inputs = y_list_prepend(inputs, yid);
+
+ yahoo_http_get(yid->yd->client_id, url, buff, 0, 0,
+ _yahoo_http_connected, yid);
+}
+
+void yahoo_chat_logon(int id, const char *from, const char *room,
+ const char *roomid)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ if (!yid)
+ return;
+
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_CHATONLINE, YPACKET_STATUS_DEFAULT,
+ yd->session_id);
+
+ yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
+ yahoo_packet_hash(pkt, 109, yd->user);
+ yahoo_packet_hash(pkt, 6, "abcde");
+
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_CHATJOIN, YPACKET_STATUS_DEFAULT,
+ yd->session_id);
+
+ yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
+ yahoo_packet_hash(pkt, 104, room);
+ yahoo_packet_hash(pkt, 129, roomid);
+ yahoo_packet_hash(pkt, 62, "2"); /* ??? */
+
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_chat_message(int id, const char *from, const char *room,
+ const char *msg, const int msgtype, const int utf8)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+ char buf[2];
+
+ if (!yid)
+ return;
+
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_COMMENT, YPACKET_STATUS_DEFAULT,
+ yd->session_id);
+
+ yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
+ yahoo_packet_hash(pkt, 104, room);
+ yahoo_packet_hash(pkt, 117, msg);
+
+ snprintf(buf, sizeof(buf), "%d", msgtype);
+ yahoo_packet_hash(pkt, 124, buf);
+
+ if (utf8)
+ yahoo_packet_hash(pkt, 97, "1");
+
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_chat_logoff(int id, const char *from)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ if (!yid)
+ return;
+
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_CHATLOGOUT, YPACKET_STATUS_DEFAULT,
+ yd->session_id);
+
+ yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
+
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_buddyicon_request(int id, const char *who)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ if (!yid)
+ return;
+
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YPACKET_STATUS_DEFAULT,
+ 0);
+ yahoo_packet_hash(pkt, 4, yd->user);
+ yahoo_packet_hash(pkt, 5, who);
+ yahoo_packet_hash(pkt, 13, "1");
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_send_picture_info(int id, const char *who, const char *url,
+ int checksum)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+ char checksum_str[10];
+
+ if (!yid)
+ return;
+
+ yd = yid->yd;
+
+ snprintf(checksum_str, sizeof(checksum_str), "%d", checksum);
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YPACKET_STATUS_DEFAULT,
+ 0);
+ yahoo_packet_hash(pkt, 1, yd->user);
+ yahoo_packet_hash(pkt, 4, yd->user);
+ yahoo_packet_hash(pkt, 5, who);
+ yahoo_packet_hash(pkt, 13, "2");
+ yahoo_packet_hash(pkt, 20, url);
+ yahoo_packet_hash(pkt, 192, checksum_str);
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_send_picture_update(int id, const char *who, int type)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+ char type_str[10];
+
+ if (!yid)
+ return;
+
+ yd = yid->yd;
+
+ snprintf(type_str, sizeof(type_str), "%d", type);
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_UPDATE,
+ YPACKET_STATUS_DEFAULT, 0);
+ yahoo_packet_hash(pkt, 1, yd->user);
+ yahoo_packet_hash(pkt, 5, who);
+ yahoo_packet_hash(pkt, 206, type_str);
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_send_picture_checksum(int id, const char *who, int checksum)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+ char checksum_str[10];
+
+ if (!yid)
+ return;
+
+ yd = yid->yd;
+
+ snprintf(checksum_str, sizeof(checksum_str), "%d", checksum);
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_CHECKSUM,
+ YPACKET_STATUS_DEFAULT, 0);
+ yahoo_packet_hash(pkt, 1, yd->user);
+ if (who != 0)
+ yahoo_packet_hash(pkt, 5, who);
+ yahoo_packet_hash(pkt, 192, checksum_str);
+ yahoo_packet_hash(pkt, 212, "1");
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_webcam_close_feed(int id, const char *who)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_webcam_user(id, who);
+
+ if (yid)
+ yahoo_input_close(yid);
+}
+
+void yahoo_webcam_get_feed(int id, const char *who)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ if (!yid)
+ return;
+
+ /*
+ * add the user to the queue. this is a dirty hack, since
+ * the yahoo server doesn't tell us who's key it's returning,
+ * we have to just hope that it sends back keys in the same
+ * order that we request them.
+ * The queue is popped in yahoo_process_webcam_key
+ */
+ webcam_queue = y_list_append(webcam_queue, who ? strdup(who) : NULL);
+
+ yd = yid->yd;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_WEBCAM, YPACKET_STATUS_DEFAULT,
+ yd->session_id);
+
+ yahoo_packet_hash(pkt, 1, yd->user);
+ if (who != NULL)
+ yahoo_packet_hash(pkt, 5, who);
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_webcam_send_image(int id, unsigned char *image, unsigned int length,
+ unsigned int timestamp)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_WEBCAM);
+ unsigned char *packet;
+ unsigned char header_len = 13;
+ unsigned int pos = 0;
+
+ if (!yid)
+ return;
+
+ packet = y_new0(unsigned char, header_len);
+
+ packet[pos++] = header_len;
+ packet[pos++] = 0;
+ packet[pos++] = 5; /* version byte?? */
+ packet[pos++] = 0;
+ pos += yahoo_put32(packet + pos, length);
+ packet[pos++] = 2; /* packet type, image */
+ pos += yahoo_put32(packet + pos, timestamp);
+ yahoo_add_to_send_queue(yid, packet, header_len);
+ FREE(packet);
+
+ if (length)
+ yahoo_add_to_send_queue(yid, image, length);
+}
+
+void yahoo_webcam_accept_viewer(int id, const char *who, int accept)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_WEBCAM);
+ char *packet = NULL;
+ char *data = NULL;
+ unsigned char header_len = 13;
+ unsigned int pos = 0;
+ unsigned int len = 0;
+
+ if (!yid)
+ return;
+
+ data = strdup("u=");
+ data = y_string_append(data, (char *)who);
+ data = y_string_append(data, "\r\n");
+ len = strlen(data);
+
+ packet = y_new0(char, header_len + len);
+ packet[pos++] = header_len;
+ packet[pos++] = 0;
+ packet[pos++] = 5; /* version byte?? */
+ packet[pos++] = 0;
+ pos += yahoo_put32(packet + pos, len);
+ packet[pos++] = 0; /* packet type */
+ pos += yahoo_put32(packet + pos, accept);
+ memcpy(packet + pos, data, len);
+ FREE(data);
+ yahoo_add_to_send_queue(yid, packet, header_len + len);
+ FREE(packet);
+}
+
+void yahoo_webcam_invite(int id, const char *who)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_packet *pkt;
+
+ if (!yid)
+ return;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YPACKET_STATUS_NOTIFY,
+ yid->yd->session_id);
+
+ yahoo_packet_hash(pkt, 49, "WEBCAMINVITE");
+ yahoo_packet_hash(pkt, 14, " ");
+ yahoo_packet_hash(pkt, 13, "0");
+ yahoo_packet_hash(pkt, 1, yid->yd->user);
+ yahoo_packet_hash(pkt, 5, who);
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+static void yahoo_search_internal(int id, int t, const char *text, int g,
+ int ar, int photo, int yahoo_only, int startpos, int total)
+{
+ struct yahoo_data *yd = find_conn_by_id(id);
+ struct yahoo_input_data *yid;
+ char url[1024];
+ char buff[1024];
+ char *ctext, *p;
+
+ if (!yd)
+ return;
+
+ yid = y_new0(struct yahoo_input_data, 1);
+ yid->yd = yd;
+ yid->type = YAHOO_CONNECTION_SEARCH;
+
+ /*
+ age range
+ .ar=1 - 13-18, 2 - 18-25, 3 - 25-35, 4 - 35-50, 5 - 50-70, 6 - 70+
+ */
+
+ snprintf(buff, sizeof(buff), "&.sq=%%20&.tt=%d&.ss=%d", total,
+ startpos);
+
+ ctext = strdup(text);
+ while ((p = strchr(ctext, ' ')))
+ *p = '+';
+
+ snprintf(url, 1024,
+ "http://members.yahoo.com/interests?.oc=m&.kw=%s&.sb=%d&.g=%d&.ar=0%s%s%s",
+ ctext, t, g, photo ? "&.p=y" : "", yahoo_only ? "&.pg=y" : "",
+ startpos ? buff : "");
+
+ FREE(ctext);
+
+ snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t);
+
+ inputs = y_list_prepend(inputs, yid);
+ yahoo_http_get(yid->yd->client_id, url, buff, 0, 0,
+ _yahoo_http_connected, yid);
+}
+
+void yahoo_search(int id, enum yahoo_search_type t, const char *text,
+ enum yahoo_search_gender g, enum yahoo_search_agerange ar, int photo,
+ int yahoo_only)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_search_state *yss;
+
+ if (!yid)
+ return;
+
+ if (!yid->ys)
+ yid->ys = y_new0(struct yahoo_search_state, 1);
+
+ yss = yid->ys;
+
+ FREE(yss->lsearch_text);
+ yss->lsearch_type = t;
+ yss->lsearch_text = strdup(text);
+ yss->lsearch_gender = g;
+ yss->lsearch_agerange = ar;
+ yss->lsearch_photo = photo;
+ yss->lsearch_yahoo_only = yahoo_only;
+
+ yahoo_search_internal(id, t, text, g, ar, photo, yahoo_only, 0, 0);
+}
+
+void yahoo_search_again(int id, int start)
+{
+ struct yahoo_input_data *yid =
+ find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ struct yahoo_search_state *yss;
+
+ if (!yid || !yid->ys)
+ return;
+
+ yss = yid->ys;
+
+ if (start == -1)
+ start = yss->lsearch_nstart + yss->lsearch_nfound;
+
+ yahoo_search_internal(id, yss->lsearch_type, yss->lsearch_text,
+ yss->lsearch_gender, yss->lsearch_agerange,
+ yss->lsearch_photo, yss->lsearch_yahoo_only,
+ start, yss->lsearch_ntotal);
+}
+
+void yahoo_send_picture(int id, const char *name, unsigned long size,
+ yahoo_get_fd_callback callback, void *data)
+{
+ /* Not Implemented */
+}
+
+/* File Transfer */
+static YList *active_file_transfers = NULL;
+
+enum {
+ FT_STATE_HEAD = 1,
+ FT_STATE_RECV,
+ FT_STATE_RECV_START,
+ FT_STATE_SEND
+};
+
+struct send_file_data {
+ int client_id;
+ char *id;
+ char *who;
+ char *filename;
+ char *ip_addr;
+ char *token;
+ int size;
+
+ struct yahoo_input_data *yid;
+ int state;
+
+ yahoo_get_fd_callback callback;
+ void *data;
+};
+
+static char *yahoo_get_random(void)
+{
+ int i = 0;
+ int r = 0;
+ int c = 0;
+ char out[25];
+
+ out[24] = '\0';
+ out[23] = '$';
+ out[22] = '$';
+
+ for (i = 0; i < 22; i++) {
+ if(r == 0)
+ r = rand();
+
+ c = r%61;
+
+ if(c<26)
+ out[i] = c + 'a';
+ else if (c<52)
+ out[i] = c - 26 + 'A';
+ else
+ out[i] = c - 52 + '0';
+
+ r /= 61;
+ }
+
+ return strdup(out);
+}
+
+static int _are_same_id(const void *sfd1, const void *id)
+{
+ return strcmp(((struct send_file_data *)sfd1)->id, (char *)id);
+}
+
+static int _are_same_yid(const void *sfd1, const void *yid)
+{
+ if(((struct send_file_data *)sfd1)->yid == yid)
+ return 0;
+ else
+ return 1;
+}
+
+static struct send_file_data *yahoo_get_active_transfer(char *id)
+{
+ YList *l = y_list_find_custom(active_file_transfers, id,
+ _are_same_id);
+
+ if(l)
+ return (struct send_file_data *)l->data;
+
+ return NULL;
+}
+
+static struct send_file_data *yahoo_get_active_transfer_with_yid(void *yid)
+{
+ YList *l = y_list_find_custom(active_file_transfers, yid,
+ _are_same_yid);
+
+ if(l)
+ return (struct send_file_data *)l->data;
+
+ return NULL;
+}
+
+static void yahoo_add_active_transfer(struct send_file_data *sfd)
+{
+ active_file_transfers = y_list_prepend(active_file_transfers, sfd);
+}
+
+static void yahoo_remove_active_transfer(struct send_file_data *sfd)
+{
+ if (sfd == NULL)
+ return;
+
+ active_file_transfers = y_list_remove(active_file_transfers, sfd);
+ free(sfd->id);
+ free(sfd->who);
+ free(sfd->filename);
+ free(sfd->ip_addr);
+ FREE(sfd);
+}
+
+static void _yahoo_ft_upload_connected(int id, void *fd, int error, void *data)
+{
+ struct send_file_data *sfd = data;
+ struct yahoo_input_data *yid = sfd->yid;
+
+ if (!fd) {
+ inputs = y_list_remove(inputs, yid);
+ FREE(yid);
+ return;
+ }
+
+ sfd->callback(id, fd, error, sfd->data);
+
+ yid->fd = fd;
+ yid->read_tag =
+ YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd,
+ YAHOO_INPUT_READ, yid);
+}
+
+static void yahoo_file_transfer_upload(struct yahoo_data *yd,
+ struct send_file_data *sfd)
+{
+ char url[256];
+ char buff[4096];
+ char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL;
+
+ struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1);
+
+ yid->yd = yd;
+ yid->type = YAHOO_CONNECTION_FT;
+
+ inputs = y_list_prepend(inputs, yid);
+ sfd->yid = yid;
+ sfd->state = FT_STATE_SEND;
+
+ token_enc = yahoo_urlencode(sfd->token);
+ sender_enc = yahoo_urlencode(yd->user);
+ recv_enc = yahoo_urlencode(sfd->who);
+
+ snprintf(url, sizeof(url),
+ "http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr,
+ token_enc, sender_enc, recv_enc);
+
+ snprintf(buff, sizeof(buff), "T=%s; Y=%s", yd->cookie_t, yd->cookie_y);
+
+ yahoo_http_post(yd->client_id, url, buff, sfd->size,
+ _yahoo_ft_upload_connected, sfd);
+
+ FREE(token_enc);
+ FREE(sender_enc);
+ FREE(recv_enc);
+}
+
+static void yahoo_init_ft_recv(struct yahoo_data *yd,
+ struct send_file_data *sfd)
+{
+ char url[256];
+ char buff[1024];
+ char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL;
+
+ struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1);
+
+ yid->yd = yd;
+ yid->type = YAHOO_CONNECTION_FT;
+
+ inputs = y_list_prepend(inputs, yid);
+ sfd->yid = yid;
+ sfd->state = FT_STATE_HEAD;
+
+ token_enc = yahoo_urlencode(sfd->token);
+ sender_enc = yahoo_urlencode(sfd->who);
+ recv_enc = yahoo_urlencode(yd->user);
+
+ snprintf(url, sizeof(url),
+ "http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr,
+ token_enc, sender_enc, recv_enc);
+
+ snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t);
+
+ yahoo_http_head(yid->yd->client_id, url, buff, 0, NULL,
+ _yahoo_http_connected, yid);
+
+ FREE(token_enc);
+ FREE(sender_enc);
+ FREE(recv_enc);
+}
+
+static void yahoo_file_transfer_accept(struct yahoo_input_data *yid,
+ struct send_file_data *sfd)
+{
+ struct yahoo_packet *pkt;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFERACCEPT,
+ YPACKET_STATUS_DEFAULT, yid->yd->session_id);
+
+ yahoo_packet_hash(pkt, 1, yid->yd->user);
+ yahoo_packet_hash(pkt, 5, sfd->who);
+ yahoo_packet_hash(pkt, 265, sfd->id);
+ yahoo_packet_hash(pkt, 27, sfd->filename);
+ yahoo_packet_hash(pkt, 249, "3");
+ yahoo_packet_hash(pkt, 251, sfd->token);
+
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+
+ yahoo_init_ft_recv(yid->yd, sfd);
+}
+
+static void yahoo_process_filetransferaccept(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ YList *l;
+ struct send_file_data *sfd;
+ char *who = NULL;
+ char *filename = NULL;
+ char *id = NULL;
+ char *token = NULL;
+
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ switch (pair->key) {
+ case 4:
+ who = pair->value;
+ break;
+ case 5:
+ /* Me... don't care */
+ break;
+ case 249:
+ break;
+ case 265:
+ id = pair->value;
+ break;
+ case 251:
+ token = pair->value;
+ break;
+ case 27:
+ filename = pair->value;
+ break;
+ }
+ }
+
+ sfd = yahoo_get_active_transfer(id);
+
+ if (sfd) {
+ sfd->token = strdup(token);
+
+ yahoo_file_transfer_upload(yid->yd, sfd);
+ }
+ else {
+ YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
+ (yid->yd->client_id, YAHOO_FILE_TRANSFER_UNKNOWN,
+ sfd ? sfd->data : NULL);
+
+ yahoo_remove_active_transfer(sfd);
+ }
+}
+
+static void yahoo_process_filetransferinfo(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ YList *l;
+ char *who = NULL;
+ char *filename = NULL;
+ char *id = NULL;
+ char *token = NULL;
+ char *ip_addr = NULL;
+
+ struct send_file_data *sfd;
+
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ switch (pair->key) {
+ case 1:
+ case 4:
+ who = pair->value;
+ break;
+ case 5:
+ /* Me... don't care */
+ break;
+ case 249:
+ break;
+ case 265:
+ id = pair->value;
+ break;
+ case 250:
+ ip_addr = pair->value;
+ break;
+ case 251:
+ token = pair->value;
+ break;
+ case 27:
+ filename = pair->value;
+ break;
+ }
+ }
+
+ sfd = yahoo_get_active_transfer(id);
+
+ if (sfd) {
+ sfd->token = strdup(token);
+ sfd->ip_addr = strdup(ip_addr);
+
+ yahoo_file_transfer_accept(yid, sfd);
+ }
+ else {
+ YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
+ (yid->yd->client_id, YAHOO_FILE_TRANSFER_UNKNOWN,
+ sfd ? sfd->data : NULL);
+
+ yahoo_remove_active_transfer(sfd);
+ }
+}
+
+static void yahoo_send_filetransferinfo(struct yahoo_data *yd,
+ struct send_file_data *sfd)
+{
+ struct yahoo_input_data *yid;
+ struct yahoo_packet *pkt;
+
+ yid = find_input_by_id_and_type(yd->client_id, YAHOO_CONNECTION_PAGER);
+ sfd->ip_addr = YAHOO_CALLBACK(ext_yahoo_get_ip_addr)("relay.yahoo.com");
+
+ if (!sfd->ip_addr) {
+ YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
+ (yd->client_id, YAHOO_FILE_TRANSFER_RELAY, sfd->data);
+
+ yahoo_remove_active_transfer(sfd);
+
+ return;
+ }
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFERINFO,
+ YPACKET_STATUS_DEFAULT, yd->session_id);
+
+ yahoo_packet_hash(pkt, 1, yd->user);
+ yahoo_packet_hash(pkt, 5, sfd->who);
+ yahoo_packet_hash(pkt, 265, sfd->id);
+ yahoo_packet_hash(pkt, 27, sfd->filename);
+ yahoo_packet_hash(pkt, 249, "3");
+ yahoo_packet_hash(pkt, 250, sfd->ip_addr);
+
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+static void yahoo_process_filetransfer(struct yahoo_input_data *yid,
+ struct yahoo_packet *pkt)
+{
+ YList *l;
+ char *who = NULL;
+ char *filename = NULL;
+ char *msg = NULL;
+ char *id = NULL;
+ int action = 0;
+ int size = 0;
+ struct yahoo_data *yd = yid->yd;
+
+ struct send_file_data *sfd;
+
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ switch (pair->key) {
+ case 4:
+ who = pair->value;
+ break;
+ case 5:
+ /* Me... don't care */
+ break;
+ case 222:
+ action = atoi(pair->value);
+ break;
+ case 265:
+ id = pair->value;
+ break;
+ case 266: /* Don't know */
+ break;
+ case 302: /* Start Data? */
+ break;
+ case 300:
+ break;
+ case 27:
+ filename = pair->value;
+ break;
+ case 28:
+ size = atoi(pair->value);
+ break;
+ case 14:
+ msg = pair->value;
+ case 301: /* End Data? */
+ break;
+ case 303:
+ break;
+
+ }
+ }
+
+ if (action == YAHOO_FILE_TRANSFER_INIT) {
+ /* Received a FT request from buddy */
+ sfd = y_new0(struct send_file_data, 1);
+
+ sfd->client_id = yd->client_id;
+ sfd->id = strdup(id);
+ sfd->who = strdup(who);
+ sfd->filename = strdup(filename);
+ sfd->size = size;
+
+ yahoo_add_active_transfer(sfd);
+
+ YAHOO_CALLBACK(ext_yahoo_got_file) (yd->client_id, yd->user,
+ who, msg, filename, size, sfd->id);
+ }
+ else {
+ /* Response to our request */
+ sfd = yahoo_get_active_transfer(id);
+
+ if (sfd && action == YAHOO_FILE_TRANSFER_ACCEPT) {
+ yahoo_send_filetransferinfo(yd, sfd);
+ }
+ else if (!sfd || action == YAHOO_FILE_TRANSFER_REJECT) {
+ YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
+ (yd->client_id, YAHOO_FILE_TRANSFER_REJECT,
+ sfd ? sfd->data : NULL);
+
+ yahoo_remove_active_transfer(sfd);
+ }
+ }
+}
+
+void yahoo_send_file(int id, const char *who, const char *msg,
+ const char *name, unsigned long size,
+ yahoo_get_fd_callback callback, void *data)
+{
+ struct yahoo_packet *pkt = NULL;
+ char size_str[10];
+ struct yahoo_input_data *yid;
+ struct yahoo_data *yd;
+ struct send_file_data *sfd;
+
+ yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+ yd = find_conn_by_id(id);
+ sfd = y_new0(struct send_file_data, 1);
+
+ sfd->client_id = id;
+ sfd->id = yahoo_get_random();
+ sfd->who = strdup(who);
+ sfd->filename = strdup(name);
+ sfd->size = size;
+ sfd->callback = callback;
+ sfd->data = data;
+
+ yahoo_add_active_transfer(sfd);
+
+ if (!yd)
+ return;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER,
+ YPACKET_STATUS_DEFAULT, yd->session_id);
+
+ snprintf(size_str, sizeof(size_str), "%ld", size);
+
+ yahoo_packet_hash(pkt, 1, yd->user);
+ yahoo_packet_hash(pkt, 5, who);
+ yahoo_packet_hash(pkt, 265, sfd->id);
+ yahoo_packet_hash(pkt, 222, "1");
+ yahoo_packet_hash(pkt, 266, "1");
+ yahoo_packet_hash(pkt, 302, "268");
+ yahoo_packet_hash(pkt, 300, "268");
+ yahoo_packet_hash(pkt, 27, name);
+ yahoo_packet_hash(pkt, 28, size_str);
+ yahoo_packet_hash(pkt, 301, "268");
+ yahoo_packet_hash(pkt, 303, "268");
+
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+}
+
+void yahoo_send_file_transfer_response(int client_id, int response, char *id, void *data)
+{
+ struct yahoo_packet *pkt = NULL;
+ char resp[2];
+ struct yahoo_input_data *yid;
+
+ struct send_file_data *sfd = yahoo_get_active_transfer(id);
+
+ sfd->data = data;
+
+ yid = find_input_by_id_and_type(client_id, YAHOO_CONNECTION_PAGER);
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER,
+ YPACKET_STATUS_DEFAULT, yid->yd->session_id);
+
+ snprintf(resp, sizeof(resp), "%d", response);
+
+ yahoo_packet_hash(pkt, 1, yid->yd->user);
+ yahoo_packet_hash(pkt, 5, sfd->who);
+ yahoo_packet_hash(pkt, 265, sfd->id);
+ yahoo_packet_hash(pkt, 222, resp);
+
+ yahoo_send_packet(yid, pkt, 0);
+
+ yahoo_packet_free(pkt);
+
+ if(response == YAHOO_FILE_TRANSFER_REJECT)
+ yahoo_remove_active_transfer(sfd);
+}
+
+static void yahoo_process_ft_connection(struct yahoo_input_data *yid, int over)
+{
+ struct send_file_data *sfd;
+ struct yahoo_data *yd = yid->yd;
+
+ sfd = yahoo_get_active_transfer_with_yid(yid);
+
+ if (!sfd) {
+ LOG(("Something funny happened. yid %p has no sfd.\n", yid));
+ return;
+ }
+
+ /*
+ * We want to handle only the complete data with HEAD since we don't
+ * want a situation where both the GET and HEAD are active.
+ * With SEND, we really can't do much with partial response
+ */
+ if ((sfd->state == FT_STATE_HEAD || sfd->state == FT_STATE_SEND)
+ && !over)
+ return;
+
+ if (sfd->state == FT_STATE_HEAD) {
+ /* Do a GET */
+ char url[256];
+ char buff[1024];
+ char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL;
+
+ struct yahoo_input_data *yid_ft =
+ y_new0(struct yahoo_input_data, 1);
+
+ yid_ft->yd = yid->yd;
+ yid_ft->type = YAHOO_CONNECTION_FT;
+
+ inputs = y_list_prepend(inputs, yid_ft);
+ sfd->yid = yid_ft;
+ sfd->state = FT_STATE_RECV;
+
+ token_enc = yahoo_urlencode(sfd->token);
+ sender_enc = yahoo_urlencode(sfd->who);
+ recv_enc = yahoo_urlencode(yd->user);
+
+ snprintf(url, sizeof(url),
+ "http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr,
+ token_enc, sender_enc, recv_enc);
+
+ snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y,
+ yd->cookie_t);
+
+
+ yahoo_http_get(yd->client_id, url, buff, 1, 1,
+ _yahoo_http_connected, yid_ft);
+
+ FREE(token_enc);
+ FREE(sender_enc);
+ FREE(recv_enc);
+ }
+ else if (sfd->state == FT_STATE_RECV ||
+ sfd->state == FT_STATE_RECV_START) {
+
+ unsigned char *data_begin = NULL;
+
+ if (yid->rxlen == 0)
+ yahoo_remove_active_transfer(sfd);
+
+ if (sfd->state != FT_STATE_RECV_START &&
+ (data_begin =
+ (unsigned char *)strstr((char *)yid->rxqueue,
+ "\r\n\r\n"))) {
+
+ sfd->state = FT_STATE_RECV_START;
+
+ yid->rxlen -= 4+(data_begin-yid->rxqueue)/sizeof(char);
+ data_begin += 4;
+
+ if (yid->rxlen > 0)
+ YAHOO_CALLBACK(ext_yahoo_got_ft_data)
+ (yd->client_id, data_begin,
+ yid->rxlen, sfd->data);
+ }
+ else if (sfd->state == FT_STATE_RECV_START)
+ YAHOO_CALLBACK(ext_yahoo_got_ft_data) (yd->client_id,
+ yid->rxqueue, yid->rxlen, sfd->data);
+
+ FREE(yid->rxqueue);
+ yid->rxqueue = NULL;
+ yid->rxlen = 0;
+ }
+ else if (sfd->state == FT_STATE_SEND) {
+ /* Sent file completed */
+ int len = 0;
+ char *off = strstr((char *)yid->rxqueue, "Content-Length: ");
+
+ if (off) {
+ off += 16;
+ len = atoi(off);
+ }
+
+ if (len < sfd->size)
+ YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
+ (yd->client_id,
+ YAHOO_FILE_TRANSFER_FAILED, sfd->data);
+ else
+ YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
+ (yd->client_id,
+ YAHOO_FILE_TRANSFER_DONE, sfd->data);
+
+ yahoo_remove_active_transfer(sfd);
+ }
+}
+
+/* End File Transfer */
+
+enum yahoo_status yahoo_current_status(int id)
+{
+ struct yahoo_data *yd = find_conn_by_id(id);
+ if (!yd)
+ return YAHOO_STATUS_OFFLINE;
+ return yd->current_status;
+}
+
+const YList *yahoo_get_buddylist(int id)
+{
+ struct yahoo_data *yd = find_conn_by_id(id);
+ if (!yd)
+ return NULL;
+ return yd->buddies;
+}
+
+const YList *yahoo_get_ignorelist(int id)
+{
+ struct yahoo_data *yd = find_conn_by_id(id);
+ if (!yd)
+ return NULL;
+ return yd->ignore;
+}
+
+const YList *yahoo_get_identities(int id)
+{
+ struct yahoo_data *yd = find_conn_by_id(id);
+ if (!yd)
+ return NULL;
+ return yd->identities;
+}
+
+const char *yahoo_get_cookie(int id, const char *which)
+{
+ struct yahoo_data *yd = find_conn_by_id(id);
+ if (!yd)
+ return NULL;
+ if (!strncasecmp(which, "y", 1))
+ return yd->cookie_y;
+ if (!strncasecmp(which, "b", 1))
+ return yd->cookie_b;
+ if (!strncasecmp(which, "t", 1))
+ return yd->cookie_t;
+ if (!strncasecmp(which, "c", 1))
+ return yd->cookie_c;
+ if (!strncasecmp(which, "login", 5))
+ return yd->login_cookie;
+ return NULL;
+}
+
+const char *yahoo_get_profile_url(void)
+{
+ return profile_url;
+}
diff --git a/protocols/yahoo/yahoo.c b/protocols/yahoo/yahoo.c
new file mode 100644
index 00000000..8b3b0c05
--- /dev/null
+++ b/protocols/yahoo/yahoo.c
@@ -0,0 +1,1023 @@
+/*
+ * libyahoo2 wrapper to BitlBee
+ *
+ * Mostly Copyright 2004-2010 Wilmer van der Gaast <wilmer@gaast.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include "nogaim.h"
+#include "yahoo2.h"
+#include "yahoo2_callbacks.h"
+
+#define BYAHOO_DEFAULT_GROUP "Buddies"
+
+/* A hack to handle removal of buddies not in the group "Buddies" correctly */
+struct byahoo_buddygroups
+{
+ char *buddy;
+ char *group;
+};
+
+struct byahoo_data
+{
+ int y2_id;
+ int current_status;
+ gboolean logged_in;
+ GSList *buddygroups;
+};
+
+struct byahoo_input_data
+{
+ int h;
+ void *d;
+};
+
+struct byahoo_conf_invitation
+{
+ char *name;
+ struct groupchat *c;
+ int yid;
+ YList *members;
+ struct im_connection *ic;
+};
+
+static GSList *byahoo_inputs = NULL;
+static int byahoo_chat_id = 0;
+
+static char *byahoo_strip( const char *in )
+{
+ int len;
+
+ /* This should get rid of the markup noise at the beginning of the string. */
+ while( *in )
+ {
+ if( g_strncasecmp( in, "<font", 5 ) == 0 ||
+ g_strncasecmp( in, "<fade", 5 ) == 0 ||
+ g_strncasecmp( in, "<alt", 4 ) == 0 )
+ {
+ char *s = strchr( in, '>' );
+ if( !s )
+ break;
+
+ in = s + 1;
+ }
+ else if( strncmp( in, "\e[", 2 ) == 0 )
+ {
+ const char *s;
+
+ for( s = in + 2; *s && *s != 'm'; s ++ );
+
+ if( *s != 'm' )
+ break;
+
+ in = s + 1;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ /* This is supposed to get rid of the noise at the end of the line. */
+ len = strlen( in );
+ while( len > 0 && ( in[len-1] == '>' || in[len-1] == 'm' ) )
+ {
+ int blen = len;
+ const char *search;
+
+ if( in[len-1] == '>' )
+ search = "</";
+ else
+ search = "\e[";
+
+ len -= 3;
+ while( len > 0 && strncmp( in + len, search, 2 ) != 0 )
+ len --;
+
+ if( len <= 0 && strncmp( in, search, 2 ) != 0 )
+ {
+ len = blen;
+ break;
+ }
+ }
+
+ return( g_strndup( in, len ) );
+}
+
+static void byahoo_init( account_t *acc )
+{
+ set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc );
+
+ acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE;
+}
+
+static void byahoo_login( account_t *acc )
+{
+ struct im_connection *ic = imcb_new( acc );
+ struct byahoo_data *yd = ic->proto_data = g_new0( struct byahoo_data, 1 );
+ char *s;
+
+ yd->logged_in = FALSE;
+ yd->current_status = YAHOO_STATUS_AVAILABLE;
+
+ if( ( s = strchr( acc->user, '@' ) ) && g_strcasecmp( s, "@yahoo.com" ) == 0 )
+ imcb_error( ic, "Your Yahoo! username should just be a username. "
+ "Do not include any @domain part." );
+
+ imcb_log( ic, "Connecting" );
+ yd->y2_id = yahoo_init( acc->user, acc->pass );
+ yahoo_login( yd->y2_id, yd->current_status );
+}
+
+static void byahoo_logout( struct im_connection *ic )
+{
+ struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data;
+ GSList *l;
+
+ while( ic->groupchats )
+ imcb_chat_free( ic->groupchats->data );
+
+ for( l = yd->buddygroups; l; l = l->next )
+ {
+ struct byahoo_buddygroups *bg = l->data;
+
+ g_free( bg->buddy );
+ g_free( bg->group );
+ g_free( bg );
+ }
+ g_slist_free( yd->buddygroups );
+
+ yahoo_logoff( yd->y2_id );
+
+ g_free( yd );
+}
+
+static void byahoo_get_info(struct im_connection *ic, char *who)
+{
+ /* Just make an URL and let the user fetch the info */
+ imcb_log(ic, "%s\n%s: %s%s", _("User Info"),
+ _("For now, fetch yourself"), yahoo_get_profile_url(),
+ who);
+}
+
+static int byahoo_buddy_msg( struct im_connection *ic, char *who, char *what, int flags )
+{
+ struct byahoo_data *yd = ic->proto_data;
+
+ yahoo_send_im( yd->y2_id, NULL, who, what, 1, 0 );
+
+ return 1;
+}
+
+static int byahoo_send_typing( struct im_connection *ic, char *who, int typing )
+{
+ struct byahoo_data *yd = ic->proto_data;
+
+ yahoo_send_typing( yd->y2_id, NULL, who, ( typing & OPT_TYPING ) != 0 );
+
+ return 1;
+}
+
+static void byahoo_set_away( struct im_connection *ic, char *state, char *msg )
+{
+ struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data;
+
+ if( state && msg == NULL )
+ {
+ /* Use these states only if msg doesn't contain additional
+ info since away messages are only supported with CUSTOM. */
+ if( g_strcasecmp( state, "Be Right Back" ) == 0 )
+ yd->current_status = YAHOO_STATUS_BRB;
+ else if( g_strcasecmp( state, "Busy" ) == 0 )
+ yd->current_status = YAHOO_STATUS_BUSY;
+ else if( g_strcasecmp( state, "Not At Home" ) == 0 )
+ yd->current_status = YAHOO_STATUS_NOTATHOME;
+ else if( g_strcasecmp( state, "Not At Desk" ) == 0 )
+ yd->current_status = YAHOO_STATUS_NOTATDESK;
+ else if( g_strcasecmp( state, "Not In Office" ) == 0 )
+ yd->current_status = YAHOO_STATUS_NOTINOFFICE;
+ else if( g_strcasecmp( state, "On Phone" ) == 0 )
+ yd->current_status = YAHOO_STATUS_ONPHONE;
+ else if( g_strcasecmp( state, "On Vacation" ) == 0 )
+ yd->current_status = YAHOO_STATUS_ONVACATION;
+ else if( g_strcasecmp( state, "Out To Lunch" ) == 0 )
+ yd->current_status = YAHOO_STATUS_OUTTOLUNCH;
+ else if( g_strcasecmp( state, "Stepped Out" ) == 0 )
+ yd->current_status = YAHOO_STATUS_STEPPEDOUT;
+ else if( g_strcasecmp( state, "Invisible" ) == 0 )
+ yd->current_status = YAHOO_STATUS_INVISIBLE;
+ else
+ yd->current_status = YAHOO_STATUS_CUSTOM;
+ }
+ else if( msg )
+ yd->current_status = YAHOO_STATUS_CUSTOM;
+ else
+ yd->current_status = YAHOO_STATUS_AVAILABLE;
+
+ yahoo_set_away( yd->y2_id, yd->current_status, msg, state ? 2 : 0 );
+}
+
+static GList *byahoo_away_states( struct im_connection *ic )
+{
+ static GList *m = NULL;
+
+ if( m == NULL )
+ {
+ m = g_list_append( m, "Be Right Back" );
+ m = g_list_append( m, "Busy" );
+ m = g_list_append( m, "Not At Home" );
+ m = g_list_append( m, "Not At Desk" );
+ m = g_list_append( m, "Not In Office" );
+ m = g_list_append( m, "On Phone" );
+ m = g_list_append( m, "On Vacation" );
+ m = g_list_append( m, "Out To Lunch" );
+ m = g_list_append( m, "Stepped Out" );
+ m = g_list_append( m, "Invisible" );
+ }
+
+ return m;
+}
+
+static void byahoo_keepalive( struct im_connection *ic )
+{
+ struct byahoo_data *yd = ic->proto_data;
+
+ yahoo_keepalive( yd->y2_id );
+}
+
+static void byahoo_add_buddy( struct im_connection *ic, char *who, char *group )
+{
+ struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data;
+ bee_user_t *bu;
+
+ if( group && ( bu = bee_user_by_handle( ic->bee, ic, who ) ) && bu->group )
+ {
+ GSList *bgl;
+
+ /* If the person is in our list already, this is a group change. */
+ yahoo_change_buddy_group( yd->y2_id, who, bu->group->name, group );
+
+ /* No idea how often people have people in multiple groups and
+ BitlBee doesn't currently support this anyway .. but keep
+ this struct up-to-date for now. */
+ for( bgl = yd->buddygroups; bgl; bgl = bgl->next )
+ {
+ struct byahoo_buddygroups *bg = bgl->data;
+
+ if( g_strcasecmp( bg->buddy, who ) == 0 &&
+ g_strcasecmp( bg->group, bu->group->name ) == 0 )
+ {
+ g_free( bg->group );
+ bg->group = g_strdup( group );
+ }
+ }
+ }
+ else
+ yahoo_add_buddy( yd->y2_id, who, group ? group : BYAHOO_DEFAULT_GROUP, NULL );
+}
+
+static void byahoo_remove_buddy( struct im_connection *ic, char *who, char *group )
+{
+ struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data;
+ GSList *bgl;
+
+ yahoo_remove_buddy( yd->y2_id, who, BYAHOO_DEFAULT_GROUP );
+
+ for( bgl = yd->buddygroups; bgl; bgl = bgl->next )
+ {
+ struct byahoo_buddygroups *bg = bgl->data;
+
+ if( g_strcasecmp( bg->buddy, who ) == 0 )
+ yahoo_remove_buddy( yd->y2_id, who, bg->group );
+ }
+}
+
+static void byahoo_chat_msg( struct groupchat *c, char *message, int flags )
+{
+ struct byahoo_data *yd = (struct byahoo_data *) c->ic->proto_data;
+
+ yahoo_conference_message( yd->y2_id, NULL, c->data, c->title, message, 1 );
+}
+
+static void byahoo_chat_invite( struct groupchat *c, char *who, char *msg )
+{
+ struct byahoo_data *yd = (struct byahoo_data *) c->ic->proto_data;
+
+ yahoo_conference_invite( yd->y2_id, NULL, c->data, c->title, msg ? msg : "" );
+}
+
+static void byahoo_chat_leave( struct groupchat *c )
+{
+ struct byahoo_data *yd = (struct byahoo_data *) c->ic->proto_data;
+
+ yahoo_conference_logoff( yd->y2_id, NULL, c->data, c->title );
+ imcb_chat_free( c );
+}
+
+static struct groupchat *byahoo_chat_with( struct im_connection *ic, char *who )
+{
+ struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data;
+ struct groupchat *c;
+ char *roomname;
+ YList *members;
+
+ roomname = g_strdup_printf( "%s-Bee-%d", ic->acc->user, byahoo_chat_id );
+
+ c = imcb_chat_new( ic, roomname );
+ imcb_chat_add_buddy( c, ic->acc->user );
+
+ /* FIXME: Free this thing when the chat's destroyed. We can't *always*
+ do this because it's not always created here. */
+ c->data = members = g_new0( YList, 1 );
+ members->data = g_strdup( who );
+
+ yahoo_conference_invite( yd->y2_id, NULL, members, roomname, "Please join my groupchat..." );
+
+ g_free( roomname );
+
+ return c;
+}
+
+static void byahoo_auth_allow( struct im_connection *ic, const char *who )
+{
+ struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data;
+
+ yahoo_confirm_buddy( yd->y2_id, who, 0, "" );
+}
+
+static void byahoo_auth_deny( struct im_connection *ic, const char *who )
+{
+ struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data;
+
+ yahoo_confirm_buddy( yd->y2_id, who, 1, "" );
+}
+
+void byahoo_initmodule( )
+{
+ struct prpl *ret = g_new0(struct prpl, 1);
+ ret->name = "yahoo";
+ ret->mms = 832; /* this guess taken from libotr UPGRADING file */
+ ret->init = byahoo_init;
+
+ ret->login = byahoo_login;
+ ret->keepalive = byahoo_keepalive;
+ ret->logout = byahoo_logout;
+
+ ret->buddy_msg = byahoo_buddy_msg;
+ ret->get_info = byahoo_get_info;
+ ret->away_states = byahoo_away_states;
+ ret->set_away = byahoo_set_away;
+ ret->add_buddy = byahoo_add_buddy;
+ ret->remove_buddy = byahoo_remove_buddy;
+ ret->send_typing = byahoo_send_typing;
+
+ ret->chat_msg = byahoo_chat_msg;
+ ret->chat_invite = byahoo_chat_invite;
+ ret->chat_leave = byahoo_chat_leave;
+ ret->chat_with = byahoo_chat_with;
+
+ ret->handle_cmp = g_strcasecmp;
+
+ ret->auth_allow = byahoo_auth_allow;
+ ret->auth_deny = byahoo_auth_deny;
+
+ register_protocol(ret);
+}
+
+static struct im_connection *byahoo_get_ic_by_id( int id )
+{
+ GSList *l;
+ struct im_connection *ic;
+ struct byahoo_data *yd;
+
+ for( l = get_connections(); l; l = l->next )
+ {
+ ic = l->data;
+ yd = ic->proto_data;
+
+ if( strcmp( ic->acc->prpl->name, "yahoo" ) == 0 && yd->y2_id == id )
+ return( ic );
+ }
+
+ return( NULL );
+}
+
+
+/* Now it's callback time! */
+
+struct byahoo_connect_callback_data
+{
+ int fd;
+ yahoo_connect_callback callback;
+ gpointer data;
+ int id;
+};
+
+void byahoo_connect_callback( gpointer data, gint source, b_input_condition cond )
+{
+ struct byahoo_connect_callback_data *d = data;
+
+ if( !byahoo_get_ic_by_id( d->id ) )
+ {
+ g_free( d );
+ return;
+ }
+
+ d->callback( NULL + d->fd, 0, d->data );
+ g_free( d );
+}
+
+struct byahoo_read_ready_data
+{
+ int id;
+ int fd;
+ int tag;
+ gpointer data;
+};
+
+gboolean byahoo_read_ready_callback( gpointer data, gint source, b_input_condition cond )
+{
+ struct byahoo_read_ready_data *d = data;
+
+ if( !byahoo_get_ic_by_id( d->id ) )
+ /* WTF doesn't libyahoo clean this up? */
+ return FALSE;
+
+ yahoo_read_ready( d->id, NULL + d->fd, d->data );
+
+ return TRUE;
+}
+
+struct byahoo_write_ready_data
+{
+ int id;
+ int fd;
+ int tag;
+ gpointer data;
+};
+
+gboolean byahoo_write_ready_callback( gpointer data, gint source, b_input_condition cond )
+{
+ struct byahoo_write_ready_data *d = data;
+
+ return yahoo_write_ready( d->id, NULL + d->fd, d->data );
+}
+
+void ext_yahoo_login_response( int id, int succ, const char *url )
+{
+ struct im_connection *ic = byahoo_get_ic_by_id( id );
+ struct byahoo_data *yd = NULL;
+
+ if( ic == NULL )
+ {
+ /* libyahoo2 seems to call this one twice when something
+ went wrong sometimes. Don't know why. Because we clean
+ up the connection on the first failure, the second
+ should be ignored. */
+
+ return;
+ }
+
+ yd = (struct byahoo_data *) ic->proto_data;
+
+ if( succ == YAHOO_LOGIN_OK )
+ {
+ imcb_connected( ic );
+
+ yd->logged_in = TRUE;
+ }
+ else
+ {
+ char *errstr;
+ int allow_reconnect = FALSE;
+
+ yd->logged_in = FALSE;
+
+ if( succ == YAHOO_LOGIN_UNAME )
+ errstr = "Incorrect Yahoo! username";
+ else if( succ == YAHOO_LOGIN_PASSWD )
+ errstr = "Incorrect Yahoo! password";
+ else if( succ == YAHOO_LOGIN_LOCK )
+ errstr = "Yahoo! account locked";
+ else if( succ == 1236 )
+ errstr = "Yahoo! account locked or machine temporarily banned";
+ else if( succ == YAHOO_LOGIN_DUPL )
+ errstr = "Logged in on a different machine or device";
+ else if( succ == YAHOO_LOGIN_SOCK )
+ {
+ errstr = "Socket problem";
+ allow_reconnect = TRUE;
+ }
+ else
+ errstr = "Unknown error";
+
+ if( url && *url )
+ imcb_error( ic, "Error %d (%s). See %s for more information.", succ, errstr, url );
+ else
+ imcb_error( ic, "Error %d (%s)", succ, errstr );
+
+ imc_logout( ic, allow_reconnect );
+ }
+}
+
+void ext_yahoo_got_buddies( int id, YList *buds )
+{
+ struct im_connection *ic = byahoo_get_ic_by_id( id );
+ struct byahoo_data *yd = ic->proto_data;
+ YList *bl = buds;
+
+ while( bl )
+ {
+ struct yahoo_buddy *b = bl->data;
+ struct byahoo_buddygroups *bg;
+
+ if( strcmp( b->group, BYAHOO_DEFAULT_GROUP ) != 0 )
+ {
+ bg = g_new0( struct byahoo_buddygroups, 1 );
+
+ bg->buddy = g_strdup( b->id );
+ bg->group = g_strdup( b->group );
+ yd->buddygroups = g_slist_append( yd->buddygroups, bg );
+ }
+
+ imcb_add_buddy( ic, b->id, b->group );
+ imcb_rename_buddy( ic, b->id, b->real_name );
+
+ bl = bl->next;
+ }
+}
+
+void ext_yahoo_got_ignore( int id, YList *igns )
+{
+}
+
+void ext_yahoo_got_identities( int id, YList *ids )
+{
+}
+
+void ext_yahoo_got_cookies( int id )
+{
+}
+
+void ext_yahoo_status_changed( int id, const char *who, int stat, const char *msg, int away, int idle, int mobile )
+{
+ struct im_connection *ic = byahoo_get_ic_by_id( id );
+ char *state_string = NULL;
+ int flags = OPT_LOGGED_IN;
+
+ if( away )
+ flags |= OPT_AWAY;
+ if( mobile )
+ flags |= OPT_MOBILE;
+
+ switch (stat)
+ {
+ case YAHOO_STATUS_BRB:
+ state_string = "Be Right Back";
+ break;
+ case YAHOO_STATUS_BUSY:
+ state_string = "Busy";
+ break;
+ case YAHOO_STATUS_NOTATHOME:
+ state_string = "Not At Home";
+ break;
+ case YAHOO_STATUS_NOTATDESK:
+ state_string = "Not At Desk";
+ break;
+ case YAHOO_STATUS_NOTINOFFICE:
+ state_string = "Not In Office";
+ break;
+ case YAHOO_STATUS_ONPHONE:
+ state_string = "On Phone";
+ break;
+ case YAHOO_STATUS_ONVACATION:
+ state_string = "On Vacation";
+ break;
+ case YAHOO_STATUS_OUTTOLUNCH:
+ state_string = "Out To Lunch";
+ break;
+ case YAHOO_STATUS_STEPPEDOUT:
+ state_string = "Stepped Out";
+ break;
+ case YAHOO_STATUS_INVISIBLE:
+ state_string = "Invisible";
+ break;
+ case YAHOO_STATUS_CUSTOM:
+ state_string = "Away";
+ break;
+ case YAHOO_STATUS_IDLE:
+ state_string = "Idle";
+ break;
+ case YAHOO_STATUS_OFFLINE:
+ state_string = "Offline";
+ flags = 0;
+ break;
+ }
+
+ imcb_buddy_status( ic, who, flags, state_string, msg );
+
+ if( stat == YAHOO_STATUS_IDLE )
+ imcb_buddy_times( ic, who, 0, idle );
+}
+
+void ext_yahoo_got_buzz( int id, const char *me, const char *who, long tm )
+{
+}
+
+void ext_yahoo_got_im( int id, const char *me, const char *who, const char *msg, long tm, int stat, int utf8 )
+{
+ struct im_connection *ic = byahoo_get_ic_by_id( id );
+ char *m;
+
+ if( msg )
+ {
+ m = byahoo_strip( msg );
+ imcb_buddy_msg( ic, (char*) who, (char*) m, 0, 0 );
+ g_free( m );
+ }
+}
+
+void ext_yahoo_got_file( int id, const char *ignored, const char *who, const char *msg,
+ const char *fname, unsigned long fesize, char *trid )
+{
+ struct im_connection *ic = byahoo_get_ic_by_id( id );
+
+ imcb_log( ic, "Got a file transfer (file = %s) from %s. Ignoring for now due to lack of support.", fname, who );
+}
+
+void ext_yahoo_got_ft_data( int id, const unsigned char *in, int len, void *data )
+{
+}
+
+void ext_yahoo_file_transfer_done( int id, int result, void *data )
+{
+}
+
+void ext_yahoo_typing_notify( int id, const char *ignored, const char *who, int stat )
+{
+ struct im_connection *ic = byahoo_get_ic_by_id( id );
+
+ if( stat == 1 )
+ imcb_buddy_typing( ic, (char*) who, OPT_TYPING );
+ else
+ imcb_buddy_typing( ic, (char*) who, 0 );
+}
+
+void ext_yahoo_system_message( int id, const char *me, const char *who, const char *msg )
+{
+ struct im_connection *ic = byahoo_get_ic_by_id( id );
+
+ imcb_log( ic, "Yahoo! system message: %s", msg );
+}
+
+void ext_yahoo_webcam_invite( int id, const char *ignored, const char *from )
+{
+ struct im_connection *ic = byahoo_get_ic_by_id( id );
+
+ imcb_log( ic, "Got a webcam invitation from %s. IRC+webcams is a no-no though...", from );
+}
+
+void ext_yahoo_error( int id, const char *err, int fatal, int num )
+{
+ struct im_connection *ic = byahoo_get_ic_by_id( id );
+
+ imcb_error( ic, "%s", err );
+}
+
+/* TODO: Clear up the mess of inp and d structures */
+int ext_yahoo_add_handler( int id, void *fd_, yahoo_input_condition cond, void *data )
+{
+ struct byahoo_input_data *inp = g_new0( struct byahoo_input_data, 1 );
+ int fd = (long) fd_;
+
+ if( cond == YAHOO_INPUT_READ )
+ {
+ struct byahoo_read_ready_data *d = g_new0( struct byahoo_read_ready_data, 1 );
+
+ d->id = id;
+ d->fd = fd;
+ d->data = data;
+
+ inp->d = d;
+ d->tag = inp->h = b_input_add( fd, B_EV_IO_READ, (b_event_handler) byahoo_read_ready_callback, (gpointer) d );
+ }
+ else if( cond == YAHOO_INPUT_WRITE )
+ {
+ struct byahoo_write_ready_data *d = g_new0( struct byahoo_write_ready_data, 1 );
+
+ d->id = id;
+ d->fd = fd;
+ d->data = data;
+
+ inp->d = d;
+ d->tag = inp->h = b_input_add( fd, B_EV_IO_WRITE, (b_event_handler) byahoo_write_ready_callback, (gpointer) d );
+ }
+ else
+ {
+ g_free( inp );
+ return -1;
+ /* Panic... */
+ }
+
+ byahoo_inputs = g_slist_append( byahoo_inputs, inp );
+ return inp->h;
+}
+
+void ext_yahoo_remove_handler( int id, int tag )
+{
+ struct byahoo_input_data *inp;
+ GSList *l = byahoo_inputs;
+
+ while( l )
+ {
+ inp = l->data;
+ if( inp->h == tag )
+ {
+ g_free( inp->d );
+ g_free( inp );
+ byahoo_inputs = g_slist_remove( byahoo_inputs, inp );
+ break;
+ }
+ l = l->next;
+ }
+
+ b_event_remove( tag );
+}
+
+int ext_yahoo_connect_async( int id, const char *host, int port, yahoo_connect_callback callback, void *data, int use_ssl )
+{
+ struct byahoo_connect_callback_data *d;
+ int fd;
+
+ d = g_new0( struct byahoo_connect_callback_data, 1 );
+ if( ( fd = proxy_connect( host, port, (b_event_handler) byahoo_connect_callback, (gpointer) d ) ) < 0 )
+ {
+ g_free( d );
+ return( fd );
+ }
+ d->fd = fd;
+ d->callback = callback;
+ d->data = data;
+ d->id = id;
+
+ return fd;
+}
+
+char *ext_yahoo_get_ip_addr( const char *domain )
+{
+ return NULL;
+}
+
+int ext_yahoo_write( void *fd, char *buf, int len )
+{
+ return write( (long) fd, buf, len );
+}
+
+int ext_yahoo_read( void *fd, char *buf, int len )
+{
+ return read( (long) fd, buf, len );
+}
+
+void ext_yahoo_close( void *fd )
+{
+ close( (long) fd );
+}
+
+void ext_yahoo_got_buddy_change_group( int id, const char *me, const char *who,
+ const char *old_group, const char *new_group )
+{
+ struct im_connection *ic = byahoo_get_ic_by_id( id );
+
+ imcb_add_buddy( ic, who, new_group );
+}
+
+/* Because we don't want asynchronous connects in BitlBee, and because
+ libyahoo doesn't seem to use this one anyway, this one is now defunct. */
+int ext_yahoo_connect(const char *host, int port)
+{
+ return -1;
+}
+
+static void byahoo_accept_conf( void *data )
+{
+ struct byahoo_conf_invitation *inv = data;
+ struct groupchat *b = NULL;
+ GSList *l;
+
+ for( l = inv->ic->groupchats; l; l = l->next )
+ {
+ b = l->data;
+ if( b == inv->c )
+ break;
+ }
+
+ if( b != NULL )
+ {
+ yahoo_conference_logon( inv->yid, NULL, inv->members, inv->name );
+ imcb_chat_add_buddy( inv->c, inv->ic->acc->user );
+ }
+ else
+ {
+ imcb_log( inv->ic, "Duplicate/corrupted invitation to `%s'.", inv->name );
+ }
+
+ g_free( inv->name );
+ g_free( inv );
+}
+
+static void byahoo_reject_conf( void *data )
+{
+ struct byahoo_conf_invitation *inv = data;
+
+ yahoo_conference_decline( inv->yid, NULL, inv->members, inv->name, "User rejected groupchat" );
+ imcb_chat_free( inv->c );
+ g_free( inv->name );
+ g_free( inv );
+}
+
+void ext_yahoo_got_conf_invite( int id, const char *ignored,
+ const char *who, const char *room, const char *msg, YList *members )
+{
+ struct im_connection *ic = byahoo_get_ic_by_id( id );
+ struct byahoo_conf_invitation *inv;
+ char txt[1024];
+ YList *m;
+
+ if( g_strcasecmp( who, ic->acc->user ) == 0 )
+ /* WTF, Yahoo! seems to echo these now? */
+ return;
+
+ inv = g_malloc( sizeof( struct byahoo_conf_invitation ) );
+ memset( inv, 0, sizeof( struct byahoo_conf_invitation ) );
+ inv->name = g_strdup( room );
+ inv->c = imcb_chat_new( ic, (char*) room );
+ inv->c->data = members;
+ inv->yid = id;
+ inv->members = members;
+ inv->ic = ic;
+
+ for( m = members; m; m = m->next )
+ if( g_strcasecmp( m->data, ic->acc->user ) != 0 )
+ imcb_chat_add_buddy( inv->c, m->data );
+
+ g_snprintf( txt, 1024, "Got an invitation to chatroom %s from %s: %s", room, who, msg );
+
+ imcb_ask( ic, txt, inv, byahoo_accept_conf, byahoo_reject_conf );
+}
+
+void ext_yahoo_conf_userdecline( int id, const char *ignored, const char *who, const char *room, const char *msg )
+{
+ struct im_connection *ic = byahoo_get_ic_by_id( id );
+
+ imcb_log( ic, "Invite to chatroom %s rejected by %s: %s", room, who, msg );
+}
+
+void ext_yahoo_conf_userjoin( int id, const char *ignored, const char *who, const char *room )
+{
+ struct im_connection *ic = byahoo_get_ic_by_id( id );
+ struct groupchat *c = bee_chat_by_title( ic->bee, ic, room );
+
+ if( c )
+ imcb_chat_add_buddy( c, (char*) who );
+}
+
+void ext_yahoo_conf_userleave( int id, const char *ignored, const char *who, const char *room )
+
+{
+ struct im_connection *ic = byahoo_get_ic_by_id( id );
+ struct groupchat *c = bee_chat_by_title( ic->bee, ic, room );
+
+ if( c )
+ imcb_chat_remove_buddy( c, (char*) who, "" );
+}
+
+void ext_yahoo_conf_message( int id, const char *ignored, const char *who, const char *room, const char *msg, int utf8 )
+{
+ struct im_connection *ic = byahoo_get_ic_by_id( id );
+ char *m = byahoo_strip( msg );
+ struct groupchat *c = bee_chat_by_title( ic->bee, ic, room );
+
+ if( c )
+ imcb_chat_msg( c, (char*) who, (char*) m, 0, 0 );
+ g_free( m );
+}
+
+void ext_yahoo_chat_cat_xml( int id, const char *xml )
+{
+}
+
+void ext_yahoo_chat_join( int id, const char *who, const char *room, const char *topic, YList *members, void *fd )
+{
+}
+
+void ext_yahoo_chat_userjoin( int id, const char *me, const char *room, struct yahoo_chat_member *who )
+{
+ free(who->id);
+ free(who->alias);
+ free(who->location);
+ free(who);
+}
+
+void ext_yahoo_chat_userleave( int id, const char *me, const char *room, const char *who )
+{
+}
+
+void ext_yahoo_chat_message( int id, const char *me, const char *who, const char *room, const char *msg, int msgtype, int utf8 )
+{
+}
+
+void ext_yahoo_chat_yahoologout( int id, const char *me )
+{
+}
+
+void ext_yahoo_chat_yahooerror( int id, const char *me )
+{
+}
+
+void ext_yahoo_contact_added( int id, const char *myid, const char *who, const char *msg )
+{
+ struct im_connection *ic = byahoo_get_ic_by_id( id );
+
+ imcb_ask_auth( ic, who, msg );
+}
+
+void ext_yahoo_rejected( int id, const char *who, const char *msg )
+{
+}
+
+void ext_yahoo_game_notify( int id, const char *me, const char *who, int stat, const char *msg )
+{
+}
+
+void ext_yahoo_mail_notify( int id, const char *from, const char *subj, int cnt )
+{
+ struct im_connection *ic = byahoo_get_ic_by_id( id );
+
+ if( !set_getbool( &ic->acc->set, "mail_notifications" ) )
+ ; /* The user doesn't care. */
+ else if( from && subj )
+ imcb_log( ic, "Received e-mail message from %s with subject `%s'", from, subj );
+ else if( cnt > 0 )
+ imcb_log( ic, "Received %d new e-mails", cnt );
+}
+
+void ext_yahoo_webcam_invite_reply( int id, const char *me, const char *from, int accept )
+{
+}
+
+void ext_yahoo_webcam_closed( int id, const char *who, int reason )
+{
+}
+
+void ext_yahoo_got_search_result( int id, int found, int start, int total, YList *contacts )
+{
+}
+
+void ext_yahoo_webcam_viewer( int id, const char *who, int connect )
+{
+}
+
+void ext_yahoo_webcam_data_request( int id, int send )
+{
+}
+
+int ext_yahoo_log( const char *fmt, ... )
+{
+ return( 0 );
+}
+
+void ext_yahoo_got_webcam_image( int id, const char * who, const unsigned char *image, unsigned int image_size, unsigned int real_size, unsigned int timestamp )
+{
+}
+
+void ext_yahoo_got_ping( int id, const char *msg )
+{
+}
+
+void ext_yahoo_got_buddyicon (int id, const char *me, const char *who, const char *url, int checksum) {}
+void ext_yahoo_got_buddyicon_checksum (int id, const char *me,const char *who, int checksum) {}
+
+void ext_yahoo_got_buddyicon_request(int id, const char *me, const char *who){}
+void ext_yahoo_buddyicon_uploaded(int id, const char *url){}
diff --git a/protocols/yahoo/yahoo2.h b/protocols/yahoo/yahoo2.h
new file mode 100644
index 00000000..589aaa5a
--- /dev/null
+++ b/protocols/yahoo/yahoo2.h
@@ -0,0 +1,247 @@
+/*
+ * libyahoo2: yahoo2.h
+ *
+ * Copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef YAHOO2_H
+#define YAHOO2_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* *** BitlBee: *** */
+#include "bitlbee.h"
+#undef free
+#define free( x ) g_free( x )
+#undef malloc
+#define malloc( x ) g_malloc( x )
+#undef calloc
+#define calloc( x, y ) g_calloc( x, y )
+#undef realloc
+#define realloc( x, y ) g_realloc( x, y )
+#undef strdup
+#define strdup( x ) g_strdup( x )
+#undef strndup
+#define strndup( x,y ) g_strndup( x,y )
+#undef snprintf
+// #define snprintf( x... ) g_snprintf( x )
+#undef strcasecmp
+#define strcasecmp( x,y ) g_strcasecmp( x,y )
+#undef strncasecmp
+#define strncasecmp( x,y,z ) g_strncasecmp( x,y,z )
+
+
+#include "yahoo2_types.h"
+
+/* returns the socket descriptor object for a given pager connection. shouldn't be needed */
+ void *yahoo_get_fd(int id);
+
+/* says how much logging to do */
+/* see yahoo2_types.h for the different values */
+ int yahoo_set_log_level(enum yahoo_log_level level);
+ enum yahoo_log_level yahoo_get_log_level(void);
+
+/* these functions should be self explanatory */
+/* who always means the buddy you're acting on */
+/* id is the successful value returned by yahoo_init */
+
+/* init returns a connection id used to identify the connection hereon */
+/* or 0 on failure */
+/* you must call init before calling any other function */
+/*
+ * The optional parameters to init are key/value pairs that specify
+ * server settings to use. This list must be NULL terminated - even
+ * if the list is empty. If a parameter isn't set, a default value
+ * will be used. Parameter keys are strings, parameter values are
+ * either strings or ints, depending on the key. Values passed in
+ * are copied, so you can use const/auto/static/pointers/whatever
+ * you want. Parameters are:
+ * NAME TYPE DEFAULT
+ * pager_host char * scs.msg.yahoo.com
+ * pager_port int 5050
+ * filetransfer_host char * filetransfer.msg.yahoo.com
+ * filetransfer_port int 80
+ * webcam_host char * webcam.yahoo.com
+ * webcam_port int 5100
+ * webcam_description char * ""
+ * local_host char * ""
+ * conn_type int Y_WCM_DSL
+ *
+ * You should set at least local_host if you intend to use webcams
+ */
+ int yahoo_init_with_attributes(const char *username,
+ const char *password, ...);
+
+/* yahoo_init does the same as yahoo_init_with_attributes, assuming defaults
+ * for all attributes */
+ int yahoo_init(const char *username, const char *password);
+
+/* release all resources held by this session */
+/* you need to call yahoo_close for a session only if
+ * yahoo_logoff is never called for it (ie, it was never logged in) */
+ void yahoo_close(int id);
+/* login logs in to the server */
+/* initial is of type enum yahoo_status. see yahoo2_types.h */
+ void yahoo_login(int id, int initial);
+ void yahoo_logoff(int id);
+/* reloads status of all buddies */
+ void yahoo_refresh(int id);
+/* activates/deactivates an identity */
+ void yahoo_set_identity_status(int id, const char *identity,
+ int active);
+/* regets the entire buddy list from the server */
+ void yahoo_get_list(int id);
+/* download buddy contact information from your yahoo addressbook */
+ void yahoo_get_yab(int id);
+/* add/modify an address book entry. if yab->dbid is set, it will */
+/* modify that entry else it creates a new entry */
+ void yahoo_set_yab(int id, struct yab *yab);
+ void yahoo_keepalive(int id);
+ void yahoo_chat_keepalive(int id);
+
+/* from is the identity you're sending from. if NULL, the default is used */
+/* utf8 is whether msg is a utf8 string or not. */
+ void yahoo_send_im(int id, const char *from, const char *who,
+ const char *msg, int utf8, int picture);
+ void yahoo_send_buzz(int id, const char *from, const char *who);
+/* if type is true, send typing notice, else send stopped typing notice */
+ void yahoo_send_typing(int id, const char *from, const char *who,
+ int typ);
+
+/* used to set away/back status. */
+/* away says whether the custom message is an away message or a sig */
+ void yahoo_set_away(int id, enum yahoo_status state, const char *msg,
+ int away);
+
+ void yahoo_add_buddy(int id, const char *who, const char *group,
+ const char *msg);
+ void yahoo_remove_buddy(int id, const char *who, const char *group);
+ void yahoo_confirm_buddy(int id, const char *who, int reject,
+ const char *msg);
+ void yahoo_stealth_buddy(int id, const char *who, int unstealth);
+/* if unignore is true, unignore, else ignore */
+ void yahoo_ignore_buddy(int id, const char *who, int unignore);
+ void yahoo_change_buddy_group(int id, const char *who,
+ const char *old_group, const char *new_group);
+ void yahoo_group_rename(int id, const char *old_group,
+ const char *new_group);
+
+ void yahoo_conference_invite(int id, const char *from, YList *who,
+ const char *room, const char *msg);
+ void yahoo_conference_addinvite(int id, const char *from,
+ const char *who, const char *room, const YList *members,
+ const char *msg);
+ void yahoo_conference_decline(int id, const char *from, YList *who,
+ const char *room, const char *msg);
+ void yahoo_conference_message(int id, const char *from, YList *who,
+ const char *room, const char *msg, int utf8);
+ void yahoo_conference_logon(int id, const char *from, YList *who,
+ const char *room);
+ void yahoo_conference_logoff(int id, const char *from, YList *who,
+ const char *room);
+
+/* Get a list of chatrooms */
+ void yahoo_get_chatrooms(int id, int chatroomid);
+/* join room with specified roomname and roomid */
+ void yahoo_chat_logon(int id, const char *from, const char *room,
+ const char *roomid);
+/* Send message "msg" to room with specified roomname, msgtype is 1-normal message or 2-/me mesage */
+ void yahoo_chat_message(int id, const char *from, const char *room,
+ const char *msg, const int msgtype, const int utf8);
+/* Log off chat */
+ void yahoo_chat_logoff(int id, const char *from);
+
+/* requests a webcam feed */
+/* who is the person who's webcam you would like to view */
+/* if who is null, then you're the broadcaster */
+ void yahoo_webcam_get_feed(int id, const char *who);
+ void yahoo_webcam_close_feed(int id, const char *who);
+
+/* sends an image when uploading */
+/* image points to a JPEG-2000 image, length is the length of the image */
+/* in bytes. The timestamp is the time in milliseconds since we started the */
+/* webcam. */
+ void yahoo_webcam_send_image(int id, unsigned char *image,
+ unsigned int length, unsigned int timestamp);
+
+/* this function should be called if we want to allow a user to watch the */
+/* webcam. Who is the user we want to accept. */
+/* Accept user (accept = 1), decline user (accept = 0) */
+ void yahoo_webcam_accept_viewer(int id, const char *who, int accept);
+
+/* send an invitation to a user to view your webcam */
+ void yahoo_webcam_invite(int id, const char *who);
+
+/* will set up a connection and initiate file transfer.
+ * callback will be called with the fd that you should write
+ * the file data to
+ */
+ void yahoo_send_file(int id, const char *who, const char *msg,
+ const char *name, unsigned long size,
+ yahoo_get_fd_callback callback, void *data);
+
+/*
+ * Respond to a file transfer request. Be sure to provide the callback data
+ * since that is your only chance to recognize future callbacks
+ */
+ void yahoo_send_file_transfer_response(int client_id, int response,
+ char *id, void *data);
+
+
+/* send a search request
+ */
+ void yahoo_search(int id, enum yahoo_search_type t, const char *text,
+ enum yahoo_search_gender g, enum yahoo_search_agerange ar,
+ int photo, int yahoo_only);
+
+/* continue last search
+ * should be called if only (start+found >= total)
+ *
+ * where the above three are passed to ext_yahoo_got_search_result
+ */
+ void yahoo_search_again(int id, int start);
+
+/* these should be called when input is available on a fd */
+/* registered by ext_yahoo_add_handler */
+/* if these return negative values, errno may be set */
+ int yahoo_read_ready(int id, void *fd, void *data);
+ int yahoo_write_ready(int id, void *fd, void *data);
+
+/* utility functions. these do not hit the server */
+ enum yahoo_status yahoo_current_status(int id);
+ const YList *yahoo_get_buddylist(int id);
+ const YList *yahoo_get_ignorelist(int id);
+ const YList *yahoo_get_identities(int id);
+/* 'which' could be y, t, c or login. This may change in later versions. */
+ const char *yahoo_get_cookie(int id, const char *which);
+
+/* returns the url used to get user profiles - you must append the user id */
+/* as of now this is http://profiles.yahoo.com/ */
+/* You'll have to do urlencoding yourself, but see yahoo_httplib.h first */
+ const char *yahoo_get_profile_url(void);
+
+ void yahoo_buddyicon_request(int id, const char *who);
+
+#include "yahoo_httplib.h"
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/protocols/yahoo/yahoo2_callbacks.h b/protocols/yahoo/yahoo2_callbacks.h
new file mode 100644
index 00000000..0dccf188
--- /dev/null
+++ b/protocols/yahoo/yahoo2_callbacks.h
@@ -0,0 +1,783 @@
+/*
+ * libyahoo2: yahoo2_callbacks.h
+ *
+ * Copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+/*
+ * The functions in this file *must* be defined in your client program
+ * If you want to use a callback structure instead of direct functions,
+ * then you must define USE_STRUCT_CALLBACKS in all files that #include
+ * this one.
+ *
+ * Register the callback structure by calling yahoo_register_callbacks -
+ * declared in this file and defined in libyahoo2.c
+ */
+
+#ifndef YAHOO2_CALLBACKS_H
+#define YAHOO2_CALLBACKS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "yahoo2_types.h"
+
+/*
+ * yahoo2_callbacks.h
+ *
+ * Callback interface for libyahoo2
+ */
+
+ typedef enum {
+ YAHOO_INPUT_READ = 1 << 0,
+ YAHOO_INPUT_WRITE = 1 << 1,
+ YAHOO_INPUT_EXCEPTION = 1 << 2
+ } yahoo_input_condition;
+
+/*
+ * A callback function called when an asynchronous connect completes.
+ *
+ * Params:
+ * fd - The file descriptor object that has been connected, or NULL on
+ * error
+ * error - The value of errno set by the call to connect or 0 if no error
+ * Set both fd and error to 0 if the connect was cancelled by the
+ * user
+ * callback_data - the callback_data passed to the ext_yahoo_connect_async
+ * function
+ */
+ typedef void (*yahoo_connect_callback) (void *fd, int error,
+ void *callback_data);
+
+/*
+ * The following functions need to be implemented in the client
+ * interface. They will be called by the library when each
+ * event occurs.
+ */
+
+/*
+ * should we use a callback structure or directly call functions
+ * if you want the structure, you *must* define USE_STRUCT_CALLBACKS
+ * both when you compile the library, and when you compile your code
+ * that uses the library
+ */
+
+#ifdef USE_STRUCT_CALLBACKS
+#define YAHOO_CALLBACK_TYPE(x) (*x)
+struct yahoo_callbacks {
+#else
+#define YAHOO_CALLBACK_TYPE(x) x
+#endif
+
+/*
+ * Name: ext_yahoo_login_response
+ * Called when the login process is complete
+ * Params:
+ * id - the id that identifies the server connection
+ * succ - enum yahoo_login_status
+ * url - url to reactivate account if locked
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_login_response) (int id, int succ,
+ const char *url);
+
+/*
+ * Name: ext_yahoo_got_buddies
+ * Called when the contact list is got from the server
+ * Params:
+ * id - the id that identifies the server connection
+ * buds - the buddy list
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddies) (int id, YList *buds);
+
+/*
+ * Name: ext_yahoo_got_ignore
+ * Called when the ignore list is got from the server
+ * Params:
+ * id - the id that identifies the server connection
+ * igns - the ignore list
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ignore) (int id, YList *igns);
+
+/*
+ * Name: ext_yahoo_got_identities
+ * Called when the contact list is got from the server
+ * Params:
+ * id - the id that identifies the server connection
+ * ids - the identity list
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_identities) (int id, YList *ids);
+
+/*
+ * Name: ext_yahoo_got_cookies
+ * Called when the cookie list is got from the server
+ * Params:
+ * id - the id that identifies the server connection
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_cookies) (int id);
+
+/*
+ * Name: ext_yahoo_got_ping
+ * Called when the ping packet is received from the server
+ * Params:
+ * id - the id that identifies the server connection
+ * errormsg - optional error message
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ping) (int id,
+ const char *errormsg);
+
+/*
+ * Name: ext_yahoo_status_changed
+ * Called when remote user's status changes.
+ * Params:
+ * id - the id that identifies the server connection
+ * who - the handle of the remote user
+ * stat - status code (enum yahoo_status)
+ * msg - the message if stat == YAHOO_STATUS_CUSTOM
+ * away - whether the contact is away or not (YAHOO_STATUS_CUSTOM)
+ * idle - this is the number of seconds he is idle [if he is idle]
+ * mobile - this is set for mobile users/buddies
+ * TODO: add support for pager, chat, and game states
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_status_changed) (int id,
+ const char *who, int stat, const char *msg, int away, int idle,
+ int mobile);
+
+/*
+ * Name: ext_yahoo_got_buzz
+ * Called when remote user sends you a buzz.
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the identity the message was sent to
+ * who - the handle of the remote user
+ * tm - timestamp of message if offline
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buzz) (int id, const char *me,
+ const char *who, long tm);
+
+/*
+ * Name: ext_yahoo_got_im
+ * Called when remote user sends you a message.
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the identity the message was sent to
+ * who - the handle of the remote user
+ * msg - the message - NULL if stat == 2
+ * tm - timestamp of message if offline
+ * stat - message status - 0
+ * 1
+ * 2 == error sending message
+ * 5
+ * utf8 - whether the message is encoded as utf8 or not
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_im) (int id, const char *me,
+ const char *who, const char *msg, long tm, int stat, int utf8);
+
+/*
+ * Name: ext_yahoo_got_conf_invite
+ * Called when remote user sends you a conference invitation.
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the identity the invitation was sent to
+ * who - the user inviting you
+ * room - the room to join
+ * msg - the message
+ * members - the initial members of the conference (null terminated list)
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_conf_invite) (int id,
+ const char *me, const char *who, const char *room,
+ const char *msg, YList *members);
+
+/*
+ * Name: ext_yahoo_conf_userdecline
+ * Called when someone declines to join the conference.
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the identity in the conference
+ * who - the user who has declined
+ * room - the room
+ * msg - the declining message
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userdecline) (int id,
+ const char *me, const char *who, const char *room,
+ const char *msg);
+
+/*
+ * Name: ext_yahoo_conf_userjoin
+ * Called when someone joins the conference.
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the identity in the conference
+ * who - the user who has joined
+ * room - the room joined
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userjoin) (int id,
+ const char *me, const char *who, const char *room);
+
+/*
+ * Name: ext_yahoo_conf_userleave
+ * Called when someone leaves the conference.
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the identity in the conference
+ * who - the user who has left
+ * room - the room left
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userleave) (int id,
+ const char *me, const char *who, const char *room);
+
+/*
+ * Name: ext_yahoo_chat_cat_xml
+ * Called when ?
+ * Params:
+ * id - the id that identifies the server connection
+ * xml - ?
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_cat_xml) (int id,
+ const char *xml);
+
+/*
+ * Name: ext_yahoo_chat_join
+ * Called when joining the chatroom.
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the identity in the chatroom
+ * room - the room joined, used in all other chat calls, freed by
+ * library after call
+ * topic - the topic of the room, freed by library after call
+ * members - the initial members of the chatroom (null terminated YList
+ * of yahoo_chat_member's) Must be freed by the client
+ * fd - the object where the connection is coming from (for tracking)
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_join) (int id, const char *me,
+ const char *room, const char *topic, YList *members, void *fd);
+
+/*
+ * Name: ext_yahoo_chat_userjoin
+ * Called when someone joins the chatroom.
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the identity in the chatroom
+ * room - the room joined
+ * who - the user who has joined, Must be freed by the client
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userjoin) (int id,
+ const char *me, const char *room,
+ struct yahoo_chat_member *who);
+
+/*
+ * Name: ext_yahoo_chat_userleave
+ * Called when someone leaves the chatroom.
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the identity in the chatroom
+ * room - the room left
+ * who - the user who has left (Just the User ID)
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userleave) (int id,
+ const char *me, const char *room, const char *who);
+
+/*
+ * Name: ext_yahoo_chat_message
+ * Called when someone messages in the chatroom.
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the identity in the chatroom
+ * room - the room
+ * who - the user who messaged (Just the user id)
+ * msg - the message
+ * msgtype - 1 = Normal message
+ * 2 = /me type message
+ * utf8 - whether the message is utf8 encoded or not
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_message) (int id,
+ const char *me, const char *who, const char *room,
+ const char *msg, int msgtype, int utf8);
+
+/*
+ *
+ * Name: ext_yahoo_chat_yahoologout
+ * called when yahoo disconnects your chat session
+ * Note this is called whenver a disconnect happens, client or server
+ * requested. Care should be taken to make sure you know the origin
+ * of the disconnect request before doing anything here (auto-join's etc)
+ * Params:
+ * id - the id that identifies this connection
+ * me - the identity in the chatroom
+ * Returns:
+ * nothing.
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahoologout) (int id,
+ const char *me);
+
+/*
+ *
+ * Name: ext_yahoo_chat_yahooerror
+ * called when yahoo sends back an error to you
+ * Note this is called whenver chat message is sent into a room
+ * in error (fd not connected, room doesn't exists etc)
+ * Care should be taken to make sure you know the origin
+ * of the error before doing anything about it.
+ * Params:
+ * id - the id that identifies this connection
+ * me - the identity in the chatroom
+ * Returns:
+ * nothing.
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahooerror) (int id,
+ const char *me);
+
+/*
+ * Name: ext_yahoo_conf_message
+ * Called when someone messages in the conference.
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the identity the conf message was sent to
+ * who - the user who messaged
+ * room - the room
+ * msg - the message
+ * utf8 - whether the message is utf8 encoded or not
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_message) (int id,
+ const char *me, const char *who, const char *room,
+ const char *msg, int utf8);
+
+/*
+ * Name: ext_yahoo_got_file
+ * Called when someone sends you a file
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the identity the file was sent to
+ * who - the user who sent the file
+ * msg - the message
+ * fname- the file name if direct transfer
+ * fsize- the file size if direct transfer
+ * trid - transfer id. Unique for this transfer
+ *
+ * NOTE: Subsequent callbacks for file transfer do not send all of this
+ * information again since it is wasteful. Implementations are expected to
+ * save this information and supply it as callback data when the file or
+ * confirmation is sent
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_file) (int id, const char *me,
+ const char *who, const char *msg, const char *fname,
+ unsigned long fesize, char *trid);
+
+/*
+ * Name: ext_yahoo_got_ft_data
+ * Called multiple times when parts of the file are received
+ * Params:
+ * id - the id that identifies the server connection
+ * in - The data
+ * len - Length of the data
+ * data - callback data
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ft_data) (int id,
+ const unsigned char *in, int len, void *data);
+
+/*
+ * Name: ext_yahoo_file_transfer_done
+ * File transfer is done
+ * Params:
+ * id - the id that identifies the server connection
+ * result - To notify if it finished successfully or with a failure
+ * data - callback data
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_file_transfer_done) (int id,
+ int result, void *data);
+
+/*
+ * Name: ext_yahoo_contact_added
+ * Called when a contact is added to your list
+ * Params:
+ * id - the id that identifies the server connection
+ * myid - the identity he was added to
+ * who - who was added
+ * msg - any message sent
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_contact_added) (int id,
+ const char *myid, const char *who, const char *msg);
+
+/*
+ * Name: ext_yahoo_rejected
+ * Called when a contact rejects your add
+ * Params:
+ * id - the id that identifies the server connection
+ * who - who rejected you
+ * msg - any message sent
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_rejected) (int id, const char *who,
+ const char *msg);
+
+/*
+ * Name: ext_yahoo_typing_notify
+ * Called when remote user starts or stops typing.
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the handle of the identity the notification is sent to
+ * who - the handle of the remote user
+ * stat - 1 if typing, 0 if stopped typing
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_typing_notify) (int id,
+ const char *me, const char *who, int stat);
+
+/*
+ * Name: ext_yahoo_game_notify
+ * Called when remote user starts or stops a game.
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the handle of the identity the notification is sent to
+ * who - the handle of the remote user
+ * stat - 1 if game, 0 if stopped gaming
+ * msg - game description and/or other text
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_game_notify) (int id, const char *me,
+ const char *who, int stat, const char *msg);
+
+/*
+ * Name: ext_yahoo_mail_notify
+ * Called when you receive mail, or with number of messages
+ * Params:
+ * id - the id that identifies the server connection
+ * from - who the mail is from - NULL if only mail count
+ * subj - the subject of the mail - NULL if only mail count
+ * cnt - mail count - 0 if new mail notification
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_mail_notify) (int id,
+ const char *from, const char *subj, int cnt);
+
+/*
+ * Name: ext_yahoo_system_message
+ * System message
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the handle of the identity the notification is sent to
+ * who - the source of the system message (there are different types)
+ * msg - the message
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_system_message) (int id,
+ const char *me, const char *who, const char *msg);
+
+/*
+ * Name: ext_yahoo_got_buddyicon
+ * Buddy icon received
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the handle of the identity the notification is sent to
+ * who - the person the buddy icon is for
+ * url - the url to use to load the icon
+ * checksum - the checksum of the icon content
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon) (int id,
+ const char *me, const char *who, const char *url, int checksum);
+
+/*
+ * Name: ext_yahoo_got_buddyicon_checksum
+ * Buddy icon checksum received
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the handle of the identity the notification is sent to
+ * who - the yahoo id of the buddy icon checksum is for
+ * checksum - the checksum of the icon content
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_checksum) (int id,
+ const char *me, const char *who, int checksum);
+
+/*
+ * Name: ext_yahoo_got_buddyicon_request
+ * Buddy icon request received
+ * Params:
+ * id - the id that identifies the server connection
+ * me - the handle of the identity the notification is sent to
+ * who - the yahoo id of the buddy that requested the buddy icon
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_request) (int id,
+ const char *me, const char *who);
+
+/*
+ * Name: ext_yahoo_got_buddyicon_request
+ * Buddy icon request received
+ * Params:
+ * id - the id that identifies the server connection
+ * url - remote url, the uploaded buddy icon can be fetched from
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_buddyicon_uploaded) (int id,
+ const char *url);
+
+/*
+ * Name: ext_yahoo_got_webcam_image
+ * Called when you get a webcam update
+ * An update can either be receiving an image, a part of an image or
+ * just an update with a timestamp
+ * Params:
+ * id - the id that identifies the server connection
+ * who - the user who's webcam we're viewing
+ * image - image data
+ * image_size - length of the image in bytes
+ * real_size - actual length of image data
+ * timestamp - milliseconds since the webcam started
+ *
+ * If the real_size is smaller then the image_size then only part of
+ * the image has been read. This function will keep being called till
+ * the total amount of bytes in image_size has been read. The image
+ * received is in JPEG-2000 Code Stream Syntax (ISO/IEC 15444-1).
+ * The size of the image will be either 160x120 or 320x240.
+ * Each webcam image contains a timestamp. This timestamp should be
+ * used to keep the image in sync since some images can take longer
+ * to transport then others. When image_size is 0 we can still receive
+ * a timestamp to stay in sync
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_webcam_image) (int id,
+ const char *who, const unsigned char *image,
+ unsigned int image_size, unsigned int real_size,
+ unsigned int timestamp);
+
+/*
+ * Name: ext_yahoo_webcam_invite
+ * Called when you get a webcam invitation
+ * Params:
+ * id - the id that identifies the server connection
+ * me - identity the invitation is to
+ * from - who the invitation is from
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite) (int id,
+ const char *me, const char *from);
+
+/*
+ * Name: ext_yahoo_webcam_invite_reply
+ * Called when you get a response to a webcam invitation
+ * Params:
+ * id - the id that identifies the server connection
+ * me - identity the invitation response is to
+ * from - who the invitation response is from
+ * accept - 0 (decline), 1 (accept)
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite_reply) (int id,
+ const char *me, const char *from, int accept);
+
+/*
+ * Name: ext_yahoo_webcam_closed
+ * Called when the webcam connection closed
+ * Params:
+ * id - the id that identifies the server connection
+ * who - the user who we where connected to
+ * reason - reason why the connection closed
+ * 1 = user stopped broadcasting
+ * 2 = user cancelled viewing permission
+ * 3 = user declines permission
+ * 4 = user does not have webcam online
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_closed) (int id,
+ const char *who, int reason);
+
+/*
+ * Name: ext_yahoo_got_search_result
+ * Called when the search result received from server
+ * Params:
+ * id - the id that identifies the server connection
+ * found - total number of results returned in the current result set
+ * start - offset from where the current result set starts
+ * total - total number of results available (start + found <= total)
+ * contacts - the list of results as a YList of yahoo_found_contact
+ * these will be freed after this function returns, so
+ * if you need to use the information, make a copy
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_search_result) (int id,
+ int found, int start, int total, YList *contacts);
+
+/*
+ * Name: ext_yahoo_error
+ * Called on error.
+ * Params:
+ * id - the id that identifies the server connection
+ * err - the error message
+ * fatal- whether this error is fatal to the connection or not
+ * num - Which error is this
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_error) (int id, const char *err,
+ int fatal, int num);
+
+/*
+ * Name: ext_yahoo_webcam_viewer
+ * Called when a viewer disconnects/connects/requests to connect
+ * Params:
+ * id - the id that identifies the server connection
+ * who - the viewer
+ * connect - 0=disconnect 1=connect 2=request
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_viewer) (int id,
+ const char *who, int connect);
+
+/*
+ * Name: ext_yahoo_webcam_data_request
+ * Called when you get a request for webcam images
+ * Params:
+ * id - the id that identifies the server connection
+ * send - whether to send images or not
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_data_request) (int id,
+ int send);
+
+/*
+ * Name: ext_yahoo_log
+ * Called to log a message.
+ * Params:
+ * fmt - the printf formatted message
+ * Returns:
+ * 0
+ */
+ int YAHOO_CALLBACK_TYPE(ext_yahoo_log) (const char *fmt, ...);
+
+/*
+ * Name: ext_yahoo_add_handler
+ * Add a listener for the fd. Must call yahoo_read_ready
+ * when a YAHOO_INPUT_READ fd is ready and yahoo_write_ready
+ * when a YAHOO_INPUT_WRITE fd is ready.
+ * Params:
+ * id - the id that identifies the server connection
+ * fd - the fd object on which to listen
+ * cond - the condition on which to call the callback
+ * data - callback data to pass to yahoo_*_ready
+ *
+ * Returns: a tag to be used when removing the handler
+ */
+ int YAHOO_CALLBACK_TYPE(ext_yahoo_add_handler) (int id, void *fd,
+ yahoo_input_condition cond, void *data);
+
+/*
+ * Name: ext_yahoo_remove_handler
+ * Remove the listener for the fd.
+ * Params:
+ * id - the id that identifies the connection
+ * tag - the handler tag to remove
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_remove_handler) (int id, int tag);
+
+/*
+ * Name: ext_yahoo_connect
+ * Connect to a host:port
+ * Params:
+ * host - the host to connect to
+ * port - the port to connect on
+ * Returns:
+ * a unix file descriptor to the socket
+ */
+ int YAHOO_CALLBACK_TYPE(ext_yahoo_connect) (const char *host, int port);
+
+/*
+ * Name: ext_yahoo_connect_async
+ * Connect to a host:port asynchronously. This function should return
+ * immediately returing a tag used to identify the connection handler,
+ * or a pre-connect error (eg: host name lookup failure).
+ * Once the connect completes (successfully or unsuccessfully), callback
+ * should be called (see the signature for yahoo_connect_callback).
+ * The callback may safely be called before this function returns, but
+ * it should not be called twice.
+ * Params:
+ * id - the id that identifies this connection
+ * host - the host to connect to
+ * port - the port to connect on
+ * callback - function to call when connect completes
+ * callback_data - data to pass to the callback function
+ * use_ssl - Whether we need an SSL connection
+ * Returns:
+ * a tag signifying the connection attempt
+ */
+ int YAHOO_CALLBACK_TYPE(ext_yahoo_connect_async) (int id,
+ const char *host, int port, yahoo_connect_callback callback,
+ void *callback_data, int use_ssl);
+
+/*
+ * Name: ext_yahoo_get_ip_addr
+ * get IP Address for a domain name
+ * Params:
+ * domain - Domain name
+ * Returns:
+ * Newly allocated string containing the IP Address in IPv4 notation
+ */
+ char *YAHOO_CALLBACK_TYPE(ext_yahoo_get_ip_addr) (const char *domain);
+
+/*
+ * Name: ext_yahoo_write
+ * Write data from the buffer into the socket for the specified connection
+ * Params:
+ * fd - the file descriptor object that identifies this connection
+ * buf - Buffer to write the data from
+ * len - Length of the data
+ * Returns:
+ * Number of bytes written or -1 for error
+ */
+ int YAHOO_CALLBACK_TYPE(ext_yahoo_write) (void *fd, char *buf, int len);
+
+/*
+ * Name: ext_yahoo_read
+ * Read data into a buffer from socket for the specified connection
+ * Params:
+ * fd - the file descriptor object that identifies this connection
+ * buf - Buffer to read the data into
+ * len - Max length to read
+ * Returns:
+ * Number of bytes read or -1 for error
+ */
+ int YAHOO_CALLBACK_TYPE(ext_yahoo_read) (void *fd, char *buf, int len);
+
+/*
+ * Name: ext_yahoo_close
+ * Close the file descriptor object and free its resources. Libyahoo2 will not
+ * use this object again.
+ * Params:
+ * fd - the file descriptor object that identifies this connection
+ * Returns:
+ * Nothing
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_close) (void *fd);
+
+/*
+ * Name: ext_yahoo_got_buddy_change_group
+ * Acknowledgement of buddy changing group
+ * Params:
+ * id: client id
+ * me: The user
+ * who: Buddy name
+ * old_group: Old group name
+ * new_group: New group name
+ * Returns:
+ * Nothing
+ */
+ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddy_change_group) (int id,
+ const char *me, const char *who, const char *old_group,
+ const char *new_group);
+
+#ifdef USE_STRUCT_CALLBACKS
+};
+
+/*
+ * if using a callback structure, call yahoo_register_callbacks
+ * before doing anything else
+ */
+void yahoo_register_callbacks(struct yahoo_callbacks *tyc);
+
+#undef YAHOO_CALLBACK_TYPE
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/protocols/yahoo/yahoo2_types.h b/protocols/yahoo/yahoo2_types.h
new file mode 100644
index 00000000..bbade5d8
--- /dev/null
+++ b/protocols/yahoo/yahoo2_types.h
@@ -0,0 +1,396 @@
+/*
+ * libyahoo2: yahoo2_types.h
+ *
+ * Copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef YAHOO2_TYPES_H
+#define YAHOO2_TYPES_H
+
+#include "yahoo_list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ enum yahoo_service { /* these are easier to see in hex */
+ YAHOO_SERVICE_LOGON = 1,
+ YAHOO_SERVICE_LOGOFF,
+ YAHOO_SERVICE_ISAWAY,
+ YAHOO_SERVICE_ISBACK,
+ YAHOO_SERVICE_IDLE, /* 5 (placemarker) */
+ YAHOO_SERVICE_MESSAGE,
+ YAHOO_SERVICE_IDACT,
+ YAHOO_SERVICE_IDDEACT,
+ YAHOO_SERVICE_MAILSTAT,
+ YAHOO_SERVICE_USERSTAT, /* 0xa */
+ YAHOO_SERVICE_NEWMAIL,
+ YAHOO_SERVICE_CHATINVITE,
+ YAHOO_SERVICE_CALENDAR,
+ YAHOO_SERVICE_NEWPERSONALMAIL,
+ YAHOO_SERVICE_NEWCONTACT,
+ YAHOO_SERVICE_ADDIDENT, /* 0x10 */
+ YAHOO_SERVICE_ADDIGNORE,
+ YAHOO_SERVICE_PING,
+ YAHOO_SERVICE_GOTGROUPRENAME, /* < 1, 36(old), 37(new) */
+ YAHOO_SERVICE_SYSMESSAGE = 0x14,
+ YAHOO_SERVICE_SKINNAME = 0x15,
+ YAHOO_SERVICE_PASSTHROUGH2 = 0x16,
+ YAHOO_SERVICE_CONFINVITE = 0x18,
+ YAHOO_SERVICE_CONFLOGON,
+ YAHOO_SERVICE_CONFDECLINE,
+ YAHOO_SERVICE_CONFLOGOFF,
+ YAHOO_SERVICE_CONFADDINVITE,
+ YAHOO_SERVICE_CONFMSG,
+ YAHOO_SERVICE_CHATLOGON,
+ YAHOO_SERVICE_CHATLOGOFF,
+ YAHOO_SERVICE_CHATMSG = 0x20,
+ YAHOO_SERVICE_GAMELOGON = 0x28,
+ YAHOO_SERVICE_GAMELOGOFF,
+ YAHOO_SERVICE_GAMEMSG = 0x2a,
+ YAHOO_SERVICE_FILETRANSFER = 0x46,
+ YAHOO_SERVICE_VOICECHAT = 0x4A,
+ YAHOO_SERVICE_NOTIFY,
+ YAHOO_SERVICE_VERIFY,
+ YAHOO_SERVICE_P2PFILEXFER,
+ YAHOO_SERVICE_PEERTOPEER = 0x4F, /* Checks if P2P possible */
+ YAHOO_SERVICE_WEBCAM,
+ YAHOO_SERVICE_AUTHRESP = 0x54,
+ YAHOO_SERVICE_LIST,
+ YAHOO_SERVICE_AUTH = 0x57,
+ YAHOO_SERVICE_AUTHBUDDY = 0x6d,
+ YAHOO_SERVICE_ADDBUDDY = 0x83,
+ YAHOO_SERVICE_REMBUDDY,
+ YAHOO_SERVICE_IGNORECONTACT, /* > 1, 7, 13 < 1, 66, 13, 0 */
+ YAHOO_SERVICE_REJECTCONTACT,
+ YAHOO_SERVICE_GROUPRENAME = 0x89, /* > 1, 65(new), 66(0), 67(old) */
+ YAHOO_SERVICE_Y7_PING = 0x8A,
+ YAHOO_SERVICE_CHATONLINE = 0x96, /* > 109(id), 1, 6(abcde) < 0,1 */
+ YAHOO_SERVICE_CHATGOTO,
+ YAHOO_SERVICE_CHATJOIN, /* > 1 104-room 129-1600326591 62-2 */
+ YAHOO_SERVICE_CHATLEAVE,
+ YAHOO_SERVICE_CHATEXIT = 0x9b,
+ YAHOO_SERVICE_CHATADDINVITE = 0x9d,
+ YAHOO_SERVICE_CHATLOGOUT = 0xa0,
+ YAHOO_SERVICE_CHATPING,
+ YAHOO_SERVICE_COMMENT = 0xa8,
+ YAHOO_SERVICE_GAME_INVITE = 0xb7,
+ YAHOO_SERVICE_STEALTH_PERM = 0xb9,
+ YAHOO_SERVICE_STEALTH_SESSION = 0xba,
+ YAHOO_SERVICE_AVATAR = 0xbc,
+ YAHOO_SERVICE_PICTURE_CHECKSUM = 0xbd,
+ YAHOO_SERVICE_PICTURE = 0xbe,
+ YAHOO_SERVICE_PICTURE_UPDATE = 0xc1,
+ YAHOO_SERVICE_PICTURE_UPLOAD = 0xc2,
+ YAHOO_SERVICE_YAB_UPDATE = 0xc4,
+ YAHOO_SERVICE_Y6_VISIBLE_TOGGLE = 0xc5, /* YMSG13, key 13: 2 = invisible, 1 = visible */
+ YAHOO_SERVICE_Y6_STATUS_UPDATE = 0xc6, /* YMSG13 */
+ YAHOO_SERVICE_PICTURE_STATUS = 0xc7, /* YMSG13, key 213: 0 = none, 1 = avatar, 2 = picture */
+ YAHOO_SERVICE_VERIFY_ID_EXISTS = 0xc8,
+ YAHOO_SERVICE_AUDIBLE = 0xd0,
+ YAHOO_SERVICE_Y7_PHOTO_SHARING = 0xd2,
+ YAHOO_SERVICE_Y7_CONTACT_DETAILS = 0xd3, /* YMSG13 */
+ YAHOO_SERVICE_Y7_CHAT_SESSION = 0xd4,
+ YAHOO_SERVICE_Y7_AUTHORIZATION = 0xd6, /* YMSG13 */
+ YAHOO_SERVICE_Y7_FILETRANSFER = 0xdc, /* YMSG13 */
+ YAHOO_SERVICE_Y7_FILETRANSFERINFO, /* YMSG13 */
+ YAHOO_SERVICE_Y7_FILETRANSFERACCEPT, /* YMSG13 */
+ YAHOO_SERVICE_Y7_MINGLE = 0xe1, /* YMSG13 */
+ YAHOO_SERVICE_Y7_CHANGE_GROUP = 0xe7, /* YMSG13 */
+ YAHOO_SERVICE_MYSTERY = 0xef, /* Don't know what this is for */
+ YAHOO_SERVICE_Y8_STATUS = 0xf0, /* YMSG15 */
+ YAHOO_SERVICE_Y8_LIST = 0Xf1, /* YMSG15 */
+ YAHOO_SERVICE_MESSAGE_CONFIRM = 0xfb,
+ YAHOO_SERVICE_WEBLOGIN = 0x0226,
+ YAHOO_SERVICE_SMS_MSG = 0x02ea
+ };
+
+ enum yahoo_status {
+ YAHOO_STATUS_AVAILABLE = 0,
+ YAHOO_STATUS_BRB,
+ YAHOO_STATUS_BUSY,
+ YAHOO_STATUS_NOTATHOME,
+ YAHOO_STATUS_NOTATDESK,
+ YAHOO_STATUS_NOTINOFFICE,
+ YAHOO_STATUS_ONPHONE,
+ YAHOO_STATUS_ONVACATION,
+ YAHOO_STATUS_OUTTOLUNCH,
+ YAHOO_STATUS_STEPPEDOUT,
+ YAHOO_STATUS_INVISIBLE = 12,
+ YAHOO_STATUS_CUSTOM = 99,
+ YAHOO_STATUS_IDLE = 999,
+ YAHOO_STATUS_OFFLINE = 0x5a55aa56 /* don't ask */
+ };
+
+ enum ypacket_status {
+ YPACKET_STATUS_DISCONNECTED = -1,
+ YPACKET_STATUS_DEFAULT = 0,
+ YPACKET_STATUS_SERVERACK = 1,
+ YPACKET_STATUS_GAME = 0x2,
+ YPACKET_STATUS_AWAY = 0x4,
+ YPACKET_STATUS_CONTINUED = 0x5,
+ YPACKET_STATUS_INVISIBLE = 12,
+ YPACKET_STATUS_NOTIFY = 0x16, /* TYPING */
+ YPACKET_STATUS_WEBLOGIN = 0x5a55aa55,
+ YPACKET_STATUS_OFFLINE = 0x5a55aa56
+ };
+
+#define YAHOO_STATUS_GAME 0x2 /* Games don't fit into the regular status model */
+
+ enum yahoo_login_status {
+ YAHOO_LOGIN_OK = 0,
+ YAHOO_LOGIN_LOGOFF = 1,
+ YAHOO_LOGIN_UNAME = 3,
+ YAHOO_LOGIN_PASSWD = 13,
+ YAHOO_LOGIN_LOCK = 14,
+ YAHOO_LOGIN_DUPL = 99,
+ YAHOO_LOGIN_SOCK = -1,
+ YAHOO_LOGIN_UNKNOWN = 999
+ };
+
+ enum yahoo_error {
+ E_UNKNOWN = -1,
+ E_CONNECTION = -2,
+ E_SYSTEM = -3,
+ E_CUSTOM = 0,
+
+ /* responses from ignore buddy */
+ E_IGNOREDUP = 2,
+ E_IGNORENONE = 3,
+ E_IGNORECONF = 12,
+
+ /* conference */
+ E_CONFNOTAVAIL = 20
+ };
+
+ enum yahoo_log_level {
+ YAHOO_LOG_NONE = 0,
+ YAHOO_LOG_FATAL,
+ YAHOO_LOG_ERR,
+ YAHOO_LOG_WARNING,
+ YAHOO_LOG_NOTICE,
+ YAHOO_LOG_INFO,
+ YAHOO_LOG_DEBUG
+ };
+
+ enum yahoo_file_transfer {
+ YAHOO_FILE_TRANSFER_INIT = 1,
+ YAHOO_FILE_TRANSFER_ACCEPT = 3,
+ YAHOO_FILE_TRANSFER_REJECT = 4,
+ YAHOO_FILE_TRANSFER_DONE = 5,
+ YAHOO_FILE_TRANSFER_RELAY,
+ YAHOO_FILE_TRANSFER_FAILED,
+ YAHOO_FILE_TRANSFER_UNKNOWN
+ };
+
+#define YAHOO_PROTO_VER 0x0010
+
+/* Yahoo style/color directives */
+#define YAHOO_COLOR_BLACK "\033[30m"
+#define YAHOO_COLOR_BLUE "\033[31m"
+#define YAHOO_COLOR_LIGHTBLUE "\033[32m"
+#define YAHOO_COLOR_GRAY "\033[33m"
+#define YAHOO_COLOR_GREEN "\033[34m"
+#define YAHOO_COLOR_PINK "\033[35m"
+#define YAHOO_COLOR_PURPLE "\033[36m"
+#define YAHOO_COLOR_ORANGE "\033[37m"
+#define YAHOO_COLOR_RED "\033[38m"
+#define YAHOO_COLOR_OLIVE "\033[39m"
+#define YAHOO_COLOR_ANY "\033[#"
+#define YAHOO_STYLE_ITALICON "\033[2m"
+#define YAHOO_STYLE_ITALICOFF "\033[x2m"
+#define YAHOO_STYLE_BOLDON "\033[1m"
+#define YAHOO_STYLE_BOLDOFF "\033[x1m"
+#define YAHOO_STYLE_UNDERLINEON "\033[4m"
+#define YAHOO_STYLE_UNDERLINEOFF "\033[x4m"
+#define YAHOO_STYLE_URLON "\033[lm"
+#define YAHOO_STYLE_URLOFF "\033[xlm"
+
+ enum yahoo_connection_type {
+ YAHOO_CONNECTION_PAGER = 0,
+ YAHOO_CONNECTION_FT,
+ YAHOO_CONNECTION_YAB,
+ YAHOO_CONNECTION_WEBCAM_MASTER,
+ YAHOO_CONNECTION_WEBCAM,
+ YAHOO_CONNECTION_CHATCAT,
+ YAHOO_CONNECTION_SEARCH,
+ YAHOO_CONNECTION_AUTH
+ };
+
+ enum yahoo_webcam_direction_type {
+ YAHOO_WEBCAM_DOWNLOAD = 0,
+ YAHOO_WEBCAM_UPLOAD
+ };
+
+ enum yahoo_stealth_visibility_type {
+ YAHOO_STEALTH_DEFAULT = 0,
+ YAHOO_STEALTH_ONLINE,
+ YAHOO_STEALTH_PERM_OFFLINE
+ };
+
+/* chat member attribs */
+#define YAHOO_CHAT_MALE 0x8000
+#define YAHOO_CHAT_FEMALE 0x10000
+#define YAHOO_CHAT_FEMALE 0x10000
+#define YAHOO_CHAT_DUNNO 0x400
+#define YAHOO_CHAT_WEBCAM 0x10
+
+ enum yahoo_webcam_conn_type { Y_WCM_DIALUP, Y_WCM_DSL, Y_WCM_T1 };
+
+ struct yahoo_webcam {
+ int direction; /* Uploading or downloading */
+ int conn_type; /* 0=Dialup, 1=DSL/Cable, 2=T1/Lan */
+
+ char *user; /* user we are viewing */
+ char *server; /* webcam server to connect to */
+ int port; /* webcam port to connect on */
+ char *key; /* key to connect to the server with */
+ char *description; /* webcam description */
+ char *my_ip; /* own ip number */
+ };
+
+ struct yahoo_webcam_data {
+ unsigned int data_size;
+ unsigned int to_read;
+ unsigned int timestamp;
+ unsigned char packet_type;
+ };
+
+ struct yahoo_data {
+ char *user;
+ char *password;
+
+ char *cookie_y;
+ char *cookie_t;
+ char *cookie_c;
+ char *cookie_b;
+ char *login_cookie;
+ char *crumb;
+ char *seed;
+
+ YList *buddies;
+ YList *ignore;
+ YList *identities;
+ char *login_id;
+
+ int current_status;
+ int initial_status;
+ int logged_in;
+
+ int session_id;
+
+ int client_id;
+
+ char *rawbuddylist;
+ char *ignorelist;
+
+ void *server_settings;
+
+ struct yahoo_process_status_entry *half_user;
+ };
+
+ struct yab {
+ int yid;
+ char *id;
+ char *fname;
+ char *lname;
+ char *nname;
+ char *email;
+ char *hphone;
+ char *wphone;
+ char *mphone;
+ int dbid;
+ };
+
+ struct yahoo_buddy {
+ char *group;
+ char *id;
+ char *real_name;
+ struct yab *yab_entry;
+ };
+
+ enum yahoo_search_type {
+ YAHOO_SEARCH_KEYWORD = 0,
+ YAHOO_SEARCH_YID,
+ YAHOO_SEARCH_NAME
+ };
+
+ enum yahoo_search_gender {
+ YAHOO_GENDER_NONE = 0,
+ YAHOO_GENDER_MALE,
+ YAHOO_GENDER_FEMALE
+ };
+
+ enum yahoo_search_agerange {
+ YAHOO_AGERANGE_NONE = 0
+ };
+
+ struct yahoo_found_contact {
+ char *id;
+ char *gender;
+ char *location;
+ int age;
+ int online;
+ };
+
+/*
+ * Function pointer to be passed to http get/post and send file
+ */
+ typedef void (*yahoo_get_fd_callback) (int id, void *fd, int error,
+ void *data);
+
+/*
+ * Function pointer to be passed to yahoo_get_url_handle
+ */
+ typedef void (*yahoo_get_url_handle_callback) (int id, void *fd,
+ int error, const char *filename, unsigned long size,
+ void *data);
+
+ struct yahoo_chat_member {
+ char *id;
+ int age;
+ int attribs;
+ char *alias;
+ char *location;
+ };
+
+ struct yahoo_process_status_entry {
+ char *name; /* 7 name */
+ int state; /* 10 state */
+ int flags; /* 13 flags, bit 0 = pager, bit 1 = chat, bit 2 = game */
+ int mobile; /* 60 mobile */
+ char *msg; /* 19 custom status message */
+ int away; /* 47 away (or invisible) */
+ int buddy_session; /* 11 state */
+ int f17; /* 17 in chat? then what about flags? */
+ int idle; /* 137 seconds idle */
+ int f138; /* 138 state */
+ char *f184; /* 184 state */
+ int f192; /* 192 state */
+ int f10001; /* 10001 state */
+ int f10002; /* 10002 state */
+ int f198; /* 198 state */
+ char *f197; /* 197 state */
+ char *f205; /* 205 state */
+ int f213; /* 213 state */
+ };
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/protocols/yahoo/yahoo_debug.h b/protocols/yahoo/yahoo_debug.h
new file mode 100644
index 00000000..59d92901
--- /dev/null
+++ b/protocols/yahoo/yahoo_debug.h
@@ -0,0 +1,38 @@
+/*
+ * libyahoo2: yahoo_debug.h
+ *
+ * Copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+extern int yahoo_log_message(char *fmt, ...);
+
+#define NOTICE(x) if(yahoo_get_log_level() >= YAHOO_LOG_NOTICE) { yahoo_log_message x; yahoo_log_message("\n"); }
+
+#define LOG(x) if(yahoo_get_log_level() >= YAHOO_LOG_INFO) { yahoo_log_message("%s:%d: ", __FILE__, __LINE__); \
+ yahoo_log_message x; \
+ yahoo_log_message("\n"); }
+
+#define WARNING(x) if(yahoo_get_log_level() >= YAHOO_LOG_WARNING) { yahoo_log_message("%s:%d: warning: ", __FILE__, __LINE__); \
+ yahoo_log_message x; \
+ yahoo_log_message("\n"); }
+
+#define DEBUG_MSG(x) if(yahoo_get_log_level() >= YAHOO_LOG_DEBUG) { yahoo_log_message("%s:%d: debug: ", __FILE__, __LINE__); \
+ yahoo_log_message x; \
+ yahoo_log_message("\n"); }
+
+
diff --git a/protocols/yahoo/yahoo_fn.c b/protocols/yahoo/yahoo_fn.c
new file mode 100644
index 00000000..9544999d
--- /dev/null
+++ b/protocols/yahoo/yahoo_fn.c
@@ -0,0 +1,4622 @@
+/*
+ * libyahoo2 - originally from gaim patches by Amatus
+ *
+ * Copyright (C) 2003-2004
+ *
+ * Some code copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
+ * libfaim code copyright 1998, 1999 Adam Fritzler <afritz@auk.cx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "yahoo_fn.h"
+
+static const unsigned char table_0[256] = {
+ 0x5A, 0x41, 0x11, 0x77, 0x29, 0x9C, 0x31, 0xAD,
+ 0x4A, 0x32, 0x1A, 0x6D, 0x56, 0x9F, 0x39, 0xA6,
+ 0x0C, 0xE8, 0x49, 0x40, 0xA4, 0x21, 0xE9, 0x01,
+ 0x91, 0x86, 0x2F, 0xB9, 0xED, 0x80, 0x51, 0xAB,
+ 0x7F, 0x92, 0xF2, 0x73, 0xCD, 0xD9, 0x75, 0x2A,
+ 0x70, 0x34, 0x35, 0x8D, 0xA8, 0x72, 0x7D, 0x9B,
+ 0x2E, 0xC5, 0x2D, 0x76, 0x1E, 0xBB, 0xE7, 0x37,
+ 0xBA, 0xB7, 0xB2, 0x03, 0x20, 0x17, 0x8A, 0x07,
+ 0xD6, 0x96, 0x13, 0x95, 0xE5, 0xF1, 0x18, 0x3B,
+ 0xA5, 0x62, 0x33, 0xC1, 0x44, 0x3D, 0x6C, 0xA7,
+ 0xBF, 0x1C, 0x60, 0xFF, 0x5B, 0xF5, 0x8E, 0xE6,
+ 0x5C, 0xCC, 0xF7, 0x69, 0x15, 0x0F, 0x0B, 0xBD,
+ 0x12, 0x9D, 0xB3, 0x65, 0x53, 0xB1, 0x14, 0xF4,
+ 0x19, 0x3E, 0xB6, 0x45, 0xCB, 0xA2, 0x7A, 0xD3,
+ 0xF8, 0xD1, 0x61, 0xEE, 0xBC, 0xC6, 0xB0, 0x5D,
+ 0x4B, 0x09, 0x26, 0xE1, 0x1D, 0x6E, 0xC3, 0xFB,
+ 0x68, 0x4C, 0x42, 0x52, 0x5F, 0xDE, 0xFD, 0xEF,
+ 0x81, 0x04, 0x6F, 0xE0, 0xF0, 0x1F, 0x0D, 0x7C,
+ 0x58, 0x4F, 0x1B, 0x30, 0xCF, 0x9A, 0x2B, 0x05,
+ 0xF6, 0x3F, 0x78, 0xAC, 0xD8, 0xEC, 0xE2, 0x25,
+ 0x93, 0xDA, 0x84, 0x8C, 0x4E, 0xD5, 0x38, 0x0A,
+ 0x06, 0x7E, 0xD4, 0x59, 0x98, 0xE3, 0x36, 0xC2,
+ 0xD2, 0xA3, 0x10, 0x79, 0xFA, 0xC9, 0x16, 0x27,
+ 0x66, 0x89, 0xFE, 0x57, 0xF3, 0x83, 0xB8, 0x28,
+ 0x3C, 0xC7, 0xCE, 0x71, 0xC8, 0xDB, 0x22, 0xE4,
+ 0xDD, 0xDF, 0x02, 0x8F, 0x5E, 0xEB, 0x48, 0x2C,
+ 0x08, 0xC4, 0x43, 0xEA, 0x50, 0x55, 0x90, 0x54,
+ 0x87, 0xCA, 0x00, 0x24, 0x6B, 0x85, 0x97, 0xD7,
+ 0xDC, 0x6A, 0x67, 0xD0, 0x88, 0xA1, 0x9E, 0xC0,
+ 0x46, 0xAE, 0x64, 0x74, 0x4D, 0xA0, 0x99, 0xB5,
+ 0x0E, 0x8B, 0xAA, 0x3A, 0xB4, 0xFC, 0xA9, 0x94,
+ 0x7B, 0xBE, 0xF9, 0xAF, 0x82, 0x63, 0x47, 0x23 };
+
+static const unsigned char table_1[256] = {
+ 0x08, 0xCB, 0x54, 0xCF, 0x97, 0x53, 0x59, 0xF1,
+ 0x66, 0xEC, 0xDB, 0x1B, 0xB1, 0xE2, 0x36, 0xEB,
+ 0xB3, 0x8F, 0x71, 0xA8, 0x90, 0x7D, 0xDA, 0xDC,
+ 0x2C, 0x2F, 0xE8, 0x6A, 0x73, 0x37, 0xAE, 0xCC,
+ 0xA1, 0x16, 0xE6, 0xFC, 0x9C, 0xA9, 0x2A, 0x3F,
+ 0x58, 0xFD, 0x56, 0x4C, 0xA5, 0xF2, 0x33, 0x99,
+ 0x1A, 0xB7, 0xFE, 0xA6, 0x1E, 0x32, 0x9E, 0x48,
+ 0x03, 0x4A, 0x78, 0xEE, 0xCA, 0xC3, 0x88, 0x7A,
+ 0xAC, 0x23, 0xAA, 0xBD, 0xDE, 0xD3, 0x67, 0x43,
+ 0xFF, 0x64, 0x8A, 0xF9, 0x04, 0xD0, 0x7B, 0xC2,
+ 0xBC, 0xF3, 0x89, 0x0E, 0xDD, 0xAB, 0x9D, 0x84,
+ 0x5A, 0x62, 0x7F, 0x6D, 0x82, 0x68, 0xA3, 0xED,
+ 0x2E, 0x07, 0x41, 0xEF, 0x2D, 0x70, 0x4F, 0x69,
+ 0x8E, 0xE7, 0x0F, 0x11, 0x19, 0xAF, 0x31, 0xFB,
+ 0x8D, 0x4B, 0x5F, 0x96, 0x75, 0x42, 0x6C, 0x46,
+ 0xE4, 0x55, 0xD6, 0x3B, 0xE1, 0xD1, 0xB0, 0xB5,
+ 0x45, 0x29, 0xC0, 0x94, 0x9F, 0xD4, 0x15, 0x17,
+ 0x3C, 0x47, 0xC8, 0xD9, 0xC6, 0x76, 0xB9, 0x02,
+ 0xE0, 0xC9, 0xB2, 0x01, 0xC1, 0x5D, 0x4E, 0x14,
+ 0xF4, 0xAD, 0xB6, 0x00, 0x72, 0xF0, 0x49, 0x0D,
+ 0xD8, 0x5E, 0x6F, 0x2B, 0x8C, 0x51, 0x83, 0xC5,
+ 0x0A, 0x85, 0xE5, 0x38, 0x7E, 0x26, 0xEA, 0x22,
+ 0x6B, 0x06, 0xD5, 0x8B, 0xBF, 0xC7, 0x35, 0x1D,
+ 0xF6, 0x24, 0x28, 0xCE, 0x9B, 0x77, 0x20, 0x60,
+ 0xF5, 0x87, 0x3D, 0x65, 0x86, 0x0C, 0xDF, 0xBA,
+ 0x12, 0xA4, 0x3A, 0x34, 0xD7, 0xA0, 0xF8, 0x63,
+ 0x52, 0x27, 0xB8, 0x18, 0xA7, 0x13, 0x91, 0x09,
+ 0x93, 0x5C, 0x10, 0x9A, 0xB4, 0xE9, 0x44, 0xC4,
+ 0x21, 0x57, 0x1C, 0x0B, 0xA2, 0x74, 0x4D, 0xBE,
+ 0xD2, 0x1F, 0xCD, 0xE3, 0x6E, 0x7C, 0x40, 0x50,
+ 0x39, 0x80, 0x98, 0xFA, 0x25, 0x92, 0x30, 0x5B,
+ 0x05, 0x95, 0xBB, 0x79, 0x61, 0x3E, 0x81, 0xF7 };
+
+static const unsigned char table_2[32] = {
+ 0x19, 0x05, 0x09, 0x1C, 0x0B, 0x1A, 0x12, 0x03,
+ 0x06, 0x04, 0x0D, 0x1D, 0x15, 0x0E, 0x1B, 0x18,
+ 0x00, 0x07, 0x08, 0x02, 0x13, 0x1F, 0x0C, 0x1E,
+ 0x16, 0x0A, 0x10, 0x0F, 0x01, 0x14, 0x11, 0x17 };
+
+static const unsigned char table_3[256] = {
+ 0xBC, 0x1B, 0xCC, 0x1E, 0x5B, 0x59, 0x4F, 0xA8,
+ 0x62, 0xC6, 0xC1, 0xBB, 0x83, 0x2D, 0xA3, 0xA6,
+ 0x5A, 0xDC, 0xE5, 0x93, 0xFB, 0x5C, 0xD6, 0x2A,
+ 0x97, 0xC7, 0x1C, 0x73, 0x08, 0x45, 0xD2, 0x89,
+ 0x4A, 0xD4, 0xCF, 0x0C, 0x1D, 0xD8, 0xCD, 0x26,
+ 0x8F, 0x11, 0x55, 0x8B, 0xD3, 0x53, 0xCE, 0x00,
+ 0xB5, 0x3B, 0x2E, 0x39, 0x88, 0x7B, 0x85, 0x46,
+ 0x54, 0xA5, 0x31, 0x40, 0x3E, 0x0A, 0x4C, 0x68,
+ 0x70, 0x0F, 0xBA, 0x0E, 0x75, 0x8A, 0xEB, 0x44,
+ 0x60, 0x6C, 0x05, 0xC9, 0xF0, 0xDD, 0x0D, 0x66,
+ 0xAB, 0xA1, 0xAD, 0xF2, 0x12, 0x6A, 0xE6, 0x27,
+ 0xF6, 0x9F, 0xDB, 0xB8, 0xF4, 0x56, 0x5E, 0x2C,
+ 0xDA, 0xFE, 0x34, 0x86, 0xF5, 0xC2, 0xB0, 0xF1,
+ 0xCB, 0xF3, 0x78, 0x9B, 0x7F, 0xB4, 0xD7, 0x58,
+ 0x74, 0x07, 0x72, 0x96, 0x02, 0xCA, 0xAC, 0xE8,
+ 0x5D, 0xA7, 0x32, 0xBD, 0x81, 0x43, 0x18, 0xF8,
+ 0x15, 0x0B, 0xE9, 0x76, 0x30, 0xBF, 0x3A, 0x22,
+ 0x9E, 0xD1, 0x79, 0x37, 0xBE, 0x8C, 0x7A, 0x98,
+ 0x21, 0x95, 0x10, 0x8D, 0xDF, 0xC0, 0x69, 0xC8,
+ 0x03, 0x6E, 0x4B, 0x36, 0xFC, 0x6F, 0xA9, 0x48,
+ 0x63, 0xE1, 0xB9, 0x24, 0x87, 0x13, 0xB2, 0xA4,
+ 0x84, 0x06, 0x14, 0x61, 0x3D, 0x92, 0xB1, 0x41,
+ 0xE2, 0x71, 0xAF, 0x16, 0xDE, 0x25, 0x82, 0xD9,
+ 0x2B, 0x33, 0x51, 0xA2, 0x4E, 0x7D, 0x94, 0xFF,
+ 0xFD, 0x5F, 0x80, 0xED, 0x64, 0xE7, 0x50, 0x6D,
+ 0xD0, 0x3C, 0x6B, 0x65, 0x77, 0x17, 0x1A, 0xEC,
+ 0xD5, 0xAA, 0xF9, 0xC4, 0x9C, 0x35, 0xE3, 0x42,
+ 0xE4, 0x19, 0x52, 0x67, 0xB7, 0x9D, 0x28, 0xC5,
+ 0x47, 0x38, 0x91, 0x57, 0xAE, 0x3F, 0x29, 0x9A,
+ 0x2F, 0xF7, 0x90, 0x04, 0xEE, 0xFA, 0x20, 0xB6,
+ 0xEA, 0x49, 0x23, 0x4D, 0xB3, 0x8E, 0xC3, 0x1F,
+ 0x7C, 0xEF, 0xE0, 0x99, 0x09, 0xA0, 0x01, 0x7E };
+
+static const unsigned char table_4[32] = {
+ 0x1F, 0x0B, 0x00, 0x1E, 0x03, 0x0E, 0x15, 0x01,
+ 0x1A, 0x17, 0x1D, 0x1B, 0x11, 0x0F, 0x0A, 0x12,
+ 0x13, 0x18, 0x02, 0x04, 0x09, 0x06, 0x0D, 0x07,
+ 0x08, 0x05, 0x10, 0x19, 0x0C, 0x14, 0x16, 0x1C };
+
+static const unsigned char table_5[256] = {
+ 0x9A, 0xAB, 0x61, 0x28, 0x0A, 0x23, 0xFC, 0xBA,
+ 0x90, 0x22, 0xB7, 0x62, 0xD9, 0x09, 0x91, 0xF4,
+ 0x7B, 0x5D, 0x6B, 0x80, 0xAC, 0x9E, 0x21, 0x72,
+ 0x64, 0x2D, 0xFF, 0x66, 0xEB, 0x5B, 0x05, 0xC8,
+ 0x1B, 0xD1, 0x55, 0xF5, 0x97, 0x08, 0xAE, 0xC7,
+ 0x00, 0xDE, 0xE1, 0x78, 0xD8, 0xB6, 0xF0, 0x17,
+ 0xE4, 0x32, 0xCD, 0x76, 0x07, 0x14, 0x7F, 0x7A,
+ 0xBF, 0xB4, 0x1D, 0x94, 0x48, 0x75, 0xFA, 0xA7,
+ 0x99, 0x7E, 0x65, 0x38, 0x29, 0x51, 0xC3, 0x83,
+ 0x7C, 0x0D, 0xA0, 0xCC, 0xF1, 0xDD, 0xE2, 0x49,
+ 0xF8, 0xD2, 0x25, 0x54, 0x9B, 0x0E, 0xB9, 0xFE,
+ 0x67, 0xC4, 0xCE, 0x13, 0xD4, 0xE7, 0xB8, 0x41,
+ 0x77, 0xDB, 0xA6, 0xB0, 0x11, 0x6A, 0x5E, 0x68,
+ 0x8D, 0xF9, 0x36, 0xD3, 0xC2, 0x3A, 0xAA, 0x59,
+ 0x03, 0xE0, 0xE3, 0xF3, 0x42, 0x2C, 0x04, 0x47,
+ 0xE6, 0x93, 0xCB, 0x6E, 0x20, 0xCA, 0x01, 0xA1,
+ 0x40, 0x2B, 0x2F, 0x5F, 0x87, 0xD0, 0xEC, 0x88,
+ 0x27, 0x58, 0xC6, 0x3E, 0xDF, 0x26, 0x5C, 0xE9,
+ 0x1F, 0x0F, 0x95, 0x1C, 0xFB, 0xA5, 0x12, 0x39,
+ 0x1E, 0x3C, 0x33, 0x43, 0x56, 0xE8, 0x82, 0xF7,
+ 0x7D, 0x89, 0xF2, 0xD7, 0x50, 0x92, 0x60, 0x4C,
+ 0x2A, 0x86, 0x16, 0x6C, 0x37, 0xC0, 0xAD, 0xB3,
+ 0x24, 0x45, 0xB1, 0xA2, 0x71, 0xA4, 0xA3, 0xED,
+ 0xC9, 0x5A, 0x4D, 0x84, 0x0C, 0x3F, 0xC5, 0x9D,
+ 0x63, 0x19, 0x79, 0x57, 0x96, 0x30, 0x74, 0xBB,
+ 0xDA, 0x1A, 0x9F, 0x44, 0xC1, 0x98, 0xE5, 0x81,
+ 0xD6, 0x18, 0x8F, 0xFD, 0x8E, 0x06, 0x6F, 0xF6,
+ 0x2E, 0x3B, 0xB5, 0x85, 0x8A, 0x9C, 0x53, 0x4A,
+ 0xA9, 0x52, 0x3D, 0x4E, 0xBE, 0xAF, 0xBC, 0xA8,
+ 0x4F, 0x6D, 0x15, 0x35, 0x8C, 0xBD, 0x34, 0x8B,
+ 0xDC, 0x0B, 0xCF, 0x31, 0xEA, 0xB2, 0x70, 0x4B,
+ 0x46, 0x73, 0x69, 0xD5, 0x10, 0xEE, 0x02, 0xEF };
+
+static const unsigned char table_6[32] = {
+ 0x1A, 0x1C, 0x0F, 0x0C, 0x00, 0x02, 0x13, 0x09,
+ 0x11, 0x05, 0x0D, 0x12, 0x18, 0x0B, 0x04, 0x10,
+ 0x14, 0x1B, 0x1E, 0x16, 0x07, 0x08, 0x03, 0x17,
+ 0x19, 0x1F, 0x01, 0x0E, 0x15, 0x06, 0x0A, 0x1D };
+
+static const unsigned char table_7[256] = {
+ 0x52, 0x11, 0x72, 0xD0, 0x76, 0xD7, 0xAE, 0x03,
+ 0x7F, 0x19, 0xF4, 0xB8, 0xB3, 0x5D, 0xCA, 0x2D,
+ 0x5C, 0x30, 0x53, 0x1A, 0x57, 0xF6, 0xAD, 0x83,
+ 0x29, 0x79, 0xD5, 0xF0, 0x0F, 0xC3, 0x8B, 0xD3,
+ 0x8E, 0x37, 0x01, 0xA6, 0xF1, 0x10, 0x04, 0x71,
+ 0xCC, 0xC6, 0xE7, 0xC2, 0x85, 0x94, 0xBD, 0x6F,
+ 0xCB, 0xEA, 0xFC, 0xA1, 0x38, 0x5E, 0x08, 0x2E,
+ 0x35, 0x42, 0x67, 0xD4, 0x56, 0x6D, 0x7C, 0xE5,
+ 0x0E, 0x7D, 0x12, 0x65, 0xF5, 0x33, 0x82, 0xC4,
+ 0x1D, 0xD2, 0x16, 0x58, 0xEC, 0xCD, 0xA8, 0xBF,
+ 0xAB, 0x07, 0x45, 0x55, 0xB7, 0x6A, 0x70, 0xF2,
+ 0xBE, 0x05, 0x6B, 0x9D, 0xEB, 0x13, 0x0D, 0x9F,
+ 0xE8, 0xA7, 0xC8, 0x31, 0x3C, 0xB6, 0x21, 0xC0,
+ 0x20, 0x60, 0x6C, 0xE2, 0xCE, 0x8C, 0xFD, 0x95,
+ 0xE3, 0x4A, 0xB5, 0xB2, 0x40, 0xB1, 0xF3, 0x17,
+ 0xF9, 0x24, 0x06, 0x22, 0x2F, 0x25, 0x93, 0x8A,
+ 0x2A, 0x7E, 0x28, 0x3D, 0x47, 0xF8, 0x89, 0xA5,
+ 0x7B, 0x9B, 0xC5, 0x84, 0x59, 0x46, 0x90, 0x74,
+ 0x69, 0xC7, 0xAA, 0xEE, 0x6E, 0xD6, 0xB0, 0x18,
+ 0x66, 0xA0, 0x7A, 0x1E, 0xFB, 0xDB, 0x4E, 0x51,
+ 0x92, 0xE4, 0xE0, 0x3E, 0xB4, 0xD8, 0x23, 0x3B,
+ 0xC1, 0x5F, 0xFE, 0x98, 0x99, 0x73, 0x09, 0xA9,
+ 0xA3, 0xDF, 0x14, 0x5A, 0x26, 0x8F, 0x0B, 0xAF,
+ 0x4C, 0x97, 0x54, 0xE1, 0x63, 0x48, 0xED, 0xBA,
+ 0xCF, 0xBB, 0x1F, 0xDC, 0xA4, 0xFA, 0x64, 0x75,
+ 0xDE, 0x81, 0x9A, 0xFF, 0x49, 0x41, 0x27, 0x62,
+ 0x02, 0x15, 0xD9, 0x86, 0xAC, 0x3F, 0x0C, 0x61,
+ 0xD1, 0x77, 0x2B, 0x1B, 0x96, 0xDA, 0x68, 0x1C,
+ 0x44, 0x32, 0xBC, 0xA2, 0x87, 0xF7, 0x91, 0x8D,
+ 0x80, 0xDD, 0x0A, 0x50, 0x34, 0x4B, 0x00, 0xB9,
+ 0x36, 0xE6, 0x78, 0x4F, 0xC9, 0xE9, 0x2C, 0x43,
+ 0x88, 0x9E, 0x9C, 0x5B, 0x4D, 0x3A, 0x39, 0xEF };
+
+static const unsigned char table_8[32] = {
+ 0x13, 0x08, 0x1E, 0x1D, 0x17, 0x16, 0x07, 0x1F,
+ 0x0E, 0x03, 0x1A, 0x19, 0x01, 0x12, 0x11, 0x10,
+ 0x09, 0x0C, 0x0F, 0x14, 0x0B, 0x05, 0x00, 0x04,
+ 0x1C, 0x18, 0x0A, 0x15, 0x02, 0x1B, 0x06, 0x0D };
+
+static const unsigned char table_9[256] = {
+ 0x20, 0x2A, 0xDA, 0xFE, 0x76, 0x0D, 0xED, 0x39,
+ 0x51, 0x4C, 0x46, 0x9A, 0xF1, 0xB0, 0x10, 0xC7,
+ 0xD1, 0x6F, 0x18, 0x24, 0xB9, 0x7A, 0x4F, 0x47,
+ 0xE0, 0x4E, 0x88, 0x09, 0x8A, 0xBA, 0x60, 0xBD,
+ 0xC2, 0x27, 0x93, 0x7D, 0x94, 0x40, 0xCB, 0x80,
+ 0xB8, 0x41, 0x84, 0x5D, 0xC1, 0x0F, 0x5E, 0x78,
+ 0x2B, 0x48, 0x28, 0x29, 0xEE, 0x81, 0x90, 0x86,
+ 0x50, 0x9C, 0xF3, 0xB2, 0x35, 0x52, 0x0C, 0x9D,
+ 0xFC, 0x69, 0xD6, 0xA6, 0x06, 0xD7, 0xC6, 0xFF,
+ 0x1C, 0x14, 0x57, 0x33, 0xE2, 0x1F, 0x83, 0xA8,
+ 0xF7, 0x99, 0xC5, 0xDC, 0x70, 0x9E, 0xF4, 0x6B,
+ 0x0A, 0x77, 0x95, 0x4A, 0x2E, 0x53, 0xF2, 0x62,
+ 0x98, 0xF8, 0x96, 0xDB, 0xE6, 0x32, 0x3C, 0x58,
+ 0xD5, 0x6D, 0xE7, 0x4B, 0xCE, 0x91, 0x43, 0xD8,
+ 0xFA, 0xE3, 0x4D, 0xD9, 0x68, 0xDE, 0xEC, 0x01,
+ 0x08, 0xD3, 0x8F, 0x19, 0xC4, 0xA7, 0x6E, 0x3E,
+ 0x63, 0x12, 0x72, 0x42, 0x9F, 0xB4, 0x04, 0x1B,
+ 0x7E, 0x11, 0x17, 0x73, 0xB5, 0x22, 0x56, 0xA1,
+ 0x89, 0xDD, 0xF5, 0x3F, 0x49, 0x26, 0x8D, 0x15,
+ 0x85, 0x75, 0x5F, 0x65, 0x82, 0xB6, 0xF6, 0xD2,
+ 0xA4, 0x55, 0x37, 0xC8, 0xA0, 0xCC, 0x66, 0x5C,
+ 0xC9, 0x25, 0x36, 0x67, 0x7C, 0xE1, 0xA3, 0xCF,
+ 0xA9, 0x59, 0x2F, 0xFB, 0xBB, 0x07, 0x87, 0xA2,
+ 0x44, 0x92, 0x13, 0x00, 0x16, 0x61, 0x38, 0xEB,
+ 0xAE, 0xD4, 0x1E, 0x64, 0x6A, 0xE4, 0xCA, 0x1D,
+ 0x6C, 0xDF, 0xAB, 0x5B, 0x03, 0x7B, 0x9B, 0x8C,
+ 0x5A, 0xFD, 0xC3, 0xB3, 0x0B, 0xAA, 0xAC, 0x8B,
+ 0xBE, 0xBC, 0x3D, 0x97, 0xCD, 0x05, 0x21, 0x8E,
+ 0xAD, 0xEA, 0x54, 0x30, 0xAF, 0x02, 0xB1, 0x34,
+ 0x0E, 0xA5, 0x3B, 0x45, 0x1A, 0x23, 0xE8, 0x7F,
+ 0xEF, 0xB7, 0x31, 0xD0, 0xBF, 0x3A, 0x79, 0xE5,
+ 0xF9, 0xF0, 0x2C, 0x74, 0xE9, 0x71, 0xC0, 0x2D };
+
+static const unsigned char table_10[32] = {
+ 0x1D, 0x12, 0x11, 0x0D, 0x1E, 0x19, 0x16, 0x1B,
+ 0x18, 0x13, 0x07, 0x17, 0x0C, 0x02, 0x00, 0x15,
+ 0x0E, 0x08, 0x05, 0x01, 0x10, 0x06, 0x04, 0x0F,
+ 0x1F, 0x1A, 0x0B, 0x09, 0x0A, 0x14, 0x1C, 0x03 };
+
+static const unsigned char table_11[256] = {
+ 0x6B, 0x1D, 0xC6, 0x0A, 0xB7, 0xAC, 0xB2, 0x11,
+ 0x29, 0xD3, 0xA2, 0x4D, 0xCB, 0x03, 0xEF, 0xA6,
+ 0xC1, 0x5D, 0x75, 0x48, 0x35, 0x6C, 0xE2, 0x84,
+ 0xAB, 0xAA, 0xD8, 0x2C, 0x0E, 0x95, 0x25, 0x27,
+ 0x7D, 0x0B, 0xD0, 0xFB, 0x14, 0xE5, 0xF2, 0x4E,
+ 0x7F, 0x2A, 0x63, 0x3C, 0xC9, 0xF6, 0xDC, 0x07,
+ 0x26, 0x55, 0xCF, 0x2B, 0xCD, 0xA7, 0x17, 0xD2,
+ 0x9A, 0x7B, 0x93, 0x78, 0x9E, 0xE6, 0x2F, 0x49,
+ 0x1E, 0xFD, 0xF0, 0xFE, 0x7C, 0x33, 0x92, 0xA3,
+ 0xC8, 0xA0, 0xA9, 0xC4, 0xA1, 0x94, 0x6D, 0x44,
+ 0x0C, 0x90, 0x3A, 0x8C, 0x8E, 0x85, 0xAF, 0x40,
+ 0x36, 0xA4, 0xD1, 0xB9, 0x19, 0x6F, 0xF4, 0xBA,
+ 0x1A, 0x73, 0xD9, 0xB5, 0xB4, 0x7A, 0xF9, 0x83,
+ 0x58, 0xAD, 0xCE, 0x60, 0x98, 0xDB, 0x1C, 0x1B,
+ 0x52, 0xB8, 0xF3, 0x96, 0xED, 0xDE, 0xB3, 0xEE,
+ 0x4F, 0xBD, 0x10, 0xD4, 0x43, 0xEA, 0xE7, 0x37,
+ 0x12, 0x3D, 0xA8, 0x22, 0x65, 0xEC, 0x5B, 0x08,
+ 0x9D, 0x0D, 0x5C, 0xB6, 0x8A, 0x79, 0x3F, 0x04,
+ 0xD6, 0x01, 0xE1, 0xBE, 0xDD, 0x50, 0xFA, 0x41,
+ 0x13, 0x91, 0xF7, 0xDA, 0x18, 0xB0, 0x45, 0x81,
+ 0x4C, 0xF5, 0x32, 0x23, 0x56, 0x5A, 0xEB, 0x97,
+ 0x34, 0x00, 0x77, 0x71, 0x4B, 0x70, 0xD5, 0x31,
+ 0x72, 0x05, 0xDF, 0xE8, 0x15, 0x3B, 0x54, 0x16,
+ 0x89, 0xE4, 0xF1, 0xD7, 0x80, 0x82, 0x4A, 0xE3,
+ 0x39, 0x06, 0x47, 0x28, 0xC2, 0x86, 0x87, 0xB1,
+ 0x62, 0x74, 0x53, 0x21, 0x67, 0x38, 0x42, 0xCA,
+ 0x9B, 0xC3, 0x51, 0x99, 0x8B, 0x1F, 0x24, 0x8D,
+ 0xF8, 0x68, 0x3E, 0x59, 0xBB, 0x61, 0x5F, 0xBC,
+ 0x09, 0x6E, 0x8F, 0x0F, 0x2D, 0xC0, 0xE0, 0x46,
+ 0x66, 0x69, 0xA5, 0xE9, 0x30, 0x9C, 0x5E, 0xAE,
+ 0xBF, 0xC7, 0x20, 0x7E, 0x6A, 0xC5, 0x88, 0xFC,
+ 0x64, 0x76, 0xFF, 0x9F, 0x2E, 0x02, 0xCC, 0x57 };
+
+static const unsigned char table_12[32] = {
+ 0x14, 0x1B, 0x18, 0x00, 0x1F, 0x15, 0x17, 0x07,
+ 0x11, 0x1A, 0x0E, 0x13, 0x12, 0x06, 0x01, 0x03,
+ 0x1C, 0x0C, 0x0B, 0x1D, 0x10, 0x0F, 0x09, 0x19,
+ 0x0D, 0x1E, 0x04, 0x05, 0x08, 0x16, 0x0A, 0x02 };
+
+static const unsigned char table_13[256] = {
+ 0x37, 0x8A, 0x1B, 0x91, 0xA5, 0x2B, 0x2D, 0x88,
+ 0x8E, 0xFE, 0x0E, 0xD3, 0xF3, 0xE9, 0x7D, 0xD1,
+ 0x24, 0xEA, 0xB1, 0x8B, 0x5C, 0xA4, 0x44, 0x7E,
+ 0x8C, 0x2C, 0x73, 0xD5, 0x50, 0x3E, 0xD7, 0x18,
+ 0xB9, 0xD6, 0xBA, 0x94, 0x0C, 0xFC, 0xCB, 0xB4,
+ 0x0D, 0x63, 0x4C, 0xDE, 0x77, 0x16, 0xFD, 0x81,
+ 0x3C, 0x11, 0x45, 0x36, 0xF6, 0x67, 0x95, 0x6D,
+ 0x6A, 0x1A, 0xA3, 0xC5, 0x92, 0x10, 0x28, 0x84,
+ 0x48, 0xA6, 0x23, 0xE3, 0x4B, 0xE1, 0xF5, 0x19,
+ 0xE0, 0x2E, 0x00, 0x61, 0x74, 0xCC, 0xF7, 0xB0,
+ 0x68, 0xC8, 0x40, 0x6F, 0x59, 0x52, 0x26, 0x99,
+ 0xC9, 0xF9, 0xC4, 0x53, 0x9B, 0xEC, 0x03, 0x17,
+ 0xE2, 0x06, 0x30, 0x7B, 0xBE, 0xCD, 0x1D, 0x3B,
+ 0xD2, 0x5B, 0x65, 0x21, 0x49, 0xB7, 0x79, 0xCF,
+ 0x82, 0x86, 0xC7, 0x62, 0xEE, 0x8D, 0xFF, 0xD4,
+ 0xC3, 0x85, 0xA7, 0xFA, 0xA9, 0x6B, 0xF2, 0x69,
+ 0x9C, 0x38, 0x78, 0xBD, 0x7F, 0xDD, 0xCE, 0xA1,
+ 0x33, 0xC2, 0x43, 0xEB, 0xD8, 0xE6, 0x2A, 0xE4,
+ 0x76, 0x6C, 0xAA, 0x46, 0x05, 0xE7, 0xA0, 0x0A,
+ 0x71, 0x98, 0x41, 0x5F, 0x0F, 0xEF, 0x51, 0xAD,
+ 0xF0, 0xED, 0x96, 0x5A, 0x42, 0x3F, 0xBF, 0x6E,
+ 0xBC, 0x5D, 0xC1, 0x15, 0x70, 0x54, 0x4D, 0x14,
+ 0xB5, 0xCA, 0x27, 0x80, 0x87, 0x39, 0x60, 0x47,
+ 0x9D, 0x2F, 0x56, 0x1F, 0xBB, 0x31, 0xF1, 0xE8,
+ 0xB3, 0x9E, 0x5E, 0x7C, 0xD0, 0xC6, 0xB2, 0x57,
+ 0x83, 0xAC, 0x09, 0x8F, 0xA2, 0x90, 0x13, 0x25,
+ 0x01, 0x08, 0x64, 0xB6, 0x02, 0xDB, 0x55, 0x32,
+ 0xAF, 0x9A, 0xC0, 0x1C, 0x12, 0x29, 0x0B, 0x72,
+ 0x4F, 0xDA, 0xAB, 0x35, 0xF8, 0x22, 0xD9, 0x4E,
+ 0x3D, 0x1E, 0xDC, 0x58, 0x20, 0x34, 0xAE, 0x66,
+ 0x75, 0x93, 0x9F, 0x3A, 0x07, 0xE5, 0x89, 0xDF,
+ 0x97, 0x4A, 0xB8, 0x7A, 0xF4, 0xFB, 0x04, 0xA8 };
+
+static const unsigned char table_14[32] = {
+ 0x04, 0x14, 0x13, 0x15, 0x1A, 0x1B, 0x0F, 0x16,
+ 0x02, 0x0D, 0x0C, 0x06, 0x10, 0x17, 0x01, 0x0B,
+ 0x1E, 0x08, 0x1C, 0x18, 0x19, 0x0A, 0x1F, 0x05,
+ 0x11, 0x09, 0x1D, 0x07, 0x0E, 0x12, 0x03, 0x00 };
+
+static const unsigned char table_15[256] = {
+ 0x61, 0x48, 0x58, 0x41, 0x7F, 0x88, 0x43, 0x42,
+ 0xD9, 0x80, 0x81, 0xFE, 0xC6, 0x49, 0xD7, 0x2C,
+ 0xE6, 0x5B, 0xEE, 0xFF, 0x2A, 0x6F, 0xBF, 0x98,
+ 0xD6, 0x20, 0xB9, 0xB1, 0x5D, 0x95, 0x72, 0x1E,
+ 0x82, 0x96, 0xDE, 0xC1, 0x40, 0xD8, 0x70, 0xA3,
+ 0xD1, 0x1F, 0xF0, 0x9F, 0x2D, 0xDC, 0x3F, 0xF9,
+ 0x5E, 0x0D, 0x15, 0x2F, 0x67, 0x31, 0x9D, 0x84,
+ 0x97, 0x0C, 0xF6, 0x79, 0xC2, 0xA7, 0xC0, 0x32,
+ 0xB3, 0xEB, 0xED, 0x71, 0x30, 0xCC, 0x4B, 0xA0,
+ 0xF5, 0xC4, 0xCD, 0x27, 0xFA, 0x11, 0x25, 0xDB,
+ 0x4F, 0xE2, 0x7E, 0xA6, 0xAF, 0x34, 0x69, 0x63,
+ 0x8F, 0x08, 0x1C, 0x85, 0xF1, 0x57, 0x78, 0xC8,
+ 0xA2, 0x83, 0xB5, 0x68, 0xF7, 0x64, 0x45, 0x26,
+ 0x3B, 0x03, 0xAD, 0x3C, 0x50, 0xD5, 0x77, 0xFC,
+ 0xFB, 0x18, 0xC9, 0xD2, 0x9C, 0xBB, 0xBA, 0x76,
+ 0x23, 0x55, 0xD3, 0x5A, 0x01, 0xE9, 0x87, 0x07,
+ 0x19, 0x09, 0x39, 0x8A, 0x91, 0x93, 0x12, 0xDF,
+ 0x22, 0xA8, 0xCF, 0x4E, 0x4D, 0x65, 0xB0, 0x0F,
+ 0x13, 0x53, 0x21, 0x8C, 0xE5, 0xB7, 0x0B, 0x0E,
+ 0x6C, 0x44, 0xCA, 0x7B, 0xC5, 0x6E, 0xCE, 0xE3,
+ 0x14, 0x29, 0xAC, 0x2E, 0xE7, 0x59, 0xE8, 0x0A,
+ 0xEA, 0x66, 0x7C, 0x94, 0x6D, 0x05, 0x9E, 0x9A,
+ 0x2B, 0x38, 0x6A, 0xCB, 0x51, 0xEF, 0x06, 0xDA,
+ 0xFD, 0x47, 0x92, 0x1D, 0xA5, 0x37, 0x33, 0xEC,
+ 0xB4, 0x52, 0x56, 0xC3, 0xF4, 0xF8, 0x8B, 0xD0,
+ 0xA4, 0x5F, 0x28, 0x89, 0x75, 0xC7, 0x04, 0x00,
+ 0xE4, 0x86, 0x36, 0x3A, 0x99, 0x16, 0x7D, 0xE0,
+ 0x7A, 0x4C, 0x54, 0x46, 0x73, 0xB2, 0xF3, 0xE1,
+ 0x62, 0xBE, 0x90, 0x4A, 0x24, 0x6B, 0x3E, 0xAA,
+ 0x1B, 0xF2, 0x60, 0xD4, 0xA9, 0x9B, 0x1A, 0xB8,
+ 0xA1, 0x35, 0xAE, 0xB6, 0x10, 0x5C, 0x17, 0xBC,
+ 0xAB, 0x8D, 0x02, 0x74, 0xBD, 0x3D, 0x8E, 0xDD };
+
+static const unsigned char table_16[256] = {
+ 0x3F, 0x9C, 0x17, 0xC1, 0x59, 0xC6, 0x23, 0x93,
+ 0x4B, 0xDF, 0xCB, 0x55, 0x2B, 0xDE, 0xCD, 0xAD,
+ 0xB3, 0xE7, 0x42, 0x2F, 0x02, 0x5A, 0x7B, 0x5C,
+ 0x8F, 0xD1, 0x11, 0xCE, 0xEC, 0xF6, 0xA4, 0xE6,
+ 0x58, 0x98, 0x6A, 0x99, 0xFB, 0x9B, 0x53, 0x21,
+ 0x8A, 0x09, 0x2E, 0x3C, 0x22, 0x38, 0xAC, 0x07,
+ 0x91, 0x46, 0xA9, 0x95, 0xC3, 0x14, 0x84, 0xDB,
+ 0x36, 0x68, 0x1D, 0xDD, 0xF9, 0x12, 0xE0, 0x3D,
+ 0x8D, 0x4D, 0x05, 0x86, 0x69, 0xC0, 0xD3, 0xD5,
+ 0xA5, 0xC9, 0xE5, 0x67, 0x6D, 0xE2, 0x7F, 0xFE,
+ 0xB2, 0x0F, 0x62, 0xCF, 0x37, 0x35, 0xF3, 0x28,
+ 0x16, 0xA6, 0x50, 0x76, 0x80, 0x00, 0x31, 0x97,
+ 0x39, 0x7C, 0x25, 0x0C, 0x64, 0xF2, 0x52, 0x1A,
+ 0x92, 0x4F, 0x2A, 0x56, 0x03, 0x4C, 0xBD, 0x10,
+ 0xB7, 0x2C, 0x8C, 0xAE, 0x73, 0xB9, 0xE9, 0xF7,
+ 0xA7, 0xE1, 0x75, 0xBC, 0xC5, 0x1C, 0x3A, 0x63,
+ 0x7A, 0x4A, 0x29, 0xD2, 0x71, 0xE8, 0x08, 0xA1,
+ 0xD4, 0xFD, 0x13, 0xFA, 0xA0, 0x27, 0x41, 0x72,
+ 0x82, 0x18, 0x51, 0x60, 0x5E, 0x66, 0x0D, 0xAA,
+ 0xD8, 0x1F, 0xAF, 0x45, 0xD0, 0xF1, 0x9F, 0x6B,
+ 0xE4, 0x44, 0x89, 0xEE, 0xC4, 0x0B, 0x6C, 0xCC,
+ 0x83, 0x77, 0xA2, 0x87, 0x0A, 0xA8, 0xED, 0x90,
+ 0x74, 0x6E, 0xF5, 0xAB, 0xA3, 0xB6, 0x5F, 0x0E,
+ 0x04, 0x9A, 0xB4, 0x8E, 0xF0, 0xFF, 0x88, 0xB5,
+ 0xF8, 0xBF, 0x8B, 0x6F, 0x4E, 0x79, 0x40, 0xCA,
+ 0x24, 0x26, 0xDC, 0x33, 0xEB, 0x2D, 0x5B, 0x1B,
+ 0x9D, 0xC7, 0x49, 0x48, 0x54, 0x85, 0xEF, 0xD7,
+ 0xC2, 0xB8, 0xC8, 0x5D, 0xD9, 0x3B, 0x15, 0xBB,
+ 0x65, 0xE3, 0xD6, 0x30, 0x3E, 0x1E, 0x32, 0x9E,
+ 0x57, 0x81, 0x34, 0x06, 0xFC, 0xBA, 0x7D, 0x20,
+ 0x70, 0xDA, 0x7E, 0x47, 0x94, 0x61, 0xB0, 0x78,
+ 0xF4, 0xBE, 0xEA, 0x19, 0x43, 0x01, 0xB1, 0x96 };
+
+static const unsigned char table_17[256] = {
+ 0x7E, 0xF1, 0xD3, 0x75, 0x87, 0xA6, 0xED, 0x9E,
+ 0xA9, 0xD5, 0xC6, 0xBF, 0xE6, 0x6A, 0xEE, 0x4B,
+ 0x34, 0xDF, 0x4C, 0x7D, 0xDD, 0xFE, 0x3F, 0xAF,
+ 0x66, 0x2D, 0x74, 0x6F, 0xFC, 0x4F, 0x5F, 0x88,
+ 0x29, 0x7B, 0xC7, 0x2A, 0x70, 0xE8, 0x1D, 0xDE,
+ 0xD0, 0x55, 0x71, 0x81, 0xC4, 0x0D, 0x50, 0x4E,
+ 0x58, 0x00, 0x96, 0x97, 0xBB, 0xD7, 0x53, 0x15,
+ 0x6C, 0x40, 0x17, 0xC9, 0xFF, 0x8F, 0x94, 0xFB,
+ 0x19, 0x9A, 0x3E, 0xB5, 0x5A, 0x5E, 0x86, 0x24,
+ 0xB8, 0x77, 0xBA, 0x85, 0x51, 0x18, 0xBE, 0x59,
+ 0x79, 0xF3, 0xD4, 0xC3, 0xAB, 0x28, 0xFD, 0x25,
+ 0x41, 0x91, 0x07, 0x8D, 0xAE, 0x49, 0xF5, 0x80,
+ 0x35, 0xA1, 0x9C, 0x3C, 0xE2, 0x65, 0xB3, 0xE0,
+ 0x16, 0xCB, 0x12, 0x6B, 0xF7, 0xB1, 0x93, 0x8A,
+ 0xCE, 0x54, 0x4D, 0xF8, 0x13, 0xA2, 0x95, 0x46,
+ 0xEA, 0x61, 0x57, 0x9D, 0x27, 0x8B, 0x3D, 0x60,
+ 0x36, 0x68, 0x06, 0x56, 0xB6, 0x1B, 0xD2, 0x89,
+ 0x10, 0xA7, 0xC5, 0x1A, 0x0B, 0x2C, 0xBD, 0x14,
+ 0x0A, 0xDC, 0x23, 0xA8, 0xE1, 0x04, 0x02, 0xC0,
+ 0xB2, 0x9B, 0xE3, 0x2E, 0x33, 0x7C, 0x32, 0xAC,
+ 0x7A, 0x39, 0xB0, 0xF9, 0x98, 0x5B, 0x3A, 0x48,
+ 0x21, 0x90, 0xB9, 0x20, 0xF0, 0xA0, 0x09, 0x1F,
+ 0x2F, 0xEF, 0xEB, 0x22, 0x78, 0x82, 0x37, 0xD6,
+ 0xD1, 0x84, 0x76, 0x01, 0xDB, 0x43, 0xC2, 0xB7,
+ 0x7F, 0xA4, 0xE5, 0xC1, 0x1C, 0x69, 0x05, 0xEC,
+ 0xD8, 0x38, 0x67, 0x42, 0x72, 0xBC, 0x73, 0xAD,
+ 0xA3, 0xE9, 0x4A, 0x8E, 0x47, 0x1E, 0xC8, 0x6E,
+ 0xDA, 0x5D, 0x2B, 0xF6, 0x30, 0x63, 0xCC, 0xF4,
+ 0xCD, 0x8C, 0x0F, 0x3B, 0xE7, 0xD9, 0xCF, 0xB4,
+ 0x03, 0x92, 0x0E, 0x31, 0xE4, 0x08, 0xF2, 0x45,
+ 0xCA, 0x83, 0x26, 0x5C, 0xA5, 0x44, 0x64, 0x6D,
+ 0x9F, 0x99, 0x62, 0xAA, 0xFA, 0x11, 0x0C, 0x52 };
+
+static const unsigned char table_18[256] = {
+ 0x0F, 0x42, 0x3D, 0x86, 0x3E, 0x66, 0xFE, 0x5C,
+ 0x52, 0xE2, 0xA3, 0xB3, 0xCE, 0x16, 0xCC, 0x95,
+ 0xB0, 0x8B, 0x82, 0x3B, 0x93, 0x7D, 0x62, 0x08,
+ 0x1C, 0x6E, 0xBB, 0xCB, 0x1D, 0x88, 0x69, 0xD4,
+ 0xC9, 0x40, 0x1F, 0xBE, 0x27, 0xBC, 0xDB, 0x38,
+ 0xE5, 0xA1, 0x71, 0xBA, 0x8A, 0x5E, 0xFD, 0x36,
+ 0x8F, 0x26, 0x6B, 0xE4, 0x20, 0x6D, 0xC5, 0xDE,
+ 0xE0, 0x83, 0x7C, 0xD5, 0xD9, 0x4D, 0xDC, 0xE3,
+ 0x0D, 0x32, 0xED, 0x0E, 0x2F, 0x21, 0xA7, 0x79,
+ 0xA0, 0xD3, 0x8C, 0x14, 0x6F, 0xB7, 0xF8, 0x85,
+ 0x5D, 0x37, 0x24, 0xD6, 0x25, 0xD2, 0x8E, 0xA5,
+ 0xB8, 0xCD, 0x5A, 0x9F, 0x05, 0xAD, 0x65, 0x9E,
+ 0x4F, 0x5B, 0x56, 0xF0, 0xAA, 0xC2, 0x28, 0xA8,
+ 0x6A, 0x01, 0x99, 0x2E, 0xA6, 0x77, 0x74, 0x64,
+ 0x76, 0x15, 0x90, 0x75, 0xAF, 0xE8, 0x39, 0x48,
+ 0x09, 0x11, 0xE1, 0x2D, 0xEC, 0xB5, 0x7A, 0xB1,
+ 0x94, 0x13, 0x41, 0x4C, 0x02, 0xA9, 0x97, 0xDF,
+ 0xC3, 0x8D, 0xEA, 0x3A, 0x9C, 0xD1, 0xA2, 0x9A,
+ 0xD7, 0x59, 0xD8, 0x18, 0xDA, 0x47, 0x89, 0x81,
+ 0xC7, 0xF5, 0xFC, 0x98, 0xCA, 0x91, 0x06, 0x68,
+ 0xC8, 0x07, 0x4A, 0x84, 0x0A, 0xE7, 0x33, 0x2C,
+ 0xEB, 0xDD, 0x5F, 0xAC, 0x23, 0x1A, 0x35, 0x70,
+ 0x43, 0x80, 0x61, 0xAE, 0xC1, 0xD0, 0x7B, 0x92,
+ 0x49, 0x51, 0x53, 0xC4, 0x34, 0x30, 0x0C, 0x4B,
+ 0x00, 0x04, 0x10, 0xFF, 0x63, 0x44, 0xB4, 0x0B,
+ 0x57, 0x72, 0xF1, 0x9D, 0x19, 0xF6, 0xB2, 0x87,
+ 0x1B, 0xEE, 0x46, 0x2A, 0xF3, 0xBF, 0x12, 0x96,
+ 0x58, 0x2B, 0xF9, 0xB6, 0xCF, 0x22, 0x3C, 0xAB,
+ 0x1E, 0x6C, 0x31, 0xC6, 0xF7, 0x78, 0x45, 0x17,
+ 0xE9, 0x7E, 0x73, 0xF2, 0x55, 0xFB, 0x3F, 0x9B,
+ 0xF4, 0xBD, 0xA4, 0x29, 0x60, 0x03, 0xB9, 0x50,
+ 0xFA, 0x4E, 0xEF, 0x54, 0xE6, 0x7F, 0xC0, 0x67 };
+
+static const unsigned char table_19[256] = {
+ 0xEA, 0xE7, 0x13, 0x14, 0xB9, 0xC0, 0xC4, 0x42,
+ 0x49, 0x6E, 0x2A, 0xA6, 0x65, 0x3C, 0x6A, 0x40,
+ 0x07, 0xCD, 0x4F, 0xFE, 0xF2, 0x2D, 0xC8, 0x30,
+ 0x9D, 0xBE, 0x1B, 0x9B, 0x4A, 0x7E, 0x9F, 0xA7,
+ 0x78, 0xAB, 0x4D, 0x1D, 0xF1, 0x96, 0x32, 0x84,
+ 0xFB, 0x80, 0x88, 0xE8, 0x41, 0x97, 0xDC, 0xD0,
+ 0x4E, 0x33, 0xA4, 0x3B, 0xE0, 0xDD, 0x36, 0xC9,
+ 0x72, 0x48, 0x8A, 0x2F, 0x35, 0xF0, 0xDF, 0x21,
+ 0xE1, 0xE5, 0x6C, 0x9A, 0x60, 0x8F, 0xB7, 0x24,
+ 0xE4, 0x9E, 0x8C, 0x0F, 0x3D, 0x28, 0xBB, 0xD6,
+ 0x69, 0xA0, 0x66, 0xC7, 0xE3, 0xD8, 0x11, 0x27,
+ 0xD9, 0x37, 0xF4, 0xF5, 0x8E, 0xD4, 0x76, 0xE2,
+ 0xDB, 0x15, 0xA2, 0x5C, 0x9C, 0xEE, 0x44, 0xED,
+ 0x2B, 0xB3, 0x75, 0x74, 0x71, 0x8B, 0x3A, 0x91,
+ 0x06, 0x19, 0xC1, 0x57, 0x89, 0xCC, 0x82, 0x10,
+ 0x17, 0xB2, 0x08, 0x70, 0x39, 0xCA, 0xBA, 0xB5,
+ 0xAA, 0xBF, 0x02, 0xBD, 0x26, 0x58, 0x04, 0x54,
+ 0x23, 0x4B, 0x90, 0x51, 0x6D, 0x98, 0xD5, 0xB0,
+ 0xAF, 0x22, 0xDA, 0xB4, 0x87, 0xFC, 0x7D, 0x18,
+ 0x6F, 0x64, 0x59, 0x09, 0x0C, 0xA5, 0x5D, 0x03,
+ 0x0A, 0xD3, 0xCE, 0x99, 0x8D, 0xC2, 0xC3, 0x62,
+ 0xD2, 0x83, 0x1A, 0xAC, 0x7C, 0x93, 0xD7, 0xA9,
+ 0x16, 0xF7, 0x77, 0xE6, 0x3E, 0x05, 0x73, 0x55,
+ 0x43, 0x95, 0x7A, 0x6B, 0x38, 0x67, 0x3F, 0xC6,
+ 0xAD, 0x0E, 0x29, 0x46, 0x45, 0xFA, 0xBC, 0xEC,
+ 0x5B, 0x7F, 0x0B, 0x1C, 0x01, 0x12, 0x85, 0x50,
+ 0xF9, 0xEF, 0x25, 0x34, 0x79, 0x2E, 0xEB, 0x00,
+ 0x5F, 0x86, 0xF8, 0x4C, 0xA8, 0x56, 0xB6, 0x5A,
+ 0xF3, 0x31, 0x94, 0x92, 0xB1, 0xB8, 0x52, 0xD1,
+ 0xCF, 0xCB, 0xA1, 0x81, 0x68, 0x47, 0xFF, 0xC5,
+ 0xFD, 0x1F, 0xDE, 0x53, 0xA3, 0x2C, 0x20, 0xF6,
+ 0x1E, 0x0D, 0xAE, 0x7B, 0x5E, 0x61, 0xE9, 0x63 };
+
+static const unsigned char table_20[32] = {
+ 0x0D, 0x0B, 0x11, 0x02, 0x05, 0x1B, 0x08, 0x1D,
+ 0x04, 0x14, 0x01, 0x09, 0x00, 0x19, 0x1E, 0x15,
+ 0x1F, 0x0A, 0x0F, 0x1C, 0x10, 0x16, 0x0C, 0x07,
+ 0x13, 0x1A, 0x06, 0x17, 0x0E, 0x12, 0x18, 0x03 };
+
+static const unsigned char table_21[256] = {
+ 0x4C, 0x94, 0xAD, 0x66, 0x9E, 0x69, 0x04, 0xA8,
+ 0x61, 0xE0, 0xE1, 0x3D, 0xFD, 0x9C, 0xFB, 0x19,
+ 0x1E, 0x80, 0x8C, 0xA0, 0xFC, 0x27, 0x26, 0x3B,
+ 0x48, 0x6D, 0x07, 0xE4, 0xEA, 0x17, 0x64, 0x9B,
+ 0xD0, 0xE2, 0xD1, 0x13, 0x39, 0xF5, 0x73, 0xD3,
+ 0x0C, 0x3A, 0x6E, 0x77, 0xFA, 0xE3, 0x2F, 0x44,
+ 0x7E, 0x72, 0x30, 0x43, 0xD4, 0x7F, 0x36, 0xD9,
+ 0xBD, 0x3E, 0x3F, 0x91, 0xBE, 0x54, 0x79, 0xA6,
+ 0x7C, 0x0E, 0xC5, 0x7A, 0x70, 0xC4, 0xD7, 0xCE,
+ 0xDA, 0xAA, 0x68, 0x8F, 0xBC, 0x96, 0x1B, 0x16,
+ 0xA2, 0xC6, 0x67, 0x09, 0x45, 0x9F, 0xCF, 0x41,
+ 0xC8, 0x60, 0x74, 0x99, 0x5D, 0x85, 0x5F, 0x50,
+ 0x33, 0x52, 0x22, 0xA9, 0xB5, 0x2D, 0x98, 0x87,
+ 0x15, 0x9A, 0xAC, 0x2C, 0xDE, 0xC0, 0xB8, 0x37,
+ 0x88, 0x1F, 0xC1, 0x4F, 0x65, 0x0F, 0x3C, 0x84,
+ 0x4B, 0x1A, 0xAB, 0xA4, 0x23, 0xCB, 0xB1, 0xC7,
+ 0xDB, 0xEF, 0x40, 0x0D, 0x46, 0xE8, 0xF4, 0x71,
+ 0x38, 0x01, 0x5C, 0x0B, 0x5E, 0xC9, 0xAF, 0xC3,
+ 0xF6, 0xB6, 0x10, 0x1D, 0xE5, 0x8A, 0x90, 0xA7,
+ 0xA3, 0x05, 0x4E, 0x14, 0x63, 0x25, 0x34, 0xEC,
+ 0x6B, 0x95, 0x21, 0x55, 0xF2, 0xF0, 0x47, 0x9D,
+ 0xF8, 0x8E, 0x02, 0x0A, 0xED, 0x97, 0xAE, 0x00,
+ 0x2A, 0xEB, 0xB2, 0xA5, 0x32, 0x06, 0x2E, 0xFE,
+ 0x8D, 0x7B, 0x7D, 0x35, 0x5A, 0xD2, 0xF1, 0xE9,
+ 0xF9, 0x62, 0xB7, 0xB9, 0x53, 0x75, 0x5B, 0x8B,
+ 0xCC, 0x6C, 0x18, 0x49, 0x89, 0x31, 0xB0, 0x92,
+ 0x6F, 0xDF, 0x03, 0x57, 0xF3, 0x58, 0xCA, 0x2B,
+ 0x93, 0xA1, 0xD6, 0x24, 0x29, 0xCD, 0x59, 0x1C,
+ 0x83, 0xB3, 0x42, 0xBF, 0x82, 0xB4, 0x11, 0x4A,
+ 0x08, 0xEE, 0x76, 0x4D, 0x12, 0xDC, 0xE6, 0xC2,
+ 0x56, 0xBA, 0x86, 0x28, 0x6A, 0x20, 0x51, 0xF7,
+ 0xFF, 0xD8, 0xE7, 0xDD, 0xBB, 0x78, 0xD5, 0x81 };
+
+static const unsigned char table_22[32] = {
+ 0x0B, 0x15, 0x1C, 0x0C, 0x06, 0x0A, 0x1D, 0x16,
+ 0x12, 0x0E, 0x04, 0x11, 0x1F, 0x0F, 0x07, 0x02,
+ 0x17, 0x13, 0x19, 0x18, 0x0D, 0x10, 0x1A, 0x05,
+ 0x03, 0x00, 0x01, 0x08, 0x09, 0x14, 0x1B, 0x1E };
+
+static const unsigned char table_23[256] = {
+ 0x36, 0x53, 0x2D, 0xD0, 0x7A, 0xF0, 0xD5, 0x1C,
+ 0x50, 0x61, 0x9A, 0x90, 0x0B, 0x29, 0x20, 0x77,
+ 0xF1, 0x82, 0xFE, 0xC1, 0xA7, 0xB6, 0x78, 0x87,
+ 0x02, 0x05, 0xCB, 0x28, 0xAE, 0xD6, 0x17, 0x1A,
+ 0x91, 0x5D, 0xB9, 0xE2, 0xDE, 0x6A, 0x4E, 0x07,
+ 0xAC, 0x38, 0x13, 0x3B, 0x46, 0xFD, 0xB7, 0xD1,
+ 0x79, 0xFB, 0x58, 0x76, 0x08, 0x47, 0x95, 0xA6,
+ 0x99, 0x9E, 0x12, 0x67, 0xC2, 0xED, 0x9C, 0x1B,
+ 0x89, 0x71, 0xB5, 0x4A, 0xAA, 0x5F, 0x34, 0x85,
+ 0x40, 0x2B, 0x9F, 0x37, 0x7C, 0x0F, 0xD4, 0x75,
+ 0x48, 0x27, 0x2E, 0xC9, 0xEB, 0x06, 0xDF, 0x8C,
+ 0x14, 0xAF, 0xEE, 0xA2, 0x74, 0x45, 0x8D, 0x70,
+ 0x6B, 0xD7, 0x56, 0xCF, 0xBC, 0x7B, 0x01, 0xC8,
+ 0x54, 0xB0, 0x3C, 0x39, 0xFA, 0x81, 0xDC, 0xBB,
+ 0x0D, 0xB2, 0xAD, 0x93, 0xC7, 0x8A, 0x73, 0x6C,
+ 0xC3, 0x04, 0x2F, 0xEF, 0x52, 0x33, 0x9D, 0x1E,
+ 0xC5, 0x65, 0x23, 0xD8, 0xB1, 0xD2, 0xE5, 0x25,
+ 0x2C, 0xE6, 0x92, 0xB4, 0xF7, 0xF4, 0x8F, 0x6E,
+ 0xE8, 0x5A, 0x8E, 0x7D, 0x4C, 0xB3, 0xFF, 0x41,
+ 0x26, 0xE3, 0x30, 0x69, 0xF8, 0x80, 0x57, 0x4F,
+ 0xA0, 0x7F, 0x66, 0x68, 0xE1, 0x7E, 0x0E, 0x31,
+ 0xE7, 0xEA, 0x3E, 0x8B, 0x4B, 0x94, 0xE9, 0xCD,
+ 0x19, 0x35, 0xA3, 0x98, 0xD9, 0x5B, 0x44, 0x2A,
+ 0xE0, 0x6D, 0xF3, 0xE4, 0x72, 0x18, 0x03, 0x59,
+ 0x84, 0x09, 0xA1, 0x9B, 0xBD, 0xDA, 0x4D, 0x63,
+ 0xCC, 0x3A, 0x10, 0xFC, 0x3F, 0x0A, 0x88, 0x24,
+ 0xF5, 0x21, 0xC4, 0x6F, 0x1F, 0x42, 0x62, 0x64,
+ 0x51, 0xDD, 0xCA, 0xF9, 0x22, 0xCE, 0xA8, 0x86,
+ 0xBA, 0xB8, 0x5C, 0xAB, 0x32, 0x00, 0x0C, 0xF2,
+ 0x83, 0xDB, 0xF6, 0x60, 0x3D, 0x16, 0xEC, 0x11,
+ 0xA4, 0xBE, 0x96, 0x5E, 0x97, 0xD3, 0xA5, 0x55,
+ 0x1D, 0x15, 0xC6, 0xBF, 0xA9, 0x43, 0xC0, 0x49 };
+
+static const unsigned char table_24[256] = {
+ 0xDC, 0x5A, 0xE6, 0x59, 0x64, 0xDA, 0x58, 0x40,
+ 0x95, 0xF8, 0x2A, 0xE0, 0x39, 0x7E, 0x32, 0x89,
+ 0x09, 0x93, 0xED, 0x55, 0xC3, 0x5B, 0x1A, 0xD1,
+ 0xA5, 0x8B, 0x0F, 0x13, 0xC9, 0xE1, 0x34, 0xD0,
+ 0xB6, 0xA2, 0xD9, 0x52, 0x57, 0x83, 0xFD, 0xE9,
+ 0xAC, 0x73, 0x6E, 0x21, 0xF1, 0x0E, 0x25, 0xCC,
+ 0x36, 0xFB, 0xF7, 0x92, 0x15, 0x30, 0x54, 0x91,
+ 0xD6, 0x9E, 0xAA, 0x35, 0x70, 0xB2, 0xC0, 0x27,
+ 0xFE, 0x04, 0xBC, 0xC7, 0x02, 0xFA, 0x7D, 0xE3,
+ 0xBE, 0x62, 0x79, 0x2B, 0x31, 0x6A, 0x8F, 0x7F,
+ 0x56, 0xF0, 0xB4, 0x0C, 0x1F, 0x68, 0xB7, 0xB9,
+ 0x0B, 0x14, 0x3E, 0xA9, 0x4B, 0x03, 0x10, 0xEE,
+ 0x2C, 0xAB, 0x8A, 0x77, 0xB1, 0xE7, 0xCA, 0xD4,
+ 0x98, 0x01, 0xAD, 0x1E, 0x50, 0x26, 0x82, 0x44,
+ 0xF3, 0xBF, 0xD3, 0x6B, 0x33, 0x0A, 0x3C, 0x5D,
+ 0xCE, 0x81, 0xC5, 0x78, 0x9F, 0xB8, 0x23, 0xDB,
+ 0x4E, 0xA1, 0x41, 0x76, 0xAE, 0x51, 0x86, 0x06,
+ 0x7A, 0x66, 0xA0, 0x5E, 0x29, 0x17, 0x84, 0x4A,
+ 0xB0, 0x3B, 0x3D, 0x71, 0x07, 0x7B, 0x0D, 0x9A,
+ 0x6F, 0x9B, 0x5C, 0x88, 0xB3, 0xD7, 0x24, 0xD5,
+ 0x48, 0xF5, 0xE8, 0xE4, 0xCF, 0x16, 0xA4, 0xC8,
+ 0xEF, 0x42, 0x22, 0xEC, 0x47, 0x69, 0x90, 0x63,
+ 0xE2, 0x1B, 0x87, 0x85, 0x3F, 0xDE, 0x8C, 0x60,
+ 0x99, 0xE5, 0x8E, 0x4F, 0xF4, 0xBA, 0xB5, 0x9C,
+ 0x37, 0x67, 0xBD, 0xA6, 0x97, 0xDD, 0xCB, 0x43,
+ 0x45, 0x19, 0x49, 0x1C, 0x75, 0xC1, 0xBB, 0xF2,
+ 0x46, 0xFC, 0x53, 0x9D, 0xD8, 0xA3, 0xDF, 0x2F,
+ 0xEB, 0x72, 0x94, 0xA8, 0x6D, 0xC6, 0x28, 0x4C,
+ 0x00, 0x38, 0xC2, 0x65, 0x05, 0x2E, 0xD2, 0x12,
+ 0xFF, 0x18, 0x61, 0x6C, 0x7C, 0x11, 0xAF, 0x96,
+ 0xCD, 0x20, 0x74, 0x08, 0x1D, 0xC4, 0xF9, 0x4D,
+ 0xEA, 0x8D, 0x2D, 0x5F, 0xF6, 0xA7, 0x80, 0x3A };
+
+static const unsigned char table_25[32] = {
+ 0x0A, 0x11, 0x17, 0x03, 0x05, 0x0B, 0x18, 0x13,
+ 0x09, 0x02, 0x00, 0x1C, 0x0C, 0x08, 0x1B, 0x14,
+ 0x06, 0x0E, 0x01, 0x0D, 0x16, 0x1E, 0x1D, 0x19,
+ 0x0F, 0x1A, 0x10, 0x04, 0x12, 0x15, 0x07, 0x1F };
+
+static const unsigned char table_26[32] = {
+ 0x19, 0x13, 0x1B, 0x01, 0x1C, 0x0D, 0x0C, 0x15,
+ 0x0B, 0x00, 0x1A, 0x0F, 0x12, 0x16, 0x08, 0x0A,
+ 0x03, 0x06, 0x14, 0x10, 0x18, 0x04, 0x11, 0x1D,
+ 0x1F, 0x07, 0x17, 0x05, 0x02, 0x0E, 0x1E, 0x09 };
+
+static const unsigned char table_27[256] = {
+ 0x72, 0xF0, 0x14, 0xCB, 0x61, 0xA5, 0xB2, 0x02,
+ 0x75, 0x22, 0xC3, 0x9D, 0x5A, 0x63, 0xFA, 0x5F,
+ 0xD9, 0x55, 0x58, 0x43, 0x24, 0x7D, 0x77, 0x93,
+ 0xBA, 0x50, 0x1D, 0xF7, 0x49, 0x18, 0xB0, 0x42,
+ 0xBB, 0xEC, 0x52, 0x38, 0xDC, 0xC8, 0x16, 0x54,
+ 0x17, 0x19, 0x89, 0x67, 0x33, 0x3C, 0x0A, 0xAD,
+ 0xC9, 0xDE, 0x81, 0xED, 0xBD, 0x0E, 0x0B, 0x6D,
+ 0x46, 0x30, 0x35, 0x2B, 0x8C, 0xA0, 0x1C, 0x0D,
+ 0xFD, 0xA1, 0x70, 0xC6, 0xD8, 0x41, 0xB3, 0xC0,
+ 0x44, 0xEB, 0x92, 0xBE, 0x6B, 0x98, 0x1A, 0x76,
+ 0x71, 0xC5, 0x51, 0x56, 0x80, 0xFC, 0x01, 0x53,
+ 0x4B, 0xD0, 0x8B, 0xD2, 0x7B, 0xE7, 0x15, 0x5D,
+ 0xE5, 0xA6, 0x8A, 0xD3, 0x9B, 0xF4, 0x69, 0x23,
+ 0xE8, 0xB6, 0xC7, 0xE2, 0x73, 0x9F, 0x88, 0xDF,
+ 0xB4, 0x28, 0xEE, 0xC2, 0x94, 0xB8, 0xF9, 0x7F,
+ 0x4A, 0x57, 0x06, 0xF6, 0xBF, 0xC1, 0xAB, 0xFB,
+ 0xA4, 0x8E, 0xD1, 0xD7, 0xF5, 0x7C, 0xA3, 0x1E,
+ 0x3B, 0x32, 0x03, 0xAA, 0x90, 0x5C, 0x48, 0xE0,
+ 0xE3, 0xCF, 0xD4, 0xEF, 0x59, 0xD5, 0x1B, 0x34,
+ 0x1F, 0x95, 0xCE, 0x7A, 0x20, 0x26, 0x87, 0xB7,
+ 0x78, 0x9C, 0x4F, 0xA2, 0x12, 0x97, 0x27, 0x3F,
+ 0xFF, 0x07, 0x84, 0x96, 0x04, 0xAF, 0xA8, 0xEA,
+ 0x2C, 0x6C, 0xAE, 0x37, 0x91, 0xA9, 0x10, 0xDB,
+ 0xCD, 0xDA, 0x08, 0x99, 0xF1, 0x4D, 0xCC, 0x68,
+ 0x79, 0x2E, 0xB1, 0x39, 0x9E, 0xE9, 0x2F, 0x6A,
+ 0x3D, 0x0F, 0x85, 0x8D, 0xCA, 0x29, 0x86, 0xD6,
+ 0xDD, 0x05, 0x25, 0x3A, 0x40, 0x21, 0x45, 0xAC,
+ 0x11, 0xF3, 0xA7, 0x09, 0x2A, 0x31, 0xE4, 0x0C,
+ 0xF8, 0x6E, 0x3E, 0xB5, 0x82, 0xFE, 0x74, 0x13,
+ 0x65, 0xE1, 0x2D, 0x8F, 0xE6, 0xC4, 0x00, 0x5B,
+ 0x4E, 0xB9, 0x66, 0xF2, 0x62, 0x36, 0x4C, 0x83,
+ 0x5E, 0x6F, 0x47, 0x64, 0xBC, 0x9A, 0x60, 0x7E };
+
+static const unsigned char table_28[32] = {
+ 0x15, 0x05, 0x08, 0x19, 0x02, 0x18, 0x1E, 0x07,
+ 0x0D, 0x0C, 0x1A, 0x06, 0x17, 0x03, 0x10, 0x09,
+ 0x01, 0x11, 0x1C, 0x04, 0x0F, 0x1F, 0x12, 0x0B,
+ 0x1B, 0x13, 0x0A, 0x16, 0x0E, 0x00, 0x1D, 0x14 };
+
+static const unsigned char table_29[256] = {
+ 0x34, 0x59, 0x05, 0x13, 0x09, 0x1D, 0xDF, 0x77,
+ 0x11, 0xA5, 0x92, 0x27, 0xCD, 0x7B, 0x5E, 0x80,
+ 0xF9, 0x50, 0x18, 0x24, 0xD4, 0x70, 0x4A, 0x39,
+ 0x66, 0xA4, 0xDB, 0xE9, 0xED, 0x48, 0xD9, 0xE7,
+ 0x32, 0xDA, 0x53, 0x8F, 0x72, 0xE1, 0xF6, 0xFE,
+ 0xD3, 0xAD, 0xA6, 0x1F, 0xB9, 0xD1, 0x0F, 0x4C,
+ 0x23, 0x90, 0x68, 0xBC, 0x4B, 0x9B, 0x3D, 0xAB,
+ 0xF0, 0x94, 0x4F, 0x1C, 0x07, 0x65, 0x7F, 0x01,
+ 0x5C, 0xD7, 0x21, 0x8C, 0xBF, 0x8E, 0xB8, 0x86,
+ 0x6C, 0x33, 0x36, 0xC1, 0x06, 0x74, 0x37, 0x84,
+ 0x41, 0xAE, 0x67, 0x29, 0xB4, 0x85, 0xCE, 0x2A,
+ 0xCB, 0x1E, 0x61, 0x9E, 0x7A, 0x44, 0x3E, 0x89,
+ 0x14, 0x20, 0x19, 0xBB, 0xE0, 0xAA, 0xCF, 0x83,
+ 0xA8, 0x93, 0x43, 0xF2, 0xAC, 0x0E, 0xD2, 0xCC,
+ 0xDD, 0x47, 0x58, 0xC9, 0xCA, 0x1B, 0x54, 0x6E,
+ 0x8A, 0x79, 0xF8, 0xC4, 0xFB, 0xD5, 0x91, 0xDE,
+ 0x12, 0x31, 0x99, 0xFA, 0x6D, 0xC8, 0x57, 0xEC,
+ 0xB7, 0x28, 0x0C, 0x52, 0xF1, 0x0D, 0xB1, 0x9A,
+ 0x26, 0x98, 0x16, 0x7D, 0xD0, 0x2E, 0x8B, 0xD8,
+ 0xE6, 0xE8, 0x30, 0xFD, 0x7C, 0x64, 0x5A, 0xBD,
+ 0x87, 0xE2, 0xA1, 0x3F, 0xC3, 0x38, 0x96, 0xA3,
+ 0x2D, 0xF3, 0x3A, 0xEE, 0xC0, 0x10, 0xEA, 0x6F,
+ 0x8D, 0x03, 0xF4, 0x51, 0x97, 0x7E, 0x56, 0x42,
+ 0x3C, 0x5D, 0x5F, 0xF5, 0x6A, 0xAF, 0xE4, 0xBE,
+ 0xBA, 0x78, 0xA0, 0x5B, 0x49, 0xA7, 0xC7, 0x9C,
+ 0x63, 0x6B, 0x00, 0x17, 0x69, 0x75, 0x3B, 0x40,
+ 0xEF, 0x45, 0xB5, 0x2B, 0x2F, 0x02, 0xC6, 0x22,
+ 0x9F, 0xFC, 0x73, 0x08, 0x81, 0xB2, 0x2C, 0x71,
+ 0x35, 0xA2, 0xE3, 0xB3, 0x9D, 0xC5, 0x0A, 0xC2,
+ 0x25, 0x82, 0xDC, 0x88, 0xA9, 0xE5, 0xF7, 0xEB,
+ 0xD6, 0x60, 0x76, 0x55, 0x0B, 0x4E, 0xFF, 0x1A,
+ 0x46, 0x62, 0xB6, 0xB0, 0x15, 0x04, 0x95, 0x4D };
+
+static const unsigned char table_30[32] = {
+ 0x00, 0x1C, 0x0E, 0x0C, 0x06, 0x16, 0x09, 0x12,
+ 0x01, 0x13, 0x0B, 0x14, 0x11, 0x08, 0x04, 0x18,
+ 0x10, 0x1B, 0x15, 0x03, 0x02, 0x19, 0x1A, 0x17,
+ 0x1E, 0x1F, 0x0F, 0x07, 0x0D, 0x05, 0x1D, 0x0A };
+
+static const unsigned char table_31[256] = {
+ 0xDF, 0xD8, 0x3F, 0xBC, 0x5F, 0xC9, 0x8E, 0x4C,
+ 0x0B, 0x3C, 0xE5, 0xBF, 0x39, 0xD5, 0x30, 0xDD,
+ 0x23, 0xC7, 0x72, 0x63, 0x1F, 0xF8, 0x96, 0x31,
+ 0x70, 0xD6, 0x9E, 0xE8, 0x9D, 0xF5, 0xEF, 0x65,
+ 0xC2, 0x50, 0x62, 0x77, 0xD3, 0x6C, 0x1A, 0x91,
+ 0xBB, 0xFF, 0xCD, 0x9B, 0xB6, 0xBA, 0xB8, 0x7A,
+ 0x14, 0xA7, 0x74, 0x89, 0xD4, 0x6E, 0x19, 0x69,
+ 0xAB, 0x01, 0x15, 0x0E, 0x87, 0x55, 0x79, 0x1C,
+ 0x18, 0xBE, 0xA8, 0xDB, 0x52, 0xD2, 0x8F, 0x7E,
+ 0x81, 0xAF, 0xFD, 0x5C, 0x3E, 0x1B, 0xB9, 0xB2,
+ 0xB7, 0x51, 0x57, 0x8C, 0xCF, 0x5B, 0xA4, 0x75,
+ 0xDE, 0x22, 0x8B, 0x10, 0x12, 0xC8, 0x35, 0x2D,
+ 0x45, 0xB5, 0xF0, 0x47, 0x88, 0x16, 0xEB, 0x67,
+ 0xD9, 0x0C, 0xF1, 0xC1, 0x34, 0x33, 0xC6, 0x78,
+ 0xB3, 0x26, 0xE3, 0xBD, 0x5D, 0x4E, 0x66, 0xE4,
+ 0xD7, 0xC4, 0xE6, 0xA1, 0xB0, 0x95, 0x2B, 0x9A,
+ 0x4A, 0x3A, 0xCB, 0x40, 0xE1, 0x60, 0x49, 0xCC,
+ 0x03, 0xAC, 0xF4, 0x97, 0x32, 0x0F, 0x38, 0x17,
+ 0xF9, 0xE0, 0xD1, 0xFB, 0x04, 0x5E, 0x68, 0x06,
+ 0xAE, 0xFA, 0xAA, 0xED, 0x24, 0x0D, 0x00, 0x61,
+ 0x20, 0xA3, 0x7B, 0x6B, 0x76, 0x27, 0xEA, 0xCE,
+ 0x6A, 0x82, 0x9F, 0x6D, 0x9C, 0x64, 0xA2, 0x11,
+ 0x37, 0x2A, 0xCA, 0x84, 0x25, 0x7C, 0x2F, 0x8D,
+ 0x90, 0xE7, 0x09, 0x93, 0xF3, 0x43, 0x71, 0xEC,
+ 0xA9, 0x7D, 0x94, 0xA6, 0x3D, 0x7F, 0x54, 0x44,
+ 0x99, 0x80, 0x41, 0xC0, 0xA0, 0x8A, 0x1E, 0xDC,
+ 0x08, 0xD0, 0x2E, 0x42, 0x05, 0x85, 0x86, 0xFE,
+ 0x3B, 0x59, 0xC3, 0x58, 0x13, 0xB4, 0x36, 0xA5,
+ 0x73, 0x28, 0x29, 0xDA, 0x4F, 0x1D, 0xB1, 0x53,
+ 0x46, 0x2C, 0xF2, 0x4D, 0xAD, 0xFC, 0x83, 0x02,
+ 0x6F, 0x07, 0xE9, 0xEE, 0x21, 0x98, 0x5A, 0xC5,
+ 0x92, 0x48, 0xF7, 0x0A, 0xF6, 0xE2, 0x4B, 0x56 };
+
+static const unsigned char table_32[256] = {
+ 0x7B, 0x0F, 0x56, 0x2F, 0x1E, 0x2A, 0x7A, 0xD1,
+ 0x02, 0x91, 0x4E, 0x37, 0x6C, 0x10, 0xA7, 0xF2,
+ 0x38, 0xAC, 0x9E, 0x2B, 0x5E, 0x23, 0xE3, 0x19,
+ 0x9B, 0xF6, 0xB0, 0x59, 0x14, 0xB9, 0xA9, 0x46,
+ 0x84, 0x1D, 0xC0, 0x98, 0xF3, 0xE1, 0xE8, 0x94,
+ 0x52, 0x35, 0xBA, 0xD8, 0x07, 0xEF, 0x31, 0xF8,
+ 0x03, 0x76, 0x9C, 0xD7, 0xE4, 0x8B, 0xAF, 0x60,
+ 0xDD, 0x51, 0x00, 0xDF, 0x11, 0x7F, 0x1C, 0xED,
+ 0x49, 0xC9, 0xF4, 0x87, 0x64, 0xFC, 0x5D, 0xAD,
+ 0x88, 0x85, 0xF7, 0x5A, 0x92, 0xDB, 0x72, 0x1A,
+ 0x83, 0x15, 0x30, 0x24, 0x9F, 0xFF, 0x5B, 0xF1,
+ 0xD2, 0xFD, 0xC2, 0xB5, 0x25, 0x22, 0x18, 0x3D,
+ 0xCD, 0x97, 0x8C, 0xCC, 0x78, 0x90, 0xAA, 0x5F,
+ 0x0A, 0x57, 0x05, 0x61, 0xD4, 0xA0, 0x3A, 0xDE,
+ 0x3B, 0xF9, 0x65, 0x68, 0x4F, 0x28, 0xFA, 0xEB,
+ 0x63, 0x2D, 0x8D, 0xD0, 0xA1, 0xFE, 0x12, 0x96,
+ 0x3C, 0x42, 0x29, 0xD6, 0xA4, 0x34, 0xBD, 0x70,
+ 0x89, 0xBE, 0xF5, 0x79, 0xAB, 0x8F, 0x32, 0xB4,
+ 0xEE, 0xE7, 0x2C, 0x04, 0x4B, 0xD5, 0xB1, 0x54,
+ 0xF0, 0xDA, 0x16, 0x77, 0xA6, 0x53, 0xB2, 0xE2,
+ 0x73, 0xBF, 0x17, 0xA8, 0x75, 0x26, 0xE0, 0xBC,
+ 0x0C, 0x71, 0xFB, 0x6D, 0x7E, 0xC5, 0xEA, 0x21,
+ 0x9D, 0x95, 0x8E, 0xA5, 0x48, 0xB8, 0x7D, 0xCB,
+ 0x01, 0x99, 0xE5, 0xBB, 0x82, 0xC4, 0xCA, 0xC1,
+ 0x58, 0x6E, 0x5C, 0x7C, 0xDC, 0x33, 0xB6, 0xC3,
+ 0x09, 0xC7, 0x1F, 0x0D, 0x43, 0x6F, 0xE9, 0x86,
+ 0x27, 0xC8, 0x44, 0xB3, 0xD3, 0xCF, 0x08, 0x66,
+ 0x1B, 0x20, 0x4D, 0xD9, 0xC6, 0x36, 0x40, 0x74,
+ 0x62, 0x6A, 0x55, 0xEC, 0x06, 0x2E, 0xE6, 0x80,
+ 0x13, 0x93, 0x50, 0xCE, 0x69, 0x3E, 0x67, 0x4A,
+ 0x81, 0x4C, 0x0B, 0x3F, 0xB7, 0x0E, 0x39, 0xAE,
+ 0x47, 0x6B, 0x8A, 0xA2, 0x9A, 0xA3, 0x45, 0x41 };
+
+static const unsigned char table_33[256] = {
+ 0xDE, 0xD3, 0x79, 0x67, 0x13, 0x5C, 0x04, 0xF2,
+ 0xD9, 0x9F, 0x65, 0x56, 0xCC, 0x3B, 0xA4, 0x9A,
+ 0x08, 0xBF, 0x26, 0xB2, 0xA7, 0x5E, 0xAA, 0xCA,
+ 0xBB, 0x2B, 0x38, 0x3F, 0xD8, 0x87, 0xFA, 0x5D,
+ 0x73, 0x8E, 0x1E, 0x93, 0x05, 0xAF, 0x3E, 0x4E,
+ 0x90, 0xDB, 0x0B, 0x33, 0x0D, 0x2F, 0x86, 0x4F,
+ 0xFD, 0xD0, 0x39, 0xB1, 0x8A, 0x1A, 0x20, 0xE6,
+ 0xCF, 0xA2, 0x82, 0xDF, 0x42, 0x9C, 0x30, 0x40,
+ 0xE3, 0xB0, 0x88, 0x5A, 0xEC, 0x25, 0xE2, 0xC4,
+ 0x12, 0x54, 0x50, 0x97, 0x96, 0x21, 0x23, 0x7B,
+ 0x1D, 0x61, 0x52, 0x34, 0x7D, 0x69, 0x16, 0xC3,
+ 0x31, 0xF8, 0x48, 0x19, 0x95, 0x01, 0x29, 0x8C,
+ 0x15, 0xAC, 0x84, 0x74, 0xAB, 0x70, 0xDA, 0x36,
+ 0xD6, 0x8F, 0xFE, 0x35, 0xD7, 0x2E, 0x89, 0x07,
+ 0x62, 0x17, 0xDC, 0x92, 0x45, 0x83, 0xB5, 0xE5,
+ 0x8B, 0xC0, 0x27, 0x85, 0x7C, 0x9D, 0x55, 0x81,
+ 0x71, 0xCD, 0xC9, 0x00, 0x02, 0xC1, 0x0A, 0x37,
+ 0xED, 0xEA, 0xC2, 0x98, 0x49, 0x06, 0x1C, 0x78,
+ 0x64, 0xCE, 0x9E, 0x4C, 0x7A, 0xB4, 0x43, 0x0F,
+ 0xE0, 0x7E, 0xBC, 0x5B, 0x51, 0xE7, 0x18, 0xF9,
+ 0x11, 0xA1, 0xF5, 0xC7, 0xCB, 0x4D, 0x6A, 0x0E,
+ 0x57, 0xF1, 0xFB, 0xB3, 0x99, 0xF0, 0x32, 0xD5,
+ 0xA9, 0x4B, 0x6F, 0x6D, 0xA8, 0xC5, 0xDD, 0x7F,
+ 0xEB, 0xBE, 0xFC, 0x2C, 0x22, 0x58, 0x03, 0x9B,
+ 0x77, 0xF7, 0xBD, 0xBA, 0xD2, 0x6B, 0xAD, 0x5F,
+ 0x10, 0x6E, 0x09, 0xD1, 0x1B, 0x24, 0xEF, 0x72,
+ 0x3D, 0x59, 0x28, 0xE1, 0xB7, 0x44, 0x8D, 0xB8,
+ 0xAE, 0x2D, 0x60, 0xA6, 0xC8, 0x0C, 0xF4, 0x41,
+ 0xA3, 0x68, 0x46, 0x6C, 0x76, 0xA0, 0xB6, 0x66,
+ 0xE4, 0x1F, 0x75, 0x4A, 0xFF, 0x2A, 0x94, 0xD4,
+ 0xF3, 0xE9, 0x91, 0x63, 0xA5, 0xB9, 0xE8, 0x14,
+ 0x80, 0x3C, 0xEE, 0x47, 0xC6, 0x3A, 0x53, 0xF6 };
+
+static const unsigned char table_34[256] = {
+ 0xF0, 0xE9, 0x3E, 0xD6, 0x89, 0xC8, 0xC7, 0x23,
+ 0x75, 0x26, 0x5F, 0x9C, 0x57, 0xB8, 0x2A, 0x29,
+ 0xE5, 0xB5, 0x68, 0xA4, 0x92, 0x46, 0x40, 0x7F,
+ 0xF2, 0xBC, 0x6A, 0xE0, 0x8F, 0x0F, 0xE4, 0x3A,
+ 0xE1, 0x30, 0x84, 0x6E, 0x82, 0x8E, 0x56, 0xC5,
+ 0x32, 0x85, 0xFB, 0x59, 0x43, 0x41, 0xC2, 0xF6,
+ 0x67, 0x5A, 0x7C, 0x34, 0xA1, 0xD0, 0x4B, 0xAC,
+ 0x61, 0x72, 0x6B, 0xAF, 0xC4, 0x20, 0x9A, 0xD4,
+ 0x74, 0x8D, 0x87, 0x83, 0xE2, 0x62, 0x6D, 0xE6,
+ 0xE7, 0xF9, 0x76, 0xCB, 0x18, 0x90, 0x4F, 0xFF,
+ 0xD3, 0x3C, 0x08, 0x79, 0x93, 0x2D, 0x95, 0xA3,
+ 0xDD, 0x5B, 0xDA, 0x7A, 0x39, 0x4D, 0xC1, 0x2E,
+ 0xCC, 0x53, 0xE8, 0xA2, 0xCF, 0x15, 0x78, 0x1C,
+ 0xEB, 0x9B, 0x7B, 0xAD, 0x31, 0x2F, 0xE3, 0xC9,
+ 0x3B, 0xEC, 0x2C, 0x49, 0x02, 0x52, 0x28, 0xBA,
+ 0x0C, 0x19, 0x24, 0xF7, 0x97, 0x09, 0xA6, 0xA0,
+ 0xDF, 0xD1, 0xD2, 0xDC, 0x51, 0xA5, 0x94, 0xFD,
+ 0x71, 0xF5, 0x50, 0x0A, 0x69, 0x25, 0x88, 0x5C,
+ 0x91, 0xD5, 0x47, 0x0B, 0x27, 0x13, 0x96, 0xD9,
+ 0xF1, 0xA9, 0x70, 0xC3, 0xBE, 0x42, 0x4E, 0x4A,
+ 0xB1, 0x07, 0xA7, 0x54, 0xFE, 0x48, 0x9F, 0x63,
+ 0x17, 0xAE, 0xB9, 0x58, 0x21, 0x35, 0xED, 0x5D,
+ 0x9D, 0x3D, 0xB4, 0xFC, 0xEA, 0x8C, 0x80, 0xA8,
+ 0x1E, 0xB0, 0xDE, 0x0D, 0x11, 0x6F, 0x04, 0x12,
+ 0xF4, 0x10, 0x64, 0x0E, 0xD7, 0x2B, 0xB3, 0x8B,
+ 0xB7, 0x01, 0x86, 0xCA, 0xFA, 0x9E, 0xEE, 0x66,
+ 0x37, 0x65, 0x81, 0x38, 0x1F, 0xAA, 0x73, 0xAB,
+ 0xBD, 0xDB, 0x14, 0xCD, 0x00, 0xBB, 0x98, 0x44,
+ 0x45, 0xB6, 0x99, 0x5E, 0xD8, 0x1D, 0x36, 0xF8,
+ 0x55, 0x6C, 0x16, 0x7E, 0x77, 0x3F, 0x22, 0xEF,
+ 0xF3, 0x7D, 0xC6, 0xCE, 0x8A, 0xB2, 0x33, 0x4C,
+ 0x03, 0x05, 0xBF, 0x06, 0x1B, 0xC0, 0x1A, 0x60 };
+
+static const unsigned char table_35[256] = {
+ 0xCC, 0x40, 0xEF, 0x1F, 0xDB, 0xE5, 0x71, 0x51,
+ 0x3B, 0x0F, 0x7D, 0x9C, 0x83, 0x17, 0x6F, 0x8F,
+ 0x13, 0xDC, 0x7F, 0xA9, 0xA5, 0xA2, 0x9D, 0xDF,
+ 0xE7, 0x97, 0x2A, 0x30, 0xF2, 0x73, 0xCF, 0x87,
+ 0x29, 0xB3, 0x86, 0x43, 0x09, 0xB0, 0x2E, 0x10,
+ 0x8E, 0xBC, 0x57, 0xBA, 0x68, 0xF5, 0xCB, 0x89,
+ 0x32, 0xC1, 0x6B, 0x1E, 0xAC, 0xB2, 0x2D, 0x6A,
+ 0x50, 0xEB, 0x18, 0x06, 0xD8, 0xC7, 0x36, 0x31,
+ 0xC5, 0xAF, 0x12, 0x15, 0xB7, 0x37, 0x4E, 0x01,
+ 0x14, 0x21, 0x44, 0x5E, 0xF4, 0xB4, 0xE4, 0x65,
+ 0xFE, 0x8A, 0xEA, 0x0D, 0xBB, 0x45, 0x8B, 0x25,
+ 0x80, 0x35, 0x61, 0xA8, 0x4A, 0x47, 0xAB, 0x91,
+ 0x1B, 0x1C, 0x05, 0x4D, 0x5A, 0xD4, 0xF1, 0x9B,
+ 0x0E, 0x98, 0xCA, 0x96, 0x42, 0x7E, 0x03, 0x5F,
+ 0xE2, 0x90, 0xBF, 0x82, 0xC9, 0x3D, 0xE0, 0x5C,
+ 0xFA, 0x3E, 0x41, 0x11, 0x79, 0x58, 0x24, 0x2C,
+ 0xC0, 0x28, 0x5D, 0xA3, 0xDE, 0x67, 0xFF, 0xA4,
+ 0x63, 0xB1, 0x22, 0x04, 0xFD, 0x70, 0x39, 0x46,
+ 0xAA, 0x0A, 0x34, 0x6C, 0xD7, 0x92, 0xA1, 0x3C,
+ 0x19, 0xD5, 0xFC, 0xAD, 0x85, 0x07, 0x00, 0x23,
+ 0xF8, 0x69, 0x56, 0x53, 0x55, 0x7A, 0xB8, 0xC8,
+ 0xDA, 0xCE, 0xF3, 0x5B, 0x49, 0xE1, 0xBE, 0xEC,
+ 0x1A, 0x88, 0x02, 0xBD, 0xF7, 0x1D, 0x64, 0xA0,
+ 0x4F, 0xD9, 0xE3, 0x95, 0xC6, 0x48, 0x2B, 0xED,
+ 0x9A, 0x9E, 0x26, 0x6E, 0xD1, 0x94, 0xB9, 0x93,
+ 0xDD, 0xF6, 0xA6, 0xFB, 0xC2, 0xB6, 0x0C, 0xE9,
+ 0x77, 0xF9, 0xCD, 0x08, 0xEE, 0x3F, 0xE6, 0x75,
+ 0xD6, 0x84, 0x76, 0x8C, 0xF0, 0xAE, 0xD2, 0x78,
+ 0x2F, 0x4B, 0x16, 0x4C, 0x27, 0x81, 0x6D, 0x99,
+ 0x38, 0xD3, 0x54, 0x62, 0x74, 0x20, 0x60, 0xC3,
+ 0x7C, 0x8D, 0x72, 0x0B, 0x52, 0xE8, 0xA7, 0x3A,
+ 0x59, 0xC4, 0x9F, 0xD0, 0x66, 0x7B, 0x33, 0xB5 };
+
+static const unsigned char table_36[256] = {
+ 0xDB, 0x6F, 0xFE, 0xB3, 0x5C, 0x1F, 0xB8, 0xBF,
+ 0xA3, 0x71, 0x11, 0x56, 0x90, 0xE2, 0x63, 0x18,
+ 0x83, 0x51, 0x21, 0xEB, 0x66, 0x08, 0xA6, 0xA5,
+ 0x1C, 0xF5, 0x14, 0x24, 0x41, 0x33, 0xA7, 0xB5,
+ 0xC7, 0x79, 0x57, 0x50, 0x85, 0xE1, 0x6D, 0xF7,
+ 0x0E, 0xDE, 0x67, 0xAB, 0xA1, 0x0B, 0xD9, 0x4A,
+ 0xCA, 0x36, 0xEA, 0xDA, 0x16, 0xEF, 0x9F, 0x0A,
+ 0x09, 0x9A, 0x1D, 0xC5, 0xD7, 0x5F, 0x19, 0xDC,
+ 0x15, 0x06, 0xE8, 0x94, 0x0C, 0x0D, 0xC9, 0x7C,
+ 0xD6, 0x62, 0xBB, 0x49, 0xF9, 0x61, 0x07, 0x9B,
+ 0x28, 0xC3, 0x9E, 0xF4, 0x38, 0x78, 0x20, 0x03,
+ 0xA2, 0x7F, 0xC2, 0x9D, 0x5E, 0x65, 0x52, 0x17,
+ 0x2E, 0x1B, 0xB0, 0x42, 0xBC, 0xFD, 0xF1, 0xD2,
+ 0xF6, 0x60, 0xD3, 0x29, 0x97, 0x3D, 0x0F, 0xB1,
+ 0x2F, 0x22, 0xDD, 0x80, 0x32, 0xF8, 0xAD, 0x70,
+ 0xB9, 0x8F, 0x37, 0xCE, 0x46, 0x58, 0xB7, 0x30,
+ 0xED, 0x7A, 0xE9, 0xC0, 0x7D, 0x13, 0x64, 0x23,
+ 0x4E, 0xC8, 0xF0, 0xCC, 0x3B, 0x45, 0x68, 0x8D,
+ 0xBE, 0x8B, 0xD8, 0x43, 0x02, 0x27, 0xE4, 0xAA,
+ 0x10, 0xF2, 0x59, 0x72, 0x40, 0x26, 0x69, 0xE5,
+ 0x05, 0x84, 0x4F, 0xE0, 0x6B, 0xC1, 0xAC, 0x4C,
+ 0xFB, 0x31, 0x77, 0x8E, 0xD4, 0x12, 0xA9, 0xB4,
+ 0xEC, 0x00, 0x76, 0x1E, 0x25, 0xAE, 0xE7, 0x3C,
+ 0x35, 0x93, 0x9C, 0xC4, 0xFC, 0x2D, 0x91, 0x04,
+ 0xAF, 0x53, 0x3F, 0xE6, 0xA4, 0xD0, 0x1A, 0xDF,
+ 0x3A, 0x55, 0x99, 0x01, 0xCB, 0x6C, 0x82, 0x3E,
+ 0x5D, 0xA8, 0x88, 0x54, 0x5B, 0x95, 0xCD, 0x8C,
+ 0x81, 0x34, 0xD1, 0x39, 0xFF, 0xEE, 0xFA, 0x8A,
+ 0x6E, 0x86, 0x92, 0x89, 0xF3, 0x6A, 0xBA, 0x2C,
+ 0xD5, 0x44, 0xC6, 0x96, 0xBD, 0xB2, 0x2B, 0x87,
+ 0x74, 0xA0, 0x73, 0x5A, 0x2A, 0x98, 0x75, 0x47,
+ 0x4B, 0xB6, 0x7B, 0x4D, 0xCF, 0x7E, 0x48, 0xE3 };
+
+static const unsigned char table_37[256] = {
+ 0x1F, 0xD6, 0xB1, 0xB3, 0x40, 0xAD, 0xDE, 0xB7,
+ 0x19, 0xB4, 0xE7, 0x0B, 0x9C, 0x2D, 0xE0, 0xF5,
+ 0xCF, 0x2C, 0x30, 0x65, 0x2F, 0xCD, 0x02, 0x91,
+ 0xCE, 0x2B, 0xBF, 0x78, 0xE6, 0xFA, 0x51, 0x48,
+ 0xFB, 0x4D, 0xBE, 0x71, 0x1A, 0x56, 0xFD, 0x81,
+ 0x33, 0x75, 0x89, 0x96, 0x37, 0x82, 0x9E, 0x93,
+ 0x41, 0x18, 0x5B, 0x2E, 0x22, 0x0F, 0xAF, 0x4B,
+ 0xB9, 0xD5, 0xEE, 0x6C, 0xE4, 0x05, 0xCC, 0x99,
+ 0xE5, 0x3B, 0x62, 0xBD, 0x7B, 0xAA, 0x4A, 0xE2,
+ 0x34, 0x43, 0xF7, 0x39, 0xFE, 0x14, 0x1D, 0xE3,
+ 0xF0, 0xA7, 0x77, 0xDF, 0xA0, 0xD3, 0xAC, 0xD9,
+ 0xEA, 0x76, 0xDD, 0xA4, 0xC5, 0xC9, 0x61, 0xF3,
+ 0xA8, 0xB0, 0x35, 0xE8, 0x68, 0xD4, 0x15, 0xF9,
+ 0x97, 0xED, 0x25, 0x0A, 0x88, 0x8F, 0x06, 0xA3,
+ 0x16, 0x36, 0x32, 0xA2, 0xC6, 0x64, 0xD7, 0x94,
+ 0xD2, 0x6D, 0x74, 0xFC, 0x44, 0x27, 0x5C, 0xFF,
+ 0x60, 0x1E, 0x58, 0x8B, 0x5E, 0xC7, 0x90, 0x17,
+ 0x63, 0xAE, 0xC3, 0x12, 0x13, 0x84, 0xEC, 0x49,
+ 0xA5, 0x9B, 0x31, 0x8D, 0xE1, 0x79, 0xF1, 0x00,
+ 0x28, 0x3D, 0xC2, 0x55, 0x20, 0x52, 0x95, 0x7E,
+ 0x42, 0x1C, 0x66, 0x92, 0x7D, 0xB6, 0xC4, 0xF4,
+ 0x80, 0xB2, 0x72, 0x6E, 0x11, 0xF6, 0x0D, 0x5A,
+ 0xEF, 0x9D, 0x69, 0x9A, 0x45, 0x67, 0x3F, 0xDA,
+ 0x8E, 0x57, 0x09, 0x7C, 0x38, 0xA6, 0x83, 0x87,
+ 0x7A, 0x08, 0x4C, 0x5F, 0x85, 0x7F, 0xD0, 0x04,
+ 0x50, 0xCB, 0xB8, 0x07, 0x24, 0x26, 0x29, 0x46,
+ 0x01, 0x03, 0xC1, 0xD8, 0xDC, 0x0E, 0x3C, 0x4F,
+ 0x53, 0x4E, 0xB5, 0xF8, 0xC0, 0x8A, 0xF2, 0xBB,
+ 0xE9, 0x5D, 0x2A, 0xBA, 0x0C, 0x1B, 0x3A, 0xA9,
+ 0x21, 0x6A, 0x70, 0xBC, 0xEB, 0xA1, 0x54, 0x10,
+ 0x98, 0x9F, 0x23, 0xD1, 0x6B, 0x59, 0x3E, 0xCA,
+ 0x73, 0xC8, 0x86, 0x47, 0xDB, 0xAB, 0x6F, 0x8C };
+
+static const unsigned char table_38[256] = {
+ 0xAA, 0x8D, 0x37, 0x94, 0x99, 0xDD, 0x70, 0x77,
+ 0x78, 0xC9, 0x0F, 0xFA, 0xE2, 0x05, 0xC2, 0x16,
+ 0x02, 0x4D, 0x44, 0x65, 0xAC, 0xB0, 0x39, 0xF8,
+ 0x06, 0x60, 0xD8, 0xE1, 0x19, 0xB4, 0x36, 0x20,
+ 0x59, 0x1D, 0xAD, 0xE4, 0xE8, 0xFF, 0x9D, 0x0D,
+ 0x51, 0x28, 0xE7, 0x8C, 0x0E, 0x97, 0xE3, 0xAE,
+ 0x6A, 0x27, 0x98, 0xDB, 0x26, 0xF6, 0xEC, 0xC6,
+ 0xC0, 0xBD, 0x68, 0x61, 0x83, 0x86, 0xE0, 0x2C,
+ 0xEE, 0x47, 0xF9, 0x5F, 0x6D, 0xBA, 0xE9, 0x72,
+ 0x8A, 0xBB, 0x08, 0x29, 0xAF, 0x1C, 0xD3, 0x5D,
+ 0xF7, 0x87, 0x6F, 0x9A, 0x2F, 0x11, 0xD9, 0x90,
+ 0x66, 0x8E, 0xEB, 0xB1, 0x2E, 0xEA, 0xA3, 0x55,
+ 0x2B, 0xCC, 0x4C, 0x4B, 0x48, 0x71, 0x3B, 0xFC,
+ 0xA4, 0x45, 0x0A, 0x8F, 0x7A, 0x13, 0x01, 0x22,
+ 0xC1, 0xF1, 0xA2, 0xB8, 0x7C, 0xF4, 0xB3, 0xB7,
+ 0x5B, 0xE5, 0x07, 0x50, 0x7E, 0x18, 0xEF, 0x91,
+ 0x5C, 0x15, 0x69, 0xBE, 0x0C, 0x93, 0x56, 0x35,
+ 0x7B, 0xCF, 0x34, 0x74, 0x3E, 0x5E, 0x31, 0x21,
+ 0x12, 0x63, 0x7F, 0x2A, 0x9B, 0xD4, 0x6B, 0xBC,
+ 0x33, 0x62, 0x30, 0x75, 0x17, 0x23, 0xB2, 0xF0,
+ 0x57, 0x67, 0x95, 0x3D, 0xCD, 0x10, 0xE6, 0xC8,
+ 0x8B, 0xA9, 0x73, 0xC4, 0x43, 0xBF, 0xA7, 0xCA,
+ 0xB5, 0xD5, 0xD6, 0x3F, 0x1A, 0x7D, 0x82, 0xA8,
+ 0x40, 0x64, 0xAB, 0x04, 0xC3, 0x1F, 0xA0, 0x5A,
+ 0x85, 0xF3, 0xDE, 0xFE, 0xDA, 0x1E, 0x81, 0x92,
+ 0x9C, 0x2D, 0x9F, 0x32, 0xB9, 0xA1, 0x96, 0xD0,
+ 0x4F, 0x38, 0x80, 0xCB, 0x6C, 0x14, 0x84, 0x1B,
+ 0xD7, 0xC5, 0xED, 0xD2, 0x3A, 0x0B, 0x88, 0xFD,
+ 0xDC, 0x49, 0x9E, 0xF5, 0xF2, 0x52, 0xA6, 0x24,
+ 0xC7, 0xB6, 0x03, 0x3C, 0xD1, 0x54, 0x41, 0xDF,
+ 0x89, 0x58, 0x79, 0xFB, 0x6E, 0xA5, 0x42, 0x25,
+ 0x09, 0x76, 0x00, 0x46, 0x4E, 0x53, 0xCE, 0x4A };
+
+static const unsigned char table_39[32] = {
+ 0x12, 0x18, 0x0E, 0x08, 0x16, 0x05, 0x06, 0x00,
+ 0x11, 0x17, 0x15, 0x1B, 0x14, 0x01, 0x1F, 0x19,
+ 0x04, 0x0D, 0x0A, 0x0F, 0x10, 0x07, 0x1D, 0x03,
+ 0x0B, 0x13, 0x0C, 0x09, 0x1E, 0x02, 0x1A, 0x1C };
+
+static const unsigned char table_40[32] = {
+ 0x16, 0x02, 0x06, 0x0E, 0x0D, 0x1C, 0x08, 0x0A,
+ 0x0F, 0x13, 0x0B, 0x18, 0x07, 0x04, 0x14, 0x01,
+ 0x1B, 0x05, 0x17, 0x1E, 0x11, 0x1A, 0x10, 0x1F,
+ 0x12, 0x19, 0x1D, 0x03, 0x0C, 0x00, 0x09, 0x15 };
+
+static const unsigned char table_41[32] = {
+ 0x13, 0x18, 0x04, 0x1F, 0x1D, 0x11, 0x03, 0x00,
+ 0x10, 0x12, 0x06, 0x0A, 0x1C, 0x07, 0x15, 0x0E,
+ 0x08, 0x05, 0x0C, 0x09, 0x01, 0x02, 0x16, 0x0B,
+ 0x1A, 0x17, 0x14, 0x1E, 0x0D, 0x0F, 0x19, 0x1B };
+
+static const unsigned char table_42[32] = {
+ 0x00, 0x08, 0x15, 0x1D, 0x05, 0x18, 0x06, 0x07,
+ 0x1F, 0x01, 0x0B, 0x03, 0x19, 0x13, 0x02, 0x1C,
+ 0x17, 0x11, 0x0E, 0x1E, 0x0C, 0x0F, 0x09, 0x1A,
+ 0x1B, 0x16, 0x10, 0x0D, 0x0A, 0x14, 0x12, 0x04 };
+
+static const unsigned char table_43[256] = {
+ 0x34, 0xB7, 0x36, 0x85, 0x5F, 0x93, 0x98, 0x70,
+ 0x1E, 0x59, 0x83, 0x60, 0x6F, 0xBF, 0xF9, 0xD0,
+ 0xB3, 0x22, 0x12, 0x38, 0xF5, 0x01, 0xC9, 0x5B,
+ 0xEF, 0x1D, 0x81, 0x64, 0xFA, 0x8F, 0x7F, 0xBC,
+ 0x05, 0x08, 0xE0, 0x8B, 0xE8, 0x86, 0x95, 0xCB,
+ 0xCA, 0x5A, 0xEB, 0x10, 0x92, 0xE2, 0x7E, 0x28,
+ 0xD9, 0xC7, 0x0D, 0x24, 0xA7, 0x02, 0x0B, 0xF1,
+ 0x7B, 0xD3, 0xFE, 0x2B, 0x89, 0x0E, 0xAE, 0xAD,
+ 0xC8, 0x82, 0x79, 0x43, 0x96, 0xDE, 0x0C, 0x9A,
+ 0x57, 0x84, 0xB4, 0x19, 0xF8, 0xF0, 0xAF, 0xBE,
+ 0x99, 0x9F, 0x46, 0xE4, 0x31, 0xDF, 0x30, 0x51,
+ 0xD4, 0xE5, 0xFC, 0x32, 0x04, 0x56, 0x7D, 0x33,
+ 0xF7, 0x18, 0x23, 0x4E, 0xC2, 0x7C, 0x6C, 0xD2,
+ 0xB1, 0x9B, 0x40, 0xA2, 0x88, 0x00, 0xA1, 0xAB,
+ 0xC6, 0x5C, 0x87, 0x3B, 0xD7, 0x27, 0x2E, 0x45,
+ 0xDA, 0x8E, 0x61, 0x5E, 0xFB, 0x09, 0x5D, 0x6B,
+ 0xA3, 0x29, 0x4F, 0xAC, 0xD1, 0x77, 0x4A, 0xA9,
+ 0xC4, 0x7A, 0x15, 0xD8, 0xAA, 0x17, 0xB9, 0x2D,
+ 0xE7, 0xBD, 0x2C, 0x62, 0x2F, 0xB2, 0xED, 0x3F,
+ 0x48, 0x26, 0x1B, 0x35, 0x20, 0x72, 0x4D, 0xFF,
+ 0xBB, 0x78, 0x1F, 0xCC, 0xEC, 0xA8, 0x9D, 0x90,
+ 0x4B, 0x13, 0xE1, 0xBA, 0xF3, 0x3C, 0x42, 0x65,
+ 0x14, 0xDD, 0x75, 0xE3, 0x4C, 0x74, 0x94, 0xCD,
+ 0xF2, 0x66, 0x06, 0xE9, 0x49, 0xB8, 0x71, 0x41,
+ 0xA0, 0x25, 0x55, 0x47, 0x97, 0x9E, 0x11, 0x54,
+ 0x1A, 0xB0, 0x3E, 0x37, 0x39, 0x1C, 0x8D, 0x03,
+ 0x6E, 0xF6, 0x80, 0x6D, 0x8C, 0x9C, 0xB6, 0xCF,
+ 0xC3, 0x91, 0x63, 0xC0, 0x07, 0x67, 0xE6, 0xF4,
+ 0xCE, 0x3D, 0xDB, 0x16, 0xFD, 0xEA, 0xD6, 0x68,
+ 0xD5, 0xA6, 0x0F, 0x58, 0x44, 0x52, 0xB5, 0xDC,
+ 0x0A, 0x69, 0xC5, 0xA5, 0xC1, 0x8A, 0x2A, 0xEE,
+ 0x73, 0x76, 0x3A, 0x21, 0x53, 0xA4, 0x50, 0x6A };
+
+static const unsigned char table_44[32] = {
+ 0x1A, 0x0E, 0x0A, 0x17, 0x1F, 0x08, 0x10, 0x14,
+ 0x0C, 0x0F, 0x09, 0x1C, 0x06, 0x18, 0x1E, 0x12,
+ 0x15, 0x00, 0x11, 0x13, 0x0D, 0x01, 0x0B, 0x03,
+ 0x16, 0x19, 0x05, 0x1D, 0x02, 0x07, 0x04, 0x1B };
+
+static const unsigned char table_45[256] = {
+ 0x5E, 0xD6, 0xE2, 0x54, 0x35, 0xC2, 0xAC, 0x9D,
+ 0x92, 0x64, 0x57, 0x65, 0xC8, 0xAE, 0x21, 0xA9,
+ 0x89, 0x48, 0x12, 0x59, 0xEC, 0xEF, 0x9F, 0xF7,
+ 0x19, 0x03, 0x83, 0xC0, 0x79, 0x5D, 0x4A, 0x10,
+ 0x8C, 0xEB, 0xFF, 0xB5, 0x3B, 0x51, 0x2D, 0xD1,
+ 0x6B, 0xC5, 0x24, 0x5C, 0xE6, 0x11, 0x94, 0x3F,
+ 0xD0, 0x2F, 0x0E, 0x95, 0x3C, 0xFE, 0x5B, 0x20,
+ 0x23, 0xE0, 0x91, 0x6F, 0xCA, 0x56, 0x0C, 0x73,
+ 0xDA, 0x67, 0x37, 0xA3, 0xA5, 0x70, 0x93, 0x1C,
+ 0x18, 0xD9, 0x42, 0x5F, 0x44, 0xF0, 0xF2, 0x14,
+ 0x58, 0x8A, 0x1D, 0x40, 0x4E, 0x0B, 0x74, 0x84,
+ 0x52, 0xCB, 0x60, 0xED, 0xAD, 0x66, 0x43, 0x6C,
+ 0x81, 0xA1, 0x27, 0xB9, 0xBA, 0x4D, 0xF5, 0x04,
+ 0xB8, 0x96, 0xA6, 0xA2, 0x7D, 0xD4, 0xEA, 0x45,
+ 0x4F, 0x55, 0xD3, 0x3E, 0x8E, 0x4C, 0xBF, 0x8B,
+ 0x9A, 0x06, 0x7A, 0xF4, 0x02, 0x88, 0x80, 0x22,
+ 0xF3, 0xBD, 0x78, 0xEE, 0xAF, 0xF8, 0x15, 0x09,
+ 0x0F, 0xB0, 0xDD, 0x99, 0x72, 0xE7, 0x90, 0xE1,
+ 0x25, 0x62, 0x8D, 0x9C, 0x13, 0x08, 0xC9, 0x28,
+ 0x2A, 0x47, 0x69, 0xDE, 0x77, 0x87, 0xBB, 0xE9,
+ 0xAA, 0x33, 0x05, 0x29, 0x34, 0x97, 0xFD, 0xA0,
+ 0x1E, 0xFC, 0xBE, 0xB1, 0x71, 0x9B, 0x50, 0xDC,
+ 0xB7, 0x31, 0x63, 0x3A, 0xDF, 0xC3, 0x1B, 0x7C,
+ 0x0A, 0xD7, 0xF6, 0xDB, 0x49, 0x53, 0x7F, 0xD2,
+ 0x30, 0xA4, 0xB3, 0x6E, 0xB2, 0x6D, 0xCD, 0x7E,
+ 0x26, 0xE8, 0x76, 0xCF, 0xE5, 0xCE, 0x16, 0xF1,
+ 0xC6, 0x68, 0x36, 0x46, 0x1F, 0x38, 0x0D, 0x41,
+ 0x17, 0xBC, 0x86, 0x9E, 0x6A, 0x7B, 0xB4, 0x01,
+ 0xCC, 0x2C, 0xE3, 0x5A, 0xB6, 0xFA, 0x00, 0x75,
+ 0x39, 0xA7, 0xC1, 0xD5, 0x98, 0xAB, 0x1A, 0x85,
+ 0xD8, 0xE4, 0xC4, 0xA8, 0x4B, 0x61, 0x2E, 0x3D,
+ 0xF9, 0x2B, 0x32, 0x8F, 0xFB, 0xC7, 0x07, 0x82 };
+
+static const unsigned char table_46[256] = {
+ 0x85, 0x78, 0xFE, 0x6C, 0x61, 0xA0, 0x71, 0xCC,
+ 0x45, 0x54, 0x7A, 0xE6, 0x82, 0x1D, 0xA6, 0x02,
+ 0x47, 0xD0, 0x23, 0x55, 0x62, 0xFA, 0x76, 0x3E,
+ 0xE3, 0x66, 0x74, 0x10, 0x5D, 0x49, 0x69, 0x0B,
+ 0x75, 0x12, 0x8D, 0x9F, 0xEE, 0x93, 0x50, 0x70,
+ 0x32, 0xBC, 0x1E, 0xD3, 0xEF, 0x7B, 0xB4, 0x92,
+ 0xFD, 0x16, 0xC2, 0xD8, 0xDE, 0x68, 0xD1, 0x64,
+ 0xC3, 0xA3, 0xB3, 0xC9, 0x08, 0xFB, 0x84, 0xC1,
+ 0x28, 0x53, 0xCF, 0xD2, 0x35, 0xD7, 0x4A, 0x01,
+ 0x44, 0xA4, 0x07, 0xAC, 0x98, 0xF1, 0xB2, 0x9A,
+ 0x94, 0x2D, 0xD4, 0x34, 0x27, 0x60, 0x1A, 0xB9,
+ 0xAF, 0x89, 0xEB, 0x8F, 0x6A, 0x13, 0x05, 0xF0,
+ 0x77, 0x5F, 0x4F, 0x58, 0x2C, 0xE7, 0xCE, 0xED,
+ 0xC0, 0x0D, 0x3A, 0xA7, 0xE2, 0x38, 0x5B, 0xE9,
+ 0x3D, 0xF2, 0xDF, 0x86, 0xE0, 0x72, 0xF7, 0x88,
+ 0xAD, 0xB7, 0x11, 0xDB, 0x73, 0x87, 0xC5, 0x22,
+ 0xE1, 0x5C, 0xD6, 0x57, 0x7E, 0x7D, 0xA2, 0xF9,
+ 0xF5, 0x9C, 0x25, 0x6F, 0x26, 0x51, 0xC8, 0x80,
+ 0x2B, 0xA8, 0x19, 0xD9, 0x65, 0xCD, 0x97, 0xEA,
+ 0xFF, 0x5E, 0x24, 0x3B, 0x4D, 0xB1, 0x1C, 0x79,
+ 0x39, 0x6B, 0xA5, 0x2A, 0x09, 0xCA, 0x04, 0xEC,
+ 0xBA, 0x18, 0x31, 0x46, 0x20, 0xBE, 0x1F, 0x3C,
+ 0x6D, 0xAA, 0xF6, 0xDD, 0xF4, 0x96, 0x03, 0x0A,
+ 0x9E, 0x83, 0xA1, 0x9D, 0xD5, 0xB0, 0x17, 0xBF,
+ 0x56, 0xAB, 0xAE, 0x1B, 0x52, 0xC6, 0x81, 0x4B,
+ 0xDC, 0x90, 0x5A, 0x9B, 0xB6, 0x0F, 0xF3, 0x67,
+ 0x30, 0x63, 0x7C, 0x40, 0x0E, 0x7F, 0x95, 0x36,
+ 0xC4, 0x4E, 0x43, 0xCB, 0x15, 0xB8, 0x00, 0x91,
+ 0x8A, 0x4C, 0x8E, 0x14, 0x06, 0x6E, 0xA9, 0x2E,
+ 0x3F, 0x48, 0x2F, 0x0C, 0xB5, 0x21, 0xBB, 0xDA,
+ 0x8B, 0x42, 0x29, 0x8C, 0x33, 0x59, 0xE8, 0xF8,
+ 0xC7, 0xE4, 0x37, 0xE5, 0xFC, 0xBD, 0x99, 0x41 };
+
+static const unsigned char table_47[32] = {
+ 0x18, 0x1D, 0x16, 0x10, 0x11, 0x04, 0x1E, 0x08,
+ 0x19, 0x0E, 0x0F, 0x02, 0x14, 0x1C, 0x07, 0x17,
+ 0x0D, 0x09, 0x12, 0x1A, 0x05, 0x01, 0x0B, 0x0A,
+ 0x13, 0x15, 0x0C, 0x00, 0x06, 0x1F, 0x03, 0x1B };
+
+static const unsigned char table_48[32] = {
+ 0x13, 0x08, 0x15, 0x01, 0x17, 0x10, 0x0F, 0x1F,
+ 0x1D, 0x0D, 0x12, 0x03, 0x06, 0x0A, 0x1C, 0x19,
+ 0x1A, 0x04, 0x1B, 0x02, 0x16, 0x1E, 0x11, 0x00,
+ 0x14, 0x09, 0x0C, 0x18, 0x05, 0x07, 0x0E, 0x0B };
+
+static const unsigned char table_49[32] = {
+ 0x1F, 0x0F, 0x19, 0x07, 0x18, 0x05, 0x1E, 0x1D,
+ 0x15, 0x08, 0x17, 0x10, 0x0A, 0x0E, 0x0C, 0x1B,
+ 0x02, 0x13, 0x03, 0x0D, 0x04, 0x1A, 0x06, 0x09,
+ 0x12, 0x1C, 0x0B, 0x16, 0x14, 0x01, 0x11, 0x00 };
+
+static const unsigned char table_50[32] = {
+ 0x16, 0x18, 0x1C, 0x0E, 0x12, 0x00, 0x04, 0x1B,
+ 0x1F, 0x13, 0x17, 0x0A, 0x1E, 0x03, 0x0C, 0x01,
+ 0x0F, 0x10, 0x02, 0x08, 0x14, 0x09, 0x19, 0x15,
+ 0x06, 0x0D, 0x0B, 0x1D, 0x05, 0x07, 0x11, 0x1A };
+
+static const unsigned char table_51[32] = {
+ 0x1C, 0x0D, 0x1B, 0x07, 0x17, 0x0E, 0x06, 0x01,
+ 0x12, 0x19, 0x03, 0x0B, 0x10, 0x08, 0x00, 0x1E,
+ 0x0A, 0x04, 0x1A, 0x1D, 0x0C, 0x18, 0x02, 0x13,
+ 0x0F, 0x11, 0x05, 0x09, 0x15, 0x16, 0x1F, 0x14 };
+
+static const unsigned char table_52[256] = {
+ 0x34, 0x0B, 0x47, 0xA3, 0x56, 0x30, 0x73, 0xD4,
+ 0x4B, 0xF6, 0xA6, 0x80, 0x22, 0x95, 0xA5, 0xBB,
+ 0xFE, 0xCD, 0x27, 0x88, 0x87, 0x18, 0x86, 0x6E,
+ 0xB9, 0x07, 0x37, 0x52, 0x0A, 0x28, 0x2C, 0xC4,
+ 0x75, 0xA1, 0x29, 0x54, 0x84, 0x08, 0x72, 0x51,
+ 0xDD, 0xF1, 0x4E, 0x1A, 0x90, 0x57, 0x20, 0xAD,
+ 0x68, 0x61, 0xAF, 0x50, 0x6B, 0x1B, 0x71, 0xEB,
+ 0x63, 0xC9, 0xB0, 0x58, 0x26, 0x40, 0xC7, 0xD9,
+ 0x70, 0xA2, 0x9A, 0x09, 0x3F, 0x92, 0x0D, 0x8C,
+ 0xC1, 0x96, 0x9F, 0x77, 0x4D, 0x5A, 0xEA, 0x11,
+ 0xD7, 0xF3, 0x33, 0x93, 0x10, 0xF2, 0x9D, 0x83,
+ 0xFF, 0x7E, 0xD2, 0x41, 0x24, 0xB4, 0x8D, 0x5C,
+ 0xCF, 0xEF, 0xE9, 0x64, 0x76, 0xD1, 0xDE, 0xE4,
+ 0x91, 0x35, 0x89, 0x19, 0x02, 0x0E, 0xF4, 0x2A,
+ 0x0F, 0xE1, 0xA8, 0x2D, 0x21, 0x23, 0xAA, 0x7C,
+ 0x78, 0x45, 0xA9, 0xDC, 0x06, 0xF9, 0xDF, 0xF7,
+ 0x03, 0xAB, 0xB5, 0x1C, 0x36, 0x7B, 0x97, 0xFA,
+ 0xE5, 0x3B, 0x2F, 0x1F, 0x9E, 0xED, 0xA7, 0x55,
+ 0x42, 0x6F, 0x1E, 0xB7, 0xE6, 0xFB, 0x12, 0xD5,
+ 0x99, 0xC6, 0x66, 0x4A, 0xE8, 0x48, 0x60, 0xB1,
+ 0x05, 0x53, 0x8A, 0xB6, 0x25, 0x8F, 0xA4, 0xD8,
+ 0x9C, 0xC0, 0x59, 0x3A, 0xBD, 0xDB, 0x44, 0x5E,
+ 0xE3, 0xDA, 0x1D, 0x32, 0xF5, 0xBA, 0x43, 0x13,
+ 0x82, 0x4C, 0xE7, 0x17, 0x15, 0x3E, 0x69, 0x2E,
+ 0xC3, 0xF0, 0x5F, 0xFD, 0xCE, 0xD3, 0xCA, 0x39,
+ 0xD6, 0x79, 0x3D, 0xC8, 0x67, 0x8B, 0x31, 0x4F,
+ 0xB3, 0xBC, 0x65, 0x00, 0x7A, 0x98, 0xC5, 0x6C,
+ 0x2B, 0x94, 0x6D, 0x74, 0x14, 0xAC, 0xCC, 0xA0,
+ 0x5B, 0xF8, 0xCB, 0x7F, 0xB2, 0xEC, 0xBF, 0x3C,
+ 0xE0, 0xAE, 0xFC, 0x62, 0x04, 0x8E, 0x85, 0x49,
+ 0x9B, 0xC2, 0x38, 0xD0, 0xEE, 0x81, 0x46, 0xE2,
+ 0x01, 0x0C, 0x5D, 0x7D, 0xB8, 0xBE, 0x6A, 0x16 };
+
+static const unsigned char table_53[256] = {
+ 0xE3, 0xF4, 0x8D, 0x72, 0x45, 0x32, 0x9D, 0xCE,
+ 0x1F, 0x6B, 0xBC, 0xDC, 0xF1, 0xEC, 0x5A, 0x3B,
+ 0xA5, 0xA2, 0x2B, 0xDD, 0x8A, 0xA3, 0x76, 0xE4,
+ 0xAF, 0xE9, 0xE1, 0x21, 0xDB, 0x9F, 0x19, 0xD3,
+ 0x26, 0x80, 0x15, 0xC2, 0x46, 0xB8, 0x17, 0x56,
+ 0x99, 0x81, 0x08, 0xD7, 0xEF, 0x8E, 0x04, 0x05,
+ 0x97, 0x2F, 0x78, 0xAD, 0xA1, 0x52, 0x36, 0x58,
+ 0x53, 0x68, 0x22, 0x70, 0x0B, 0x79, 0xE6, 0xFA,
+ 0xC3, 0x91, 0xE2, 0xF7, 0xF6, 0x75, 0x2D, 0x0A,
+ 0x90, 0xEB, 0xA6, 0x35, 0xA7, 0x10, 0xB5, 0xFB,
+ 0xE7, 0xAA, 0x1E, 0x43, 0xBB, 0x3C, 0x65, 0x25,
+ 0x2C, 0x59, 0x62, 0x2A, 0xF9, 0x4B, 0x95, 0x5E,
+ 0x20, 0x11, 0x42, 0x27, 0x44, 0xE8, 0x14, 0x6F,
+ 0xD1, 0xD8, 0x00, 0x3A, 0x5B, 0x18, 0x89, 0x02,
+ 0x61, 0xD6, 0xC5, 0x98, 0xD0, 0x5F, 0x34, 0x29,
+ 0xFD, 0x31, 0x1A, 0xCD, 0x0F, 0x9E, 0xCA, 0x7B,
+ 0xEA, 0x93, 0x71, 0x5C, 0x0E, 0x57, 0x33, 0xC4,
+ 0x37, 0xF5, 0x83, 0xB0, 0xDF, 0x49, 0x74, 0x54,
+ 0x1D, 0x24, 0xB9, 0x16, 0x1C, 0x28, 0xDE, 0x4A,
+ 0xF0, 0x01, 0x86, 0x82, 0xCC, 0x12, 0x8C, 0x06,
+ 0x30, 0xA8, 0x7A, 0x73, 0x66, 0x7C, 0xC6, 0xB6,
+ 0xF2, 0x13, 0xBF, 0x40, 0x85, 0x77, 0x09, 0x3D,
+ 0x67, 0x63, 0x3F, 0x7F, 0xF3, 0x87, 0x8F, 0xFF,
+ 0x92, 0xC7, 0x4C, 0x23, 0xBA, 0xCB, 0xB1, 0xED,
+ 0x0C, 0x60, 0x47, 0xFE, 0x38, 0x5D, 0xCF, 0x8B,
+ 0x4D, 0xA9, 0x2E, 0xE5, 0xA4, 0x1B, 0x88, 0x3E,
+ 0x7D, 0xF8, 0xC0, 0xD5, 0x6D, 0x6C, 0x48, 0xAC,
+ 0x9B, 0x51, 0x7E, 0x6E, 0x50, 0x0D, 0x9A, 0xB3,
+ 0xEE, 0x07, 0x4F, 0x69, 0x9C, 0x03, 0xD9, 0xD4,
+ 0xB4, 0xD2, 0xAE, 0x4E, 0x55, 0xB7, 0xC9, 0x41,
+ 0x39, 0x6A, 0xC8, 0xA0, 0xB2, 0xC1, 0x84, 0xFC,
+ 0xAB, 0x64, 0xE0, 0xBE, 0xDA, 0xBD, 0x96, 0x94 };
+
+static const unsigned char table_54[32] = {
+ 0x01, 0x02, 0x1D, 0x10, 0x0E, 0x11, 0x08, 0x14,
+ 0x12, 0x09, 0x15, 0x17, 0x16, 0x04, 0x06, 0x1B,
+ 0x07, 0x1A, 0x18, 0x13, 0x0A, 0x1E, 0x1C, 0x1F,
+ 0x0C, 0x0B, 0x0D, 0x05, 0x0F, 0x00, 0x19, 0x03 };
+
+static const unsigned char table_55[32] = {
+ 0x01, 0x12, 0x13, 0x09, 0x0B, 0x19, 0x03, 0x0E,
+ 0x02, 0x1F, 0x1D, 0x1B, 0x1E, 0x11, 0x06, 0x05,
+ 0x00, 0x16, 0x07, 0x0C, 0x15, 0x0D, 0x1A, 0x08,
+ 0x18, 0x10, 0x0F, 0x17, 0x1C, 0x0A, 0x04, 0x14 };
+
+static const unsigned char table_56[256] = {
+ 0xEF, 0x06, 0x5F, 0x11, 0x4B, 0x60, 0x13, 0xBB,
+ 0x79, 0xD7, 0xE4, 0x6D, 0x22, 0xB4, 0x15, 0x50,
+ 0x29, 0x17, 0xD2, 0xE3, 0x37, 0x8C, 0x46, 0x7C,
+ 0xA2, 0xF5, 0x65, 0x16, 0xCB, 0x04, 0x3E, 0xDF,
+ 0x8E, 0xDE, 0x53, 0xF1, 0xF4, 0xD1, 0x3B, 0xEE,
+ 0x9A, 0x09, 0x9B, 0x6C, 0xF6, 0xCC, 0xFB, 0x40,
+ 0xE0, 0xFD, 0x2B, 0x1D, 0x73, 0x18, 0xCD, 0x31,
+ 0x3F, 0x9E, 0xAD, 0xC9, 0x43, 0x4E, 0x99, 0x3A,
+ 0x8F, 0x92, 0x85, 0xFC, 0x12, 0x41, 0x20, 0xE8,
+ 0x2A, 0xC0, 0x1C, 0x38, 0x74, 0x0B, 0xF3, 0x05,
+ 0x0D, 0x1F, 0x94, 0x9C, 0xAC, 0x00, 0x59, 0x0C,
+ 0xB3, 0x8D, 0xA8, 0x75, 0xB7, 0x68, 0x2F, 0x27,
+ 0x6F, 0x69, 0x76, 0xD8, 0xEC, 0xA5, 0xB2, 0x6A,
+ 0x19, 0x72, 0x1A, 0xB6, 0xE5, 0x77, 0xC6, 0x44,
+ 0x9D, 0xCA, 0x82, 0x35, 0x36, 0x5E, 0xA9, 0x25,
+ 0xFA, 0x5C, 0x24, 0x30, 0x39, 0x0E, 0x2C, 0x7D,
+ 0xE6, 0x88, 0xA0, 0x63, 0xB8, 0x6B, 0x01, 0xDD,
+ 0xDA, 0x9F, 0x45, 0x83, 0xE2, 0x7F, 0x1B, 0x56,
+ 0xAF, 0x14, 0xC3, 0x49, 0xBF, 0x78, 0x70, 0x58,
+ 0x23, 0xA3, 0xBD, 0x34, 0x47, 0x2D, 0x0A, 0xD4,
+ 0x33, 0x03, 0x1E, 0xC1, 0x87, 0xAE, 0x3C, 0x95,
+ 0xB0, 0x42, 0x91, 0xB9, 0x5A, 0x61, 0xAA, 0xCF,
+ 0xF2, 0x51, 0xA6, 0xF8, 0xDC, 0x71, 0xAB, 0x48,
+ 0x66, 0x90, 0x97, 0xC4, 0x08, 0xF9, 0xD0, 0x7B,
+ 0xDB, 0xBA, 0x8B, 0xC2, 0xC5, 0x2E, 0xF7, 0x5B,
+ 0xFF, 0x21, 0x81, 0x54, 0xD3, 0x62, 0x57, 0x4C,
+ 0x6E, 0x02, 0x98, 0xFE, 0x7E, 0xE7, 0xBC, 0x07,
+ 0x28, 0x5D, 0x86, 0xCE, 0xEA, 0x84, 0xF0, 0xE1,
+ 0x93, 0x80, 0xE9, 0xC7, 0x4A, 0xED, 0xB1, 0x26,
+ 0x89, 0x3D, 0x4F, 0xA7, 0xA1, 0xD6, 0xB5, 0x4D,
+ 0x67, 0xA4, 0x55, 0x10, 0x0F, 0xD9, 0x52, 0x32,
+ 0x96, 0xD5, 0xEB, 0x64, 0x8A, 0xC8, 0x7A, 0xBE };
+
+static const unsigned char table_57[256] = {
+ 0xD1, 0x9B, 0x15, 0x06, 0xB4, 0xF6, 0x97, 0xF0,
+ 0xC6, 0x5B, 0x88, 0x12, 0x25, 0xFA, 0x7B, 0x79,
+ 0xD6, 0xAB, 0xDC, 0x47, 0x85, 0x61, 0x67, 0x0B,
+ 0xF3, 0x20, 0x44, 0x53, 0x2A, 0x3B, 0x2D, 0xE8,
+ 0x17, 0x71, 0xC3, 0xB7, 0x7F, 0x35, 0xEB, 0x10,
+ 0x03, 0x0D, 0x60, 0x96, 0x27, 0xBB, 0x39, 0x50,
+ 0x95, 0x55, 0xCC, 0xD4, 0x2F, 0x51, 0xB3, 0x05,
+ 0xA5, 0xAD, 0xBC, 0x18, 0xE2, 0xAE, 0x07, 0x87,
+ 0xC4, 0x8D, 0xBE, 0x77, 0xC2, 0x16, 0xFC, 0x33,
+ 0x4C, 0x4F, 0xE6, 0xA6, 0x57, 0x9F, 0x37, 0x91,
+ 0xED, 0x4A, 0xF7, 0xB5, 0x52, 0x7C, 0xBD, 0x30,
+ 0xA0, 0x2C, 0x8C, 0xB0, 0x0C, 0xDA, 0x6F, 0x9E,
+ 0xEE, 0x43, 0x40, 0x8F, 0x8B, 0x76, 0xA4, 0x68,
+ 0xFF, 0x6D, 0x58, 0xC9, 0xF9, 0x6E, 0x3F, 0x56,
+ 0xCA, 0x49, 0xC8, 0x5D, 0xCD, 0xC7, 0x99, 0xEC,
+ 0x72, 0x38, 0x0A, 0xA9, 0xC5, 0x04, 0x64, 0xBF,
+ 0xB6, 0x29, 0x80, 0x2E, 0x19, 0x0E, 0x82, 0x45,
+ 0xBA, 0xD7, 0x1E, 0x86, 0xA8, 0xD8, 0x24, 0xDB,
+ 0xCF, 0xE1, 0x54, 0xB2, 0x3E, 0x4D, 0x90, 0x42,
+ 0x5F, 0x59, 0x0F, 0xCE, 0x8E, 0xA2, 0xA7, 0x1D,
+ 0x22, 0xFD, 0x81, 0x63, 0xE5, 0x6A, 0xE7, 0x93,
+ 0x41, 0x46, 0x66, 0x89, 0x13, 0xEA, 0x69, 0x1C,
+ 0x83, 0xF2, 0x08, 0xB8, 0x01, 0x23, 0x26, 0xFB,
+ 0x78, 0xAA, 0x31, 0x11, 0x1B, 0x98, 0xDD, 0xAC,
+ 0xB9, 0xFE, 0x94, 0x74, 0xAF, 0x32, 0xD0, 0x5A,
+ 0xA1, 0xF4, 0x6B, 0x8A, 0xE3, 0x65, 0xDE, 0xCB,
+ 0x73, 0x3D, 0xA3, 0x7E, 0xDF, 0xD2, 0x6C, 0x7A,
+ 0x36, 0xD9, 0x62, 0x4B, 0xEF, 0xC1, 0x1F, 0x00,
+ 0x34, 0xB1, 0xF8, 0xE4, 0xD5, 0x09, 0x1A, 0x9A,
+ 0x70, 0x48, 0x9D, 0xF1, 0xE0, 0x9C, 0xD3, 0x5C,
+ 0x75, 0x02, 0x2B, 0x92, 0x21, 0x7D, 0xF5, 0x5E,
+ 0x4E, 0x3C, 0x84, 0x14, 0x28, 0x3A, 0xE9, 0xC0 };
+
+static const unsigned char table_58[256] = {
+ 0xE9, 0x81, 0x60, 0xA7, 0x18, 0xA0, 0x0F, 0x55,
+ 0x2B, 0x52, 0xE0, 0x8B, 0x9D, 0x85, 0xD2, 0xA3,
+ 0x3F, 0x6E, 0xB1, 0xAF, 0xE3, 0x36, 0xE2, 0x19,
+ 0x56, 0xB0, 0x09, 0xB5, 0x79, 0x43, 0xE1, 0x06,
+ 0x45, 0xB6, 0xC0, 0x22, 0xEE, 0x41, 0xEC, 0x01,
+ 0x66, 0x2D, 0x87, 0x38, 0x16, 0x37, 0xFA, 0x29,
+ 0x96, 0xA4, 0xC3, 0x23, 0x59, 0x7E, 0x92, 0x78,
+ 0x10, 0x2A, 0x4C, 0x0E, 0x9B, 0x4A, 0x35, 0xF4,
+ 0x42, 0x0C, 0xD8, 0xD7, 0x24, 0x2C, 0xDD, 0x8E,
+ 0x5B, 0xF5, 0x33, 0x48, 0xEF, 0xDE, 0x4B, 0xBC,
+ 0x51, 0xAB, 0x7C, 0xE4, 0x63, 0x70, 0x9A, 0xAC,
+ 0x54, 0x1D, 0x25, 0xC5, 0xEA, 0xB3, 0x05, 0xF7,
+ 0xC1, 0x1F, 0xE8, 0x97, 0xBB, 0x32, 0x6D, 0xC7,
+ 0x28, 0x61, 0xDB, 0x4D, 0x77, 0x72, 0x65, 0x8C,
+ 0x80, 0x3A, 0x76, 0x47, 0xA8, 0x03, 0x04, 0x12,
+ 0xCE, 0xA9, 0x75, 0x3C, 0x49, 0xF8, 0x64, 0xDF,
+ 0x57, 0xA2, 0x69, 0x44, 0xAD, 0x3E, 0x4F, 0x0B,
+ 0x74, 0x67, 0xC9, 0x1A, 0x17, 0xAA, 0x02, 0x6F,
+ 0xDA, 0xF2, 0xC6, 0x27, 0x53, 0xD6, 0xFD, 0xCA,
+ 0x8D, 0x93, 0x89, 0xD5, 0x6B, 0x4E, 0x90, 0x82,
+ 0x30, 0xE7, 0xC4, 0xD9, 0x8A, 0x7F, 0xB4, 0xFC,
+ 0xCF, 0xA1, 0xAE, 0x1C, 0x39, 0x1B, 0x7B, 0x5E,
+ 0x88, 0x7D, 0xD3, 0x71, 0x2E, 0x98, 0x13, 0x8F,
+ 0xCC, 0x84, 0x73, 0xCD, 0x21, 0x0D, 0x5C, 0xA5,
+ 0x3D, 0x9E, 0x99, 0xC2, 0xF3, 0x34, 0x14, 0x62,
+ 0x46, 0x0A, 0x07, 0x08, 0xFF, 0xFB, 0xB7, 0xBF,
+ 0x5D, 0x91, 0xB8, 0x83, 0xBE, 0x94, 0xBA, 0xF9,
+ 0xEB, 0xE5, 0xCB, 0x95, 0x40, 0x31, 0xE6, 0x86,
+ 0xD4, 0xFE, 0xD0, 0x7A, 0x26, 0xB9, 0xDC, 0x2F,
+ 0xBD, 0xF0, 0x5F, 0x00, 0x9C, 0x6A, 0x5A, 0x3B,
+ 0xF1, 0xC8, 0x9F, 0xED, 0x50, 0x20, 0x15, 0x11,
+ 0x68, 0x1E, 0xF6, 0xA6, 0x6C, 0xB2, 0xD1, 0x58 };
+
+static const unsigned char table_59[256] = {
+ 0x4C, 0x85, 0x2B, 0x14, 0xCC, 0x4D, 0x5F, 0xD7,
+ 0xCE, 0x28, 0xC5, 0x0B, 0xA1, 0x99, 0x08, 0xDE,
+ 0x42, 0xD1, 0x82, 0x5C, 0xC9, 0x8F, 0x72, 0x12,
+ 0xCB, 0x0D, 0x04, 0xFA, 0xCD, 0xE5, 0x9A, 0x6F,
+ 0xCF, 0x92, 0xB5, 0x88, 0x87, 0xBF, 0x90, 0x7C,
+ 0xAC, 0xBE, 0x36, 0x21, 0x7D, 0x7F, 0xC7, 0x9F,
+ 0x75, 0xBB, 0x61, 0x16, 0x17, 0x63, 0xAE, 0xC4,
+ 0x23, 0x89, 0xE0, 0x37, 0x91, 0x5E, 0xC8, 0xE4,
+ 0xFD, 0xD5, 0xA2, 0xC6, 0x5A, 0xEF, 0x9B, 0xD6,
+ 0x27, 0xEE, 0x60, 0x1C, 0xDF, 0xDA, 0xF1, 0xD2,
+ 0x1E, 0x01, 0x9D, 0x44, 0x03, 0xD8, 0x11, 0x53,
+ 0x4F, 0x6C, 0x8B, 0xB7, 0x40, 0xF2, 0x79, 0x20,
+ 0x74, 0x97, 0x3E, 0x3D, 0x05, 0xD4, 0x70, 0x30,
+ 0x54, 0x59, 0xE7, 0x15, 0xE1, 0xEB, 0x71, 0x83,
+ 0xFE, 0x66, 0xB1, 0xA6, 0xF7, 0x8E, 0x6A, 0xEA,
+ 0x65, 0x7E, 0xA3, 0xCA, 0x2D, 0x4B, 0xB8, 0x9C,
+ 0x35, 0xC3, 0xB6, 0x49, 0x32, 0x25, 0xB3, 0xB0,
+ 0x76, 0xC0, 0xF5, 0x00, 0x8A, 0xAF, 0x19, 0xDB,
+ 0xDD, 0x47, 0xDC, 0x07, 0xB2, 0x4A, 0x55, 0xE6,
+ 0x69, 0xEC, 0xED, 0x06, 0x94, 0xB9, 0xA7, 0x56,
+ 0x2C, 0xAA, 0xE3, 0x22, 0x3B, 0x98, 0x77, 0x52,
+ 0x3C, 0x64, 0xF8, 0x13, 0x78, 0xFC, 0xFB, 0xF3,
+ 0xD3, 0xF9, 0x29, 0x45, 0x51, 0x8C, 0xA0, 0x38,
+ 0xD9, 0xA5, 0x62, 0x3A, 0x6E, 0xD0, 0xE8, 0x7A,
+ 0x33, 0x1D, 0xB4, 0x73, 0x02, 0xFF, 0x10, 0x80,
+ 0x6B, 0xF0, 0xA4, 0xBA, 0xF6, 0xC2, 0x0E, 0xE2,
+ 0x81, 0x43, 0x84, 0x86, 0x1F, 0x31, 0x2F, 0xA9,
+ 0x1B, 0x2A, 0x4E, 0xF4, 0x95, 0x5B, 0x3F, 0x34,
+ 0x39, 0x7B, 0x0A, 0x26, 0x6D, 0x57, 0x50, 0x09,
+ 0x9E, 0xA8, 0xBC, 0x24, 0x93, 0x67, 0x41, 0x96,
+ 0x0C, 0x46, 0xBD, 0xE9, 0x68, 0x18, 0xAB, 0x2E,
+ 0x5D, 0x1A, 0x8D, 0xC1, 0x58, 0x48, 0xAD, 0x0F };
+
+static const unsigned char table_60[32] = {
+ 0x1C, 0x06, 0x1E, 0x10, 0x1D, 0x05, 0x00, 0x0E,
+ 0x0C, 0x02, 0x11, 0x19, 0x15, 0x18, 0x16, 0x07,
+ 0x1F, 0x0B, 0x14, 0x01, 0x0F, 0x09, 0x0D, 0x13,
+ 0x03, 0x08, 0x12, 0x04, 0x1B, 0x0A, 0x17, 0x1A };
+
+static const unsigned char table_61[256] = {
+ 0xC5, 0xA6, 0xF2, 0x6B, 0x4B, 0x58, 0xE0, 0x41,
+ 0xC6, 0x2F, 0x13, 0xFE, 0xC1, 0x34, 0x3F, 0x24,
+ 0x10, 0xBF, 0x8B, 0xC9, 0x26, 0x2E, 0x68, 0xBE,
+ 0x28, 0x54, 0x93, 0x11, 0x21, 0x03, 0xFF, 0x50,
+ 0x31, 0x71, 0x2C, 0x6C, 0x91, 0x8F, 0x3B, 0x40,
+ 0x3E, 0xE5, 0xA5, 0x80, 0xEA, 0x7C, 0x9D, 0x18,
+ 0x84, 0x5A, 0x73, 0x3A, 0x33, 0x43, 0xA1, 0x47,
+ 0xB1, 0xEE, 0xFB, 0x79, 0x5E, 0xAF, 0xB9, 0x48,
+ 0x0F, 0x88, 0x65, 0x67, 0x6F, 0xDB, 0x25, 0xE4,
+ 0xB0, 0x87, 0xD0, 0x46, 0xB5, 0xB7, 0x53, 0xD4,
+ 0x1E, 0x76, 0xB4, 0x90, 0xDD, 0xA3, 0xF7, 0x57,
+ 0xD2, 0xCC, 0x5D, 0xE3, 0xB3, 0xD8, 0x5F, 0x2B,
+ 0x69, 0x4A, 0x9B, 0x39, 0x1A, 0x8D, 0x05, 0x8A,
+ 0x44, 0x15, 0xAE, 0xF3, 0xA8, 0x92, 0x02, 0xAB,
+ 0xB8, 0xDA, 0x0A, 0x0C, 0xED, 0xD7, 0x77, 0x98,
+ 0x3D, 0x19, 0x95, 0x36, 0xE7, 0x7F, 0x66, 0xEF,
+ 0x86, 0xDC, 0xCB, 0x9C, 0x63, 0xE6, 0x1D, 0x14,
+ 0x9A, 0x22, 0xBD, 0xD6, 0x89, 0x2D, 0xD1, 0xF9,
+ 0xA2, 0xDE, 0xF5, 0x5C, 0x8E, 0x2A, 0x29, 0xCA,
+ 0x7A, 0x8C, 0x38, 0x9F, 0xBB, 0xDF, 0xEC, 0x30,
+ 0x00, 0xFC, 0xAC, 0x81, 0xB2, 0xE8, 0xC0, 0xA7,
+ 0x7B, 0x07, 0x52, 0x74, 0x70, 0x0E, 0x51, 0x6A,
+ 0x62, 0x0D, 0x85, 0x1B, 0x4F, 0x96, 0x55, 0x1C,
+ 0x32, 0x6E, 0x01, 0xF6, 0x08, 0xFD, 0x17, 0x35,
+ 0xF0, 0x16, 0xC8, 0x23, 0xE9, 0x59, 0x3C, 0x37,
+ 0x5B, 0x42, 0xD3, 0x49, 0x7D, 0x83, 0x78, 0xAD,
+ 0x94, 0x9E, 0x56, 0xB6, 0xF1, 0xC3, 0x75, 0xF8,
+ 0xFA, 0x09, 0x4C, 0xD9, 0x97, 0xF4, 0x7E, 0x6D,
+ 0xBC, 0x4D, 0x64, 0xCD, 0x12, 0x99, 0x45, 0xCE,
+ 0x61, 0x20, 0x0B, 0xA0, 0x82, 0xD5, 0xE1, 0x72,
+ 0xA9, 0x1F, 0x06, 0x27, 0xC7, 0x04, 0xE2, 0xBA,
+ 0xCF, 0x60, 0xAA, 0xA4, 0xEB, 0xC4, 0x4E, 0xC2 };
+
+static const unsigned char table_62[256] = {
+ 0x01, 0x59, 0xEC, 0xFC, 0x51, 0xD2, 0xE4, 0x9D,
+ 0xAA, 0x61, 0xD5, 0xCA, 0x63, 0x5D, 0xCE, 0x36,
+ 0xB9, 0x49, 0x76, 0xA9, 0x14, 0x4C, 0x90, 0x28,
+ 0x66, 0x17, 0x4F, 0x1E, 0x1A, 0x47, 0x30, 0xE8,
+ 0xFD, 0x86, 0x2E, 0x7B, 0x7E, 0xCC, 0x34, 0x13,
+ 0x94, 0x45, 0x38, 0x74, 0x29, 0xB0, 0x37, 0xC3,
+ 0x26, 0x6C, 0x39, 0xA3, 0x89, 0xEB, 0xA2, 0x20,
+ 0x00, 0xE0, 0x73, 0xE7, 0xB5, 0xCB, 0xED, 0x3E,
+ 0x79, 0x09, 0xFA, 0x32, 0x54, 0xBA, 0x05, 0x96,
+ 0xDE, 0x23, 0xD0, 0xA1, 0xAB, 0xFE, 0xF2, 0x22,
+ 0xB2, 0x9B, 0x7D, 0x44, 0x12, 0x3D, 0x40, 0x82,
+ 0xA0, 0xA8, 0x33, 0xDC, 0xF7, 0xFB, 0xAC, 0x41,
+ 0x8A, 0x9C, 0x60, 0x11, 0xC8, 0xF0, 0xEA, 0x57,
+ 0x3A, 0x42, 0xCD, 0x1D, 0x3C, 0xC6, 0x97, 0x62,
+ 0x55, 0x9F, 0xF3, 0x93, 0x91, 0xDA, 0x6A, 0xE5,
+ 0x27, 0x8E, 0x4E, 0xFF, 0xA4, 0x80, 0x04, 0xE1,
+ 0x2B, 0x5E, 0xC0, 0x64, 0xC2, 0xD8, 0x46, 0x8C,
+ 0xD4, 0x0F, 0xC4, 0x43, 0xD9, 0x9E, 0x4B, 0x5C,
+ 0x0A, 0x8B, 0xBF, 0xD7, 0x7A, 0x81, 0x3B, 0x4A,
+ 0x58, 0xB6, 0x21, 0x1F, 0xC1, 0xBD, 0xB1, 0x77,
+ 0x72, 0x1C, 0x4D, 0xBC, 0xA5, 0x65, 0xC7, 0xF5,
+ 0xB4, 0x2D, 0x69, 0x71, 0xE6, 0x8F, 0xBB, 0x03,
+ 0xAF, 0xD6, 0x08, 0x75, 0xB7, 0x31, 0xF4, 0x2A,
+ 0x48, 0x70, 0x0C, 0x8D, 0xD1, 0x87, 0x2F, 0x16,
+ 0x5A, 0x5B, 0x98, 0xA6, 0xC5, 0x99, 0x50, 0x07,
+ 0xDD, 0x92, 0x25, 0x68, 0x0D, 0xBE, 0x78, 0x0B,
+ 0xAD, 0x84, 0x6B, 0x19, 0x52, 0x7C, 0xF6, 0xB3,
+ 0x56, 0x83, 0x88, 0xEE, 0x2C, 0x1B, 0x6E, 0x53,
+ 0x67, 0xE2, 0x6F, 0x15, 0x06, 0x10, 0x18, 0x85,
+ 0xF1, 0x6D, 0xF9, 0xC9, 0xAE, 0x3F, 0xB8, 0x95,
+ 0x35, 0xDF, 0xEF, 0xA7, 0x7F, 0x24, 0xF8, 0xE3,
+ 0xCF, 0xE9, 0xDB, 0xD3, 0x02, 0x9A, 0x0E, 0x5F };
+
+static const unsigned char table_63[256] = {
+ 0x0C, 0x02, 0xEE, 0x94, 0x2D, 0x76, 0x96, 0x75,
+ 0x21, 0xDC, 0x37, 0x03, 0xC0, 0xF7, 0xDF, 0xEF,
+ 0xB1, 0x1D, 0xCF, 0x15, 0x5A, 0xB4, 0xCC, 0x81,
+ 0x89, 0x6B, 0xA5, 0x2E, 0x6D, 0xD4, 0x08, 0x44,
+ 0x2A, 0x60, 0x50, 0xBF, 0x40, 0x7D, 0x5F, 0x64,
+ 0x93, 0x70, 0xA4, 0x7F, 0xC9, 0xEB, 0x0A, 0xF8,
+ 0x9F, 0xA8, 0xBC, 0x25, 0xE5, 0xF3, 0x1B, 0xD7,
+ 0x29, 0x13, 0x0D, 0x69, 0x20, 0x5C, 0x0F, 0x91,
+ 0x4F, 0x62, 0x06, 0x26, 0x41, 0xED, 0xDA, 0x53,
+ 0x65, 0xFF, 0xCD, 0x3F, 0xF6, 0x01, 0xCE, 0xA2,
+ 0x04, 0xDE, 0x27, 0x87, 0xBA, 0x86, 0x24, 0x78,
+ 0xAF, 0xE1, 0x3D, 0xD0, 0xC8, 0x1F, 0x4A, 0x2C,
+ 0x9A, 0xF0, 0xCB, 0xAD, 0x0B, 0x59, 0xC5, 0x58,
+ 0xEA, 0x8A, 0xA1, 0x45, 0xB7, 0x5D, 0xB5, 0x77,
+ 0x2B, 0x47, 0x05, 0x00, 0xAC, 0x61, 0xFA, 0x33,
+ 0x74, 0x31, 0xCA, 0x22, 0x42, 0x8B, 0xFE, 0x09,
+ 0xB2, 0x6E, 0x1A, 0xBE, 0xAA, 0x7B, 0xEC, 0xF4,
+ 0x51, 0x66, 0x28, 0x12, 0xFC, 0x5E, 0x67, 0xF5,
+ 0xB9, 0x82, 0x90, 0x8E, 0x8D, 0x17, 0xE7, 0xE8,
+ 0xB0, 0xC3, 0x16, 0xA0, 0x4B, 0xB6, 0xFB, 0x7E,
+ 0xC4, 0x85, 0x4C, 0x1E, 0xC7, 0x39, 0x4E, 0xA9,
+ 0xE3, 0x4D, 0x32, 0x72, 0x35, 0x80, 0xE0, 0x34,
+ 0xB8, 0x73, 0x98, 0x49, 0x92, 0x30, 0xD5, 0xD2,
+ 0xA3, 0x54, 0x7A, 0x84, 0x8F, 0x6C, 0xFD, 0x43,
+ 0x3A, 0x36, 0x3B, 0xD9, 0x48, 0x6A, 0x14, 0x79,
+ 0xD1, 0x57, 0x88, 0xDB, 0xE4, 0x9B, 0xF9, 0x99,
+ 0x10, 0x71, 0xC1, 0x68, 0x9E, 0x11, 0xAB, 0xBD,
+ 0x7C, 0x3E, 0x3C, 0x18, 0x9D, 0x97, 0xF2, 0xE6,
+ 0xA6, 0xF1, 0x46, 0xC2, 0x19, 0xBB, 0x52, 0xD8,
+ 0x95, 0xD3, 0x23, 0xAE, 0x07, 0x2F, 0xE9, 0x63,
+ 0x1C, 0x55, 0x6F, 0x9C, 0x56, 0x38, 0xC6, 0x5B,
+ 0x8C, 0xE2, 0x83, 0xA7, 0xD6, 0x0E, 0xB3, 0xDD };
+
+static const unsigned char table_64[32] = {
+ 0x03, 0x05, 0x0D, 0x09, 0x1A, 0x16, 0x08, 0x10,
+ 0x06, 0x1E, 0x1C, 0x15, 0x02, 0x04, 0x17, 0x0C,
+ 0x18, 0x0B, 0x19, 0x11, 0x1B, 0x14, 0x13, 0x0A,
+ 0x0E, 0x00, 0x1D, 0x1F, 0x01, 0x0F, 0x07, 0x12 };
+
+static const unsigned char table_65[32] = {
+ 0x01, 0x0A, 0x1E, 0x14, 0x10, 0x1D, 0x0D, 0x17,
+ 0x0E, 0x0C, 0x0F, 0x12, 0x04, 0x1A, 0x05, 0x02,
+ 0x08, 0x1C, 0x09, 0x1F, 0x0B, 0x13, 0x19, 0x1B,
+ 0x11, 0x00, 0x16, 0x06, 0x03, 0x18, 0x15, 0x07 };
+
+static const unsigned char table_66[32] = {
+ 0x1C, 0x18, 0x0C, 0x09, 0x05, 0x03, 0x15, 0x12,
+ 0x0D, 0x02, 0x08, 0x0E, 0x19, 0x07, 0x13, 0x17,
+ 0x1E, 0x1D, 0x1F, 0x11, 0x06, 0x0A, 0x0B, 0x14,
+ 0x0F, 0x10, 0x01, 0x1B, 0x00, 0x04, 0x1A, 0x16 };
+
+static const unsigned char table_67[256] = {
+ 0x6B, 0x49, 0xC8, 0x86, 0xFF, 0xC0, 0x5D, 0xEF,
+ 0xF7, 0x06, 0xE0, 0x98, 0xA9, 0x72, 0x71, 0xD5,
+ 0xBA, 0x7F, 0x10, 0xD1, 0xBE, 0x41, 0x9C, 0x40,
+ 0x28, 0x8E, 0xE5, 0x74, 0x47, 0x9E, 0x3E, 0x7C,
+ 0xB5, 0xCD, 0x3F, 0x20, 0xF2, 0xA6, 0xDC, 0x97,
+ 0x32, 0x6D, 0x52, 0xF5, 0x16, 0x05, 0xFE, 0x04,
+ 0x3D, 0x53, 0x50, 0x23, 0x39, 0x77, 0x08, 0x60,
+ 0x75, 0x18, 0x4A, 0xC6, 0xBB, 0xE7, 0xF1, 0xAB,
+ 0xEB, 0x88, 0xB6, 0x82, 0x6E, 0x91, 0xF3, 0x34,
+ 0x3A, 0x42, 0x1A, 0xDF, 0xA1, 0xB3, 0x92, 0xBF,
+ 0xB7, 0x00, 0xD4, 0xDE, 0x31, 0xF0, 0x1C, 0xDA,
+ 0x4F, 0x61, 0x67, 0x2C, 0x07, 0xF9, 0x15, 0xA4,
+ 0x7A, 0x26, 0x45, 0x2A, 0x12, 0x9F, 0xF4, 0x14,
+ 0x8C, 0x90, 0xFC, 0xC5, 0x4B, 0x87, 0xE2, 0xC7,
+ 0xD0, 0x8A, 0xE8, 0xDD, 0xEE, 0x3C, 0x2F, 0x22,
+ 0x6A, 0x54, 0x37, 0x9B, 0x84, 0x25, 0x8F, 0xE3,
+ 0xD7, 0xD8, 0x4E, 0xAD, 0x0F, 0x4C, 0x56, 0xA2,
+ 0xD3, 0xB0, 0x73, 0x0B, 0xAE, 0xEA, 0x1D, 0x01,
+ 0x36, 0xB4, 0x2D, 0xC4, 0x19, 0x58, 0x1E, 0x62,
+ 0xE9, 0xB2, 0x5B, 0x5A, 0xBD, 0xD6, 0x65, 0x94,
+ 0x9A, 0x55, 0xCC, 0x99, 0x1B, 0x85, 0x2B, 0xBC,
+ 0x8D, 0x46, 0x81, 0xB8, 0xA3, 0x29, 0x5F, 0x35,
+ 0x5C, 0xB1, 0x1F, 0x13, 0x17, 0xCB, 0x51, 0x02,
+ 0x09, 0x7E, 0xA7, 0x69, 0x6F, 0x95, 0x30, 0x7B,
+ 0xCA, 0x48, 0xAF, 0xAA, 0x0E, 0x44, 0x38, 0xB9,
+ 0x0D, 0x11, 0xA0, 0xD9, 0x0C, 0xDB, 0xF8, 0x68,
+ 0x33, 0x79, 0x59, 0x66, 0x4D, 0x03, 0xE1, 0x89,
+ 0xE4, 0x3B, 0x78, 0xC2, 0x64, 0x6C, 0x27, 0xC9,
+ 0xCF, 0xAC, 0xED, 0xFA, 0x5E, 0x2E, 0x76, 0x57,
+ 0x93, 0xEC, 0x80, 0xA8, 0xE6, 0xCE, 0xC1, 0xA5,
+ 0x9D, 0xD2, 0xC3, 0x0A, 0x7D, 0x70, 0xF6, 0x63,
+ 0x24, 0x43, 0x21, 0x83, 0xFB, 0xFD, 0x8B, 0x96 };
+
+static const unsigned char table_68[256] = {
+ 0x93, 0xFF, 0x83, 0x70, 0x12, 0x2D, 0x1C, 0xD6,
+ 0xF9, 0xEE, 0xCF, 0x94, 0x7B, 0xB5, 0xA4, 0x84,
+ 0x99, 0xF7, 0x67, 0x32, 0xFC, 0x8A, 0xE3, 0xE4,
+ 0xCE, 0xC6, 0x77, 0x7E, 0xDA, 0x42, 0x85, 0xF0,
+ 0x7D, 0x48, 0x28, 0x79, 0xDE, 0x5B, 0xE2, 0x0F,
+ 0x75, 0xC5, 0x2C, 0x4F, 0xF3, 0xEC, 0x14, 0x10,
+ 0x9C, 0x6E, 0x59, 0x4A, 0x20, 0x34, 0xA3, 0x89,
+ 0xE0, 0x4E, 0x52, 0x88, 0x81, 0x5F, 0x6F, 0x71,
+ 0x17, 0x3B, 0x21, 0xB4, 0xCB, 0x9B, 0x18, 0x13,
+ 0xE8, 0xE1, 0x02, 0x2E, 0xED, 0x00, 0xA7, 0x1B,
+ 0x06, 0xF4, 0x27, 0xDC, 0x35, 0x2F, 0x08, 0x9D,
+ 0x7C, 0xC0, 0x36, 0xA6, 0x6B, 0xDF, 0x4C, 0xBC,
+ 0xFE, 0xDB, 0xA5, 0xA8, 0x8D, 0x73, 0x7F, 0xC7,
+ 0x8E, 0x60, 0x31, 0x61, 0x4B, 0x29, 0xD7, 0xE9,
+ 0xBD, 0xAB, 0xCC, 0xFA, 0xD9, 0xEF, 0xC2, 0xD4,
+ 0x19, 0x11, 0x15, 0xC9, 0xB1, 0xD5, 0x64, 0x97,
+ 0xE7, 0x8F, 0x05, 0x44, 0xF8, 0xF1, 0x58, 0x47,
+ 0x2A, 0x03, 0x1F, 0xAF, 0x0D, 0x04, 0x23, 0xB8,
+ 0x24, 0x51, 0xB2, 0x54, 0x41, 0x53, 0x5C, 0xAE,
+ 0xB7, 0xB3, 0xB6, 0x3D, 0x37, 0x39, 0x55, 0xBF,
+ 0x0B, 0x7A, 0x57, 0x3C, 0x0E, 0x40, 0x6A, 0xF5,
+ 0x72, 0xDD, 0xBB, 0x8B, 0xAA, 0x46, 0xA0, 0x30,
+ 0x56, 0x78, 0x38, 0xBA, 0x9E, 0x92, 0x87, 0xFB,
+ 0x66, 0x90, 0x1E, 0xB9, 0x96, 0x65, 0xA2, 0x50,
+ 0x1D, 0xC3, 0x26, 0x22, 0xD0, 0x0A, 0x43, 0xF2,
+ 0xB0, 0xEB, 0xAC, 0x62, 0x98, 0x3F, 0xD3, 0x69,
+ 0xA1, 0x9F, 0x16, 0x95, 0xE6, 0xF6, 0x2B, 0x25,
+ 0x1A, 0xD2, 0xBE, 0x09, 0x5D, 0x45, 0xC4, 0xFD,
+ 0x5A, 0x07, 0x0C, 0x82, 0x3E, 0x49, 0x74, 0x6C,
+ 0x68, 0x5E, 0xCA, 0xEA, 0xCD, 0x9A, 0xAD, 0xD1,
+ 0x33, 0x86, 0x76, 0x80, 0xE5, 0xC8, 0xD8, 0xA9,
+ 0x8C, 0x6D, 0x91, 0x63, 0x3A, 0x4D, 0xC1, 0x01 };
+
+static const unsigned char table_69[256] = {
+ 0x21, 0x6B, 0x9B, 0xAE, 0x11, 0x5A, 0x91, 0xC2,
+ 0x47, 0x8E, 0x87, 0x86, 0x4F, 0xFC, 0x8F, 0x66,
+ 0x97, 0x2F, 0x61, 0x9C, 0x5B, 0x4C, 0xB3, 0x14,
+ 0x77, 0x48, 0x62, 0xE1, 0x54, 0x64, 0xDD, 0xCD,
+ 0x30, 0xB7, 0x2D, 0xD2, 0xC3, 0xC0, 0x0B, 0xD8,
+ 0x53, 0x98, 0x16, 0x56, 0x7A, 0x35, 0x50, 0xD9,
+ 0xE8, 0x2C, 0x32, 0x55, 0x17, 0x5D, 0x79, 0xEB,
+ 0xC8, 0x75, 0x67, 0xE2, 0x4B, 0xBA, 0xFE, 0x57,
+ 0x10, 0xF4, 0x70, 0x2A, 0xBB, 0xA6, 0x72, 0x36,
+ 0xAF, 0x8D, 0xAB, 0x90, 0xE3, 0x2B, 0xB2, 0x26,
+ 0x93, 0x01, 0xBD, 0x71, 0xF9, 0x05, 0xC7, 0x80,
+ 0x29, 0xCC, 0x3B, 0x22, 0xF2, 0x12, 0x81, 0x34,
+ 0xF6, 0x1A, 0x8B, 0xDF, 0x28, 0x46, 0x9E, 0x6A,
+ 0x23, 0x85, 0x74, 0xE7, 0xE6, 0x52, 0xA0, 0x49,
+ 0xF0, 0x19, 0x25, 0xAC, 0x78, 0x42, 0xD6, 0xA2,
+ 0x37, 0x65, 0x4D, 0x94, 0x02, 0x6F, 0xB4, 0xC6,
+ 0x99, 0xD3, 0x9A, 0x33, 0xB8, 0x00, 0xCA, 0xE4,
+ 0x45, 0xAD, 0x1B, 0x6C, 0x03, 0xA8, 0x07, 0x8A,
+ 0x60, 0x69, 0xFF, 0xF7, 0xA7, 0x27, 0x95, 0xF5,
+ 0x82, 0xCB, 0xEC, 0xED, 0x4E, 0xFB, 0xA4, 0x59,
+ 0xDA, 0xCF, 0x2E, 0x20, 0xFA, 0x31, 0xD1, 0xEA,
+ 0x4A, 0xE9, 0x5E, 0xA9, 0xA1, 0x08, 0x1C, 0x96,
+ 0x38, 0xB9, 0xEE, 0x7F, 0xAA, 0xF1, 0x7D, 0x3A,
+ 0xA5, 0x43, 0xC5, 0xE0, 0x24, 0x39, 0x0D, 0xDE,
+ 0xB0, 0xF8, 0xBE, 0x58, 0x7E, 0x51, 0xD4, 0x89,
+ 0x15, 0x40, 0x3E, 0xB1, 0x1F, 0x5F, 0x68, 0x63,
+ 0x84, 0x3D, 0x88, 0xBC, 0x41, 0xEF, 0xB5, 0xBF,
+ 0x06, 0x6E, 0x9D, 0x3F, 0x0E, 0x76, 0x5C, 0xDC,
+ 0x13, 0xF3, 0xE5, 0x8C, 0x7C, 0x04, 0x0A, 0xD5,
+ 0x18, 0xC4, 0x44, 0x09, 0xC9, 0x1D, 0x9F, 0xFD,
+ 0xD0, 0x0F, 0x6D, 0xD7, 0x92, 0x7B, 0x0C, 0xA3,
+ 0x73, 0xDB, 0xB6, 0x83, 0xCE, 0x1E, 0xC1, 0x3C };
+
+static const unsigned char table_70[256] = {
+ 0x54, 0x23, 0xF1, 0x09, 0x9D, 0xEB, 0x26, 0xD9,
+ 0x6C, 0xC1, 0xBC, 0x3D, 0x6E, 0xB0, 0x5F, 0xE2,
+ 0x59, 0x4D, 0x95, 0xFA, 0xD8, 0x29, 0xAA, 0x8E,
+ 0xF5, 0xEF, 0x43, 0x76, 0xFD, 0x0D, 0x4F, 0xAD,
+ 0xB7, 0xFC, 0xA8, 0x9F, 0x62, 0xC2, 0x7B, 0x10,
+ 0x0B, 0xF2, 0x73, 0xA9, 0x46, 0x4C, 0x53, 0xD7,
+ 0x0A, 0x50, 0x89, 0x63, 0x48, 0xD6, 0xA2, 0x44,
+ 0xE6, 0x8D, 0x69, 0x2C, 0xF9, 0xC0, 0x35, 0x06,
+ 0x66, 0x21, 0x9E, 0xD2, 0x98, 0xF7, 0x9B, 0xE7,
+ 0x12, 0xB8, 0xA5, 0xBA, 0xE0, 0x79, 0x71, 0x7E,
+ 0x8C, 0x24, 0xED, 0x7C, 0x60, 0x81, 0xC3, 0x5C,
+ 0x2B, 0xE5, 0xEE, 0xB5, 0xA4, 0x05, 0x03, 0x34,
+ 0x16, 0x2A, 0xA3, 0x2D, 0x3F, 0xDF, 0x07, 0x5B,
+ 0xAE, 0x47, 0x61, 0x08, 0x18, 0xDB, 0x6D, 0x3C,
+ 0x96, 0xD5, 0xAB, 0x78, 0x94, 0x45, 0x20, 0x9A,
+ 0xE4, 0x13, 0x68, 0xDD, 0xDE, 0x31, 0x14, 0x57,
+ 0x02, 0x52, 0x56, 0x1C, 0x1B, 0xE9, 0xD0, 0xA1,
+ 0x22, 0x64, 0xB2, 0x7A, 0xCF, 0x5D, 0x00, 0x0F,
+ 0xF8, 0x5E, 0x36, 0x58, 0x40, 0xAF, 0x19, 0x32,
+ 0x2E, 0xB3, 0x72, 0xBE, 0xB9, 0xD3, 0xCD, 0x7D,
+ 0x4A, 0x1D, 0x33, 0x2F, 0xAC, 0x27, 0x41, 0xE8,
+ 0x55, 0xCB, 0x0E, 0x5A, 0x77, 0xFB, 0x8B, 0x86,
+ 0x75, 0x8A, 0x51, 0xEC, 0xDA, 0xC6, 0xA6, 0xCC,
+ 0x91, 0x4B, 0x11, 0xF6, 0xEA, 0xD1, 0xB6, 0x4E,
+ 0x82, 0x04, 0x92, 0x30, 0xF4, 0x25, 0x88, 0x1E,
+ 0x9C, 0xA0, 0xC8, 0x6A, 0x93, 0x87, 0x1F, 0xB4,
+ 0xB1, 0x8F, 0x65, 0xCA, 0xFE, 0xFF, 0x97, 0x15,
+ 0x99, 0x28, 0x80, 0x42, 0x70, 0x85, 0x0C, 0x3B,
+ 0xBD, 0xE1, 0xA7, 0x17, 0xC9, 0x3A, 0xBB, 0x6B,
+ 0x37, 0xF0, 0xC5, 0x39, 0x6F, 0x01, 0x83, 0x67,
+ 0x74, 0xCE, 0xDC, 0x90, 0x3E, 0xF3, 0x7F, 0xC4,
+ 0x49, 0x84, 0x38, 0xC7, 0xE3, 0xD4, 0x1A, 0xBF };
+
+static const unsigned char table_71[32] = {
+ 0x17, 0x13, 0x0E, 0x1A, 0x0D, 0x18, 0x19, 0x10,
+ 0x14, 0x11, 0x16, 0x05, 0x04, 0x00, 0x12, 0x0A,
+ 0x02, 0x07, 0x03, 0x0B, 0x09, 0x1F, 0x1C, 0x0F,
+ 0x0C, 0x06, 0x1B, 0x08, 0x1D, 0x01, 0x15, 0x1E };
+
+static const unsigned char table_72[256] = {
+ 0xC9, 0xA7, 0x1B, 0xEC, 0x2B, 0x8B, 0xB0, 0xEB,
+ 0x7F, 0x39, 0x25, 0xD9, 0x1D, 0xD5, 0x67, 0xA0,
+ 0xB3, 0xAC, 0x3B, 0xC8, 0x82, 0xC0, 0xE3, 0x9E,
+ 0x4C, 0x9B, 0xAF, 0xFD, 0x91, 0x86, 0x5F, 0x92,
+ 0xB4, 0x42, 0x3C, 0x45, 0x12, 0xC4, 0xE2, 0xE1,
+ 0x6C, 0x1F, 0xC6, 0x40, 0x93, 0x2A, 0xC2, 0x72,
+ 0x2E, 0x14, 0x51, 0xA5, 0x70, 0xBD, 0xA2, 0xC7,
+ 0x7D, 0xF1, 0x9F, 0x64, 0xC1, 0xF7, 0x80, 0xFF,
+ 0x50, 0x49, 0x8C, 0x66, 0x13, 0x48, 0x6A, 0x0A,
+ 0x26, 0x94, 0x83, 0x1E, 0x84, 0xBB, 0x57, 0x27,
+ 0x44, 0x5B, 0x62, 0xF6, 0x09, 0x4F, 0x77, 0x76,
+ 0x2D, 0x7E, 0xCD, 0x0B, 0x24, 0xFE, 0x81, 0xB8,
+ 0x21, 0x85, 0xCF, 0xA8, 0x75, 0x56, 0x37, 0x17,
+ 0xAA, 0x23, 0xE5, 0xE8, 0x9A, 0x9D, 0x2F, 0x04,
+ 0x31, 0x4A, 0x7C, 0xFC, 0xD6, 0xE4, 0x29, 0xC3,
+ 0xFB, 0x36, 0x1C, 0x0C, 0xCE, 0xEE, 0x0D, 0xF3,
+ 0x46, 0xF8, 0x41, 0x0E, 0x68, 0xAB, 0x2C, 0x69,
+ 0x96, 0x90, 0x28, 0xED, 0x02, 0x63, 0x07, 0xAD,
+ 0xB2, 0xDC, 0x05, 0xE6, 0x78, 0x03, 0xA4, 0x7A,
+ 0x5C, 0x52, 0x95, 0x5D, 0x88, 0x01, 0xDF, 0x35,
+ 0x5E, 0xB6, 0x06, 0x4D, 0x15, 0x89, 0x59, 0x3F,
+ 0xF0, 0xA1, 0xA3, 0x99, 0x19, 0xEA, 0xDB, 0xE0,
+ 0x6B, 0x71, 0x6E, 0xB7, 0x65, 0x54, 0x9C, 0xBC,
+ 0x98, 0xDD, 0x4B, 0x60, 0x3D, 0xBF, 0xF5, 0xD1,
+ 0xD7, 0xF9, 0x55, 0x61, 0xA9, 0xB1, 0x6D, 0xDE,
+ 0x79, 0xAE, 0x1A, 0x34, 0x3A, 0x4E, 0xCB, 0x38,
+ 0xBA, 0x97, 0x00, 0x74, 0xEF, 0xD8, 0x18, 0x33,
+ 0x7B, 0xFA, 0x22, 0x32, 0x20, 0xCA, 0x8A, 0xBE,
+ 0xA6, 0x43, 0x11, 0x10, 0xD0, 0xD3, 0x87, 0x73,
+ 0x6F, 0xF4, 0x8D, 0xCC, 0x30, 0x0F, 0x16, 0xDA,
+ 0xB5, 0xC5, 0xD4, 0x47, 0x8E, 0xE7, 0x58, 0x8F,
+ 0x08, 0x53, 0xF2, 0xB9, 0x5A, 0x3E, 0xE9, 0xD2 };
+
+static const unsigned char table_73[256] = {
+ 0x36, 0x37, 0xED, 0xD8, 0xBF, 0xD7, 0x12, 0xB7,
+ 0x40, 0x32, 0x19, 0x4A, 0x44, 0x2A, 0xCE, 0xA5,
+ 0x29, 0x13, 0x43, 0x51, 0x5C, 0xD0, 0x76, 0x6E,
+ 0x41, 0xD6, 0xE2, 0x4F, 0xB8, 0x27, 0x2E, 0xCF,
+ 0xD9, 0xE0, 0x69, 0xC0, 0x59, 0x77, 0x62, 0x6F,
+ 0x53, 0xE7, 0x93, 0xD4, 0xAD, 0xC8, 0x4C, 0xC2,
+ 0x2C, 0xBE, 0xAA, 0xA0, 0x22, 0x78, 0x14, 0xB3,
+ 0xB0, 0xEA, 0xBA, 0x9A, 0x33, 0x1B, 0x31, 0x6C,
+ 0xFC, 0x0A, 0x0B, 0xA1, 0xE4, 0x75, 0x7C, 0xE3,
+ 0x65, 0x21, 0xA9, 0xA4, 0x4E, 0x3C, 0x5F, 0x39,
+ 0x74, 0xA2, 0x9E, 0x03, 0x70, 0xD2, 0xFD, 0x1D,
+ 0x25, 0x72, 0x73, 0x8E, 0x7B, 0xB2, 0x6A, 0x92,
+ 0x81, 0xF3, 0xF0, 0x46, 0x08, 0x85, 0xE6, 0x30,
+ 0x05, 0x7E, 0xEC, 0x0D, 0xDD, 0x42, 0x2F, 0x5B,
+ 0xB9, 0xCB, 0x84, 0x0C, 0x16, 0xC7, 0x24, 0xFA,
+ 0xF9, 0x8F, 0x20, 0xAC, 0x10, 0x55, 0xC3, 0x1A,
+ 0x8B, 0x94, 0x3D, 0xDB, 0xC9, 0x04, 0xB5, 0xCC,
+ 0xC6, 0x98, 0xB6, 0x8D, 0x0F, 0x3A, 0x06, 0x4B,
+ 0xEF, 0x35, 0x68, 0x3F, 0xEE, 0xE5, 0x63, 0xC5,
+ 0x60, 0x88, 0x52, 0x2D, 0x6D, 0xAB, 0xCD, 0xC4,
+ 0x1F, 0xF4, 0xCA, 0x67, 0x7D, 0x1C, 0xDA, 0x34,
+ 0xDE, 0x86, 0xAE, 0xF1, 0x61, 0x09, 0xF5, 0xF6,
+ 0x49, 0xE9, 0xF2, 0x48, 0x1E, 0xD3, 0x56, 0x18,
+ 0x9B, 0xB1, 0x57, 0x9D, 0xBB, 0x5E, 0xAF, 0x87,
+ 0x9F, 0x8A, 0xC1, 0x79, 0xA7, 0xA8, 0xFB, 0xDC,
+ 0x47, 0x3E, 0x97, 0x80, 0x91, 0xA6, 0x7A, 0xA3,
+ 0x9C, 0x11, 0x02, 0x2B, 0x58, 0xD1, 0xF7, 0x00,
+ 0x83, 0x01, 0xE8, 0xFE, 0x50, 0x23, 0x66, 0x4D,
+ 0xD5, 0x82, 0x89, 0x3B, 0xEB, 0xE1, 0xF8, 0x5A,
+ 0x15, 0x7F, 0x8C, 0x17, 0x96, 0x28, 0x5D, 0x64,
+ 0x26, 0x38, 0x71, 0x0E, 0x45, 0xDF, 0xB4, 0x99,
+ 0xFF, 0x90, 0x6B, 0xBC, 0x54, 0x95, 0xBD, 0x07 };
+
+static const unsigned char table_74[256] = {
+ 0xA7, 0xCF, 0x99, 0x1A, 0x13, 0xC7, 0xE9, 0xC4,
+ 0xB6, 0x0E, 0x15, 0x09, 0xFF, 0xDF, 0xBE, 0x03,
+ 0xAD, 0xF1, 0xB0, 0x3C, 0x4A, 0x9B, 0xF5, 0x12,
+ 0xA1, 0x2C, 0xDB, 0x51, 0x5E, 0x6F, 0xE6, 0x49,
+ 0x27, 0xBB, 0xAE, 0x56, 0xC0, 0x0C, 0x77, 0x60,
+ 0x5B, 0x69, 0xA2, 0xF0, 0x24, 0x8E, 0xE1, 0xA4,
+ 0xBC, 0x9F, 0x50, 0xD4, 0x61, 0x19, 0x67, 0x00,
+ 0x7B, 0xAB, 0xDD, 0x26, 0xCD, 0x6C, 0xE8, 0xA8,
+ 0x7A, 0x93, 0xEF, 0x20, 0x52, 0x1F, 0x1B, 0x46,
+ 0x25, 0x3B, 0x1E, 0x65, 0xC2, 0xF9, 0x10, 0xB2,
+ 0xB3, 0xD9, 0x21, 0xD2, 0x11, 0x94, 0xE2, 0xFC,
+ 0x38, 0x9E, 0x36, 0x87, 0xAA, 0x53, 0x45, 0x68,
+ 0x2B, 0xE7, 0x07, 0xFA, 0xD3, 0x8D, 0x3F, 0x17,
+ 0xC1, 0x06, 0x72, 0x62, 0x8C, 0x55, 0x73, 0x8A,
+ 0xC9, 0x2E, 0x5A, 0x7D, 0x02, 0x6D, 0xF8, 0x4B,
+ 0xE4, 0xBF, 0xEC, 0xB7, 0x31, 0xDC, 0xF4, 0xB8,
+ 0x47, 0x64, 0x0A, 0x33, 0x48, 0xAC, 0xFB, 0x05,
+ 0x3E, 0x34, 0x1C, 0x97, 0x1D, 0x63, 0x37, 0x2D,
+ 0xB1, 0x92, 0xED, 0x9D, 0x4C, 0xD5, 0x4E, 0x9A,
+ 0x0D, 0x79, 0x0F, 0xBD, 0x95, 0xBA, 0x08, 0x2A,
+ 0xC6, 0x7E, 0x88, 0xCB, 0xA6, 0x29, 0x70, 0x35,
+ 0x66, 0xCA, 0x89, 0x75, 0x6A, 0x4F, 0xB5, 0x6B,
+ 0x74, 0xDE, 0x01, 0x04, 0x81, 0x91, 0x90, 0x18,
+ 0x32, 0x0B, 0x7F, 0x44, 0xB4, 0xAF, 0xF2, 0xEB,
+ 0x22, 0xFD, 0x14, 0xA0, 0xFE, 0x8B, 0xB9, 0x16,
+ 0x86, 0xE3, 0xD7, 0xDA, 0xC5, 0x3A, 0x41, 0x83,
+ 0xD1, 0x28, 0x54, 0x30, 0xE0, 0x40, 0xA5, 0x57,
+ 0x8F, 0x84, 0xD6, 0x96, 0x39, 0xE5, 0x42, 0x80,
+ 0xA9, 0x58, 0xCE, 0x5D, 0xEE, 0x5F, 0xA3, 0xD0,
+ 0xC8, 0x59, 0x43, 0x4D, 0x5C, 0xF7, 0xCC, 0x76,
+ 0x6E, 0xF3, 0x23, 0x3D, 0x85, 0x82, 0x78, 0xF6,
+ 0x2F, 0xD8, 0xC3, 0x7C, 0x9C, 0x98, 0xEA, 0x71 };
+
+static const unsigned char table_75[256] = {
+ 0xE7, 0xA5, 0x30, 0xE1, 0x9D, 0x81, 0xBE, 0x83,
+ 0xB2, 0x1E, 0xE4, 0x69, 0x2F, 0x2B, 0x0D, 0xEB,
+ 0x7C, 0x59, 0x2D, 0xAA, 0x01, 0x0C, 0xDB, 0xED,
+ 0xC4, 0xEE, 0x5D, 0x38, 0x72, 0xD8, 0x70, 0xCE,
+ 0x0B, 0xF6, 0x7F, 0x48, 0x26, 0x9E, 0xA3, 0x44,
+ 0xD6, 0xCF, 0x0F, 0x6B, 0xFD, 0x23, 0x98, 0xAB,
+ 0x11, 0xD4, 0x92, 0x91, 0x5E, 0x08, 0x4D, 0xC6,
+ 0xF0, 0xA8, 0x7E, 0x8A, 0x1D, 0xA1, 0x97, 0x76,
+ 0x3E, 0x64, 0x07, 0x24, 0xDE, 0x75, 0xA4, 0xCC,
+ 0x1A, 0x04, 0x4B, 0x6C, 0xFA, 0xB0, 0xC7, 0x35,
+ 0xE2, 0x56, 0x61, 0xA0, 0xE9, 0x27, 0xDF, 0xC3,
+ 0xE5, 0xF4, 0x8D, 0xB4, 0xD3, 0x52, 0xD7, 0x49,
+ 0xCD, 0x31, 0x6E, 0x3F, 0x4E, 0x6A, 0x5B, 0x65,
+ 0xCA, 0x14, 0x71, 0x53, 0xD9, 0x47, 0x28, 0x7D,
+ 0x17, 0x06, 0x5C, 0xFE, 0xBA, 0xB8, 0xAC, 0x15,
+ 0xE8, 0xE0, 0x9A, 0xDD, 0x1F, 0xBC, 0x95, 0x42,
+ 0xCB, 0x58, 0x00, 0x85, 0xD5, 0x62, 0xC9, 0xB6,
+ 0x05, 0x80, 0x4C, 0x3C, 0x1C, 0xF5, 0x03, 0xF8,
+ 0x96, 0x77, 0x02, 0x19, 0xF2, 0xFB, 0x5F, 0xC2,
+ 0xAE, 0x60, 0x1B, 0xAD, 0x8F, 0xC1, 0x33, 0xA6,
+ 0x20, 0xBF, 0xA7, 0xC8, 0x74, 0x18, 0x90, 0xE3,
+ 0x68, 0x09, 0x7A, 0x79, 0xB5, 0xDA, 0xF3, 0x0E,
+ 0x66, 0x84, 0xB3, 0xBB, 0xE6, 0xF7, 0xB7, 0x7B,
+ 0x39, 0x4A, 0x12, 0x4F, 0xC5, 0x41, 0x54, 0xD0,
+ 0xFF, 0x87, 0x63, 0x40, 0x99, 0x21, 0x29, 0xD2,
+ 0x3D, 0x37, 0x3A, 0x93, 0xFC, 0x25, 0xF1, 0xD1,
+ 0x2C, 0x6D, 0x8C, 0x5A, 0x8E, 0x9B, 0xBD, 0xAF,
+ 0x10, 0x55, 0xF9, 0x9F, 0x43, 0x0A, 0x50, 0x16,
+ 0x57, 0xB1, 0xC0, 0x73, 0x82, 0xEF, 0x88, 0x6F,
+ 0xEA, 0x2A, 0xEC, 0x2E, 0x86, 0x45, 0x51, 0x22,
+ 0xA9, 0x34, 0x94, 0x3B, 0xB9, 0x9C, 0xA2, 0x13,
+ 0x89, 0x46, 0x78, 0xDC, 0x32, 0x8B, 0x67, 0x36 };
+
+static const unsigned char table_76[256] = {
+ 0x3D, 0x66, 0x40, 0xC5, 0x1D, 0xF5, 0xE7, 0xB7,
+ 0x2C, 0x23, 0x09, 0xC2, 0x68, 0xE6, 0xD3, 0x8D,
+ 0x35, 0x94, 0x93, 0xF0, 0x43, 0x97, 0x2B, 0x4B,
+ 0x1A, 0xEB, 0x00, 0x4C, 0x6F, 0xE4, 0x92, 0xEA,
+ 0xB8, 0xA3, 0xA6, 0xEC, 0x11, 0x5E, 0x61, 0x81,
+ 0xE1, 0x48, 0xC9, 0xCB, 0xDB, 0x2E, 0x3B, 0xED,
+ 0x36, 0x52, 0x3A, 0xD2, 0x4F, 0x4E, 0x22, 0x96,
+ 0x57, 0x2D, 0x62, 0x53, 0xCF, 0xD9, 0x5B, 0x9F,
+ 0x8E, 0x78, 0xC6, 0x07, 0x7D, 0xA1, 0x02, 0xB4,
+ 0xF4, 0xB6, 0x34, 0x98, 0xDA, 0xA9, 0xD4, 0x54,
+ 0x99, 0x82, 0x0A, 0xD8, 0x88, 0x5D, 0x3C, 0xD0,
+ 0xAB, 0x31, 0xFB, 0x03, 0x17, 0x46, 0xE8, 0xE2,
+ 0xA4, 0xFF, 0xB0, 0xAA, 0xAD, 0x7C, 0x55, 0x49,
+ 0x75, 0x6B, 0x10, 0x24, 0xC0, 0x04, 0xB1, 0xBF,
+ 0x6A, 0xF6, 0x15, 0xEF, 0x5C, 0x60, 0x27, 0x3E,
+ 0x38, 0x63, 0xC1, 0x76, 0xFD, 0x84, 0xE0, 0xCD,
+ 0xFE, 0x30, 0xCE, 0xBB, 0xDC, 0x1E, 0x1B, 0xBC,
+ 0xB5, 0xE9, 0x9E, 0x8F, 0x0D, 0x3F, 0x91, 0x19,
+ 0x28, 0x37, 0x26, 0x42, 0x08, 0x9A, 0x0C, 0x83,
+ 0x90, 0x6D, 0x74, 0x65, 0xF2, 0x4A, 0xDE, 0x8B,
+ 0x67, 0x0E, 0x8C, 0x5F, 0xF9, 0x7F, 0x5A, 0x86,
+ 0x69, 0x45, 0x44, 0xD5, 0xF7, 0xE5, 0x8A, 0xA8,
+ 0xC8, 0x7E, 0x05, 0x64, 0xEE, 0x79, 0xBE, 0x7A,
+ 0x14, 0xD6, 0x50, 0x18, 0x25, 0xBD, 0x85, 0xE3,
+ 0xA2, 0x70, 0xCC, 0x59, 0x71, 0x77, 0xFA, 0x47,
+ 0x9B, 0x1F, 0x9D, 0xBA, 0x29, 0x4D, 0xF8, 0xDF,
+ 0xC4, 0x72, 0x2F, 0xAE, 0x06, 0x51, 0x41, 0xAF,
+ 0xF3, 0xDD, 0x87, 0xB2, 0x9C, 0xC7, 0x12, 0x16,
+ 0x20, 0xA7, 0x21, 0x73, 0xF1, 0x58, 0xD7, 0x7B,
+ 0xB9, 0xB3, 0x32, 0x01, 0x80, 0x1C, 0x39, 0x0B,
+ 0x13, 0x56, 0x6C, 0x89, 0x33, 0x6E, 0x2A, 0xA5,
+ 0xD1, 0x95, 0xC3, 0xA0, 0x0F, 0xCA, 0xAC, 0xFC };
+
+static const unsigned char table_77[32] = {
+ 0x1C, 0x0D, 0x1E, 0x01, 0x06, 0x16, 0x18, 0x17,
+ 0x0B, 0x1F, 0x04, 0x0F, 0x00, 0x19, 0x08, 0x0A,
+ 0x11, 0x03, 0x05, 0x07, 0x09, 0x0C, 0x15, 0x14,
+ 0x1A, 0x12, 0x13, 0x0E, 0x1D, 0x10, 0x02, 0x1B };
+
+static const unsigned char table_78[32] = {
+ 0x0E, 0x02, 0x17, 0x12, 0x1E, 0x09, 0x15, 0x03,
+ 0x01, 0x0B, 0x0F, 0x11, 0x10, 0x0A, 0x16, 0x06,
+ 0x07, 0x00, 0x1C, 0x1D, 0x1F, 0x0C, 0x18, 0x04,
+ 0x13, 0x0D, 0x1B, 0x08, 0x19, 0x14, 0x05, 0x1A };
+
+static const unsigned char table_79[32] = {
+ 0x12, 0x0B, 0x11, 0x01, 0x07, 0x0E, 0x1A, 0x0D,
+ 0x1E, 0x18, 0x14, 0x1F, 0x0A, 0x17, 0x19, 0x1B,
+ 0x00, 0x10, 0x0C, 0x08, 0x13, 0x02, 0x0F, 0x1D,
+ 0x09, 0x06, 0x04, 0x16, 0x15, 0x1C, 0x05, 0x03 };
+
+static const unsigned char table_80[256] = {
+ 0x14, 0xE7, 0x31, 0x0F, 0xD1, 0x5F, 0xED, 0x1E,
+ 0xA6, 0x77, 0x20, 0x57, 0x34, 0x64, 0x33, 0x0B,
+ 0x5A, 0xB4, 0x83, 0x62, 0xFD, 0x8E, 0xE4, 0xF3,
+ 0xBD, 0xA5, 0xC8, 0x6D, 0x3E, 0x4F, 0x01, 0x7A,
+ 0xD3, 0x45, 0x3C, 0xF2, 0x68, 0xFF, 0xE6, 0x84,
+ 0xC2, 0xC1, 0x53, 0x72, 0x8C, 0xA1, 0xC7, 0x00,
+ 0x89, 0x97, 0x69, 0xA4, 0xF8, 0xAA, 0xAD, 0x8F,
+ 0x24, 0xC6, 0x9A, 0xAC, 0xE5, 0xAB, 0x6B, 0x79,
+ 0x99, 0x60, 0x28, 0x2B, 0x3B, 0xAF, 0x1C, 0x80,
+ 0xA3, 0x8A, 0x1A, 0xB5, 0xE1, 0x9F, 0xDA, 0x78,
+ 0xD7, 0xC4, 0x87, 0x5D, 0xE9, 0x27, 0xFB, 0x18,
+ 0x94, 0x3A, 0xCE, 0x3F, 0xF6, 0x12, 0x75, 0x37,
+ 0x6E, 0x9E, 0x29, 0x6C, 0xF7, 0x7D, 0x92, 0x08,
+ 0x42, 0xB2, 0xBF, 0x0C, 0xB6, 0x25, 0xE0, 0x49,
+ 0x43, 0x91, 0x98, 0xBB, 0xDC, 0x63, 0xEA, 0xA8,
+ 0x74, 0x38, 0x35, 0xCD, 0x07, 0x70, 0x81, 0x41,
+ 0xC9, 0x51, 0xBC, 0xA9, 0x59, 0xD4, 0xB8, 0x2C,
+ 0x7C, 0x2D, 0xB3, 0x6F, 0x11, 0x86, 0x9D, 0x46,
+ 0xF0, 0x65, 0x76, 0x04, 0x0E, 0xCA, 0xBE, 0x5C,
+ 0xF9, 0x71, 0x9C, 0x21, 0x4C, 0x02, 0xFE, 0x8D,
+ 0xD5, 0x26, 0x40, 0xC3, 0x32, 0x9B, 0xB0, 0x5E,
+ 0x48, 0xC5, 0x85, 0x4B, 0x0A, 0xCC, 0x58, 0x52,
+ 0x61, 0x13, 0xEF, 0x4A, 0xEE, 0x03, 0xD9, 0xDE,
+ 0xA7, 0x19, 0x09, 0x7F, 0x5B, 0x96, 0xBA, 0x0D,
+ 0xCF, 0xD2, 0x06, 0x1F, 0xD8, 0xDB, 0xEC, 0xA0,
+ 0xDD, 0x66, 0x10, 0xA2, 0xDF, 0x30, 0xF4, 0x88,
+ 0xCB, 0x36, 0x82, 0xE3, 0x73, 0x17, 0x55, 0x15,
+ 0xF5, 0xB7, 0x23, 0xB1, 0xD6, 0xE2, 0x47, 0x7E,
+ 0x67, 0xE8, 0x1D, 0x16, 0x8B, 0xEB, 0xD0, 0x3D,
+ 0x6A, 0x54, 0x2A, 0x4E, 0x93, 0xFA, 0x44, 0x05,
+ 0x2F, 0x50, 0x2E, 0x95, 0xAE, 0x1B, 0x56, 0x7B,
+ 0x39, 0xB9, 0xC0, 0x22, 0xF1, 0x4D, 0x90, 0xFC };
+
+static const unsigned char table_81[32] = {
+ 0x03, 0x02, 0x1D, 0x0E, 0x09, 0x1A, 0x0C, 0x11,
+ 0x1C, 0x0D, 0x08, 0x12, 0x19, 0x10, 0x04, 0x17,
+ 0x15, 0x05, 0x0A, 0x00, 0x13, 0x16, 0x1B, 0x18,
+ 0x1E, 0x0B, 0x0F, 0x01, 0x07, 0x14, 0x1F, 0x06 };
+
+static const unsigned char table_82[256] = {
+ 0x53, 0xD3, 0x64, 0x89, 0x7D, 0xA5, 0x66, 0xA4,
+ 0x09, 0x46, 0x17, 0x2C, 0xAF, 0x8C, 0x21, 0x5F,
+ 0x3B, 0x22, 0xE3, 0x05, 0x07, 0x28, 0x2F, 0xAB,
+ 0xF4, 0x8E, 0x51, 0x31, 0x02, 0xC7, 0x48, 0x13,
+ 0x24, 0x12, 0xB8, 0xE5, 0xBD, 0xAE, 0x7E, 0xCC,
+ 0xC9, 0x98, 0x08, 0xEE, 0xDB, 0x1B, 0xE8, 0x3D,
+ 0x8F, 0xF2, 0xFB, 0x36, 0x4D, 0x94, 0x9C, 0x16,
+ 0xF7, 0x42, 0x9B, 0x2B, 0xFD, 0x7B, 0x77, 0x3F,
+ 0xC3, 0xFC, 0x23, 0x93, 0x50, 0x0C, 0x79, 0x18,
+ 0x47, 0xE1, 0xCB, 0xA7, 0xB6, 0x85, 0xE6, 0x61,
+ 0x2D, 0xD8, 0x9F, 0x80, 0xE9, 0x14, 0x0B, 0x1C,
+ 0x40, 0x76, 0x2A, 0x25, 0x0E, 0x99, 0xAC, 0xC4,
+ 0xEB, 0x29, 0x41, 0x8A, 0x73, 0x06, 0x57, 0xC6,
+ 0x8D, 0xFA, 0x5A, 0xCD, 0x67, 0xB2, 0xD9, 0x0A,
+ 0x1E, 0xEF, 0x3E, 0xA0, 0x45, 0x03, 0x27, 0xF1,
+ 0x38, 0x54, 0xC1, 0x7A, 0xFE, 0x52, 0x75, 0xD4,
+ 0x74, 0x7C, 0xD2, 0x68, 0xEA, 0x4C, 0x97, 0xF9,
+ 0xF5, 0x8B, 0x0F, 0x84, 0xA8, 0x6E, 0x9E, 0x11,
+ 0x6B, 0xBC, 0x4B, 0x6C, 0x9A, 0xF0, 0xA3, 0x1F,
+ 0x92, 0x19, 0xA2, 0x3A, 0x15, 0x04, 0xC5, 0x62,
+ 0xD5, 0x96, 0x90, 0x32, 0xAA, 0xD6, 0xCF, 0x35,
+ 0xB4, 0x81, 0x2E, 0x01, 0x10, 0x49, 0x70, 0xDE,
+ 0xDD, 0x88, 0xB9, 0x6D, 0x60, 0xBB, 0x44, 0xF8,
+ 0x3C, 0xEC, 0x34, 0x82, 0x95, 0x72, 0x58, 0x4E,
+ 0xE4, 0x0D, 0xBE, 0xDA, 0x83, 0x4A, 0x00, 0xBF,
+ 0xD0, 0xC8, 0x26, 0xB3, 0x65, 0x1A, 0x69, 0xCA,
+ 0xF3, 0xD7, 0x6F, 0x55, 0xE2, 0xFF, 0x5D, 0xDC,
+ 0x20, 0xF6, 0x63, 0xED, 0xE0, 0x59, 0x9D, 0xB1,
+ 0x1D, 0xAD, 0x91, 0xA1, 0xB7, 0xA9, 0xDF, 0xC0,
+ 0x39, 0xD1, 0x43, 0xCE, 0x4F, 0x5C, 0xE7, 0x37,
+ 0x5E, 0x33, 0x5B, 0xA6, 0xC2, 0xB0, 0xBA, 0x30,
+ 0x6A, 0x78, 0xB5, 0x71, 0x56, 0x87, 0x7F, 0x86 };
+
+static const unsigned char table_83[32] = {
+ 0x1B, 0x0A, 0x1F, 0x01, 0x10, 0x08, 0x0E, 0x18,
+ 0x06, 0x04, 0x00, 0x1C, 0x0C, 0x19, 0x0D, 0x16,
+ 0x02, 0x03, 0x09, 0x07, 0x13, 0x0F, 0x05, 0x12,
+ 0x17, 0x1E, 0x1A, 0x1D, 0x0B, 0x11, 0x14, 0x15 };
+
+static const unsigned char table_84[32] = {
+ 0x02, 0x1A, 0x0D, 0x15, 0x01, 0x16, 0x1E, 0x00,
+ 0x08, 0x1B, 0x04, 0x10, 0x1C, 0x18, 0x19, 0x14,
+ 0x0C, 0x11, 0x0B, 0x0E, 0x03, 0x0A, 0x07, 0x12,
+ 0x1D, 0x17, 0x13, 0x06, 0x0F, 0x05, 0x09, 0x1F };
+
+static const unsigned char table_85[256] = {
+ 0xC6, 0x7C, 0xCE, 0xBD, 0x84, 0x3E, 0x0B, 0xD8,
+ 0xFE, 0xCC, 0x46, 0x50, 0xD1, 0xFB, 0xA0, 0x6D,
+ 0xEA, 0xE2, 0x40, 0x51, 0x13, 0xB0, 0xD6, 0xB1,
+ 0xA8, 0xDF, 0x61, 0xA4, 0x80, 0x21, 0xB3, 0x33,
+ 0x06, 0x6B, 0xE3, 0x8C, 0xA1, 0x18, 0xBA, 0x03,
+ 0xD7, 0x8D, 0x54, 0x12, 0x4C, 0xEE, 0x9E, 0xCF,
+ 0x04, 0x2A, 0x08, 0xBB, 0xC2, 0xD4, 0xC3, 0x4A,
+ 0xD5, 0xFA, 0x36, 0x2F, 0x14, 0x3F, 0xED, 0x05,
+ 0x17, 0x28, 0x75, 0xFC, 0xA2, 0x1F, 0x4B, 0x6F,
+ 0x91, 0x7E, 0x4E, 0x96, 0x3B, 0xF3, 0x1D, 0x78,
+ 0xEB, 0x68, 0xF1, 0xA7, 0x9F, 0xC7, 0x59, 0x6C,
+ 0x92, 0xE6, 0x66, 0x07, 0x8A, 0x25, 0x26, 0x72,
+ 0x30, 0x5A, 0x81, 0x2C, 0x58, 0x32, 0xCB, 0xE0,
+ 0xF9, 0x48, 0x83, 0x9B, 0xA5, 0xE1, 0xA6, 0x64,
+ 0xFF, 0xC9, 0x8F, 0x53, 0x3D, 0x24, 0xC8, 0xDE,
+ 0x02, 0x7D, 0x09, 0xB4, 0x0A, 0x95, 0x0F, 0xE4,
+ 0xDB, 0xB7, 0x71, 0x4D, 0x1C, 0xAC, 0x35, 0xCD,
+ 0x29, 0xDD, 0xC1, 0xF2, 0xF4, 0xC0, 0x5C, 0x74,
+ 0xDC, 0x87, 0xFD, 0x4F, 0x11, 0x0E, 0x5D, 0x3C,
+ 0x01, 0x73, 0xE9, 0xD9, 0x10, 0x9A, 0x5B, 0xC5,
+ 0x98, 0x34, 0x15, 0xAE, 0xF7, 0xAA, 0x67, 0x23,
+ 0xBC, 0x8B, 0x7B, 0x65, 0xA9, 0xB6, 0x77, 0x00,
+ 0x19, 0x0C, 0x5E, 0x99, 0xF0, 0x55, 0x86, 0x97,
+ 0x69, 0xDA, 0x38, 0x9C, 0x16, 0xE8, 0x27, 0xAF,
+ 0x2E, 0x47, 0x6A, 0xD0, 0x79, 0x44, 0x45, 0x2B,
+ 0x5F, 0x85, 0xF5, 0x62, 0x70, 0x22, 0x7F, 0xF6,
+ 0x88, 0x93, 0x60, 0x42, 0x3A, 0x39, 0x49, 0x6E,
+ 0x89, 0x52, 0x20, 0xF8, 0xCA, 0xD2, 0x76, 0xB9,
+ 0xAB, 0x7A, 0x9D, 0xD3, 0xBE, 0x1A, 0xAD, 0x41,
+ 0x56, 0x31, 0x90, 0xB5, 0xB2, 0xEC, 0xA3, 0xE5,
+ 0x8E, 0x1B, 0xEF, 0xBF, 0x94, 0xC4, 0x0D, 0xB8,
+ 0x2D, 0x57, 0xE7, 0x82, 0x1E, 0x37, 0x63, 0x43 };
+
+static const unsigned char table_86[32] = {
+ 0x11, 0x07, 0x0F, 0x0A, 0x19, 0x1D, 0x0B, 0x09,
+ 0x1C, 0x1E, 0x14, 0x06, 0x0C, 0x16, 0x13, 0x04,
+ 0x15, 0x18, 0x00, 0x0D, 0x12, 0x05, 0x08, 0x02,
+ 0x10, 0x1A, 0x1F, 0x01, 0x17, 0x0E, 0x03, 0x1B };
+
+static const unsigned char table_87[32] = {
+ 0x17, 0x0E, 0x1D, 0x13, 0x0B, 0x19, 0x03, 0x06,
+ 0x09, 0x01, 0x0D, 0x15, 0x1C, 0x16, 0x18, 0x1B,
+ 0x11, 0x10, 0x00, 0x1E, 0x1F, 0x08, 0x12, 0x0F,
+ 0x02, 0x04, 0x07, 0x1A, 0x14, 0x0A, 0x0C, 0x05 };
+
+static const unsigned char table_88[32] = {
+ 0x09, 0x08, 0x17, 0x10, 0x0A, 0x07, 0x1C, 0x1F,
+ 0x04, 0x0E, 0x01, 0x0C, 0x0D, 0x1B, 0x03, 0x15,
+ 0x02, 0x1E, 0x18, 0x19, 0x0F, 0x06, 0x1A, 0x0B,
+ 0x05, 0x11, 0x14, 0x00, 0x16, 0x1D, 0x12, 0x13 };
+
+static const unsigned char table_89[32] = {
+ 0x15, 0x1C, 0x1D, 0x14, 0x0F, 0x1A, 0x05, 0x02,
+ 0x07, 0x09, 0x06, 0x08, 0x1F, 0x00, 0x10, 0x13,
+ 0x0D, 0x03, 0x0C, 0x18, 0x0E, 0x16, 0x1B, 0x1E,
+ 0x12, 0x04, 0x11, 0x0A, 0x01, 0x0B, 0x17, 0x19 };
+
+static const unsigned char table_90[256] = {
+ 0x62, 0x36, 0x64, 0x0E, 0x4C, 0x6C, 0xBE, 0xCF,
+ 0x25, 0x5A, 0x3D, 0x12, 0x54, 0x9F, 0xE7, 0xA5,
+ 0xDE, 0xD7, 0xB2, 0x60, 0x18, 0x8D, 0x89, 0x70,
+ 0x48, 0x66, 0x1C, 0xA6, 0x17, 0x9B, 0xDF, 0x9A,
+ 0x82, 0xB9, 0x2E, 0xFA, 0x83, 0x5B, 0x7A, 0x61,
+ 0xFC, 0x6B, 0x8B, 0x4E, 0x0F, 0xAD, 0x78, 0xE1,
+ 0xE8, 0x15, 0x1A, 0xF7, 0xA3, 0x3A, 0x04, 0xE3,
+ 0x30, 0x8C, 0x06, 0xC4, 0x05, 0x32, 0x1F, 0x6A,
+ 0xB8, 0x37, 0x58, 0xF5, 0x74, 0x63, 0xD4, 0xAC,
+ 0xA4, 0xF3, 0xEC, 0xBB, 0x8E, 0x65, 0xA0, 0xEE,
+ 0x6D, 0x11, 0xDD, 0xEA, 0x68, 0x2B, 0xDA, 0x0B,
+ 0xEF, 0xC3, 0x8F, 0x03, 0x77, 0x1B, 0xFB, 0x1E,
+ 0x5C, 0xD9, 0xCB, 0x33, 0x55, 0xF1, 0xA1, 0xF9,
+ 0x7C, 0x38, 0x95, 0x00, 0x6E, 0x85, 0xC2, 0x7F,
+ 0xBF, 0x84, 0x2A, 0x13, 0x72, 0x81, 0xE9, 0x59,
+ 0x41, 0x69, 0x3B, 0x0C, 0x90, 0xB4, 0x51, 0x2F,
+ 0xA2, 0xFE, 0xF8, 0x49, 0x57, 0xE5, 0x96, 0xFF,
+ 0xCD, 0xD5, 0xCE, 0xAA, 0x40, 0xB0, 0x4D, 0xBA,
+ 0xDB, 0xC7, 0x46, 0x86, 0xD1, 0xCA, 0xC0, 0x67,
+ 0x9C, 0x21, 0xAE, 0xB3, 0x7B, 0x87, 0xE2, 0x71,
+ 0xE6, 0x39, 0xA8, 0x22, 0x07, 0x2C, 0x44, 0x52,
+ 0xA7, 0xF0, 0x4A, 0x92, 0x56, 0x28, 0x43, 0x8A,
+ 0x5E, 0x53, 0x93, 0x47, 0x97, 0x88, 0x76, 0x79,
+ 0x91, 0x26, 0xC1, 0x3F, 0xB7, 0xF6, 0x3E, 0x80,
+ 0xA9, 0xC6, 0x01, 0xD2, 0xEB, 0x9E, 0x4B, 0xBC,
+ 0xC8, 0xB5, 0x02, 0x5F, 0x98, 0x9D, 0x5D, 0x35,
+ 0xD0, 0x16, 0xB1, 0x23, 0x7D, 0xAF, 0x10, 0x3C,
+ 0xAB, 0x14, 0x09, 0x2D, 0x0D, 0xC5, 0x1D, 0xD6,
+ 0x42, 0xF2, 0x34, 0x73, 0xF4, 0xFD, 0xE0, 0x24,
+ 0x6F, 0xD3, 0x75, 0xD8, 0xCC, 0xB6, 0x99, 0x4F,
+ 0x29, 0x0A, 0x08, 0xE4, 0x27, 0x19, 0x31, 0xC9,
+ 0x20, 0x94, 0x45, 0xED, 0xDC, 0xBD, 0x7E, 0x50 };
+
+static const unsigned char table_91[32] = {
+ 0x03, 0x04, 0x0C, 0x18, 0x10, 0x0D, 0x13, 0x1B,
+ 0x1F, 0x07, 0x11, 0x17, 0x1C, 0x1D, 0x05, 0x06,
+ 0x0A, 0x12, 0x02, 0x1A, 0x0B, 0x01, 0x0E, 0x08,
+ 0x14, 0x16, 0x00, 0x15, 0x19, 0x09, 0x0F, 0x1E };
+
+static const unsigned char table_92[32] = {
+ 0x1E, 0x10, 0x01, 0x07, 0x11, 0x16, 0x15, 0x17,
+ 0x1F, 0x14, 0x0C, 0x1C, 0x06, 0x03, 0x00, 0x18,
+ 0x08, 0x0E, 0x02, 0x1B, 0x09, 0x0D, 0x19, 0x05,
+ 0x0F, 0x12, 0x0B, 0x13, 0x0A, 0x04, 0x1D, 0x1A };
+
+static const unsigned char table_93[256] = {
+ 0x76, 0x78, 0xA2, 0x94, 0x0E, 0x7F, 0xDF, 0xC1,
+ 0xB9, 0xE1, 0x3D, 0x59, 0x6F, 0x1E, 0x53, 0x99,
+ 0x80, 0xE3, 0x21, 0xF8, 0x65, 0xB8, 0x08, 0xBC,
+ 0x29, 0x17, 0xFD, 0x33, 0x35, 0xF2, 0x70, 0xC7,
+ 0x25, 0xD0, 0xCD, 0x7A, 0xB7, 0x9B, 0xA5, 0xC3,
+ 0x00, 0x90, 0xDC, 0xB1, 0x0C, 0x20, 0x67, 0x8D,
+ 0x43, 0x49, 0xF3, 0x96, 0x14, 0x1A, 0xC8, 0x19,
+ 0x72, 0xD7, 0x8A, 0x38, 0x66, 0xDA, 0xDD, 0x2E,
+ 0xBE, 0xD5, 0x91, 0x7C, 0x3A, 0x92, 0x8E, 0xE7,
+ 0x51, 0xB5, 0xA8, 0xD9, 0x0B, 0x2A, 0xBA, 0x81,
+ 0x41, 0x0F, 0xBD, 0x4E, 0x31, 0x23, 0x9C, 0x8B,
+ 0x2B, 0x1D, 0x04, 0x3E, 0x8C, 0xF0, 0x45, 0xA0,
+ 0x1C, 0x44, 0x55, 0x5E, 0xF1, 0x98, 0x54, 0x5D,
+ 0x9D, 0x84, 0xAE, 0x09, 0xA9, 0xC5, 0x83, 0x60,
+ 0x86, 0x95, 0xB4, 0xFA, 0x6B, 0xA7, 0x9A, 0xCA,
+ 0x8F, 0x4F, 0x0A, 0x7B, 0xB0, 0x02, 0xEA, 0xA4,
+ 0x18, 0xDB, 0xD3, 0x64, 0xEB, 0xFC, 0xC4, 0xC9,
+ 0xF5, 0xD6, 0xCC, 0x75, 0x0D, 0x5C, 0x93, 0x4A,
+ 0x6D, 0xC0, 0x1F, 0x50, 0xE6, 0x16, 0xEE, 0x07,
+ 0xFB, 0x74, 0x56, 0x58, 0x52, 0x89, 0x79, 0x68,
+ 0xB6, 0xFE, 0x01, 0xD4, 0x7E, 0x06, 0xBF, 0xCB,
+ 0x5B, 0xC2, 0xC6, 0x32, 0xAC, 0x26, 0x22, 0xD2,
+ 0x82, 0x46, 0x69, 0x15, 0x2C, 0xF7, 0xAD, 0x13,
+ 0x4D, 0xA3, 0xF6, 0x2D, 0x48, 0x71, 0x57, 0x11,
+ 0x63, 0x05, 0x5F, 0x9E, 0x4B, 0xAB, 0xA6, 0x61,
+ 0xBB, 0xA1, 0x3C, 0x97, 0xF9, 0x03, 0x40, 0x12,
+ 0xCF, 0x37, 0xE4, 0x10, 0x6A, 0xED, 0xFF, 0x62,
+ 0x42, 0x4C, 0xAF, 0x9F, 0xE5, 0xE8, 0xD8, 0xD1,
+ 0x28, 0x3F, 0x1B, 0xE9, 0xCE, 0x6C, 0x27, 0x88,
+ 0xEF, 0x2F, 0xE0, 0x30, 0x87, 0x5A, 0x73, 0xB3,
+ 0x6E, 0x3B, 0x7D, 0x77, 0x36, 0xAA, 0x39, 0xDE,
+ 0x24, 0x34, 0xE2, 0xEC, 0x85, 0x47, 0xF4, 0xB2 };
+
+static const unsigned char table_94[32] = {
+ 0x1C, 0x07, 0x05, 0x1A, 0x10, 0x1D, 0x14, 0x12,
+ 0x08, 0x0F, 0x0C, 0x01, 0x04, 0x1B, 0x16, 0x0A,
+ 0x11, 0x02, 0x1F, 0x13, 0x0D, 0x1E, 0x17, 0x06,
+ 0x0E, 0x09, 0x15, 0x19, 0x03, 0x18, 0x00, 0x0B };
+
+static const unsigned char table_95[32] = {
+ 0x12, 0x10, 0x11, 0x15, 0x03, 0x0A, 0x14, 0x05,
+ 0x1D, 0x07, 0x17, 0x0D, 0x09, 0x08, 0x1B, 0x1F,
+ 0x0B, 0x06, 0x19, 0x0E, 0x18, 0x04, 0x00, 0x02,
+ 0x1E, 0x1C, 0x01, 0x0C, 0x1A, 0x0F, 0x13, 0x16 };
+
+static const unsigned char table_96[256] = {
+ 0x1C, 0x6E, 0xCD, 0xB4, 0xB3, 0x93, 0xA8, 0x2E,
+ 0x4F, 0x09, 0xE3, 0x72, 0x64, 0x13, 0x21, 0xF5,
+ 0x89, 0xB2, 0xD2, 0x22, 0x5D, 0x63, 0x90, 0xC4,
+ 0x42, 0x9B, 0x07, 0xCA, 0x16, 0x19, 0x5C, 0x2B,
+ 0x3D, 0xA0, 0x69, 0x5F, 0x52, 0x41, 0x66, 0xC0,
+ 0x55, 0xDA, 0x82, 0x40, 0x25, 0x02, 0x3C, 0xDD,
+ 0xAE, 0xD7, 0xD6, 0xDB, 0x04, 0x78, 0x05, 0x4A,
+ 0x4C, 0x81, 0x00, 0xBE, 0x45, 0xC5, 0x30, 0xB0,
+ 0x65, 0x5A, 0xA9, 0x38, 0x75, 0x26, 0x85, 0x4E,
+ 0xF0, 0xA2, 0x91, 0x8A, 0x54, 0xD0, 0x3E, 0x0D,
+ 0xFE, 0xF2, 0x0A, 0x23, 0x24, 0x37, 0x32, 0x0B,
+ 0xCB, 0xB5, 0x28, 0x6A, 0x95, 0x49, 0x53, 0x9A,
+ 0xEE, 0x2C, 0x9D, 0xD4, 0x1D, 0x46, 0xC9, 0x79,
+ 0xCC, 0xDF, 0x17, 0xE8, 0x6D, 0x29, 0x0E, 0x80,
+ 0xE0, 0x62, 0xA1, 0xFA, 0x10, 0xF6, 0x03, 0xC1,
+ 0x15, 0x14, 0x1F, 0x99, 0x97, 0xD5, 0x9E, 0x3F,
+ 0x7B, 0x2F, 0xEF, 0x2A, 0x68, 0x83, 0xE2, 0x1B,
+ 0xC8, 0x87, 0x12, 0x70, 0xC7, 0x36, 0xD3, 0x73,
+ 0x8B, 0x7D, 0x47, 0x9F, 0xD9, 0xFB, 0x6C, 0x5B,
+ 0xFC, 0xAA, 0xB9, 0xB1, 0x0C, 0x31, 0x8E, 0xF3,
+ 0x92, 0xA3, 0x4B, 0xF1, 0xC2, 0x3A, 0x67, 0xEA,
+ 0x77, 0x11, 0xB6, 0xE4, 0x1A, 0x33, 0xD1, 0xBA,
+ 0xF9, 0xAC, 0x43, 0xE5, 0xC3, 0xC6, 0xFD, 0xF4,
+ 0x44, 0x6F, 0xB7, 0x88, 0xA7, 0xF8, 0x34, 0x94,
+ 0x6B, 0x27, 0xDE, 0x1E, 0xDC, 0x01, 0x61, 0x50,
+ 0xAD, 0x74, 0x4D, 0x86, 0xF7, 0x8D, 0x9C, 0x0F,
+ 0x5E, 0xBD, 0x08, 0x84, 0x18, 0xED, 0xA5, 0x39,
+ 0xAB, 0x98, 0x48, 0xE6, 0x2D, 0x96, 0xCF, 0x7F,
+ 0xFF, 0xBB, 0x8F, 0xEC, 0xBF, 0xE7, 0x56, 0xA4,
+ 0x35, 0x76, 0xA6, 0xAF, 0xBC, 0x71, 0xE9, 0xB8,
+ 0x7E, 0x7C, 0x06, 0x3B, 0xEB, 0x60, 0x7A, 0x8C,
+ 0x59, 0xCE, 0xE1, 0x57, 0x20, 0x58, 0x51, 0xD8 };
+
+static const unsigned char table_97[256] = {
+ 0x15, 0x2D, 0xAF, 0x36, 0xCF, 0xD3, 0xD0, 0xED,
+ 0xB2, 0x1B, 0xFE, 0x92, 0xBD, 0xAD, 0x58, 0x0F,
+ 0x76, 0x3C, 0x47, 0x03, 0x2E, 0x4C, 0x40, 0xF7,
+ 0x39, 0xA7, 0x72, 0x22, 0x95, 0xF3, 0x8C, 0xE0,
+ 0x79, 0xB6, 0x75, 0x82, 0x94, 0x8F, 0x44, 0xFC,
+ 0xB0, 0x05, 0xE9, 0x10, 0x68, 0xE7, 0xF1, 0xA5,
+ 0xA8, 0xE2, 0x6F, 0xBE, 0xE5, 0x54, 0xA2, 0xC6,
+ 0xDB, 0x1C, 0x9E, 0x6D, 0x14, 0xA1, 0x26, 0x34,
+ 0x1E, 0x1A, 0x06, 0x53, 0xEE, 0x67, 0xA9, 0x73,
+ 0xD5, 0x59, 0x2F, 0x61, 0xE6, 0x74, 0xD6, 0x97,
+ 0xC0, 0x0C, 0xB1, 0x6E, 0x6C, 0x33, 0xC8, 0x77,
+ 0x8B, 0x49, 0x43, 0xE3, 0xB5, 0xDE, 0x6A, 0xA0,
+ 0x78, 0x2A, 0xC9, 0xF9, 0x9A, 0xDC, 0x90, 0x55,
+ 0xF4, 0x16, 0x5E, 0x3F, 0xC5, 0x7C, 0xFA, 0x09,
+ 0x8E, 0x87, 0xF2, 0x9D, 0x70, 0x27, 0x9B, 0xC4,
+ 0xCD, 0x91, 0x4B, 0xB4, 0x18, 0xE1, 0x3D, 0x5D,
+ 0x7A, 0xEA, 0xF0, 0x65, 0xB9, 0xF6, 0xC3, 0x66,
+ 0x21, 0x96, 0xD1, 0xB8, 0x56, 0x62, 0x48, 0x28,
+ 0x3A, 0x86, 0x63, 0xD4, 0xD7, 0x41, 0x8D, 0x20,
+ 0xC2, 0x98, 0x37, 0xD8, 0x85, 0x42, 0x0D, 0x31,
+ 0x84, 0x4E, 0x11, 0x46, 0x2B, 0x19, 0xCC, 0xB7,
+ 0x69, 0x13, 0x6B, 0x29, 0x38, 0x7E, 0x0E, 0xD2,
+ 0x3B, 0x60, 0x89, 0x7F, 0xEF, 0x07, 0x08, 0xCA,
+ 0xBF, 0x3E, 0xA3, 0xAA, 0x52, 0x4A, 0x45, 0x00,
+ 0xC7, 0xF8, 0x57, 0xEB, 0x93, 0x9C, 0x4D, 0x7B,
+ 0x2C, 0xBB, 0xFB, 0xFF, 0x35, 0x4F, 0x32, 0xA6,
+ 0x23, 0x8A, 0xDD, 0x12, 0xA4, 0x81, 0x17, 0x1D,
+ 0x1F, 0xCB, 0x0A, 0x71, 0x02, 0xAC, 0xDF, 0x24,
+ 0xAB, 0x7D, 0x30, 0x5C, 0x01, 0x5A, 0xBA, 0xEC,
+ 0x51, 0xF5, 0x0B, 0x64, 0xCE, 0xAE, 0x5B, 0x50,
+ 0x80, 0x88, 0xE8, 0x5F, 0x04, 0xDA, 0xE4, 0xBC,
+ 0x83, 0x25, 0x9F, 0xD9, 0x99, 0xC1, 0xFD, 0xB3 };
+
+static const unsigned char table_98[256] = {
+ 0xC8, 0xE6, 0x38, 0x93, 0xE5, 0x03, 0x18, 0x1F,
+ 0xE9, 0x5A, 0xB6, 0xAF, 0xC3, 0x95, 0x00, 0x51,
+ 0xC0, 0xFD, 0x32, 0xE8, 0x96, 0x57, 0xF0, 0xAA,
+ 0xDC, 0x71, 0xF8, 0x01, 0x40, 0x0A, 0x4F, 0xB0,
+ 0x1B, 0x9D, 0x16, 0x92, 0xF3, 0x5E, 0xA9, 0x3C,
+ 0xBE, 0x6A, 0xA7, 0xE3, 0x35, 0x0D, 0xAD, 0xDB,
+ 0x48, 0xE0, 0x7E, 0xC6, 0xB4, 0x6D, 0x17, 0x41,
+ 0x3E, 0xE2, 0x87, 0x12, 0xE1, 0x53, 0xD9, 0x8A,
+ 0xAC, 0xA6, 0xD8, 0xFA, 0x36, 0x0B, 0x06, 0xDF,
+ 0x6C, 0x4E, 0xA4, 0xBC, 0xC9, 0xEE, 0x44, 0x26,
+ 0xF2, 0xE4, 0x9E, 0x34, 0xEF, 0x05, 0x0F, 0x7F,
+ 0xD1, 0xCD, 0x67, 0x28, 0xC1, 0x8E, 0x7D, 0x90,
+ 0x8F, 0x60, 0x1E, 0x19, 0xBD, 0x77, 0xB8, 0xD5,
+ 0x3D, 0x8C, 0x31, 0x99, 0x08, 0xDD, 0x04, 0x30,
+ 0x61, 0xFB, 0xEB, 0x98, 0x15, 0xFC, 0x10, 0xDE,
+ 0x20, 0xBA, 0xA1, 0xB3, 0xD4, 0x91, 0x6F, 0x9F,
+ 0x94, 0x5B, 0x42, 0xCB, 0x75, 0x1C, 0xBB, 0x5C,
+ 0x5D, 0xD6, 0x66, 0x50, 0xB9, 0xF1, 0x82, 0x7B,
+ 0x33, 0x23, 0x4A, 0xA5, 0x55, 0x97, 0xEA, 0x37,
+ 0xF4, 0x64, 0x6E, 0xBF, 0x8B, 0xB1, 0x07, 0x9A,
+ 0x43, 0x11, 0x65, 0xC2, 0x02, 0xDA, 0x9B, 0x25,
+ 0xCA, 0x3B, 0x7A, 0xCE, 0xA8, 0xCF, 0xF7, 0x56,
+ 0x6B, 0xF9, 0x47, 0x2A, 0x2E, 0x1D, 0x2D, 0xE7,
+ 0x46, 0xD0, 0x62, 0x4C, 0x80, 0x4B, 0x2B, 0xF5,
+ 0x69, 0x9C, 0x45, 0xED, 0x83, 0xAB, 0x74, 0x39,
+ 0xA3, 0x85, 0xD7, 0x5F, 0xB2, 0x86, 0x22, 0x29,
+ 0x89, 0x49, 0x1A, 0xC4, 0x52, 0xEC, 0x8D, 0x73,
+ 0xD3, 0x7C, 0x79, 0xD2, 0x14, 0x4D, 0x84, 0xA2,
+ 0x0E, 0x70, 0x78, 0x72, 0xB7, 0xA0, 0xC5, 0x81,
+ 0x58, 0x0C, 0x68, 0x27, 0xFF, 0xF6, 0xAE, 0xCC,
+ 0x88, 0xFE, 0x24, 0x2F, 0x76, 0x3F, 0x59, 0x21,
+ 0x54, 0x3A, 0x13, 0x09, 0x2C, 0xB5, 0xC7, 0x63 };
+
+static const unsigned char table_99[32] = {
+ 0x19, 0x00, 0x10, 0x18, 0x09, 0x11, 0x13, 0x1D,
+ 0x08, 0x1A, 0x02, 0x05, 0x03, 0x17, 0x12, 0x01,
+ 0x1F, 0x14, 0x06, 0x07, 0x15, 0x0D, 0x0F, 0x0B,
+ 0x0E, 0x16, 0x1E, 0x04, 0x1B, 0x0A, 0x0C, 0x1C };
+
+static const unsigned char table_100[256] = {
+ 0x9B, 0x3A, 0xAE, 0x60, 0x27, 0x67, 0x1E, 0x4E,
+ 0x91, 0xDA, 0x85, 0x43, 0x5C, 0xCC, 0x89, 0x55,
+ 0x75, 0x56, 0xF2, 0x86, 0xEB, 0xC4, 0x0D, 0xE6,
+ 0x63, 0x88, 0x38, 0x59, 0x68, 0xD0, 0x18, 0xF0,
+ 0xBA, 0x28, 0xF5, 0x80, 0x02, 0x5B, 0xE1, 0xA4,
+ 0x7A, 0x4B, 0x8E, 0xF7, 0x9E, 0x99, 0x70, 0xEF,
+ 0x66, 0x50, 0xB1, 0xCD, 0x9A, 0xAF, 0x5F, 0x21,
+ 0xE5, 0x5D, 0x14, 0xD4, 0x34, 0x22, 0xC3, 0x0F,
+ 0x44, 0xB6, 0x92, 0xCE, 0xB4, 0x6E, 0xB0, 0x00,
+ 0xF9, 0xB5, 0x10, 0xEA, 0x45, 0x2F, 0x2B, 0xF4,
+ 0xF6, 0xFE, 0xCB, 0x0A, 0x42, 0xF8, 0xE7, 0xFD,
+ 0xC8, 0xC2, 0x6C, 0x9C, 0x57, 0xA1, 0x46, 0x04,
+ 0xE9, 0x97, 0x40, 0x32, 0x19, 0xFA, 0x51, 0xD1,
+ 0x6D, 0x4C, 0x2A, 0xD9, 0x95, 0x26, 0x72, 0x1B,
+ 0x83, 0x93, 0x5A, 0x15, 0x33, 0xC5, 0x77, 0x13,
+ 0xE0, 0x36, 0x37, 0xDB, 0xA7, 0xC7, 0x81, 0x62,
+ 0xC1, 0x47, 0x64, 0x74, 0x1D, 0x84, 0x29, 0x39,
+ 0x41, 0x35, 0x09, 0x90, 0x20, 0x9F, 0x8C, 0x7D,
+ 0x3E, 0x07, 0xB9, 0x76, 0x06, 0xA3, 0x31, 0x7F,
+ 0x49, 0x6F, 0x3D, 0xD5, 0x25, 0xAC, 0xDF, 0x0B,
+ 0x3C, 0x79, 0x01, 0x8F, 0x82, 0x2E, 0xFC, 0x98,
+ 0xA5, 0x58, 0xA0, 0x4A, 0x7C, 0x24, 0xDD, 0x05,
+ 0x4D, 0x12, 0xBC, 0xAA, 0xE2, 0xAB, 0xD3, 0xBF,
+ 0x94, 0x2D, 0x54, 0xBB, 0xAD, 0xB7, 0x6A, 0xE3,
+ 0xBD, 0x5E, 0x8D, 0x08, 0x3B, 0xB8, 0x73, 0x8A,
+ 0x16, 0xD2, 0x69, 0xE8, 0xEE, 0x53, 0xD8, 0xDC,
+ 0x48, 0xCF, 0xC6, 0xA9, 0x1A, 0xCA, 0x17, 0x11,
+ 0xED, 0xC0, 0xA6, 0x1F, 0x96, 0x8B, 0xFF, 0x78,
+ 0x03, 0x61, 0x1C, 0xA8, 0x3F, 0x9D, 0x0E, 0xC9,
+ 0xE4, 0xA2, 0x52, 0xEC, 0x4F, 0xD6, 0xF3, 0x6B,
+ 0x87, 0xB3, 0x7E, 0xDE, 0xD7, 0x71, 0x65, 0xF1,
+ 0x30, 0x0C, 0xB2, 0x7B, 0xBE, 0xFB, 0x23, 0x2C };
+
+static const unsigned char table_101[32] = {
+ 0x18, 0x08, 0x14, 0x17, 0x03, 0x10, 0x19, 0x04,
+ 0x0D, 0x1C, 0x06, 0x1D, 0x1E, 0x12, 0x11, 0x0B,
+ 0x0F, 0x02, 0x0E, 0x1B, 0x13, 0x05, 0x07, 0x16,
+ 0x15, 0x0A, 0x0C, 0x1A, 0x00, 0x01, 0x1F, 0x09 };
+
+static const unsigned char table_102[32] = {
+ 0x17, 0x1F, 0x0E, 0x05, 0x13, 0x0C, 0x14, 0x1A,
+ 0x0F, 0x01, 0x12, 0x1C, 0x00, 0x07, 0x0D, 0x02,
+ 0x10, 0x16, 0x04, 0x11, 0x1D, 0x03, 0x1E, 0x18,
+ 0x06, 0x15, 0x0A, 0x19, 0x09, 0x08, 0x1B, 0x0B };
+
+static const unsigned char table_103[32] = {
+ 0x0F, 0x09, 0x1E, 0x11, 0x0D, 0x08, 0x10, 0x00,
+ 0x01, 0x1F, 0x1D, 0x1C, 0x12, 0x04, 0x07, 0x05,
+ 0x19, 0x14, 0x1B, 0x02, 0x1A, 0x15, 0x17, 0x16,
+ 0x18, 0x0B, 0x0A, 0x13, 0x0C, 0x0E, 0x03, 0x06 };
+
+static const unsigned char table_104[256] = {
+ 0xA4, 0x9F, 0x78, 0x39, 0x3D, 0x81, 0x51, 0x24,
+ 0x46, 0x2A, 0x56, 0xE8, 0xDF, 0x73, 0xA8, 0xA2,
+ 0x0D, 0xDC, 0xA5, 0x4F, 0xF0, 0x93, 0xC0, 0x76,
+ 0x38, 0x70, 0xB0, 0x30, 0x98, 0x13, 0x8B, 0x14,
+ 0x26, 0x45, 0x0F, 0x7D, 0x34, 0x72, 0x6B, 0x89,
+ 0x43, 0xE2, 0x96, 0x5B, 0xEF, 0x2B, 0xF9, 0xDE,
+ 0x82, 0xB5, 0x61, 0x4A, 0x17, 0xC2, 0x5A, 0xCB,
+ 0xB2, 0x8D, 0xE4, 0xEC, 0xD9, 0x80, 0xBC, 0x62,
+ 0x67, 0x11, 0xA9, 0x3A, 0xE1, 0xC4, 0xEA, 0xD2,
+ 0x71, 0xD0, 0xDB, 0xE5, 0x7B, 0x08, 0x77, 0xD6,
+ 0x10, 0x19, 0x48, 0xEB, 0xAA, 0x2C, 0x0C, 0x59,
+ 0xBE, 0xF6, 0x28, 0x50, 0x90, 0x87, 0xCD, 0x04,
+ 0x1F, 0x79, 0x99, 0x5C, 0x49, 0x06, 0x8A, 0x3E,
+ 0x5F, 0x5E, 0x15, 0x23, 0x2D, 0xB6, 0xA6, 0x7A,
+ 0x03, 0x20, 0xDA, 0xFB, 0x35, 0x75, 0xC7, 0x47,
+ 0xB9, 0x7C, 0xA1, 0xCE, 0xC5, 0xDD, 0xFD, 0x6C,
+ 0x05, 0xAC, 0x09, 0xB4, 0x95, 0xD1, 0xB1, 0x63,
+ 0xFF, 0xAE, 0xD5, 0x25, 0x1E, 0x6E, 0x57, 0x18,
+ 0x74, 0xE6, 0x2F, 0x9A, 0xE7, 0x42, 0x65, 0xF5,
+ 0x58, 0x27, 0x33, 0x9C, 0xCF, 0xB7, 0xC3, 0xF1,
+ 0x12, 0x1D, 0xB8, 0xF4, 0x64, 0x4D, 0xD4, 0xBD,
+ 0xE3, 0xAB, 0x44, 0x60, 0xAF, 0xCC, 0x0A, 0xFC,
+ 0xD3, 0x21, 0x0B, 0x1A, 0x6D, 0x83, 0xA7, 0x8E,
+ 0x3C, 0xC1, 0xED, 0xF3, 0x2E, 0x86, 0xC9, 0x41,
+ 0x02, 0xF7, 0xC8, 0x40, 0x1B, 0xF8, 0xF2, 0x07,
+ 0x5D, 0x4E, 0xC6, 0x29, 0xD7, 0x4B, 0x7E, 0x31,
+ 0x94, 0x32, 0x01, 0x92, 0xE9, 0x36, 0x0E, 0x7F,
+ 0x85, 0x16, 0xFA, 0x00, 0x88, 0x3F, 0x68, 0x4C,
+ 0x22, 0x55, 0xBF, 0x9D, 0xE0, 0x6A, 0xAD, 0xBA,
+ 0x91, 0xCA, 0xA3, 0x1C, 0xEE, 0xD8, 0x3B, 0x66,
+ 0x69, 0x9B, 0x84, 0xA0, 0xB3, 0x6F, 0xFE, 0x52,
+ 0x97, 0xBB, 0x37, 0x8C, 0x54, 0x53, 0x9E, 0x8F };
+
+static const unsigned char table_105[256] = {
+ 0x7B, 0x35, 0x11, 0x79, 0x07, 0x2F, 0xF6, 0x82,
+ 0x8E, 0xB4, 0x6E, 0xD2, 0x6D, 0xC5, 0x8C, 0x1C,
+ 0xE0, 0xD6, 0x34, 0xF0, 0x4F, 0x25, 0x59, 0xE8,
+ 0xDF, 0x1D, 0xEB, 0x32, 0x86, 0x51, 0xA4, 0xF2,
+ 0x5C, 0xD1, 0xC8, 0x41, 0xEC, 0x9D, 0x62, 0xAC,
+ 0xDD, 0x3E, 0xB8, 0x65, 0x75, 0x89, 0x12, 0x6C,
+ 0x40, 0x4E, 0xC7, 0x27, 0xE1, 0x37, 0xCF, 0x09,
+ 0x16, 0x78, 0xAA, 0x58, 0x0D, 0xE6, 0x54, 0xFE,
+ 0x8F, 0xFD, 0xF9, 0x61, 0x26, 0x3F, 0x2E, 0xCD,
+ 0x2C, 0x04, 0xB2, 0x80, 0x0F, 0x14, 0x6F, 0xC6,
+ 0xAB, 0xFB, 0x13, 0xDB, 0x9A, 0x21, 0xB3, 0xC0,
+ 0xA9, 0x19, 0x70, 0xF3, 0x2B, 0xAE, 0x9B, 0x49,
+ 0xB7, 0xA8, 0x24, 0x1B, 0x48, 0xEA, 0xED, 0xD9,
+ 0x47, 0x9E, 0x9C, 0x69, 0x3C, 0x66, 0xBB, 0x06,
+ 0x46, 0x38, 0x17, 0xB5, 0xCB, 0x05, 0x4A, 0x5E,
+ 0x15, 0x20, 0xB9, 0xB6, 0x33, 0x4C, 0x7D, 0xA3,
+ 0xD7, 0xB1, 0x23, 0x72, 0xC3, 0x4B, 0x63, 0xBE,
+ 0xF7, 0x5B, 0x74, 0x64, 0x77, 0xCC, 0xD3, 0x85,
+ 0xDE, 0x1A, 0x31, 0x97, 0xA2, 0x8B, 0xFC, 0x10,
+ 0x5F, 0xDC, 0xD5, 0xB0, 0xBD, 0x55, 0xC1, 0xE7,
+ 0x0C, 0x50, 0x43, 0x39, 0x71, 0x52, 0xE5, 0xAF,
+ 0x8A, 0x60, 0x92, 0x2D, 0xD8, 0x03, 0xF5, 0x28,
+ 0xCA, 0xEF, 0xD0, 0xC2, 0x53, 0x91, 0xA6, 0x73,
+ 0x56, 0xA5, 0xF1, 0x57, 0x42, 0xF4, 0xD4, 0x36,
+ 0x8D, 0xBC, 0xE9, 0x7E, 0x02, 0x76, 0x18, 0x0B,
+ 0x84, 0x5A, 0xE2, 0xBF, 0x68, 0x95, 0x29, 0x98,
+ 0xAD, 0x88, 0x1F, 0x81, 0x67, 0xA1, 0x3A, 0xA7,
+ 0x22, 0xF8, 0x01, 0xA0, 0xCE, 0x7A, 0xDA, 0x30,
+ 0xC4, 0xE4, 0xEE, 0x7C, 0x3B, 0x4D, 0x3D, 0xE3,
+ 0xFA, 0x6A, 0x7F, 0x99, 0x00, 0x93, 0x0E, 0xFF,
+ 0x90, 0x0A, 0x2A, 0x5D, 0x96, 0x08, 0x6B, 0x83,
+ 0xBA, 0x1E, 0x44, 0x87, 0x45, 0x9F, 0xC9, 0x94 };
+
+static const unsigned char table_106[32] = {
+ 0x03, 0x11, 0x07, 0x1B, 0x0F, 0x14, 0x0C, 0x01,
+ 0x04, 0x02, 0x09, 0x0A, 0x05, 0x12, 0x06, 0x1F,
+ 0x1C, 0x0E, 0x0D, 0x15, 0x18, 0x08, 0x00, 0x10,
+ 0x1E, 0x1D, 0x17, 0x19, 0x13, 0x16, 0x0B, 0x1A };
+
+static const unsigned char table_107[32] = {
+ 0x13, 0x1B, 0x06, 0x11, 0x1C, 0x07, 0x08, 0x0E,
+ 0x10, 0x05, 0x09, 0x18, 0x04, 0x15, 0x1E, 0x0F,
+ 0x1F, 0x12, 0x02, 0x00, 0x17, 0x19, 0x1A, 0x0D,
+ 0x03, 0x0C, 0x0A, 0x1D, 0x14, 0x01, 0x16, 0x0B };
+
+static const unsigned char table_108[256] = {
+ 0x99, 0xA3, 0x48, 0xE8, 0x5A, 0x7D, 0x97, 0xCA,
+ 0x7F, 0x06, 0x9B, 0x04, 0xE0, 0xF3, 0x18, 0xAE,
+ 0x59, 0xA0, 0x2B, 0x15, 0x85, 0x3E, 0x12, 0x93,
+ 0x3D, 0x28, 0x32, 0xF5, 0x20, 0x5D, 0x86, 0x00,
+ 0x1B, 0x2E, 0x36, 0x10, 0x5E, 0x6C, 0xD8, 0x29,
+ 0xB6, 0x3F, 0x05, 0x1C, 0xCE, 0xC2, 0x34, 0x5F,
+ 0x5C, 0x79, 0xD1, 0x1F, 0xA2, 0xEE, 0x8A, 0x69,
+ 0xB5, 0x87, 0x96, 0x6D, 0x4D, 0xC1, 0x61, 0x2C,
+ 0x11, 0xE7, 0x8E, 0xBF, 0x1E, 0x53, 0xD0, 0x58,
+ 0x76, 0xA4, 0x60, 0xA9, 0xB0, 0xF9, 0xEA, 0x3C,
+ 0x52, 0x9A, 0x24, 0xF1, 0x9F, 0xD3, 0x40, 0x0A,
+ 0x63, 0x78, 0x6A, 0x8B, 0x08, 0x22, 0x16, 0x83,
+ 0x6B, 0xD2, 0x49, 0x19, 0xBD, 0xFD, 0x62, 0x72,
+ 0xA8, 0x55, 0xAB, 0x0C, 0xB9, 0x13, 0xD5, 0xF0,
+ 0xF2, 0x84, 0xAF, 0x2F, 0x7B, 0x2A, 0x21, 0x0F,
+ 0xDA, 0x30, 0x71, 0xD6, 0x81, 0xE6, 0xEC, 0x41,
+ 0x90, 0x50, 0x66, 0x0E, 0xA7, 0xB8, 0xF7, 0x3A,
+ 0xB2, 0xCF, 0x3B, 0xFC, 0x56, 0x6F, 0xC3, 0xA6,
+ 0xC9, 0xA1, 0x8D, 0xBB, 0x9D, 0x75, 0xF6, 0xAA,
+ 0x7E, 0xF8, 0x33, 0xEF, 0xBC, 0x7C, 0x23, 0x1A,
+ 0x92, 0x6E, 0x2D, 0x8F, 0xED, 0xB7, 0xB1, 0x1D,
+ 0x67, 0x39, 0xAC, 0x0D, 0x74, 0xDB, 0x7A, 0x94,
+ 0x07, 0x09, 0xC0, 0xD7, 0xAD, 0xFE, 0x54, 0x91,
+ 0xDE, 0x45, 0xA5, 0x77, 0xCB, 0x37, 0xC6, 0x38,
+ 0x89, 0x88, 0x17, 0xD9, 0x4F, 0xDF, 0x25, 0xFB,
+ 0xFA, 0x4C, 0x80, 0x35, 0x82, 0xF4, 0x95, 0xC8,
+ 0xFF, 0xE9, 0x31, 0x01, 0x14, 0xB3, 0x02, 0x9E,
+ 0x4E, 0x43, 0x46, 0xC7, 0xEB, 0x51, 0xE5, 0x47,
+ 0xB4, 0xE3, 0xDC, 0x57, 0xC4, 0x98, 0x03, 0xE1,
+ 0xBA, 0x68, 0xCD, 0x27, 0xC5, 0x0B, 0xD4, 0x64,
+ 0x4B, 0x9C, 0x70, 0x65, 0x4A, 0xE4, 0x42, 0xDD,
+ 0xCC, 0xE2, 0x44, 0x73, 0xBE, 0x26, 0x8C, 0x5B };
+
+static const unsigned char table_109[256] = {
+ 0xE3, 0x95, 0xDB, 0x09, 0x82, 0x0A, 0x8F, 0x9E,
+ 0xC9, 0xDC, 0x28, 0x35, 0x0F, 0x8B, 0xA8, 0xA5,
+ 0x7F, 0x3D, 0x8C, 0xD1, 0x93, 0x57, 0x04, 0xAA,
+ 0x6A, 0x98, 0x81, 0xDD, 0x16, 0x67, 0x2E, 0xDF,
+ 0xED, 0xF7, 0xB2, 0xBD, 0x14, 0xB6, 0x76, 0xC8,
+ 0x75, 0x9F, 0x48, 0xAE, 0xBB, 0xB0, 0xF3, 0xE2,
+ 0xD4, 0x59, 0xD8, 0x9C, 0x64, 0xC1, 0x73, 0x21,
+ 0x6D, 0x96, 0x7B, 0x62, 0x56, 0x55, 0xCC, 0xFD,
+ 0xCE, 0x41, 0xA3, 0x43, 0x33, 0xAF, 0x23, 0x9D,
+ 0x6F, 0x65, 0x19, 0x52, 0xAD, 0xC6, 0xD3, 0x3F,
+ 0x66, 0xFF, 0xD0, 0x30, 0x6C, 0xC0, 0xEB, 0xCF,
+ 0x51, 0x88, 0x38, 0x72, 0x69, 0x77, 0x3B, 0xFA,
+ 0xBA, 0xB7, 0xA1, 0x91, 0xE0, 0x89, 0xAB, 0x44,
+ 0x1B, 0x05, 0x5B, 0xB9, 0x71, 0x47, 0x7E, 0xFB,
+ 0x02, 0xC7, 0x99, 0x6E, 0x42, 0x20, 0x90, 0x1F,
+ 0x4A, 0x85, 0x1A, 0xEA, 0x0C, 0x0D, 0xB3, 0xDA,
+ 0xE7, 0x13, 0xE6, 0xD7, 0x6B, 0x12, 0x46, 0x53,
+ 0xB5, 0xF8, 0x1D, 0x83, 0x54, 0x49, 0x8A, 0x26,
+ 0x4D, 0xDE, 0xF6, 0x03, 0xA2, 0x7D, 0x0E, 0xA0,
+ 0x68, 0x79, 0xCA, 0x0B, 0x5D, 0x40, 0x4F, 0x80,
+ 0xC2, 0xD6, 0x87, 0x70, 0xF0, 0xD2, 0x92, 0xEE,
+ 0xBE, 0x74, 0x5F, 0xBC, 0xA4, 0x4B, 0xFE, 0x37,
+ 0x60, 0xA9, 0x06, 0xA7, 0xE1, 0xF5, 0x2B, 0x10,
+ 0xEF, 0x2C, 0x07, 0x86, 0x7A, 0x27, 0xE9, 0xC5,
+ 0xAC, 0x32, 0x22, 0xF2, 0xE5, 0x8D, 0x31, 0x01,
+ 0x34, 0xA6, 0xB8, 0xC3, 0x3C, 0xE4, 0x08, 0x94,
+ 0x15, 0x4E, 0xB4, 0x39, 0x58, 0x00, 0x3E, 0x29,
+ 0x45, 0x3A, 0x84, 0x36, 0xF1, 0x2A, 0x50, 0x11,
+ 0xC4, 0x5A, 0xFC, 0xBF, 0xD9, 0xF9, 0x17, 0x9B,
+ 0x8E, 0x18, 0x63, 0x4C, 0x2F, 0x78, 0x2D, 0x5E,
+ 0x9A, 0xCD, 0x24, 0xEC, 0x7C, 0x97, 0x61, 0xCB,
+ 0x1E, 0xF4, 0xD5, 0xB1, 0x5C, 0x25, 0xE8, 0x1C };
+
+static const unsigned char table_110[256] = {
+ 0xC3, 0x06, 0x3C, 0xCB, 0xD2, 0x44, 0x9D, 0x48,
+ 0x28, 0xAA, 0xA9, 0xD0, 0x64, 0x25, 0x56, 0xCA,
+ 0xC2, 0xF8, 0x5C, 0xAE, 0x4E, 0x63, 0xB2, 0xE9,
+ 0x35, 0x11, 0xA8, 0x1A, 0x76, 0x15, 0xE0, 0x26,
+ 0x97, 0x99, 0xD4, 0x43, 0x80, 0xEE, 0xC1, 0x69,
+ 0xA6, 0x1E, 0x7A, 0x42, 0x55, 0x38, 0xBF, 0x75,
+ 0x0E, 0x29, 0xF5, 0xF3, 0x36, 0x7D, 0x51, 0xE8,
+ 0xE5, 0xEB, 0x68, 0x60, 0x0C, 0x70, 0xFD, 0xCC,
+ 0xE3, 0x23, 0x09, 0x6D, 0x2D, 0x6C, 0x5E, 0xB6,
+ 0x98, 0x8B, 0x1F, 0x50, 0x34, 0x8D, 0x10, 0x92,
+ 0x82, 0x85, 0xD5, 0x79, 0x02, 0xA4, 0x0A, 0xBC,
+ 0x40, 0xC6, 0xA3, 0x72, 0x8F, 0xC4, 0xA5, 0xE4,
+ 0x49, 0xD6, 0xCE, 0xA1, 0x12, 0x4F, 0x30, 0x31,
+ 0xDE, 0x2A, 0xF7, 0x95, 0xB5, 0x96, 0x14, 0x08,
+ 0xE6, 0x3D, 0x86, 0xF2, 0x47, 0x74, 0xB8, 0x5D,
+ 0x1D, 0x2B, 0x3A, 0x93, 0x7C, 0x6A, 0x01, 0xA0,
+ 0x9A, 0x4D, 0xB7, 0x71, 0xA7, 0x41, 0xC5, 0x65,
+ 0xC8, 0x89, 0xD1, 0x3E, 0x0D, 0xD8, 0xFF, 0x6F,
+ 0x7F, 0xA2, 0xFE, 0xD9, 0xF0, 0x4A, 0x07, 0x1C,
+ 0x0F, 0x6E, 0x03, 0x81, 0x1B, 0x05, 0xDF, 0x52,
+ 0xF1, 0x8A, 0xF9, 0xDD, 0x91, 0x3B, 0xD7, 0xE1,
+ 0x54, 0xAD, 0x90, 0x5A, 0x7B, 0xC7, 0x32, 0x62,
+ 0x16, 0x27, 0xB9, 0x66, 0x21, 0x88, 0xBD, 0x18,
+ 0x77, 0x8E, 0x94, 0x8C, 0x9B, 0x46, 0x9C, 0xB1,
+ 0xD3, 0x53, 0xB0, 0xBE, 0xAC, 0xAF, 0x73, 0x24,
+ 0xDA, 0x58, 0xE2, 0xFC, 0x78, 0xEA, 0xCD, 0xFA,
+ 0x37, 0xED, 0x13, 0x19, 0xC0, 0x59, 0x83, 0xBA,
+ 0x3F, 0x57, 0x00, 0x7E, 0xC9, 0x2E, 0x17, 0x5B,
+ 0x84, 0xF6, 0xE7, 0x22, 0xFB, 0x5F, 0x4C, 0x2C,
+ 0x61, 0x9F, 0x45, 0x39, 0xB3, 0xEC, 0x04, 0x87,
+ 0x67, 0xDC, 0x0B, 0xF4, 0x20, 0xAB, 0x6B, 0x9E,
+ 0x4B, 0xCF, 0xB4, 0x2F, 0xBB, 0xEF, 0xDB, 0x33 };
+
+static const unsigned char table_111[32] = {
+ 0x09, 0x0F, 0x00, 0x15, 0x12, 0x17, 0x1A, 0x0D,
+ 0x1C, 0x0B, 0x01, 0x0A, 0x05, 0x1E, 0x1D, 0x0C,
+ 0x1B, 0x08, 0x19, 0x18, 0x14, 0x07, 0x0E, 0x03,
+ 0x10, 0x16, 0x11, 0x1F, 0x04, 0x06, 0x02, 0x13 };
+
+static const unsigned char table_112[256] = {
+ 0xF9, 0x7D, 0xBE, 0xD5, 0x9F, 0xB8, 0x95, 0x43,
+ 0xDB, 0xAE, 0x7E, 0xEC, 0x5B, 0x58, 0x18, 0x49,
+ 0x4B, 0x9D, 0x1C, 0x3E, 0x61, 0xD1, 0xF6, 0x2F,
+ 0x41, 0x82, 0x51, 0x37, 0x72, 0x79, 0x05, 0x2A,
+ 0xC2, 0xB0, 0xE2, 0xE7, 0xB2, 0xF3, 0x1B, 0x92,
+ 0x86, 0xBB, 0xDC, 0x90, 0x1A, 0x19, 0xD7, 0xBA,
+ 0x2C, 0x7B, 0xEF, 0xC7, 0x8A, 0x81, 0xEB, 0xDE,
+ 0x73, 0x4E, 0xB7, 0x97, 0xCA, 0x29, 0x85, 0xC1,
+ 0xA5, 0x7F, 0xFE, 0x56, 0xE9, 0x9E, 0x21, 0x76,
+ 0x3A, 0x88, 0x70, 0xC6, 0xD3, 0x8C, 0x47, 0xC8,
+ 0x83, 0x48, 0xC3, 0x6A, 0x9C, 0x80, 0x53, 0xBD,
+ 0xFD, 0x54, 0x09, 0x91, 0x94, 0xAA, 0x7A, 0x59,
+ 0x71, 0xDD, 0xA8, 0x07, 0xCB, 0x0F, 0xE0, 0x9A,
+ 0x36, 0x4C, 0x4D, 0x0D, 0xA4, 0x96, 0x6F, 0x14,
+ 0x22, 0x38, 0xAD, 0x02, 0xF4, 0x0B, 0xEA, 0x93,
+ 0x20, 0x04, 0xBC, 0xE8, 0x6C, 0xFB, 0x10, 0x6B,
+ 0x40, 0xB6, 0x24, 0x17, 0x06, 0x31, 0xD9, 0x33,
+ 0xF5, 0x99, 0x57, 0xCD, 0xAB, 0x67, 0x5C, 0x30,
+ 0x1E, 0x34, 0xB4, 0x3F, 0x16, 0x42, 0xA2, 0x68,
+ 0x27, 0xB3, 0x1D, 0xED, 0x5F, 0x52, 0xF7, 0x3C,
+ 0x65, 0x5D, 0xE5, 0x23, 0x0C, 0x6D, 0x84, 0x6E,
+ 0xDA, 0x77, 0xF8, 0x15, 0xFA, 0x69, 0xD0, 0xA7,
+ 0x11, 0xAC, 0xA6, 0xA3, 0x1F, 0x2E, 0xBF, 0x4A,
+ 0x8F, 0xFC, 0xEE, 0xC9, 0x26, 0x12, 0xC0, 0xB1,
+ 0x45, 0x0E, 0x3D, 0x7C, 0xCE, 0x13, 0x8E, 0x98,
+ 0x46, 0x2B, 0xC5, 0x66, 0x28, 0x32, 0xD2, 0x03,
+ 0xE3, 0xC4, 0x9B, 0x89, 0x5E, 0xF0, 0xCF, 0x3B,
+ 0x2D, 0x50, 0xB5, 0x00, 0x0A, 0xD6, 0x55, 0xE1,
+ 0x62, 0x63, 0x64, 0x87, 0xAF, 0x78, 0xB9, 0xF2,
+ 0x25, 0x44, 0xFF, 0x39, 0xF1, 0x08, 0x4F, 0x74,
+ 0xA9, 0x8B, 0x75, 0x01, 0xA0, 0xE4, 0x35, 0x8D,
+ 0xA1, 0xCC, 0xDF, 0x60, 0xD8, 0x5A, 0xE6, 0xD4 };
+
+static const unsigned char table_113[256] = {
+ 0x46, 0x9D, 0x39, 0xB2, 0x8D, 0x3B, 0x59, 0x5A,
+ 0xD0, 0x9C, 0xE4, 0x04, 0x01, 0xE2, 0xB3, 0xD2,
+ 0xD7, 0x18, 0x40, 0xD8, 0xF1, 0xEF, 0x3A, 0x1D,
+ 0x8E, 0xE5, 0xD9, 0xD3, 0xCB, 0x49, 0x4C, 0xCF,
+ 0xC0, 0xD6, 0xB5, 0x73, 0x77, 0x82, 0x54, 0xA2,
+ 0xB1, 0xB0, 0x84, 0x5D, 0xC7, 0xDE, 0x31, 0x2F,
+ 0x50, 0x78, 0xBE, 0x94, 0x64, 0x44, 0x60, 0x7A,
+ 0x1A, 0x6E, 0x09, 0x6F, 0xBF, 0x76, 0x81, 0x38,
+ 0x22, 0xC3, 0xEE, 0x8F, 0xFB, 0x32, 0xED, 0x92,
+ 0xAE, 0xE6, 0x5F, 0xAA, 0xAC, 0x0D, 0xA3, 0x47,
+ 0x1F, 0x11, 0xC1, 0x29, 0xAF, 0xFD, 0x1C, 0xDB,
+ 0x00, 0x23, 0xB9, 0xB8, 0x91, 0x41, 0x27, 0x37,
+ 0x43, 0x02, 0x26, 0xF6, 0x7D, 0x0A, 0x85, 0x93,
+ 0x97, 0x2E, 0x20, 0x55, 0x13, 0x4B, 0x6C, 0xE7,
+ 0xFC, 0x25, 0xFA, 0x9E, 0x5B, 0xA1, 0xDF, 0x2C,
+ 0x3E, 0xBC, 0xEA, 0x42, 0x7C, 0x36, 0x30, 0xEB,
+ 0xBD, 0x8B, 0x87, 0x16, 0x3D, 0x5C, 0x07, 0xBA,
+ 0xB4, 0x1B, 0xC2, 0xE3, 0x71, 0x9A, 0x5E, 0x4D,
+ 0xF2, 0xCC, 0x0E, 0xE1, 0x34, 0x75, 0x58, 0x89,
+ 0x17, 0xD4, 0x68, 0x80, 0x2B, 0x74, 0x70, 0x8A,
+ 0x63, 0xE8, 0x56, 0x24, 0xD1, 0x57, 0x35, 0x6D,
+ 0x3C, 0xA6, 0xC8, 0x7E, 0xA8, 0x4E, 0xC4, 0x33,
+ 0xA9, 0x62, 0x61, 0x7F, 0x21, 0x98, 0x2A, 0xAD,
+ 0xB6, 0xA7, 0xF5, 0x3F, 0x15, 0x45, 0xF8, 0xA4,
+ 0x95, 0x88, 0xDC, 0x96, 0x90, 0x08, 0x9B, 0xF9,
+ 0x06, 0x14, 0x05, 0xF0, 0xF7, 0xA0, 0xE0, 0x65,
+ 0xCA, 0xA5, 0x9F, 0x79, 0xCD, 0x4F, 0x72, 0xB7,
+ 0x4A, 0x0F, 0x66, 0xC5, 0x0C, 0x52, 0xF3, 0x69,
+ 0x83, 0x03, 0x99, 0x1E, 0x2D, 0xDA, 0x8C, 0x53,
+ 0x28, 0xDD, 0xE9, 0x0B, 0xC9, 0xF4, 0x48, 0x12,
+ 0x6A, 0x19, 0xCE, 0xAB, 0x51, 0xD5, 0x6B, 0xBB,
+ 0xFE, 0x7B, 0x67, 0xFF, 0x10, 0xEC, 0xC6, 0x86 };
+
+static const unsigned char table_114[32] = {
+ 0x11, 0x10, 0x04, 0x1D, 0x08, 0x15, 0x1A, 0x1B,
+ 0x14, 0x18, 0x0F, 0x17, 0x16, 0x07, 0x1E, 0x0E,
+ 0x12, 0x0A, 0x13, 0x0B, 0x0C, 0x00, 0x06, 0x02,
+ 0x1F, 0x19, 0x09, 0x1C, 0x01, 0x0D, 0x03, 0x05 };
+
+static const unsigned char table_115[256] = {
+ 0xB7, 0xBB, 0x63, 0x0D, 0xF0, 0x33, 0x5A, 0x05,
+ 0xF2, 0x7F, 0x64, 0xDB, 0x51, 0xC9, 0x2C, 0x85,
+ 0x4F, 0x41, 0xA4, 0x42, 0xCF, 0xA6, 0x52, 0x2F,
+ 0x26, 0xEF, 0xFB, 0x29, 0x40, 0x16, 0xF7, 0xED,
+ 0x23, 0x69, 0x8A, 0xDF, 0x77, 0x28, 0x93, 0x14,
+ 0x82, 0x0C, 0xBE, 0x3D, 0x20, 0xB4, 0x79, 0x94,
+ 0x54, 0xF8, 0x07, 0xB1, 0xE1, 0x66, 0x73, 0xD3,
+ 0x19, 0x15, 0xFF, 0x03, 0x6A, 0x9A, 0xDC, 0x1C,
+ 0xB3, 0x5D, 0x76, 0x68, 0x47, 0x6C, 0xF9, 0xFD,
+ 0xE9, 0xDD, 0x01, 0x65, 0xBD, 0x80, 0x0E, 0x7A,
+ 0x8D, 0x99, 0x13, 0x7C, 0xA5, 0xA7, 0x1A, 0xCC,
+ 0xB8, 0xE6, 0x2B, 0xB2, 0xB6, 0xD0, 0x62, 0x2D,
+ 0x4D, 0xD2, 0xB9, 0x04, 0x46, 0xAE, 0xAA, 0x44,
+ 0xDA, 0x92, 0x4B, 0x4E, 0xC4, 0xE2, 0xFE, 0xA2,
+ 0x75, 0x7B, 0xC3, 0xFA, 0x9F, 0x37, 0x9D, 0x1E,
+ 0x72, 0xD4, 0x1F, 0x4A, 0x9B, 0xE5, 0x6D, 0xEC,
+ 0x5C, 0x7D, 0x98, 0xE8, 0xEE, 0x86, 0xD1, 0xC8,
+ 0xEA, 0x55, 0xBF, 0xAF, 0xDE, 0x32, 0x09, 0x3A,
+ 0x8F, 0x57, 0x83, 0x43, 0x61, 0xC6, 0x8E, 0x96,
+ 0x22, 0xA3, 0x97, 0x91, 0x5F, 0x11, 0x3B, 0x5B,
+ 0x1B, 0x34, 0x49, 0x95, 0xF1, 0x6F, 0x89, 0xA8,
+ 0xC0, 0x36, 0x0A, 0x3F, 0x60, 0x50, 0xE7, 0x08,
+ 0xCE, 0x25, 0xC1, 0x71, 0xF6, 0x59, 0x58, 0x56,
+ 0x4C, 0xAB, 0x27, 0xAC, 0x06, 0xCB, 0x00, 0x30,
+ 0x84, 0x3E, 0xC2, 0x1D, 0x02, 0xE0, 0xC5, 0xD6,
+ 0x18, 0x70, 0xA9, 0x88, 0xD9, 0x39, 0x8B, 0x6E,
+ 0xF4, 0x24, 0xA0, 0x48, 0x45, 0x21, 0x87, 0x78,
+ 0x38, 0x90, 0xE3, 0xCA, 0xF5, 0xD7, 0x2A, 0x53,
+ 0x9C, 0xCD, 0x31, 0x35, 0xAD, 0x74, 0xD8, 0x12,
+ 0xBC, 0x9E, 0x6B, 0x67, 0xB0, 0xBA, 0xE4, 0x10,
+ 0x5E, 0xFC, 0xC7, 0x0F, 0x2E, 0x81, 0x7E, 0xA1,
+ 0x8C, 0x17, 0xB5, 0xEB, 0xD5, 0xF3, 0x0B, 0x3C };
+
+static const unsigned char table_116[32] = {
+ 0x00, 0x05, 0x10, 0x1C, 0x0C, 0x1A, 0x04, 0x1B,
+ 0x0A, 0x0D, 0x14, 0x0B, 0x07, 0x03, 0x12, 0x1E,
+ 0x06, 0x11, 0x01, 0x08, 0x15, 0x09, 0x1F, 0x0F,
+ 0x19, 0x18, 0x16, 0x02, 0x13, 0x0E, 0x17, 0x1D };
+
+static const unsigned char table_117[256] = {
+ 0xD0, 0x9A, 0xAB, 0xA8, 0xA7, 0xDF, 0x28, 0xCE,
+ 0x3E, 0x51, 0xBF, 0x76, 0x03, 0xA0, 0x53, 0x3F,
+ 0x90, 0x93, 0x87, 0x67, 0x98, 0x3D, 0xEA, 0x8B,
+ 0x55, 0xCF, 0x10, 0xF3, 0x25, 0xFC, 0x9F, 0x41,
+ 0x6B, 0x54, 0x6E, 0x0B, 0x83, 0x35, 0x69, 0x7D,
+ 0xE0, 0x88, 0x4B, 0xE9, 0x1E, 0x96, 0x91, 0x57,
+ 0xBD, 0x72, 0x21, 0x3C, 0xA6, 0x99, 0x6C, 0xF6,
+ 0x13, 0xFA, 0x29, 0xED, 0xDB, 0x16, 0x4D, 0x07,
+ 0x45, 0xA5, 0xE3, 0x0E, 0x31, 0xBC, 0x56, 0x5C,
+ 0xB2, 0x23, 0xDA, 0x74, 0xFF, 0x02, 0x8F, 0xF4,
+ 0x2A, 0xC9, 0x89, 0xAA, 0x05, 0xB1, 0xD1, 0x1F,
+ 0x4F, 0xB0, 0x7A, 0x2C, 0x14, 0xD9, 0xE7, 0x66,
+ 0x62, 0x1A, 0x4C, 0xC0, 0xC6, 0x63, 0x7F, 0xB4,
+ 0xF1, 0x43, 0xFE, 0x61, 0xA3, 0xCC, 0xE8, 0x6D,
+ 0xBA, 0x65, 0x42, 0x2B, 0xCA, 0xD5, 0x52, 0x3A,
+ 0xCD, 0x1D, 0x24, 0xD7, 0x47, 0xDE, 0x9E, 0x95,
+ 0x85, 0x48, 0x86, 0xE1, 0xC5, 0xD2, 0x34, 0xAF,
+ 0x40, 0xFB, 0xE6, 0x4E, 0xC8, 0xF5, 0x7B, 0x5A,
+ 0xCB, 0xD4, 0x97, 0x6F, 0x0C, 0x79, 0x9C, 0x20,
+ 0x59, 0x19, 0x68, 0x2E, 0x09, 0x64, 0x73, 0x50,
+ 0xC2, 0x2F, 0x0D, 0xEF, 0x9D, 0x94, 0x00, 0x81,
+ 0xE2, 0x46, 0x5F, 0xB8, 0x0A, 0x12, 0x75, 0x1C,
+ 0x8C, 0xB6, 0x71, 0xAC, 0x04, 0x60, 0xA9, 0x5B,
+ 0xF8, 0x30, 0x49, 0x44, 0x4A, 0xBE, 0x6A, 0xEB,
+ 0xD3, 0xD8, 0x36, 0xB3, 0x3B, 0x17, 0x80, 0xA4,
+ 0xEC, 0x26, 0x82, 0xB5, 0x37, 0x5D, 0x1B, 0x2D,
+ 0xE5, 0xA2, 0x0F, 0xB7, 0xC4, 0xF2, 0x70, 0x39,
+ 0xF9, 0xC7, 0xBB, 0x8A, 0x32, 0x78, 0xC3, 0x5E,
+ 0xD6, 0xE4, 0x22, 0x9B, 0x18, 0x8E, 0xEE, 0x27,
+ 0x8D, 0x33, 0x11, 0x77, 0x01, 0x06, 0x38, 0xF0,
+ 0x7E, 0x08, 0x15, 0xB9, 0x7C, 0xAD, 0x84, 0xDD,
+ 0xC1, 0xFD, 0x92, 0xA1, 0xF7, 0xAE, 0xDC, 0x58 };
+
+static const unsigned char table_118[256] = {
+ 0x38, 0xA0, 0xA6, 0xFC, 0x7C, 0x5A, 0x97, 0x1D,
+ 0xFD, 0x00, 0x20, 0xA2, 0x72, 0x10, 0x1F, 0x48,
+ 0x98, 0x7E, 0xDF, 0x2D, 0x80, 0x0A, 0x27, 0xDC,
+ 0xCF, 0xBF, 0x92, 0x94, 0x53, 0xCC, 0x0E, 0x74,
+ 0xA7, 0x60, 0x08, 0x15, 0x87, 0x6F, 0xB3, 0xA3,
+ 0xED, 0x59, 0x09, 0x4F, 0x9E, 0x9A, 0xEE, 0x83,
+ 0x56, 0x32, 0x34, 0xC7, 0x24, 0xE7, 0x96, 0x4D,
+ 0xAE, 0xE3, 0xBD, 0xE2, 0x36, 0x4A, 0xB6, 0x8B,
+ 0xF2, 0xC1, 0xD7, 0x40, 0x31, 0x4B, 0xDA, 0xF1,
+ 0xB1, 0x70, 0xA8, 0xC3, 0xC6, 0x8A, 0xE6, 0x77,
+ 0x21, 0x7D, 0xD5, 0x0C, 0x43, 0xC4, 0xF0, 0x1B,
+ 0x18, 0xA1, 0x85, 0xE1, 0xFF, 0x8D, 0xE5, 0x6E,
+ 0x9B, 0x51, 0x1C, 0xA4, 0x5C, 0x8E, 0x69, 0x49,
+ 0x23, 0xCD, 0x52, 0xF8, 0x3E, 0x91, 0x5E, 0x1E,
+ 0x25, 0xB4, 0x93, 0xCB, 0xE0, 0x47, 0xBC, 0x4E,
+ 0x33, 0xB7, 0x75, 0x1A, 0x11, 0x9C, 0x3F, 0xEC,
+ 0xD1, 0x46, 0xDD, 0xAA, 0xB8, 0x99, 0x86, 0x67,
+ 0x58, 0xF9, 0x16, 0x17, 0x6D, 0x5F, 0x2B, 0xA5,
+ 0xD3, 0x8F, 0x55, 0x71, 0xD2, 0xBA, 0x5B, 0x3C,
+ 0x82, 0xB5, 0x41, 0xE4, 0x90, 0x45, 0x6C, 0xF6,
+ 0xDE, 0xA9, 0x84, 0x62, 0x19, 0x3B, 0xB9, 0xC8,
+ 0x2C, 0xB0, 0x76, 0x57, 0xD8, 0x26, 0x9D, 0x89,
+ 0xC9, 0x54, 0xFB, 0x07, 0xCE, 0x22, 0x5D, 0x64,
+ 0x65, 0xAD, 0x01, 0xDB, 0x14, 0x4C, 0x37, 0x03,
+ 0x6B, 0xAF, 0xD0, 0x7F, 0x9F, 0xBB, 0xEB, 0xC0,
+ 0x50, 0x66, 0x68, 0x0B, 0x42, 0x2A, 0xD4, 0xF5,
+ 0x61, 0x63, 0xF3, 0x39, 0xBE, 0xC5, 0xEF, 0x28,
+ 0x3A, 0xAB, 0x79, 0x05, 0xE9, 0x12, 0x73, 0x3D,
+ 0xB2, 0x8C, 0xCA, 0x29, 0x0F, 0xF4, 0x7B, 0x13,
+ 0x88, 0x44, 0xC2, 0x2E, 0xFA, 0xFE, 0x04, 0x35,
+ 0xE8, 0x06, 0x7A, 0x78, 0x0D, 0x81, 0xF7, 0xEA,
+ 0xD9, 0x2F, 0x02, 0xAC, 0x30, 0x6A, 0xD6, 0x95 };
+
+static const unsigned char table_119[32] = {
+ 0x14, 0x0A, 0x1C, 0x00, 0x0C, 0x1F, 0x1E, 0x0B,
+ 0x12, 0x1D, 0x17, 0x08, 0x07, 0x04, 0x09, 0x10,
+ 0x03, 0x1B, 0x0E, 0x1A, 0x05, 0x0D, 0x11, 0x15,
+ 0x18, 0x02, 0x06, 0x01, 0x19, 0x16, 0x13, 0x0F };
+
+static const unsigned char table_120[256] = {
+ 0xCE, 0x89, 0xB2, 0x72, 0x04, 0x77, 0x64, 0xAE,
+ 0x80, 0x99, 0xB5, 0x00, 0x7B, 0x50, 0x9D, 0xE3,
+ 0x87, 0x37, 0x6D, 0x3D, 0x32, 0xBA, 0x20, 0xF0,
+ 0xDC, 0xBD, 0x61, 0x26, 0xD4, 0xA6, 0x70, 0x54,
+ 0xC1, 0x7D, 0x82, 0xFF, 0x81, 0x83, 0x2F, 0xF5,
+ 0x3B, 0x42, 0x08, 0x5C, 0x30, 0x59, 0xBB, 0xC2,
+ 0x33, 0x5D, 0xEE, 0xB7, 0xF7, 0x2B, 0x76, 0xD0,
+ 0x43, 0x1C, 0x48, 0xFC, 0x01, 0xCD, 0x27, 0x1D,
+ 0x5A, 0x96, 0x95, 0x03, 0xC6, 0x1F, 0x09, 0xCB,
+ 0xF6, 0x47, 0xA9, 0x93, 0xA7, 0xD2, 0xDB, 0x51,
+ 0xB0, 0x7A, 0xE6, 0x62, 0x0F, 0x12, 0x57, 0xF4,
+ 0x35, 0xFE, 0xA4, 0xDF, 0x5B, 0xF3, 0x67, 0x85,
+ 0x98, 0xE4, 0xAB, 0x75, 0x4C, 0xE2, 0x25, 0x74,
+ 0x3A, 0x45, 0xDE, 0xEF, 0x4A, 0x97, 0x86, 0x24,
+ 0xE9, 0x8F, 0xD8, 0xD7, 0x60, 0xAD, 0x36, 0x8E,
+ 0x1E, 0xB9, 0x4F, 0x6B, 0x8C, 0x06, 0x23, 0x94,
+ 0x0E, 0xD3, 0x49, 0x14, 0x90, 0xAF, 0x65, 0xEC,
+ 0xF9, 0x0D, 0xED, 0x6C, 0xBE, 0x7F, 0xA5, 0xC5,
+ 0xEA, 0x78, 0x2E, 0xBC, 0xD5, 0xDA, 0x18, 0xE1,
+ 0x10, 0x2D, 0xB4, 0x16, 0x4B, 0xE8, 0xC4, 0x8D,
+ 0x19, 0x1B, 0x02, 0x66, 0xB6, 0xE7, 0x9C, 0x7C,
+ 0xC9, 0xA0, 0x2A, 0x53, 0x13, 0xDD, 0xF8, 0xA8,
+ 0x0A, 0x6E, 0xCF, 0x6F, 0x7E, 0xE0, 0x3E, 0xE5,
+ 0x07, 0xCC, 0x38, 0xD1, 0xF2, 0x2C, 0x9A, 0xAC,
+ 0x88, 0x79, 0xB8, 0xC8, 0xBF, 0x63, 0x71, 0x69,
+ 0x52, 0x39, 0x9F, 0x22, 0x3F, 0x9E, 0x44, 0xFA,
+ 0x73, 0x6A, 0x8B, 0xA2, 0xD6, 0x1A, 0x9B, 0xB1,
+ 0x8A, 0x4D, 0x58, 0xA1, 0x46, 0x5F, 0x55, 0x56,
+ 0x21, 0x05, 0x15, 0x92, 0xAA, 0xEB, 0x31, 0x68,
+ 0xFB, 0x41, 0xC3, 0x4E, 0xB3, 0x40, 0x34, 0x17,
+ 0xD9, 0x29, 0x3C, 0x0C, 0xF1, 0x0B, 0x28, 0x84,
+ 0x5E, 0xCA, 0xFD, 0x11, 0xA3, 0xC7, 0xC0, 0x91 };
+
+static const unsigned char table_121[32] = {
+ 0x1E, 0x12, 0x06, 0x1D, 0x15, 0x1F, 0x13, 0x0B,
+ 0x10, 0x0D, 0x1C, 0x01, 0x0A, 0x0E, 0x02, 0x19,
+ 0x04, 0x1A, 0x03, 0x11, 0x00, 0x16, 0x0C, 0x17,
+ 0x14, 0x08, 0x18, 0x05, 0x09, 0x0F, 0x1B, 0x07 };
+
+static const unsigned char table_122[256] = {
+ 0x85, 0xDF, 0x7F, 0x7C, 0x56, 0xF0, 0x0C, 0x7D,
+ 0x76, 0xA8, 0x58, 0x31, 0x25, 0x8A, 0x0D, 0x23,
+ 0x05, 0x0F, 0x12, 0x64, 0x8E, 0x5D, 0xF4, 0x2C,
+ 0x18, 0xFA, 0x4B, 0xFE, 0x91, 0xBF, 0x95, 0x0B,
+ 0xF1, 0x88, 0x10, 0xD8, 0x3E, 0x53, 0x96, 0xB5,
+ 0x75, 0x24, 0x8F, 0xD6, 0x68, 0x5C, 0x93, 0x1F,
+ 0x6B, 0xC2, 0xAB, 0xED, 0x1E, 0xC0, 0xBC, 0x47,
+ 0xE9, 0xD1, 0xDE, 0xCA, 0xF6, 0x62, 0x43, 0xEB,
+ 0xA2, 0xB4, 0x08, 0xE6, 0x74, 0x0E, 0xA1, 0x72,
+ 0x66, 0x61, 0x21, 0x2E, 0x32, 0x63, 0x29, 0xD7,
+ 0x1C, 0x22, 0xAC, 0xE7, 0x54, 0xF3, 0x65, 0x17,
+ 0x9F, 0x78, 0x79, 0x4C, 0xDD, 0x27, 0x90, 0x36,
+ 0x19, 0x44, 0x03, 0xD9, 0x4A, 0x5A, 0x34, 0xF9,
+ 0x97, 0xA6, 0x70, 0x39, 0x28, 0x77, 0x6E, 0xB7,
+ 0x8C, 0x02, 0x5E, 0x9B, 0x8D, 0x59, 0x6F, 0xA5,
+ 0x07, 0xE2, 0x41, 0x51, 0xC9, 0x3C, 0xE8, 0xE1,
+ 0xB3, 0x16, 0x50, 0x04, 0xE3, 0x1D, 0x3B, 0xD2,
+ 0x4D, 0x35, 0x71, 0xDA, 0x9E, 0xA7, 0xE4, 0xE0,
+ 0xB6, 0x2B, 0xEA, 0x84, 0x55, 0xF8, 0x57, 0x3D,
+ 0x73, 0x42, 0xC6, 0x0A, 0x92, 0x6A, 0xAE, 0xF5,
+ 0xFC, 0xD5, 0x15, 0x52, 0x7E, 0x14, 0x81, 0x13,
+ 0xE5, 0x49, 0x38, 0x2A, 0x94, 0x5B, 0xA3, 0x11,
+ 0x8B, 0x80, 0xBB, 0x01, 0x9C, 0xA4, 0xDB, 0xF7,
+ 0xA9, 0x20, 0xF2, 0x1A, 0xDC, 0x33, 0x3A, 0xEF,
+ 0xD3, 0xFD, 0x30, 0xB0, 0x1B, 0xC4, 0x06, 0xD4,
+ 0x6D, 0x87, 0x2F, 0x60, 0x5F, 0xC5, 0x09, 0x37,
+ 0xAF, 0x00, 0xCB, 0x9D, 0xA0, 0xB9, 0x45, 0x86,
+ 0x4F, 0x6C, 0x67, 0xFB, 0x40, 0x3F, 0xCC, 0xB8,
+ 0xC8, 0x82, 0x98, 0x99, 0x7B, 0xB1, 0xCD, 0xD0,
+ 0xBD, 0x48, 0xAD, 0x26, 0x7A, 0x9A, 0x46, 0xFF,
+ 0x89, 0xC7, 0xC1, 0xCF, 0xBE, 0xAA, 0xEC, 0xBA,
+ 0xCE, 0x2D, 0x4E, 0x83, 0xC3, 0x69, 0xEE, 0xB2 };
+
+static const unsigned char table_123[256] = {
+ 0x9D, 0xFB, 0x3C, 0x81, 0xAA, 0x05, 0xB2, 0xBE,
+ 0xD1, 0x5F, 0x4C, 0xE0, 0xA3, 0xF4, 0xDE, 0x35,
+ 0xFE, 0x1B, 0x37, 0x99, 0x94, 0x7A, 0x10, 0xAB,
+ 0xC0, 0xA4, 0xB5, 0xFF, 0x8F, 0x3B, 0xB4, 0x51,
+ 0x04, 0xE9, 0xB9, 0xC1, 0x98, 0xC5, 0x82, 0x38,
+ 0x4D, 0x71, 0xFC, 0x33, 0xC4, 0x50, 0x5D, 0x88,
+ 0xB8, 0x5C, 0x32, 0xE2, 0xBB, 0xCD, 0x60, 0x2C,
+ 0xD4, 0x7E, 0x27, 0x59, 0x2B, 0x1F, 0x53, 0xF6,
+ 0x25, 0x86, 0xAE, 0x21, 0xFA, 0x31, 0xD7, 0x0F,
+ 0x17, 0xDA, 0x7F, 0xC9, 0x46, 0x19, 0x08, 0xA8,
+ 0xCF, 0x13, 0xCC, 0x03, 0x3F, 0x22, 0x6E, 0xEB,
+ 0x4A, 0x63, 0x73, 0xBD, 0x36, 0xED, 0x30, 0x57,
+ 0x65, 0xF8, 0x41, 0x61, 0x1E, 0xA0, 0xC6, 0x45,
+ 0x3E, 0x75, 0x28, 0x87, 0xCB, 0xD6, 0x16, 0xD8,
+ 0xDF, 0xEF, 0xEA, 0xA7, 0x58, 0xB0, 0x1D, 0xE6,
+ 0x47, 0x76, 0xD9, 0x96, 0xE7, 0xDC, 0x00, 0x80,
+ 0xDD, 0xB7, 0x9A, 0xE1, 0xF5, 0x9C, 0x4B, 0xE3,
+ 0xBC, 0x8D, 0xF2, 0x2F, 0x9F, 0x6C, 0x93, 0xAF,
+ 0xA9, 0xC2, 0x5E, 0x24, 0x15, 0xD2, 0x09, 0x0D,
+ 0xDB, 0x4F, 0x91, 0x0E, 0x64, 0x34, 0x4E, 0xAD,
+ 0x62, 0x44, 0x23, 0x85, 0xB6, 0xAC, 0xC7, 0xCA,
+ 0x84, 0xF9, 0x8C, 0xBF, 0x14, 0x7C, 0x8E, 0x92,
+ 0xF0, 0x0B, 0xCE, 0x90, 0x7D, 0x70, 0x9E, 0x54,
+ 0x39, 0x5B, 0x6D, 0x52, 0xEE, 0xA2, 0x6F, 0x78,
+ 0x2D, 0x95, 0x8B, 0x02, 0x3D, 0x7B, 0x69, 0xC3,
+ 0x49, 0xA5, 0x1A, 0x26, 0xD5, 0x6B, 0xE8, 0xFD,
+ 0xB3, 0xD3, 0x20, 0x55, 0x18, 0x06, 0xF3, 0xB1,
+ 0x0C, 0xC8, 0x07, 0x12, 0xF7, 0x01, 0x2E, 0x72,
+ 0x97, 0xA6, 0x11, 0x89, 0x56, 0x5A, 0x29, 0xBA,
+ 0x67, 0x42, 0x83, 0x6A, 0x2A, 0xF1, 0xA1, 0x9B,
+ 0xE5, 0xE4, 0x74, 0x66, 0x1C, 0x68, 0xEC, 0x40,
+ 0x48, 0x77, 0xD0, 0x0A, 0x8A, 0x3A, 0x43, 0x79 };
+
+static const unsigned char table_124[256] = {
+ 0x6C, 0xC3, 0x28, 0x2F, 0x42, 0x4B, 0x7C, 0x3C,
+ 0xCE, 0x24, 0xC8, 0x51, 0x25, 0x3F, 0x49, 0x8D,
+ 0x1E, 0x5C, 0x89, 0x3A, 0x98, 0x47, 0x0B, 0x12,
+ 0xA9, 0xB1, 0xD7, 0xB6, 0x5D, 0xF9, 0x5A, 0xBC,
+ 0xFA, 0x06, 0x7D, 0x08, 0xFC, 0x37, 0x54, 0x4F,
+ 0xD4, 0xCD, 0xA7, 0x5E, 0xE0, 0x92, 0x82, 0x56,
+ 0xF1, 0x2B, 0xC4, 0xE2, 0x29, 0xEA, 0x35, 0x57,
+ 0x33, 0x4E, 0x1A, 0x17, 0x8B, 0x85, 0xBF, 0xD5,
+ 0x18, 0xB3, 0x0D, 0x71, 0x45, 0x81, 0xB4, 0x27,
+ 0xD1, 0xE1, 0xFF, 0x44, 0x9E, 0xA4, 0x15, 0x9A,
+ 0x90, 0xC7, 0x79, 0xE3, 0x4C, 0xE9, 0x3D, 0x6B,
+ 0xF5, 0xF4, 0xEE, 0xAA, 0xDB, 0x07, 0x09, 0xCF,
+ 0x7B, 0x95, 0xA0, 0x53, 0x8F, 0xA1, 0x9D, 0xBE,
+ 0x6F, 0xAE, 0x96, 0x46, 0x59, 0x01, 0x84, 0xCC,
+ 0x3B, 0x8E, 0xF7, 0x4D, 0x6E, 0xDC, 0xE8, 0x36,
+ 0x7A, 0xE5, 0xBD, 0xE7, 0x9F, 0x2C, 0x52, 0xAB,
+ 0x55, 0x13, 0x1D, 0xFB, 0x58, 0x9C, 0xDF, 0xC0,
+ 0x30, 0x73, 0x67, 0x39, 0x74, 0xD3, 0x11, 0xD2,
+ 0x0E, 0x20, 0xB7, 0x02, 0xB9, 0x1C, 0x86, 0x76,
+ 0x10, 0x68, 0x9B, 0x63, 0x48, 0x8A, 0xB2, 0xB8,
+ 0xAF, 0x26, 0x99, 0x04, 0xB0, 0xE4, 0xEF, 0xEB,
+ 0xEC, 0x6D, 0x61, 0xC1, 0xD0, 0x38, 0xC9, 0x19,
+ 0x60, 0xA8, 0xA6, 0xF8, 0x80, 0xC5, 0x03, 0x0F,
+ 0x22, 0x2D, 0x88, 0x32, 0x77, 0x70, 0xFE, 0x0C,
+ 0x31, 0x40, 0x5F, 0xED, 0xA5, 0x93, 0x43, 0xF0,
+ 0x8C, 0xE6, 0x34, 0x21, 0xD9, 0xC2, 0xD8, 0xC6,
+ 0x6A, 0xD6, 0xCB, 0xAC, 0x75, 0xB5, 0x78, 0x0A,
+ 0xA3, 0x69, 0x16, 0xBA, 0x50, 0x2A, 0x41, 0x83,
+ 0xF6, 0x64, 0x00, 0x65, 0x7E, 0xDD, 0x5B, 0xDA,
+ 0x14, 0xFD, 0x3E, 0x7F, 0xCA, 0x66, 0x4A, 0x1F,
+ 0xA2, 0xAD, 0xF2, 0x23, 0xBB, 0x72, 0xF3, 0x94,
+ 0x62, 0x1B, 0xDE, 0x91, 0x87, 0x97, 0x05, 0x2E };
+
+static const unsigned char table_125[32] = {
+ 0x1A, 0x18, 0x12, 0x15, 0x00, 0x1C, 0x01, 0x0B,
+ 0x19, 0x1B, 0x1F, 0x11, 0x07, 0x10, 0x1E, 0x06,
+ 0x17, 0x04, 0x0A, 0x0E, 0x0D, 0x0C, 0x16, 0x08,
+ 0x02, 0x03, 0x13, 0x14, 0x09, 0x1D, 0x05, 0x0F };
+
+static const unsigned char table_126[32] = {
+ 0x1C, 0x1D, 0x07, 0x12, 0x18, 0x1A, 0x19, 0x09,
+ 0x0F, 0x14, 0x1F, 0x0B, 0x13, 0x04, 0x0E, 0x1E,
+ 0x0C, 0x0D, 0x01, 0x17, 0x1B, 0x16, 0x0A, 0x05,
+ 0x15, 0x10, 0x11, 0x08, 0x00, 0x03, 0x06, 0x02 };
+
+static const unsigned char table_127[256] = {
+ 0xA0, 0x66, 0xD8, 0x08, 0xEA, 0x39, 0x78, 0xAB,
+ 0x61, 0x4E, 0xC7, 0xD1, 0xA3, 0x1C, 0x9F, 0xCB,
+ 0x19, 0x51, 0x15, 0x92, 0x23, 0xFD, 0x7D, 0x1D,
+ 0x95, 0xAE, 0x0E, 0x8B, 0xE6, 0x7F, 0x86, 0x6D,
+ 0x06, 0xBD, 0x20, 0x1F, 0x3A, 0xE4, 0x54, 0x91,
+ 0x69, 0xD3, 0xE3, 0x3D, 0x4D, 0x31, 0x49, 0xA4,
+ 0x41, 0xF3, 0xE0, 0x11, 0x14, 0x9B, 0x96, 0x5A,
+ 0xC4, 0x8E, 0x34, 0xDB, 0xBA, 0x83, 0xD9, 0x81,
+ 0xAF, 0x58, 0x8A, 0x79, 0x13, 0xBC, 0x85, 0x37,
+ 0x9E, 0x6C, 0x57, 0x71, 0x8D, 0x97, 0x5F, 0x6F,
+ 0x1E, 0x74, 0x27, 0xFC, 0x5C, 0x7A, 0x64, 0x87,
+ 0xF5, 0xC6, 0xF2, 0x4F, 0xDE, 0x80, 0xAA, 0x84,
+ 0x2E, 0xDC, 0xE7, 0x40, 0x75, 0xC5, 0xB3, 0xC8,
+ 0xCE, 0x21, 0x02, 0x67, 0xB7, 0x10, 0x47, 0x6A,
+ 0xEE, 0x53, 0x2C, 0x16, 0x05, 0xC0, 0x63, 0x4C,
+ 0x0D, 0xBB, 0xC3, 0x38, 0x46, 0x68, 0x7E, 0xF9,
+ 0xB8, 0xB4, 0x3E, 0x36, 0xD5, 0xEC, 0x0B, 0xF6,
+ 0x33, 0x0A, 0x0F, 0x5B, 0xFB, 0x45, 0xEB, 0xA9,
+ 0x6E, 0x6B, 0xCF, 0x55, 0x99, 0xAC, 0x22, 0xBE,
+ 0xB1, 0xA2, 0x3F, 0x25, 0x77, 0x8F, 0x7C, 0xF1,
+ 0xD4, 0x59, 0xA8, 0xE5, 0xD7, 0xCA, 0xA1, 0x93,
+ 0xE9, 0xAD, 0xF7, 0x94, 0xEF, 0xED, 0x3C, 0x2A,
+ 0x88, 0xB5, 0x35, 0x9D, 0x9C, 0x32, 0x5E, 0xB6,
+ 0x48, 0x9A, 0x7B, 0x26, 0x50, 0x90, 0x04, 0xA7,
+ 0xDD, 0x09, 0xB9, 0x98, 0xB2, 0xFE, 0xDF, 0x44,
+ 0x89, 0x29, 0x5D, 0xE2, 0x72, 0xC9, 0x28, 0x03,
+ 0x43, 0x8C, 0x52, 0x18, 0xC1, 0x56, 0x1B, 0x1A,
+ 0x01, 0x65, 0xDA, 0xBF, 0x07, 0xFF, 0x76, 0xE8,
+ 0x30, 0xA5, 0x4A, 0xA6, 0x12, 0x62, 0x24, 0x60,
+ 0x4B, 0x73, 0x0C, 0xF0, 0xFA, 0x42, 0xF4, 0x00,
+ 0xD2, 0xD0, 0xD6, 0x3B, 0xC2, 0x2F, 0xE1, 0x2B,
+ 0x70, 0xF8, 0x17, 0xCD, 0xB0, 0xCC, 0x82, 0x2D };
+
+static const unsigned char table_128[32] = {
+ 0x1A, 0x1C, 0x09, 0x17, 0x1B, 0x0B, 0x16, 0x1E,
+ 0x14, 0x0C, 0x12, 0x0E, 0x05, 0x03, 0x1F, 0x15,
+ 0x19, 0x0D, 0x10, 0x13, 0x0A, 0x01, 0x00, 0x11,
+ 0x02, 0x08, 0x0F, 0x18, 0x07, 0x04, 0x1D, 0x06 };
+
+static const unsigned char table_129[256] = {
+ 0x9D, 0x5F, 0xE8, 0x99, 0x57, 0x07, 0x16, 0xA6,
+ 0x9F, 0xB6, 0xDE, 0xED, 0x2D, 0xB3, 0xC0, 0x8E,
+ 0xCC, 0x49, 0xCE, 0xB0, 0x1B, 0xB1, 0x7A, 0xE0,
+ 0xEB, 0x28, 0xDB, 0x7D, 0x88, 0xC8, 0x06, 0x6C,
+ 0x02, 0xD0, 0x85, 0x7E, 0xDF, 0xF5, 0x78, 0xE5,
+ 0xA9, 0x71, 0xD9, 0xDD, 0xDC, 0xEE, 0x8C, 0x54,
+ 0xA0, 0x86, 0xFE, 0x0E, 0x55, 0xF7, 0x41, 0x47,
+ 0x1D, 0x15, 0xD6, 0xA4, 0xFF, 0x1F, 0x25, 0xF8,
+ 0x12, 0xE9, 0x74, 0x7B, 0x04, 0xE6, 0x4C, 0x31,
+ 0xA2, 0xBE, 0x0C, 0xB9, 0x17, 0xBD, 0x3D, 0xF0,
+ 0x9E, 0x4D, 0x4E, 0xB2, 0xE7, 0x40, 0xC9, 0x8A,
+ 0x67, 0x5E, 0x19, 0x0F, 0xB7, 0x22, 0x8D, 0xBA,
+ 0xFC, 0x93, 0x14, 0xEA, 0xFD, 0x0D, 0xD5, 0x38,
+ 0xA1, 0x84, 0x1C, 0x35, 0x60, 0x37, 0x43, 0x9C,
+ 0xCF, 0xEF, 0x3A, 0x72, 0xF2, 0x61, 0x75, 0x6A,
+ 0x42, 0xAC, 0xD3, 0x48, 0x77, 0xC5, 0x29, 0xF6,
+ 0x58, 0x79, 0xFA, 0x5D, 0xC7, 0x70, 0x53, 0x9A,
+ 0x6F, 0xC1, 0x0A, 0x90, 0x8F, 0x3E, 0x3B, 0x8B,
+ 0xEC, 0xBC, 0x20, 0x27, 0xC3, 0x66, 0x3F, 0x33,
+ 0xA5, 0x44, 0x2E, 0x32, 0x65, 0x18, 0xFB, 0x59,
+ 0x52, 0x50, 0xE2, 0x63, 0x2B, 0xCD, 0x64, 0xCB,
+ 0xD2, 0x68, 0x10, 0xA7, 0xAE, 0x11, 0xA8, 0x96,
+ 0x69, 0xAF, 0xC2, 0x34, 0x5C, 0x56, 0xE3, 0xF9,
+ 0xDA, 0x51, 0x81, 0x4A, 0x05, 0x00, 0xB8, 0x7C,
+ 0x30, 0x2F, 0x46, 0xB4, 0xC6, 0x87, 0x4B, 0x94,
+ 0x80, 0xF4, 0x7F, 0x3C, 0x26, 0xF1, 0x5B, 0xAB,
+ 0x91, 0x6E, 0x08, 0x76, 0x98, 0xD1, 0xE1, 0x36,
+ 0x21, 0xCA, 0xD8, 0x24, 0x9B, 0x39, 0xBB, 0xAD,
+ 0x13, 0x62, 0x97, 0x1A, 0x6D, 0x2C, 0x5A, 0xC4,
+ 0xD4, 0xA3, 0x03, 0xBF, 0x1E, 0xE4, 0xF3, 0x95,
+ 0x23, 0x73, 0x92, 0xB5, 0x01, 0x83, 0x82, 0xAA,
+ 0x09, 0x45, 0x6B, 0xD7, 0x0B, 0x89, 0x4F, 0x2A };
+
+static const unsigned char table_130[32] = {
+ 0x07, 0x03, 0x15, 0x0B, 0x02, 0x11, 0x17, 0x14,
+ 0x05, 0x10, 0x0A, 0x0F, 0x01, 0x1C, 0x1D, 0x0E,
+ 0x12, 0x06, 0x18, 0x16, 0x1A, 0x09, 0x13, 0x19,
+ 0x1B, 0x00, 0x08, 0x0D, 0x0C, 0x1E, 0x04, 0x1F };
+
+static const unsigned char table_131[32] = {
+ 0x1D, 0x13, 0x1B, 0x10, 0x07, 0x03, 0x0A, 0x02,
+ 0x00, 0x0C, 0x0E, 0x0B, 0x0D, 0x18, 0x12, 0x1F,
+ 0x1A, 0x04, 0x15, 0x11, 0x1E, 0x08, 0x1C, 0x14,
+ 0x19, 0x05, 0x0F, 0x17, 0x06, 0x01, 0x09, 0x16 };
+
+static const unsigned char table_132[256] = {
+ 0x33, 0x8D, 0x45, 0x6F, 0xFF, 0xF5, 0xB6, 0x53,
+ 0x3B, 0xF3, 0x07, 0xA4, 0x97, 0xEB, 0x6B, 0xA5,
+ 0xD3, 0xDC, 0x7B, 0x79, 0x93, 0xE7, 0xF7, 0x67,
+ 0x9C, 0x4F, 0x88, 0xF9, 0x3A, 0x2B, 0x27, 0x48,
+ 0x47, 0x18, 0xF4, 0xAD, 0xB4, 0x8F, 0x2A, 0x76,
+ 0x17, 0xE9, 0x1F, 0x40, 0x0C, 0x59, 0xD1, 0x4C,
+ 0x20, 0x31, 0x73, 0x54, 0xCD, 0x68, 0x08, 0x52,
+ 0x10, 0x62, 0x3D, 0xD2, 0x77, 0xF2, 0xD7, 0x30,
+ 0xCA, 0x16, 0x01, 0x50, 0x9F, 0x3F, 0x75, 0xED,
+ 0x90, 0x6A, 0x34, 0xCE, 0x05, 0x78, 0x5E, 0xD6,
+ 0x85, 0xCC, 0x29, 0xB8, 0xC1, 0x0D, 0xCB, 0x80,
+ 0x2E, 0x04, 0x00, 0x44, 0x32, 0x95, 0xBF, 0xFE,
+ 0x6E, 0x7C, 0xFD, 0xA7, 0x3C, 0x5C, 0xF0, 0xEC,
+ 0xAC, 0xF8, 0xB9, 0xC0, 0x1B, 0x3E, 0xE8, 0x66,
+ 0x5D, 0xDE, 0x49, 0x71, 0xAA, 0xAF, 0x21, 0x64,
+ 0x28, 0x8A, 0x4E, 0x98, 0x58, 0xA2, 0x23, 0xCF,
+ 0x9E, 0x63, 0x61, 0x91, 0x12, 0xC6, 0x8C, 0x19,
+ 0xA8, 0xD4, 0xC7, 0xDD, 0xFC, 0xBD, 0x38, 0xDF,
+ 0xEA, 0x2D, 0x7E, 0x7D, 0xE3, 0xE0, 0xC3, 0xD9,
+ 0x8B, 0x11, 0xF1, 0x4D, 0xC8, 0xB5, 0x55, 0xAE,
+ 0xE1, 0x89, 0xE5, 0xB3, 0xBC, 0x69, 0x9D, 0xA6,
+ 0x09, 0x9A, 0x74, 0x35, 0x1A, 0xFB, 0x24, 0xB7,
+ 0x13, 0x14, 0x94, 0x0A, 0x86, 0x0F, 0x60, 0x51,
+ 0xB0, 0x84, 0x22, 0x5B, 0x87, 0x43, 0x57, 0x0B,
+ 0x2F, 0x5F, 0x02, 0xD0, 0xBB, 0xA3, 0xC9, 0x7A,
+ 0xBE, 0xC2, 0x26, 0x46, 0xDB, 0x1E, 0x1D, 0x92,
+ 0xE2, 0xB2, 0x37, 0x6D, 0xD5, 0x4A, 0x0E, 0x4B,
+ 0x8E, 0xC5, 0x42, 0x99, 0xEE, 0xE4, 0xB1, 0x06,
+ 0xAB, 0x5A, 0x56, 0x41, 0x65, 0xBA, 0xFA, 0x83,
+ 0x15, 0xDA, 0x72, 0xA1, 0x81, 0x1C, 0xA9, 0x36,
+ 0x25, 0x96, 0x6C, 0x39, 0x82, 0xE6, 0x2C, 0x9B,
+ 0xC4, 0x7F, 0xA0, 0xD8, 0xEF, 0x03, 0x70, 0xF6 };
+
+static const unsigned char table_133[256] = {
+ 0x02, 0xF0, 0xED, 0xC4, 0xE4, 0x67, 0x60, 0x8B,
+ 0xF3, 0x77, 0x92, 0xE0, 0x85, 0x93, 0x1E, 0x8E,
+ 0x9A, 0x38, 0x61, 0x20, 0xB7, 0x68, 0xE1, 0x5E,
+ 0xD5, 0x63, 0xA9, 0xA5, 0xBE, 0x36, 0x12, 0x4D,
+ 0x86, 0x16, 0xD6, 0xB1, 0x23, 0x64, 0x4F, 0x62,
+ 0xFC, 0xA3, 0xD3, 0x04, 0x7D, 0x8C, 0xE2, 0xFF,
+ 0x5D, 0x30, 0xF5, 0x95, 0x1B, 0x5F, 0x73, 0xAA,
+ 0xE8, 0x07, 0x87, 0xDC, 0x54, 0x7C, 0xEE, 0x00,
+ 0xB8, 0xDE, 0x55, 0xBA, 0xD0, 0x50, 0xBB, 0x89,
+ 0x1C, 0xCC, 0x0E, 0xC0, 0x42, 0x11, 0xD8, 0xA2,
+ 0x2E, 0x33, 0xFE, 0x26, 0xD4, 0x10, 0xDA, 0xC5,
+ 0xFB, 0xAF, 0x98, 0x78, 0xB5, 0xBD, 0xC8, 0x8D,
+ 0x46, 0xA0, 0xD1, 0x7B, 0xBC, 0x75, 0xAB, 0x25,
+ 0xB2, 0x43, 0x57, 0xB6, 0xEC, 0xF4, 0x66, 0x05,
+ 0x9C, 0x08, 0x53, 0x80, 0xEA, 0x21, 0x2C, 0x6C,
+ 0x17, 0x71, 0xD2, 0x70, 0x76, 0x9E, 0x6B, 0x7A,
+ 0x58, 0xA7, 0xBF, 0x29, 0x03, 0x1F, 0x06, 0xC1,
+ 0xDD, 0x2F, 0x5C, 0x0B, 0x0D, 0x8A, 0x0A, 0xCB,
+ 0xCA, 0x6F, 0x19, 0x6A, 0xFA, 0xF7, 0xA8, 0xA1,
+ 0xEB, 0x88, 0x44, 0xAC, 0x01, 0x4E, 0x59, 0x94,
+ 0x72, 0x2B, 0xE9, 0x0F, 0x22, 0x9B, 0x27, 0x37,
+ 0x41, 0xF9, 0xF2, 0xE3, 0xEF, 0xB3, 0xD9, 0x2A,
+ 0x31, 0xC2, 0x0C, 0x15, 0x90, 0x14, 0xF6, 0x83,
+ 0xFD, 0x96, 0x9D, 0x7F, 0xA4, 0x39, 0xE7, 0x3F,
+ 0xE6, 0xC7, 0xCD, 0x1A, 0xCF, 0x48, 0x3C, 0x51,
+ 0x6D, 0x5B, 0x74, 0xC3, 0xC9, 0x09, 0x3D, 0x9F,
+ 0xDB, 0x32, 0x40, 0x18, 0xD7, 0xCE, 0x69, 0x49,
+ 0x3A, 0xF1, 0xB9, 0x56, 0x91, 0x99, 0x84, 0x24,
+ 0x7E, 0x34, 0x4B, 0xA6, 0x47, 0xB4, 0x6E, 0xDF,
+ 0x65, 0x3B, 0xAD, 0x45, 0x13, 0xC6, 0x81, 0xF8,
+ 0x4A, 0x2D, 0x8F, 0x4C, 0x97, 0x28, 0x3E, 0xE5,
+ 0x5A, 0x35, 0xB0, 0xAE, 0x82, 0x79, 0x1D, 0x52 };
+
+static const unsigned char table_134[32] = {
+ 0x09, 0x0F, 0x10, 0x0C, 0x03, 0x15, 0x07, 0x17,
+ 0x0E, 0x0B, 0x1D, 0x08, 0x19, 0x11, 0x00, 0x0A,
+ 0x01, 0x06, 0x18, 0x16, 0x0D, 0x13, 0x14, 0x12,
+ 0x02, 0x1B, 0x1A, 0x04, 0x05, 0x1F, 0x1C, 0x1E };
+
+static const unsigned char table_135[256] = {
+ 0x14, 0x34, 0xEA, 0x02, 0x2B, 0x5A, 0x10, 0x51,
+ 0xF3, 0x8F, 0x28, 0xB2, 0x50, 0x8B, 0x01, 0xCC,
+ 0x80, 0x15, 0x29, 0x42, 0xF4, 0x1D, 0xFB, 0xBB,
+ 0x1F, 0x43, 0x8C, 0x17, 0x1E, 0x81, 0x04, 0x98,
+ 0x46, 0xD8, 0xD5, 0x65, 0x4C, 0x1C, 0xDB, 0x40,
+ 0x5F, 0x1A, 0x31, 0x74, 0xF1, 0x64, 0x19, 0x05,
+ 0xFC, 0xF0, 0x73, 0xB6, 0x23, 0x77, 0x9C, 0xCE,
+ 0x70, 0xEF, 0xDA, 0xE0, 0xA2, 0x78, 0x84, 0xEB,
+ 0x9E, 0xC5, 0x95, 0xA3, 0xF6, 0xCA, 0xAD, 0x52,
+ 0xD0, 0x3F, 0x54, 0xA7, 0x33, 0xA9, 0x09, 0x6A,
+ 0x89, 0x7E, 0x75, 0xA8, 0xD6, 0x79, 0x9F, 0xAB,
+ 0x8E, 0x11, 0x0E, 0x3B, 0xAA, 0xE6, 0x85, 0x53,
+ 0x0A, 0x59, 0xEC, 0x94, 0xD7, 0x41, 0x86, 0x7D,
+ 0x2F, 0xC7, 0xDE, 0x06, 0xCB, 0x13, 0xBA, 0x58,
+ 0xC8, 0xC9, 0x07, 0x67, 0x7F, 0xA5, 0xB4, 0x2C,
+ 0x48, 0x6C, 0xB8, 0xD1, 0x30, 0xD3, 0x35, 0x4F,
+ 0x88, 0x26, 0x93, 0x32, 0x71, 0x3E, 0x3D, 0xF7,
+ 0x6D, 0x03, 0xED, 0x8A, 0x36, 0x55, 0x9B, 0x66,
+ 0x8D, 0x27, 0x7C, 0xF9, 0xA6, 0xC3, 0x20, 0x69,
+ 0x4A, 0xE3, 0x99, 0x5C, 0xBC, 0x45, 0x16, 0x6B,
+ 0xB9, 0x49, 0x82, 0xFF, 0xBD, 0xDD, 0xE9, 0x0C,
+ 0xD4, 0x44, 0xFD, 0x22, 0xE5, 0xAC, 0x61, 0xC4,
+ 0x90, 0x47, 0x37, 0x72, 0xA4, 0x7A, 0x24, 0x4D,
+ 0x5B, 0x12, 0x38, 0x92, 0x87, 0x1B, 0xE1, 0xA0,
+ 0x91, 0x3C, 0xEE, 0x6F, 0xC1, 0x0F, 0x56, 0xC2,
+ 0x9A, 0xF8, 0x18, 0xE8, 0xD2, 0xDC, 0x4B, 0xCF,
+ 0x39, 0xF5, 0xFE, 0x2A, 0x2D, 0x9D, 0xA1, 0xFA,
+ 0xE7, 0xBF, 0x6E, 0xE4, 0x2E, 0xB3, 0xCD, 0xE2,
+ 0xAF, 0x7B, 0xC0, 0x68, 0x97, 0xB5, 0x5D, 0xB7,
+ 0x21, 0x57, 0x83, 0x76, 0xB1, 0xAE, 0x5E, 0x0D,
+ 0x96, 0x4E, 0x08, 0xC6, 0x0B, 0xDF, 0x3A, 0xB0,
+ 0x00, 0x63, 0xD9, 0xBE, 0xF2, 0x60, 0x25, 0x62 };
+
+static const unsigned char table_136[256] = {
+ 0xD3, 0x1A, 0x00, 0xED, 0x59, 0x24, 0xA3, 0xF2,
+ 0xBA, 0x58, 0x4C, 0x5C, 0x75, 0x48, 0x98, 0xB0,
+ 0xCF, 0xC3, 0xF7, 0x88, 0x70, 0xB3, 0x3D, 0x3E,
+ 0x03, 0xF9, 0xC9, 0xFD, 0x80, 0x44, 0x7F, 0x3B,
+ 0x95, 0x5F, 0x31, 0x47, 0x15, 0x07, 0xB8, 0x08,
+ 0xCE, 0xDA, 0x71, 0x9F, 0x83, 0xB1, 0x55, 0x16,
+ 0xE6, 0xB2, 0xC7, 0xBE, 0x54, 0xE7, 0x2E, 0x8D,
+ 0x12, 0x21, 0x41, 0x69, 0xFE, 0x28, 0x11, 0x56,
+ 0x5A, 0xDD, 0xB6, 0x87, 0x78, 0x82, 0x4D, 0x7B,
+ 0x50, 0x9A, 0x9E, 0x62, 0xF8, 0x0A, 0x64, 0xF1,
+ 0x4E, 0x33, 0xAD, 0xBB, 0x79, 0x76, 0xD8, 0xCD,
+ 0x86, 0x34, 0x29, 0xD5, 0x7D, 0x72, 0xC5, 0xC1,
+ 0xDF, 0x09, 0x4A, 0xB4, 0xD2, 0x7A, 0xF0, 0xCC,
+ 0x0F, 0xA7, 0xD6, 0x2B, 0x20, 0x26, 0xEF, 0xAB,
+ 0x74, 0x1E, 0xE3, 0x77, 0xCB, 0x7C, 0x73, 0x5E,
+ 0x6B, 0x0D, 0x65, 0xA6, 0x30, 0xFB, 0xD0, 0xB7,
+ 0xAA, 0x94, 0x9D, 0x85, 0x13, 0x18, 0xA8, 0xF3,
+ 0xE0, 0xBC, 0x45, 0xCA, 0xC8, 0xDC, 0xE2, 0x3C,
+ 0x23, 0xE5, 0xB9, 0x90, 0x49, 0xA5, 0xE4, 0x36,
+ 0xFC, 0x53, 0xF6, 0xE8, 0xC6, 0x2C, 0x02, 0x25,
+ 0xC0, 0x8F, 0x61, 0xA4, 0x39, 0x8C, 0x5D, 0xAE,
+ 0x22, 0x1C, 0x2F, 0xD4, 0x6C, 0xD1, 0x51, 0xEA,
+ 0x4F, 0x7E, 0xA0, 0xF5, 0x6A, 0x32, 0xA2, 0x01,
+ 0xB5, 0x10, 0x2A, 0xAC, 0xA9, 0x06, 0xC4, 0x91,
+ 0x68, 0xE1, 0xBD, 0x14, 0x38, 0xFA, 0x6E, 0x3F,
+ 0x37, 0x66, 0xDB, 0x57, 0x43, 0x1B, 0x67, 0xAF,
+ 0x1F, 0x0B, 0x6D, 0x2D, 0x89, 0x04, 0x4B, 0x52,
+ 0xC2, 0xBF, 0xA1, 0x92, 0x99, 0x6F, 0x63, 0x81,
+ 0x27, 0x05, 0x96, 0x3A, 0xEC, 0x0E, 0x97, 0xD9,
+ 0xDE, 0x46, 0x35, 0x8B, 0x8E, 0x8A, 0xF4, 0xFF,
+ 0x60, 0xD7, 0xE9, 0x17, 0xEB, 0x9C, 0x84, 0x0C,
+ 0x93, 0x1D, 0x9B, 0x5B, 0x40, 0xEE, 0x42, 0x19 };
+
+static const unsigned char table_137[32] = {
+ 0x0F, 0x09, 0x02, 0x06, 0x18, 0x0B, 0x1E, 0x05,
+ 0x11, 0x1D, 0x16, 0x01, 0x13, 0x10, 0x0E, 0x1A,
+ 0x1B, 0x00, 0x0D, 0x08, 0x15, 0x14, 0x19, 0x17,
+ 0x03, 0x1F, 0x0A, 0x12, 0x0C, 0x07, 0x04, 0x1C };
+
+static const unsigned char table_138[32] = {
+ 0x0D, 0x1C, 0x1F, 0x15, 0x0F, 0x14, 0x1B, 0x12,
+ 0x09, 0x0B, 0x19, 0x07, 0x11, 0x16, 0x0C, 0x04,
+ 0x13, 0x05, 0x1D, 0x03, 0x0E, 0x0A, 0x08, 0x1E,
+ 0x01, 0x06, 0x18, 0x17, 0x10, 0x1A, 0x02, 0x00 };
+
+static const unsigned char table_139[32] = {
+ 0x05, 0x15, 0x1D, 0x02, 0x0F, 0x03, 0x17, 0x1A,
+ 0x0A, 0x00, 0x1F, 0x12, 0x0E, 0x11, 0x1B, 0x13,
+ 0x0B, 0x0D, 0x09, 0x18, 0x1E, 0x08, 0x14, 0x07,
+ 0x0C, 0x04, 0x16, 0x19, 0x1C, 0x06, 0x10, 0x01 };
+
+static const unsigned char table_140[32] = {
+ 0x06, 0x1E, 0x0C, 0x11, 0x13, 0x08, 0x15, 0x01,
+ 0x1D, 0x03, 0x0F, 0x19, 0x18, 0x04, 0x00, 0x14,
+ 0x12, 0x1A, 0x0B, 0x0E, 0x02, 0x1B, 0x07, 0x05,
+ 0x1F, 0x17, 0x09, 0x0A, 0x0D, 0x16, 0x10, 0x1C };
+
+static const unsigned char table_141[256] = {
+ 0xE1, 0x0A, 0x28, 0xCD, 0x8A, 0x1E, 0x26, 0x10,
+ 0xC0, 0x6F, 0x06, 0x2C, 0xF8, 0x51, 0x6C, 0x8F,
+ 0xA8, 0x8C, 0x41, 0xF4, 0xED, 0x36, 0xAC, 0x89,
+ 0xBD, 0x9D, 0x42, 0x50, 0x95, 0x07, 0x2A, 0x9B,
+ 0x7E, 0xA3, 0x6B, 0x30, 0x72, 0x4E, 0xBE, 0xD8,
+ 0x8B, 0x5B, 0x1A, 0x56, 0x05, 0xEF, 0xEE, 0x64,
+ 0xFF, 0xFD, 0x93, 0xB5, 0xD6, 0x04, 0x57, 0xAE,
+ 0x4D, 0x6D, 0x2F, 0xBA, 0x40, 0xE0, 0xDB, 0xF2,
+ 0xCC, 0x08, 0x35, 0x02, 0xC4, 0x65, 0x66, 0x76,
+ 0xA1, 0x97, 0x9F, 0x6A, 0x90, 0xA7, 0x34, 0x1B,
+ 0x18, 0xB9, 0xA2, 0xDE, 0x23, 0x1F, 0xCB, 0xE6,
+ 0xAB, 0xCF, 0xAD, 0x4A, 0xF7, 0x24, 0xD0, 0xE8,
+ 0x8D, 0x49, 0xEA, 0x0F, 0x94, 0x22, 0xD3, 0x74,
+ 0x71, 0x0D, 0x21, 0x14, 0x39, 0x4B, 0x16, 0x25,
+ 0x5A, 0xB7, 0x17, 0x67, 0x59, 0x47, 0x27, 0x4F,
+ 0x32, 0x3B, 0x63, 0x0C, 0xF0, 0xF3, 0x7B, 0xC7,
+ 0xCA, 0x3A, 0x9A, 0xE2, 0xD5, 0xFA, 0x91, 0xFC,
+ 0x86, 0x81, 0x99, 0xB4, 0xBC, 0x7C, 0xC5, 0xBF,
+ 0xC1, 0xF5, 0x77, 0xA4, 0x79, 0x11, 0x8E, 0x75,
+ 0x55, 0x3D, 0x78, 0x20, 0x37, 0x3E, 0x85, 0xE4,
+ 0x2E, 0x82, 0xA9, 0x7A, 0x31, 0xC9, 0xB3, 0xFE,
+ 0x4C, 0x7D, 0xC3, 0xA0, 0x0E, 0x96, 0x5C, 0xC6,
+ 0x1C, 0x5F, 0xD7, 0xDD, 0x83, 0xC8, 0x9E, 0xEC,
+ 0x3F, 0xAF, 0x38, 0x9C, 0xD9, 0xB6, 0xDA, 0xD4,
+ 0x61, 0x44, 0x43, 0xAA, 0xB1, 0xCE, 0xE7, 0x84,
+ 0x00, 0x0B, 0xFB, 0x68, 0xC2, 0x3C, 0x58, 0xB2,
+ 0x69, 0x7F, 0x33, 0x2B, 0x80, 0x03, 0xE9, 0x88,
+ 0x29, 0x12, 0x01, 0x6E, 0x62, 0xF1, 0xA6, 0xF9,
+ 0x5D, 0xD2, 0xE3, 0x53, 0x09, 0x2D, 0xBB, 0x15,
+ 0xEB, 0x13, 0xA5, 0xF6, 0x73, 0x19, 0x60, 0xB0,
+ 0xD1, 0x48, 0x92, 0x1D, 0x52, 0x5E, 0x45, 0x70,
+ 0x98, 0x54, 0xB8, 0xDC, 0x46, 0xDF, 0x87, 0xE5 };
+
+static const unsigned char table_142[256] = {
+ 0x90, 0x94, 0xBE, 0x14, 0x99, 0xEB, 0x45, 0x0F,
+ 0x34, 0x4A, 0xE3, 0x79, 0xD2, 0x64, 0x4D, 0x69,
+ 0x91, 0xDE, 0xB9, 0x1C, 0x59, 0x20, 0x6C, 0x0B,
+ 0x16, 0xC7, 0x1D, 0x18, 0x02, 0x7D, 0x13, 0xB2,
+ 0x7B, 0x81, 0xCF, 0x61, 0xA3, 0x33, 0x00, 0x73,
+ 0x5A, 0x8A, 0xA1, 0xA8, 0x31, 0xAC, 0xF0, 0x67,
+ 0xAE, 0xA5, 0x2A, 0x96, 0x58, 0xF4, 0xB7, 0x0E,
+ 0xE1, 0x54, 0x27, 0x83, 0x09, 0x85, 0xF8, 0x84,
+ 0xEA, 0xAD, 0x06, 0xED, 0x43, 0xFF, 0xA2, 0x6E,
+ 0x68, 0x46, 0x74, 0x47, 0x3C, 0xAA, 0xBC, 0x55,
+ 0xA7, 0xC3, 0x82, 0xDC, 0xBF, 0x38, 0x80, 0x15,
+ 0xF6, 0xB3, 0x92, 0x7C, 0x93, 0x3F, 0xE9, 0x4C,
+ 0x35, 0x30, 0x32, 0xF3, 0x88, 0xC0, 0x49, 0x6D,
+ 0xCE, 0x42, 0xDF, 0xFD, 0x78, 0x6A, 0x24, 0xCA,
+ 0xB8, 0xFC, 0xA6, 0x5F, 0x29, 0xFE, 0x0C, 0x5C,
+ 0x0D, 0x23, 0x8B, 0x9D, 0xD4, 0x03, 0x2C, 0x9C,
+ 0x77, 0xD8, 0x39, 0x8C, 0x57, 0xD5, 0xE0, 0x8F,
+ 0xC6, 0xB0, 0xCD, 0x48, 0xC9, 0xA0, 0xDA, 0xC8,
+ 0xD1, 0x5B, 0xAB, 0x37, 0x5D, 0x63, 0xAF, 0xF9,
+ 0x17, 0x1B, 0xE5, 0xF1, 0x36, 0xC1, 0x04, 0x26,
+ 0x6F, 0x9E, 0xD9, 0x2F, 0x7F, 0xB5, 0x3A, 0xD6,
+ 0xE6, 0x40, 0x07, 0xCB, 0x7E, 0x3E, 0xC5, 0x22,
+ 0xEC, 0xE2, 0xD3, 0x4E, 0x65, 0x2D, 0x70, 0xE7,
+ 0x10, 0x19, 0xD0, 0xEF, 0xBD, 0xC2, 0x44, 0xB4,
+ 0xF7, 0xA4, 0x53, 0x9F, 0x86, 0xFA, 0xE8, 0x4B,
+ 0x28, 0x3D, 0x9B, 0x56, 0x89, 0x6B, 0x25, 0x71,
+ 0x60, 0x11, 0x9A, 0x5E, 0x1A, 0x52, 0x08, 0x4F,
+ 0xB1, 0xDD, 0xBB, 0x98, 0xFB, 0x12, 0x3B, 0x0A,
+ 0x2E, 0xDB, 0x62, 0x8D, 0xC4, 0x75, 0xA9, 0x2B,
+ 0xE4, 0x97, 0x72, 0xF5, 0xEE, 0xF2, 0xB6, 0x21,
+ 0xBA, 0x7A, 0x76, 0x41, 0x50, 0x66, 0x05, 0x8E,
+ 0xCC, 0x1E, 0x87, 0xD7, 0x01, 0x1F, 0x51, 0x95 };
+
+static const unsigned char table_143[32] = {
+ 0x0E, 0x16, 0x18, 0x11, 0x0C, 0x01, 0x12, 0x1F,
+ 0x08, 0x15, 0x0A, 0x06, 0x1C, 0x1E, 0x02, 0x1A,
+ 0x17, 0x03, 0x07, 0x13, 0x05, 0x19, 0x10, 0x0F,
+ 0x0D, 0x14, 0x09, 0x0B, 0x1B, 0x00, 0x1D, 0x04 };
+
+static const unsigned char table_144[32] = {
+ 0x00, 0x1B, 0x17, 0x19, 0x1D, 0x11, 0x0D, 0x1A,
+ 0x13, 0x03, 0x1E, 0x09, 0x10, 0x0E, 0x15, 0x05,
+ 0x0B, 0x1C, 0x1F, 0x08, 0x0A, 0x06, 0x01, 0x0F,
+ 0x16, 0x14, 0x02, 0x04, 0x07, 0x18, 0x12, 0x0C };
+
+static const unsigned char table_145[256] = {
+ 0xF9, 0x2C, 0x38, 0x74, 0xDA, 0x65, 0x85, 0x0E,
+ 0xBA, 0x64, 0xDB, 0xE3, 0xB6, 0x8B, 0x0B, 0x5E,
+ 0x01, 0x0F, 0x12, 0x8C, 0xD4, 0xCC, 0xB1, 0x7B,
+ 0xE7, 0xBC, 0x2E, 0x87, 0x84, 0x3B, 0xF8, 0x4C,
+ 0x8E, 0x59, 0x2D, 0xAA, 0xCE, 0x28, 0x1B, 0xEE,
+ 0x7F, 0x5C, 0xFB, 0x62, 0x05, 0xD9, 0xDD, 0x9D,
+ 0x49, 0x66, 0x82, 0x71, 0xD2, 0xC7, 0xEB, 0xCF,
+ 0x5B, 0x41, 0x25, 0xC8, 0x6C, 0xFF, 0x78, 0x97,
+ 0x0C, 0xA2, 0x50, 0x7A, 0xAF, 0x2F, 0xB0, 0x7E,
+ 0xBB, 0x73, 0xA0, 0x9B, 0x09, 0xDE, 0x35, 0xE9,
+ 0x5A, 0x70, 0x56, 0xC5, 0x81, 0x19, 0x55, 0xAB,
+ 0xC1, 0xB4, 0x2A, 0x30, 0x54, 0x6F, 0x3E, 0x46,
+ 0x5D, 0x37, 0xF5, 0x57, 0x6B, 0x7C, 0x43, 0xE1,
+ 0x4A, 0x3F, 0xB2, 0x4B, 0x77, 0xB5, 0x44, 0xD6,
+ 0x91, 0x11, 0x72, 0xE8, 0xBE, 0xA5, 0xA8, 0xD3,
+ 0x9A, 0x17, 0x86, 0x88, 0x16, 0x3C, 0x36, 0xD8,
+ 0x6E, 0x07, 0x8D, 0x5F, 0xFA, 0xF1, 0x24, 0x7D,
+ 0x20, 0x60, 0x0D, 0x89, 0xC9, 0x29, 0xA7, 0x2B,
+ 0x4E, 0x10, 0x9F, 0xE5, 0x61, 0x32, 0x3A, 0xBF,
+ 0x93, 0xE6, 0xF3, 0x52, 0x80, 0xC4, 0x02, 0x22,
+ 0xA4, 0xBD, 0xF0, 0x48, 0x51, 0xF2, 0xD7, 0x33,
+ 0x00, 0x53, 0x98, 0xEC, 0x47, 0x39, 0xB9, 0x90,
+ 0x76, 0x4F, 0x68, 0x3D, 0x9C, 0x92, 0xD5, 0xB8,
+ 0xAE, 0xD0, 0xF4, 0x67, 0x58, 0xC0, 0x06, 0x08,
+ 0x14, 0x31, 0xDC, 0xA1, 0x15, 0xDF, 0xCA, 0xE2,
+ 0x23, 0xFE, 0xE4, 0x8F, 0x0A, 0xFC, 0x8A, 0xA3,
+ 0xC6, 0xCD, 0x6A, 0x75, 0xFD, 0x42, 0xB7, 0x79,
+ 0x96, 0x1D, 0x63, 0x18, 0xA9, 0x1C, 0x83, 0x6D,
+ 0xE0, 0x34, 0x04, 0xA6, 0x13, 0xAC, 0xD1, 0xF7,
+ 0x26, 0xC3, 0x1F, 0x27, 0x45, 0x95, 0xCB, 0x21,
+ 0xED, 0x1A, 0x9E, 0x99, 0xEA, 0x40, 0x94, 0x4D,
+ 0x69, 0xF6, 0xEF, 0xC2, 0xAD, 0x03, 0xB3, 0x1E };
+
+static const unsigned char table_146[256] = {
+ 0x1C, 0xF5, 0x16, 0xD2, 0xCC, 0xDC, 0x1E, 0x29,
+ 0xE3, 0x17, 0x3B, 0x66, 0x6A, 0xF7, 0x03, 0xB2,
+ 0x92, 0x45, 0x4D, 0xD6, 0x0C, 0x5E, 0xE6, 0x01,
+ 0xDE, 0xCE, 0x83, 0xFA, 0x35, 0x02, 0x85, 0xC4,
+ 0x2E, 0x89, 0x8D, 0xE7, 0x30, 0x93, 0xDD, 0x70,
+ 0x80, 0xD9, 0x6D, 0x81, 0x07, 0x8E, 0xA9, 0xA6,
+ 0x5F, 0xC9, 0xF3, 0x9D, 0x65, 0xE8, 0x88, 0x0B,
+ 0x49, 0xAA, 0xB7, 0x6C, 0x11, 0xFC, 0x6F, 0xA3,
+ 0xF8, 0x52, 0x0E, 0xD4, 0x08, 0x25, 0x27, 0x33,
+ 0x2F, 0xF0, 0x2B, 0x47, 0xDA, 0x4C, 0x39, 0x54,
+ 0xB9, 0xC1, 0xEA, 0x7C, 0x44, 0xEB, 0x06, 0xE1,
+ 0x8C, 0x9B, 0x74, 0x42, 0x4F, 0x0A, 0x69, 0x2A,
+ 0x2D, 0xA1, 0x19, 0xD5, 0xC3, 0x87, 0x68, 0xFF,
+ 0xEC, 0xE4, 0x86, 0xCF, 0xF6, 0x79, 0x34, 0xA8,
+ 0x72, 0xF4, 0x8B, 0xAF, 0xA5, 0x00, 0xBA, 0x5C,
+ 0x23, 0xB8, 0xC8, 0x59, 0xBF, 0x6E, 0xCB, 0x20,
+ 0x1F, 0x53, 0x97, 0x4B, 0xD0, 0x55, 0x5B, 0xDF,
+ 0x8A, 0xED, 0x9A, 0x62, 0xC5, 0xD7, 0x18, 0x82,
+ 0xC7, 0x12, 0x15, 0x1B, 0xC0, 0x38, 0xCA, 0x26,
+ 0xDB, 0xAE, 0xF9, 0x90, 0x1A, 0xF2, 0x56, 0x32,
+ 0x21, 0x3C, 0x43, 0xEE, 0xA4, 0x13, 0x94, 0xA2,
+ 0x46, 0x77, 0xBC, 0xB6, 0x9C, 0x0D, 0xCD, 0x37,
+ 0x63, 0x60, 0x6B, 0x3A, 0x3E, 0xA7, 0xD8, 0xFE,
+ 0xFB, 0xEF, 0x67, 0xFD, 0xAD, 0xF1, 0x09, 0x1D,
+ 0xE9, 0x51, 0xB4, 0x95, 0x75, 0x0F, 0xB3, 0xD3,
+ 0xAB, 0x22, 0xBB, 0x61, 0x7F, 0x5A, 0x58, 0x7B,
+ 0x73, 0xC2, 0x05, 0xE0, 0x14, 0xE2, 0xAC, 0x91,
+ 0xBE, 0x4E, 0xC6, 0x7A, 0x84, 0x50, 0x28, 0x3F,
+ 0xB0, 0x04, 0x7E, 0xD1, 0x40, 0xBD, 0xE5, 0x71,
+ 0xB1, 0x78, 0x41, 0x9E, 0x57, 0x64, 0x8F, 0x24,
+ 0x4A, 0x9F, 0x3D, 0x31, 0x36, 0x5D, 0xA0, 0x2C,
+ 0x7D, 0x96, 0x76, 0x99, 0xB5, 0x48, 0x98, 0x10 };
+
+static const unsigned char table_147[32] = {
+ 0x17, 0x07, 0x0D, 0x16, 0x00, 0x1B, 0x1F, 0x09,
+ 0x10, 0x11, 0x14, 0x0A, 0x02, 0x06, 0x13, 0x0C,
+ 0x08, 0x1E, 0x0F, 0x12, 0x05, 0x15, 0x19, 0x01,
+ 0x1C, 0x1A, 0x03, 0x18, 0x04, 0x0B, 0x1D, 0x0E };
+
+static const unsigned char table_148[256] = {
+ 0xFB, 0x23, 0xBC, 0x5A, 0x8C, 0x02, 0x42, 0x3B,
+ 0x95, 0x0C, 0x21, 0x0E, 0x14, 0xDF, 0x11, 0xC0,
+ 0xDB, 0x5E, 0xD3, 0xEA, 0xCE, 0xB4, 0x32, 0x12,
+ 0x70, 0x68, 0xA3, 0x25, 0x5B, 0x4B, 0x47, 0xA5,
+ 0x84, 0x9B, 0xFA, 0xD1, 0xE1, 0x3C, 0x20, 0x93,
+ 0x41, 0x26, 0x81, 0x39, 0x17, 0xA4, 0xCF, 0xB9,
+ 0xC5, 0x5F, 0x1C, 0xB3, 0x88, 0xC2, 0x92, 0x30,
+ 0x0A, 0xB8, 0xA0, 0xE2, 0x50, 0x2B, 0x48, 0x1E,
+ 0xD5, 0x13, 0xC7, 0x46, 0x9E, 0x2A, 0xF7, 0x7E,
+ 0xE8, 0x82, 0x60, 0x7A, 0x36, 0x97, 0x0F, 0x8F,
+ 0x8B, 0x80, 0xE0, 0xEB, 0xB1, 0xC6, 0x6E, 0xAE,
+ 0x90, 0x76, 0xA7, 0x31, 0xBE, 0x9C, 0x18, 0x6D,
+ 0xAB, 0x6C, 0x7B, 0xFE, 0x62, 0x05, 0xE9, 0x66,
+ 0x2E, 0x38, 0xB5, 0xB2, 0xFD, 0xFC, 0x7F, 0xE3,
+ 0xA1, 0xF1, 0x99, 0x4D, 0x79, 0x22, 0xD2, 0x37,
+ 0x29, 0x01, 0x54, 0x00, 0xBD, 0x51, 0x1B, 0x07,
+ 0x0B, 0x4A, 0xEE, 0x57, 0xDA, 0x1A, 0x06, 0xCA,
+ 0xCB, 0x9A, 0xC9, 0x7D, 0xE4, 0xDC, 0xE5, 0x8D,
+ 0x75, 0x4F, 0xF6, 0xA2, 0x65, 0x7C, 0xD9, 0x9D,
+ 0x03, 0x27, 0x2D, 0x4C, 0x49, 0xD4, 0x5D, 0x3E,
+ 0xBA, 0x1D, 0xD8, 0x91, 0x74, 0x10, 0xF8, 0xDE,
+ 0xEF, 0xF0, 0x6A, 0x04, 0x72, 0x08, 0x78, 0x3A,
+ 0x53, 0xC4, 0x34, 0xF2, 0x64, 0xAF, 0x86, 0xC3,
+ 0xF3, 0x73, 0x67, 0xCC, 0x58, 0xF4, 0x96, 0xAC,
+ 0x3D, 0xE7, 0x15, 0x8E, 0x19, 0x61, 0xF9, 0xB6,
+ 0xCD, 0x87, 0xAA, 0xB0, 0x1F, 0x6F, 0xAD, 0x28,
+ 0xC8, 0x69, 0x56, 0xC1, 0x71, 0xED, 0xE6, 0x98,
+ 0x6B, 0x59, 0xB7, 0xF5, 0x2C, 0xEC, 0xA8, 0x94,
+ 0x89, 0xBB, 0xA9, 0xD7, 0x2F, 0x8A, 0x4E, 0xD6,
+ 0x33, 0x16, 0x0D, 0x83, 0x5C, 0x52, 0x85, 0xA6,
+ 0x40, 0x45, 0x9F, 0x44, 0x63, 0x35, 0x77, 0xFF,
+ 0x09, 0x43, 0xBF, 0xD0, 0x55, 0xDD, 0x3F, 0x24 };
+
+static const unsigned char table_149[32] = {
+ 0x1B, 0x0B, 0x0C, 0x06, 0x1F, 0x17, 0x04, 0x1A,
+ 0x1E, 0x02, 0x0F, 0x16, 0x0E, 0x09, 0x10, 0x01,
+ 0x13, 0x19, 0x11, 0x00, 0x0A, 0x05, 0x03, 0x1C,
+ 0x18, 0x1D, 0x14, 0x0D, 0x07, 0x08, 0x15, 0x12 };
+
+static const unsigned char table_150[256] = {
+ 0x57, 0xBC, 0x9D, 0x46, 0x14, 0xD0, 0x94, 0x95,
+ 0x1B, 0x12, 0xB8, 0xD4, 0x53, 0x73, 0x83, 0xE6,
+ 0x75, 0xE1, 0xD1, 0x0D, 0xDF, 0x23, 0x13, 0x40,
+ 0xF1, 0x0C, 0xA0, 0xC1, 0x22, 0xDA, 0xE8, 0xFB,
+ 0xE5, 0xC4, 0x16, 0x9C, 0x3F, 0xC3, 0x78, 0x3A,
+ 0x06, 0xC7, 0xA8, 0x79, 0xA4, 0xB3, 0x55, 0x88,
+ 0xA9, 0x82, 0xE3, 0x68, 0xFC, 0x3B, 0x26, 0x81,
+ 0xB4, 0x0A, 0x7D, 0x96, 0xDB, 0x2C, 0xE2, 0xCD,
+ 0x92, 0x5C, 0xED, 0x0E, 0x42, 0x98, 0xBE, 0xB7,
+ 0x63, 0x25, 0x7B, 0xD9, 0xEF, 0x11, 0xB9, 0xA3,
+ 0xFA, 0x00, 0x2A, 0x91, 0x71, 0xBF, 0xB2, 0x3D,
+ 0x20, 0x4C, 0xB0, 0x8C, 0x3C, 0x27, 0xAF, 0x09,
+ 0x10, 0x5D, 0x2B, 0x1D, 0xBD, 0x4B, 0x54, 0xD3,
+ 0xAB, 0x1A, 0xE7, 0xF8, 0x56, 0x65, 0xA5, 0xAD,
+ 0xEC, 0x17, 0x45, 0x28, 0xCA, 0xEA, 0x01, 0xF5,
+ 0x34, 0x84, 0x43, 0x8B, 0x03, 0x02, 0x90, 0x6B,
+ 0x60, 0xCE, 0x19, 0x86, 0x4F, 0x08, 0x35, 0x9A,
+ 0xAE, 0x07, 0xE0, 0xB6, 0xD6, 0x2D, 0xD2, 0x89,
+ 0x5F, 0xA6, 0x72, 0x05, 0x36, 0xB5, 0xC0, 0x5A,
+ 0x4D, 0xD7, 0x30, 0x37, 0x87, 0x50, 0xA2, 0x48,
+ 0x29, 0xAC, 0xDE, 0x93, 0x24, 0x6E, 0x1E, 0xF7,
+ 0x52, 0x5E, 0x41, 0xC8, 0xEB, 0x31, 0x7E, 0xE9,
+ 0x67, 0x7A, 0x47, 0x85, 0x8D, 0x74, 0x9E, 0x64,
+ 0x38, 0x9B, 0xBA, 0xCC, 0x9F, 0x8E, 0xEE, 0x0F,
+ 0xB1, 0x7C, 0x6A, 0xBB, 0x2E, 0x58, 0x70, 0x7F,
+ 0x4E, 0x4A, 0x1C, 0x5B, 0xF0, 0xA1, 0x61, 0xF6,
+ 0x15, 0x33, 0xE4, 0xF9, 0x2F, 0x62, 0x1F, 0x76,
+ 0x32, 0xCB, 0x49, 0xFE, 0x8F, 0xD5, 0xDC, 0x66,
+ 0x0B, 0x3E, 0xC5, 0x21, 0xC6, 0x6C, 0x18, 0xC2,
+ 0x6D, 0xFF, 0x51, 0x99, 0xCF, 0xFD, 0x59, 0xA7,
+ 0xAA, 0x8A, 0xF2, 0x69, 0x39, 0x6F, 0x77, 0xDD,
+ 0x97, 0xC9, 0xF3, 0x04, 0xD8, 0xF4, 0x80, 0x44 };
+
+static const unsigned char table_151[256] = {
+ 0x78, 0x6C, 0xC5, 0x0C, 0x2D, 0xA7, 0x97, 0x9C,
+ 0x22, 0x76, 0x3E, 0x81, 0x51, 0x47, 0x59, 0x71,
+ 0xB1, 0xA2, 0x4A, 0x3C, 0xB5, 0x16, 0x06, 0x95,
+ 0xB9, 0x01, 0xE6, 0x91, 0x96, 0x1C, 0x1B, 0xAD,
+ 0x61, 0x64, 0xB2, 0xE7, 0x29, 0x19, 0x52, 0x3B,
+ 0xFA, 0xAF, 0x30, 0xDB, 0xD4, 0x0B, 0xFE, 0x75,
+ 0x1F, 0xBE, 0xCB, 0xF6, 0xEA, 0x31, 0xF8, 0xD8,
+ 0xA3, 0x82, 0x73, 0x1D, 0x99, 0xF0, 0xCC, 0xB6,
+ 0x46, 0x26, 0xAA, 0x8C, 0x87, 0x90, 0x24, 0x8F,
+ 0x7A, 0x13, 0xEE, 0xD1, 0xA9, 0x05, 0xB3, 0xF7,
+ 0x02, 0x7C, 0x4C, 0x1E, 0xFF, 0xE5, 0x77, 0xAB,
+ 0xD6, 0x98, 0x20, 0x4D, 0xC4, 0x23, 0xF4, 0xA4,
+ 0x85, 0x9A, 0x8E, 0x1A, 0x0E, 0xF5, 0x15, 0x60,
+ 0x38, 0x72, 0xE9, 0xF1, 0xC3, 0x68, 0xF2, 0x93,
+ 0xD3, 0x2A, 0x48, 0x74, 0xC2, 0x57, 0xA1, 0x7D,
+ 0x94, 0x37, 0x92, 0x5C, 0xE1, 0x41, 0x83, 0xD5,
+ 0x65, 0x14, 0xA6, 0xDC, 0x44, 0x27, 0xEF, 0xD7,
+ 0x25, 0x10, 0x2C, 0x7F, 0x40, 0xA5, 0x55, 0xBD,
+ 0x2B, 0x0D, 0xD0, 0xFC, 0xDF, 0xA0, 0x04, 0x00,
+ 0x62, 0xB4, 0x5A, 0xEB, 0x6B, 0x84, 0x7E, 0x6A,
+ 0xDE, 0xED, 0x66, 0x03, 0xFB, 0x2E, 0x4F, 0x4E,
+ 0xBB, 0x36, 0x5B, 0x18, 0xE3, 0x69, 0x3F, 0xEC,
+ 0xE4, 0xD2, 0x0A, 0x34, 0x63, 0xCF, 0xA8, 0xF9,
+ 0x9B, 0x7B, 0x6F, 0xE8, 0x49, 0xC1, 0x09, 0x54,
+ 0xF3, 0x50, 0x67, 0x79, 0xC0, 0x9F, 0x8D, 0x5F,
+ 0x17, 0x70, 0x11, 0xC8, 0xBC, 0xC6, 0xE0, 0x35,
+ 0x39, 0xC7, 0x6E, 0x21, 0xBF, 0xDA, 0x6D, 0x28,
+ 0x0F, 0xDD, 0x33, 0xAC, 0x8A, 0x12, 0xC9, 0xCD,
+ 0xB8, 0x45, 0xAE, 0x32, 0xCE, 0xE2, 0x56, 0xFD,
+ 0x42, 0x89, 0x86, 0xCA, 0x4B, 0x3D, 0x5E, 0xBA,
+ 0x8B, 0x5D, 0xB0, 0xB7, 0xD9, 0x58, 0x2F, 0x08,
+ 0x43, 0x3A, 0x53, 0x9E, 0x80, 0x88, 0x07, 0x9D };
+
+static const unsigned char table_152[32] = {
+ 0x02, 0x1A, 0x17, 0x1D, 0x01, 0x03, 0x13, 0x1E,
+ 0x05, 0x18, 0x06, 0x0A, 0x0C, 0x04, 0x1B, 0x00,
+ 0x1C, 0x09, 0x1F, 0x16, 0x07, 0x0F, 0x0B, 0x0E,
+ 0x14, 0x12, 0x0D, 0x10, 0x19, 0x11, 0x08, 0x15 };
+
+static const unsigned char table_153[32] = {
+ 0x0E, 0x14, 0x12, 0x1E, 0x1C, 0x02, 0x06, 0x16,
+ 0x18, 0x0D, 0x17, 0x0C, 0x1D, 0x11, 0x08, 0x19,
+ 0x07, 0x0F, 0x13, 0x04, 0x03, 0x1B, 0x0B, 0x1F,
+ 0x1A, 0x0A, 0x05, 0x10, 0x00, 0x01, 0x15, 0x09 };
+
+static const unsigned char table_154[256] = {
+ 0x27, 0x5A, 0x08, 0x5B, 0xF4, 0x39, 0x13, 0x6F,
+ 0x67, 0xEA, 0x22, 0xCA, 0x5C, 0xCF, 0x18, 0x7C,
+ 0x05, 0x87, 0x60, 0xCC, 0x40, 0xC6, 0xE8, 0x6D,
+ 0xF5, 0x2A, 0x2D, 0xA2, 0x8C, 0x82, 0xE9, 0xDC,
+ 0xD6, 0x65, 0x74, 0x8E, 0x42, 0x4F, 0x3E, 0x55,
+ 0xFF, 0xC7, 0x9D, 0x0F, 0x81, 0xE2, 0x4C, 0xE6,
+ 0xEB, 0x4D, 0x70, 0xD1, 0x49, 0x43, 0x3D, 0x69,
+ 0x0C, 0x45, 0x28, 0x00, 0x99, 0xAE, 0xEC, 0xB8,
+ 0xC3, 0x17, 0x93, 0x8D, 0x36, 0x3C, 0x46, 0x2B,
+ 0x29, 0xC5, 0xB4, 0xB1, 0xD0, 0x0D, 0xAD, 0xFE,
+ 0xE5, 0xA8, 0x3B, 0x1A, 0x2C, 0xDF, 0x07, 0x86,
+ 0xB0, 0xD3, 0x7A, 0x59, 0x79, 0x8B, 0xC1, 0x9A,
+ 0x30, 0xDB, 0x24, 0xF3, 0xD8, 0x04, 0x25, 0xC2,
+ 0xA3, 0x98, 0x96, 0x7B, 0x71, 0x4E, 0x5E, 0x58,
+ 0xA5, 0x51, 0x88, 0xDA, 0xF8, 0xC0, 0x7D, 0xF6,
+ 0x31, 0x5F, 0x09, 0x16, 0x21, 0x62, 0x01, 0x64,
+ 0x9B, 0x3A, 0x2F, 0x61, 0x19, 0xA1, 0xB7, 0xE0,
+ 0xB9, 0x12, 0xA0, 0xBA, 0x6E, 0x8A, 0xFB, 0xD9,
+ 0x38, 0x1B, 0xD5, 0xB3, 0x10, 0xED, 0xE4, 0x6A,
+ 0x32, 0xBD, 0x75, 0xD4, 0x1C, 0xFD, 0x73, 0x77,
+ 0x54, 0xC8, 0x97, 0x47, 0x35, 0x94, 0xE3, 0xCD,
+ 0x6B, 0xBB, 0xF9, 0xAC, 0x11, 0x14, 0xAF, 0x78,
+ 0x3F, 0xCE, 0x26, 0x44, 0xEE, 0xFC, 0x15, 0x66,
+ 0x4B, 0xA6, 0x20, 0x23, 0xBE, 0x84, 0x1D, 0x7E,
+ 0x0B, 0x56, 0x92, 0x0A, 0xFA, 0xF7, 0x48, 0x33,
+ 0x9E, 0x8F, 0xAB, 0x5D, 0x41, 0x50, 0xA4, 0x7F,
+ 0x80, 0x4A, 0x68, 0x06, 0x2E, 0x6C, 0xC4, 0x02,
+ 0x0E, 0x63, 0xF0, 0xC9, 0x91, 0xB2, 0xD2, 0x03,
+ 0x37, 0xEF, 0x9C, 0x90, 0x83, 0x76, 0x1E, 0xA9,
+ 0x85, 0xB6, 0x57, 0xD7, 0xF2, 0xF1, 0xE7, 0xDE,
+ 0xCB, 0xAA, 0xBF, 0x89, 0x1F, 0xA7, 0xBC, 0x9F,
+ 0x53, 0xE1, 0xDD, 0x72, 0x95, 0x52, 0x34, 0xB5 };
+
+static const unsigned char table_155[256] = {
+ 0x75, 0x58, 0xC5, 0xA5, 0x83, 0x16, 0xF3, 0x7F,
+ 0x94, 0xDE, 0xA0, 0xF6, 0xFD, 0x89, 0xA8, 0x06,
+ 0x98, 0x01, 0xD9, 0x69, 0xB7, 0x0F, 0xEA, 0x73,
+ 0x32, 0xF0, 0x49, 0xBF, 0x02, 0xE7, 0x22, 0x3F,
+ 0xDB, 0x30, 0x5F, 0x20, 0x6A, 0x93, 0x07, 0xBC,
+ 0x09, 0x0D, 0x37, 0x24, 0x90, 0x15, 0x80, 0xAF,
+ 0x8F, 0x59, 0x28, 0xFF, 0x6D, 0x1E, 0x52, 0x62,
+ 0xE2, 0xDD, 0x85, 0x48, 0xB5, 0xAB, 0x68, 0xAC,
+ 0x7E, 0x26, 0x2C, 0xF9, 0x2A, 0xBE, 0x5B, 0xCE,
+ 0x87, 0x1D, 0x96, 0xBD, 0xEF, 0x29, 0xA9, 0xC3,
+ 0x9D, 0x57, 0x79, 0x6B, 0x7A, 0x82, 0x78, 0x0A,
+ 0x91, 0xF2, 0x7C, 0xC2, 0x25, 0x88, 0xE3, 0x47,
+ 0x64, 0x46, 0x8D, 0x19, 0xF4, 0xE6, 0xF1, 0x53,
+ 0x9C, 0x54, 0x23, 0xAD, 0xA3, 0x86, 0x3A, 0x04,
+ 0x67, 0x1C, 0xF5, 0x43, 0x05, 0x42, 0xD6, 0x4B,
+ 0xFB, 0xD4, 0x2B, 0x08, 0x45, 0xD8, 0xCD, 0xEB,
+ 0x31, 0x4A, 0x5A, 0x34, 0x9B, 0xEC, 0x4D, 0xB4,
+ 0xC6, 0xFE, 0xD5, 0x5E, 0xC1, 0x39, 0x81, 0xCF,
+ 0x03, 0x6E, 0x95, 0x50, 0xA1, 0x3B, 0xB3, 0xE5,
+ 0x3D, 0xB1, 0xB2, 0x41, 0x17, 0x2F, 0x2E, 0xE4,
+ 0x1F, 0xDC, 0xB0, 0xB6, 0x18, 0x6F, 0x44, 0x12,
+ 0x0B, 0xCC, 0x4E, 0xC0, 0x51, 0x14, 0x76, 0x3C,
+ 0xB9, 0x9F, 0xA4, 0xD3, 0xA7, 0xE8, 0x13, 0x55,
+ 0xC8, 0x8C, 0xD2, 0xEE, 0x65, 0xB8, 0xAA, 0x6C,
+ 0x2D, 0x4F, 0x56, 0xFA, 0x61, 0x4C, 0xE0, 0x5C,
+ 0xA6, 0x1A, 0xD1, 0x38, 0xD7, 0x72, 0x60, 0x74,
+ 0xE1, 0xBA, 0x84, 0x3E, 0x40, 0xF8, 0xC7, 0x36,
+ 0x27, 0x0C, 0x70, 0x97, 0x9A, 0x7D, 0x35, 0x71,
+ 0xCA, 0x1B, 0x99, 0x8E, 0xAE, 0x66, 0x63, 0xE9,
+ 0xC9, 0x11, 0x8A, 0x21, 0x92, 0x5D, 0x77, 0x10,
+ 0xD0, 0xC4, 0xF7, 0x7B, 0x9E, 0xCB, 0xED, 0x0E,
+ 0x8B, 0x33, 0xFC, 0xBB, 0x00, 0xA2, 0xDF, 0xDA };
+
+static const unsigned char table_156[256] = {
+ 0x31, 0x25, 0xB1, 0xD3, 0xAF, 0xAE, 0x84, 0x2C,
+ 0x71, 0x5E, 0xD8, 0x80, 0x6F, 0x3E, 0x48, 0x86,
+ 0xED, 0x54, 0x6A, 0xC3, 0xBC, 0xBF, 0x0E, 0xEA,
+ 0x10, 0xA2, 0x9D, 0x91, 0x32, 0xE2, 0x7E, 0x1B,
+ 0x49, 0x27, 0xFF, 0xDD, 0x8A, 0x2F, 0x8D, 0x38,
+ 0xFA, 0x3C, 0x03, 0x14, 0x0F, 0x89, 0xCC, 0x07,
+ 0x1A, 0xA0, 0x97, 0x37, 0xA6, 0xD6, 0x63, 0x87,
+ 0xA1, 0xC2, 0x4B, 0x39, 0xCB, 0xCF, 0x69, 0x4E,
+ 0xC9, 0x28, 0x1C, 0xBB, 0x42, 0x2B, 0xA9, 0x78,
+ 0x5B, 0xF6, 0xE0, 0xD0, 0x5F, 0x46, 0x98, 0xCE,
+ 0x1F, 0x7A, 0x34, 0x8B, 0xFD, 0x9B, 0xEF, 0x74,
+ 0x05, 0xF2, 0x02, 0xC6, 0xDF, 0x73, 0x5C, 0x8E,
+ 0xDE, 0x88, 0x57, 0x3B, 0x85, 0xBD, 0xC0, 0x3A,
+ 0x45, 0x4D, 0x2D, 0x72, 0x0C, 0x60, 0xCA, 0x5D,
+ 0x06, 0x04, 0x3D, 0x51, 0x15, 0xAD, 0xE8, 0x67,
+ 0xBA, 0x43, 0x7D, 0xF8, 0xB2, 0xE6, 0xAB, 0xF4,
+ 0x23, 0x6E, 0xF0, 0x6B, 0x0B, 0x2E, 0xC8, 0xC4,
+ 0x4F, 0xA8, 0x6D, 0x26, 0xE9, 0x9C, 0x22, 0xB7,
+ 0x00, 0xB3, 0x0A, 0x7C, 0x44, 0x55, 0x75, 0xD5,
+ 0xAA, 0x66, 0x56, 0x24, 0x83, 0x90, 0xA4, 0xF5,
+ 0xCD, 0xEC, 0x18, 0xDC, 0xFE, 0x96, 0xA3, 0xF7,
+ 0xD2, 0xFB, 0xD1, 0x65, 0xC5, 0x08, 0x7B, 0x70,
+ 0x16, 0x9A, 0x20, 0x09, 0x29, 0xDA, 0x52, 0x5A,
+ 0x59, 0xB4, 0x77, 0x62, 0x9E, 0x19, 0x7F, 0x82,
+ 0x4C, 0xB6, 0x0D, 0x58, 0xEE, 0x1D, 0xB9, 0x93,
+ 0x50, 0xD9, 0x30, 0xE4, 0x13, 0x01, 0x36, 0x8F,
+ 0x53, 0x3F, 0x64, 0xA5, 0xB5, 0xD7, 0x81, 0x41,
+ 0x17, 0xE5, 0x94, 0xE3, 0xF9, 0x61, 0x76, 0xE1,
+ 0x9F, 0xFC, 0x1E, 0x12, 0xDB, 0x21, 0x79, 0x2A,
+ 0xAC, 0xF3, 0x6C, 0xC1, 0x95, 0x92, 0xEB, 0xA7,
+ 0x11, 0xC7, 0xB8, 0x4A, 0x33, 0xB0, 0x99, 0xE7,
+ 0xF1, 0x68, 0xBE, 0x35, 0x40, 0x8C, 0xD4, 0x47 };
+
+static const unsigned char table_157[32] = {
+ 0x00, 0x0D, 0x03, 0x02, 0x11, 0x04, 0x18, 0x0B,
+ 0x14, 0x1D, 0x1C, 0x13, 0x1B, 0x17, 0x10, 0x15,
+ 0x01, 0x19, 0x07, 0x09, 0x1A, 0x16, 0x12, 0x1E,
+ 0x08, 0x06, 0x0C, 0x0E, 0x1F, 0x0F, 0x0A, 0x05 };
+
+static const unsigned char table_158[256] = {
+ 0x68, 0x26, 0x80, 0x0B, 0xB8, 0xD5, 0x8C, 0xB7,
+ 0x65, 0xEF, 0xBC, 0x94, 0x28, 0xB9, 0xB2, 0xD2,
+ 0x92, 0xA4, 0x55, 0x27, 0xE0, 0x40, 0x6C, 0x41,
+ 0x25, 0xBD, 0xAF, 0xEA, 0xB1, 0x19, 0xA5, 0xC9,
+ 0x0E, 0xED, 0xB4, 0xF9, 0x8B, 0x6A, 0xAE, 0xD8,
+ 0x64, 0x83, 0xC1, 0xD3, 0x04, 0xF4, 0xFA, 0xC3,
+ 0x46, 0x2C, 0xA8, 0xBB, 0x3A, 0x47, 0x33, 0x8F,
+ 0x52, 0x86, 0x08, 0x9D, 0x1D, 0x59, 0x8E, 0x91,
+ 0x32, 0xCF, 0x6B, 0x75, 0xB0, 0x7F, 0xC7, 0x24,
+ 0x05, 0x6F, 0x00, 0x1C, 0x2D, 0xAC, 0xDA, 0x45,
+ 0x73, 0xB3, 0x3E, 0xD6, 0x54, 0x61, 0x03, 0x77,
+ 0xF8, 0xD9, 0xE2, 0x4B, 0xFF, 0xF2, 0x0C, 0x4F,
+ 0x93, 0x71, 0xA7, 0x3D, 0x66, 0x88, 0x98, 0xF1,
+ 0xB6, 0x7A, 0x2B, 0xCD, 0x44, 0x3C, 0x37, 0x5A,
+ 0x96, 0x23, 0x9F, 0xBF, 0x7D, 0x5E, 0x2A, 0x35,
+ 0x72, 0x79, 0xE1, 0xA3, 0x84, 0x99, 0x38, 0x49,
+ 0xC8, 0xDB, 0x30, 0xDC, 0xAD, 0x3F, 0xF6, 0x09,
+ 0x69, 0x95, 0xE5, 0x67, 0xA1, 0xFD, 0xF7, 0x1B,
+ 0xEC, 0x17, 0xD4, 0xEB, 0x29, 0x36, 0x3B, 0x15,
+ 0xDE, 0x2E, 0xC5, 0x70, 0x6D, 0x53, 0x56, 0xAB,
+ 0xC0, 0x43, 0xC2, 0xE7, 0x31, 0xE6, 0xA6, 0x78,
+ 0x5C, 0x7C, 0x48, 0x10, 0x87, 0xCC, 0x9E, 0x7E,
+ 0x5F, 0xE9, 0x07, 0x5B, 0xF5, 0xEE, 0xB5, 0xCA,
+ 0x62, 0x18, 0xBE, 0x20, 0x16, 0xDF, 0x13, 0x4E,
+ 0x7B, 0x02, 0x11, 0x4C, 0x51, 0x85, 0x0D, 0x22,
+ 0xF3, 0x14, 0x63, 0x76, 0xD0, 0x0F, 0xE4, 0xCB,
+ 0xCE, 0xA0, 0x82, 0xE3, 0x01, 0xAA, 0x5D, 0x4A,
+ 0x4D, 0xFB, 0x39, 0x8A, 0x2F, 0xDD, 0xE8, 0x06,
+ 0x1A, 0x90, 0x81, 0x50, 0x8D, 0x89, 0x97, 0x1E,
+ 0xFC, 0x60, 0x12, 0x42, 0x9C, 0xF0, 0x34, 0xD7,
+ 0xD1, 0x1F, 0x0A, 0x21, 0xA9, 0x6E, 0xC4, 0xBA,
+ 0x9A, 0x57, 0xA2, 0x74, 0xC6, 0xFE, 0x9B, 0x58 };
+
+static const unsigned char table_159[256] = {
+ 0xE5, 0xBF, 0x84, 0x56, 0xD6, 0x43, 0x3E, 0xA5,
+ 0x64, 0x87, 0x44, 0x63, 0x4A, 0x4C, 0x8D, 0x24,
+ 0x1C, 0xDA, 0x89, 0x52, 0x80, 0x4F, 0xE4, 0xBC,
+ 0xC5, 0xF4, 0x27, 0x75, 0x9C, 0xF0, 0xE1, 0x06,
+ 0x99, 0x48, 0xF2, 0x57, 0x34, 0x9A, 0xA8, 0x62,
+ 0xC9, 0xD5, 0x16, 0x6D, 0x55, 0xFA, 0x37, 0x5A,
+ 0x2A, 0xC6, 0x45, 0xDD, 0x1B, 0x76, 0x50, 0xE2,
+ 0x69, 0x41, 0x6C, 0xC4, 0x3C, 0x47, 0xA9, 0x92,
+ 0x00, 0x3D, 0x6F, 0xE7, 0x7A, 0x3A, 0x33, 0x53,
+ 0xF7, 0x03, 0xA7, 0xB1, 0x15, 0x78, 0x0B, 0x67,
+ 0x2E, 0x21, 0xF1, 0xD4, 0xB3, 0x98, 0x60, 0x58,
+ 0xBB, 0x82, 0x1E, 0x70, 0x0A, 0xA2, 0x02, 0x17,
+ 0xFF, 0x9F, 0xD2, 0xAF, 0xC7, 0xDC, 0x68, 0x83,
+ 0x42, 0xCA, 0x08, 0x39, 0x20, 0xEC, 0x77, 0x96,
+ 0x5B, 0xAD, 0x09, 0x6B, 0x40, 0xC2, 0x91, 0x51,
+ 0x10, 0xD9, 0xF9, 0xC1, 0xB5, 0xDF, 0xDB, 0xC0,
+ 0x7D, 0xAB, 0xAE, 0x54, 0x35, 0xF3, 0xA1, 0xE6,
+ 0xEA, 0x14, 0xBA, 0xFC, 0xE8, 0xEB, 0xF6, 0xBD,
+ 0x8C, 0x72, 0x1F, 0xE9, 0xFB, 0x7C, 0xCF, 0x49,
+ 0xE3, 0xA3, 0x22, 0x9D, 0x46, 0x71, 0x94, 0x31,
+ 0x2D, 0x65, 0x2B, 0x32, 0x18, 0xB6, 0x90, 0xF8,
+ 0x11, 0x5F, 0xA0, 0xEF, 0xED, 0x1A, 0x25, 0x2C,
+ 0x3B, 0xFD, 0x2F, 0x73, 0xB9, 0x7E, 0xDE, 0xB4,
+ 0x97, 0x0F, 0x7F, 0x86, 0x93, 0x07, 0x19, 0xCE,
+ 0xE0, 0xB7, 0xEE, 0x26, 0xD1, 0x01, 0x59, 0x5C,
+ 0xC3, 0x79, 0x8B, 0xD3, 0x4B, 0x04, 0xD0, 0x29,
+ 0x0D, 0x3F, 0xB2, 0x30, 0xCC, 0x36, 0xFE, 0xB0,
+ 0xF5, 0x8E, 0xA6, 0x8A, 0xC8, 0xD8, 0x05, 0xB8,
+ 0x12, 0xBE, 0x81, 0x4D, 0x38, 0xAC, 0x1D, 0x9E,
+ 0x66, 0x5E, 0x7B, 0x6E, 0x0C, 0xCD, 0x6A, 0x88,
+ 0xAA, 0x0E, 0x61, 0x5D, 0x95, 0x4E, 0xD7, 0x74,
+ 0xCB, 0x9B, 0x13, 0x8F, 0xA4, 0x28, 0x23, 0x85 };
+
+static const unsigned char table_160[256] = {
+ 0x35, 0x44, 0x0E, 0x92, 0x75, 0x83, 0x9D, 0x53,
+ 0xA5, 0x90, 0xF8, 0xF7, 0x54, 0x74, 0xDF, 0x3D,
+ 0x5A, 0xAA, 0xC6, 0x26, 0x7A, 0xFC, 0x79, 0x6C,
+ 0x56, 0xB3, 0x32, 0xE3, 0x1C, 0xF9, 0xDC, 0xE6,
+ 0xA2, 0x93, 0x71, 0xFF, 0x1D, 0xEB, 0xB2, 0x04,
+ 0x96, 0x46, 0x0C, 0x2B, 0x17, 0xEE, 0x28, 0x25,
+ 0xD9, 0xAE, 0x11, 0xA7, 0x40, 0x45, 0xFB, 0x80,
+ 0x18, 0xF1, 0xCB, 0x2E, 0x24, 0xF3, 0xEC, 0x4F,
+ 0xAB, 0xD7, 0xD4, 0xC4, 0xFD, 0x4B, 0xAD, 0xC9,
+ 0x4C, 0x08, 0xAC, 0xF4, 0xCD, 0xB7, 0xF2, 0x15,
+ 0x02, 0x2F, 0x16, 0x34, 0x65, 0x8A, 0x87, 0xCC,
+ 0x50, 0x0F, 0x9B, 0xC2, 0xC8, 0x7B, 0xEA, 0x8E,
+ 0xE4, 0xD6, 0x97, 0x30, 0xA8, 0xA0, 0x94, 0xC5,
+ 0xE8, 0x12, 0x27, 0xCE, 0x84, 0xDD, 0xB1, 0x47,
+ 0x7E, 0xE7, 0xE1, 0x3A, 0x37, 0x21, 0x2D, 0x3B,
+ 0x20, 0x60, 0x1E, 0x1B, 0x82, 0xBE, 0xA3, 0x70,
+ 0x98, 0xBF, 0xA6, 0x4D, 0x76, 0x86, 0x42, 0x9F,
+ 0xCF, 0xE0, 0x14, 0x4A, 0x0B, 0xB4, 0x36, 0xF5,
+ 0x85, 0xB8, 0xC0, 0x6A, 0xE9, 0x7D, 0xBD, 0x4E,
+ 0x8F, 0x51, 0x0D, 0x5B, 0x6B, 0x58, 0x5F, 0x03,
+ 0x6F, 0xBC, 0x5D, 0x1F, 0x7F, 0xDB, 0x00, 0xC1,
+ 0x13, 0xF0, 0xD1, 0xFA, 0xDA, 0x05, 0x39, 0xD3,
+ 0x38, 0xD2, 0x89, 0xE2, 0x88, 0x5E, 0x5C, 0x6D,
+ 0xCA, 0xB0, 0x01, 0x63, 0x8B, 0x59, 0xA4, 0xD0,
+ 0x78, 0x19, 0xB5, 0x62, 0x1A, 0x69, 0x8D, 0x9C,
+ 0x22, 0x3F, 0x9E, 0x33, 0x72, 0x2A, 0x41, 0x29,
+ 0xFE, 0xF6, 0x64, 0x7C, 0x66, 0xB6, 0xAF, 0x23,
+ 0x8C, 0x68, 0x6E, 0x49, 0x07, 0x99, 0x77, 0x3E,
+ 0x9A, 0x73, 0xD8, 0x55, 0x0A, 0x3C, 0xBA, 0xA9,
+ 0x52, 0xED, 0x91, 0x09, 0x95, 0xC7, 0x43, 0xD5,
+ 0x57, 0x61, 0x81, 0xEF, 0x06, 0xDE, 0x48, 0x31,
+ 0xBB, 0x2C, 0xE5, 0xC3, 0x67, 0xA1, 0x10, 0xB9 };
+
+static const unsigned char table_161[256] = {
+ 0x8F, 0x1A, 0x81, 0xA2, 0x2C, 0x56, 0x6D, 0xCD,
+ 0x4A, 0x33, 0x50, 0xE9, 0xE0, 0x12, 0x5A, 0x43,
+ 0x2D, 0x4F, 0xEA, 0x95, 0xFD, 0x49, 0xAB, 0xA3,
+ 0x79, 0x42, 0x0B, 0xB8, 0x89, 0x40, 0x71, 0x14,
+ 0x80, 0x55, 0xAF, 0xCF, 0x3E, 0x64, 0x8B, 0x74,
+ 0xBF, 0x9C, 0x24, 0x97, 0xD1, 0xBA, 0x48, 0xD2,
+ 0x08, 0x1F, 0xDD, 0xA7, 0xDC, 0x92, 0x30, 0x75,
+ 0x31, 0x37, 0x67, 0x06, 0x68, 0x72, 0x6F, 0x05,
+ 0x8A, 0x7C, 0x4C, 0x3C, 0x19, 0x28, 0x86, 0x3D,
+ 0x93, 0xDA, 0xF4, 0xC7, 0x17, 0x85, 0xAC, 0x02,
+ 0x78, 0x04, 0xAD, 0x03, 0x8D, 0x11, 0xC5, 0x9D,
+ 0x3A, 0x73, 0x82, 0x59, 0x51, 0x9F, 0x27, 0x47,
+ 0xE7, 0xED, 0x1E, 0xFF, 0x34, 0x01, 0x5B, 0x4B,
+ 0xCA, 0x6C, 0x69, 0xBB, 0x3B, 0xC4, 0x5F, 0xDF,
+ 0x09, 0x6B, 0x7D, 0xC9, 0x88, 0x45, 0x57, 0xD3,
+ 0x2A, 0x4E, 0xF1, 0xC2, 0xA9, 0xB6, 0x18, 0xD4,
+ 0xA0, 0x1C, 0x4D, 0x0E, 0xE5, 0xE1, 0xD7, 0xB2,
+ 0x0C, 0x3F, 0x00, 0x61, 0x16, 0x0D, 0x32, 0x62,
+ 0x58, 0x63, 0xEE, 0xEF, 0x2F, 0x5D, 0xB0, 0x20,
+ 0x7A, 0x10, 0xE6, 0xA1, 0xF9, 0xD8, 0x6E, 0xCB,
+ 0xF0, 0x9B, 0x84, 0x8E, 0xF2, 0xFE, 0xC8, 0x7F,
+ 0xBD, 0xF8, 0x07, 0xC6, 0x39, 0xBC, 0xCC, 0x22,
+ 0x54, 0x15, 0x9A, 0xA4, 0xC1, 0x2B, 0x1B, 0x25,
+ 0xDE, 0x6A, 0xDB, 0x90, 0xEB, 0xB7, 0xD0, 0x44,
+ 0xA6, 0xB9, 0xB1, 0x23, 0x9E, 0x65, 0x83, 0xFA,
+ 0x96, 0xB5, 0x0F, 0xF6, 0xD6, 0xE8, 0x53, 0x13,
+ 0x76, 0xD5, 0x35, 0x87, 0xE3, 0x38, 0xF5, 0xAE,
+ 0xB3, 0xCE, 0xE2, 0x70, 0xD9, 0x66, 0x5C, 0x26,
+ 0xC3, 0xFC, 0xF7, 0x94, 0xF3, 0xEC, 0xFB, 0x99,
+ 0x91, 0x77, 0xB4, 0x46, 0xA5, 0x98, 0x7B, 0x1D,
+ 0x52, 0x2E, 0xA8, 0x60, 0x5E, 0x29, 0x21, 0x7E,
+ 0xBE, 0x0A, 0x36, 0x41, 0xC0, 0x8C, 0xE4, 0xAA };
+
+static const unsigned char table_162[256] = {
+ 0xF7, 0x1B, 0xC0, 0x31, 0x5A, 0x23, 0xEA, 0xE9,
+ 0xFB, 0x14, 0x6A, 0xE8, 0x04, 0x65, 0x5B, 0x2C,
+ 0x41, 0xD9, 0xEB, 0xE4, 0x8D, 0x1D, 0xCA, 0x8F,
+ 0x5E, 0x43, 0xAF, 0x46, 0x0A, 0x01, 0x0C, 0xB4,
+ 0x95, 0x52, 0x92, 0xE0, 0x10, 0x57, 0x0F, 0x71,
+ 0xB1, 0x26, 0xD8, 0x05, 0x69, 0x3C, 0x54, 0xDF,
+ 0xFF, 0x9D, 0x51, 0xA0, 0xA1, 0x0B, 0xC1, 0x20,
+ 0x6D, 0xFA, 0x47, 0x15, 0x09, 0xD3, 0xE1, 0xA9,
+ 0x66, 0x12, 0x5C, 0x49, 0x1E, 0x3B, 0xD0, 0x8B,
+ 0x62, 0xBD, 0x06, 0xE5, 0x00, 0x98, 0x4E, 0x32,
+ 0xB0, 0x2D, 0x2A, 0x7F, 0x03, 0xD5, 0x99, 0x7E,
+ 0xAB, 0x22, 0xC6, 0xC3, 0x2F, 0x4C, 0x33, 0x45,
+ 0xE3, 0x3F, 0xF9, 0xB2, 0xFE, 0x36, 0xE7, 0xF8,
+ 0x55, 0x0D, 0x56, 0x1F, 0x4B, 0xE6, 0x50, 0x81,
+ 0xCE, 0x80, 0xCD, 0x67, 0x6B, 0xCF, 0x2E, 0x9B,
+ 0xBC, 0xBE, 0x11, 0x75, 0x4D, 0xAC, 0x59, 0x40,
+ 0x85, 0x0E, 0xC9, 0x17, 0xA3, 0x60, 0xED, 0x16,
+ 0xA4, 0xDD, 0xEE, 0x96, 0x77, 0x83, 0x34, 0xD2,
+ 0xCB, 0xFC, 0x6C, 0x08, 0xEC, 0x35, 0xF2, 0x6F,
+ 0x3A, 0x7B, 0x21, 0x4A, 0x70, 0xEF, 0xAD, 0xDE,
+ 0x90, 0x9E, 0x7D, 0x64, 0x2B, 0x79, 0xF5, 0xF3,
+ 0x13, 0x1C, 0x7A, 0x07, 0x4F, 0x78, 0x89, 0xB6,
+ 0x97, 0xF1, 0xD7, 0x7C, 0x48, 0xAE, 0x39, 0xA8,
+ 0xA6, 0x86, 0x3E, 0x27, 0x87, 0x73, 0x82, 0x24,
+ 0x30, 0x74, 0x5F, 0xD1, 0x9F, 0x9C, 0x1A, 0x8C,
+ 0x42, 0x6E, 0x28, 0xB9, 0xF0, 0xC4, 0x68, 0x25,
+ 0xC5, 0xDC, 0xB8, 0x29, 0xD6, 0x84, 0x3D, 0xBB,
+ 0x88, 0x76, 0xFD, 0x61, 0x94, 0x91, 0xDA, 0xB7,
+ 0x72, 0xBA, 0xC2, 0xDB, 0xB5, 0xA5, 0xE2, 0x18,
+ 0xF6, 0xAA, 0x8A, 0x19, 0x63, 0x9A, 0xA7, 0xC8,
+ 0xD4, 0x02, 0x8E, 0x37, 0xF4, 0xB3, 0xA2, 0x53,
+ 0x38, 0xCC, 0x58, 0x44, 0xBF, 0x93, 0x5D, 0xC7 };
+
+static const unsigned char table_163[32] = {
+ 0x1B, 0x14, 0x12, 0x15, 0x11, 0x1D, 0x17, 0x19,
+ 0x10, 0x09, 0x08, 0x06, 0x1A, 0x16, 0x07, 0x13,
+ 0x1F, 0x0B, 0x1C, 0x05, 0x0E, 0x00, 0x18, 0x0A,
+ 0x04, 0x01, 0x03, 0x0C, 0x0D, 0x1E, 0x02, 0x0F };
+
+static const unsigned char table_164[32] = {
+ 0x15, 0x00, 0x10, 0x0B, 0x1D, 0x0A, 0x06, 0x1C,
+ 0x0D, 0x1F, 0x17, 0x0F, 0x03, 0x14, 0x13, 0x12,
+ 0x1B, 0x18, 0x08, 0x1E, 0x16, 0x09, 0x1A, 0x04,
+ 0x02, 0x0C, 0x0E, 0x01, 0x07, 0x19, 0x11, 0x05 };
+
+static const unsigned char table_165[256] = {
+ 0x98, 0xF5, 0x1D, 0xFB, 0x13, 0x20, 0x41, 0xA3,
+ 0xE3, 0x76, 0x49, 0x7E, 0x60, 0xD8, 0x68, 0x30,
+ 0x88, 0x45, 0xD5, 0x77, 0x00, 0xC3, 0x09, 0x31,
+ 0x44, 0x18, 0xD4, 0x14, 0xC8, 0x1B, 0x8B, 0x38,
+ 0x08, 0x52, 0xD1, 0xF3, 0x69, 0x9F, 0xDA, 0x61,
+ 0x16, 0x1C, 0xE4, 0x7D, 0xEE, 0xD9, 0x5E, 0x4C,
+ 0xA7, 0xAA, 0xA6, 0xF6, 0xCF, 0xA0, 0xBA, 0x10,
+ 0xE2, 0xDE, 0x0F, 0xEA, 0xBC, 0x32, 0x63, 0xC0,
+ 0x54, 0xC5, 0xBE, 0x71, 0x80, 0x56, 0x5C, 0xA4,
+ 0xAD, 0x15, 0x9D, 0x11, 0x43, 0x67, 0x95, 0xAE,
+ 0xC6, 0xC4, 0x91, 0x9C, 0xE5, 0x37, 0xE1, 0x7A,
+ 0xDB, 0xEF, 0x03, 0x65, 0x86, 0x66, 0x2A, 0xB5,
+ 0xBF, 0xB4, 0x0D, 0xB3, 0xD7, 0x2D, 0x01, 0xEB,
+ 0x8C, 0xF2, 0x5A, 0x2E, 0x64, 0x25, 0x02, 0xCB,
+ 0x4A, 0xB0, 0xCE, 0x35, 0xA8, 0x47, 0x85, 0x33,
+ 0x34, 0x24, 0x23, 0x7B, 0xB6, 0x48, 0x83, 0x40,
+ 0x87, 0x57, 0x3C, 0xD6, 0xCD, 0x2C, 0x6D, 0xE7,
+ 0xBB, 0xED, 0x81, 0x5D, 0x55, 0x46, 0xDD, 0xD3,
+ 0x70, 0xBD, 0xB8, 0x75, 0x53, 0x6E, 0xD0, 0x99,
+ 0xCA, 0x58, 0xC7, 0x4B, 0x3D, 0xA5, 0x50, 0x7C,
+ 0x93, 0x51, 0xB7, 0xFD, 0x05, 0x3A, 0xE8, 0x8F,
+ 0x28, 0x74, 0x39, 0xF0, 0x7F, 0x4F, 0x06, 0x36,
+ 0xB2, 0x19, 0x2F, 0x1F, 0x8D, 0x0C, 0xB9, 0xFC,
+ 0x89, 0x21, 0x12, 0xF7, 0x3F, 0x94, 0x6F, 0xDC,
+ 0x3E, 0x4E, 0x3B, 0xC9, 0x07, 0x9B, 0x17, 0x9A,
+ 0x73, 0x6A, 0x5B, 0xA1, 0x1E, 0x8A, 0x04, 0x72,
+ 0x6C, 0xA2, 0xEC, 0x96, 0xFE, 0xF8, 0x84, 0xC1,
+ 0x79, 0x0E, 0x62, 0x90, 0x8E, 0xF4, 0x42, 0x29,
+ 0x92, 0x9E, 0xAC, 0x82, 0x4D, 0xAF, 0x2B, 0x6B,
+ 0xA9, 0xFF, 0x0A, 0xAB, 0x22, 0x5F, 0xDF, 0xD2,
+ 0x0B, 0x78, 0xF1, 0xE6, 0x59, 0x27, 0xC2, 0xE0,
+ 0x1A, 0x26, 0xCC, 0xB1, 0xF9, 0xFA, 0x97, 0xE9 };
+
+static const unsigned char table_166[256] = {
+ 0xCB, 0xEA, 0x2A, 0x36, 0x6D, 0x93, 0x4E, 0xD5,
+ 0xBC, 0x6A, 0xD4, 0x68, 0xF7, 0x18, 0xAB, 0x8B,
+ 0x66, 0x95, 0x94, 0x64, 0xB7, 0x00, 0x4D, 0x97,
+ 0x38, 0xB3, 0xFC, 0xE1, 0xBB, 0x63, 0xF3, 0x1F,
+ 0x6B, 0x2C, 0x2F, 0x5E, 0xA4, 0x7E, 0xFB, 0xF4,
+ 0xA8, 0x8A, 0x65, 0x53, 0x90, 0x58, 0x40, 0x60,
+ 0x28, 0x8E, 0x35, 0x49, 0xED, 0xBD, 0x1B, 0x0B,
+ 0xBA, 0xB8, 0x61, 0x50, 0xE9, 0x39, 0xEF, 0xC3,
+ 0x74, 0xB6, 0x46, 0x8D, 0xD9, 0x32, 0x92, 0x9A,
+ 0x30, 0x01, 0xF2, 0x41, 0xB9, 0xE7, 0x3A, 0xB0,
+ 0x80, 0x15, 0xDE, 0x7D, 0x7F, 0x09, 0xC2, 0x76,
+ 0xF8, 0x12, 0x59, 0xDD, 0x1D, 0xE6, 0x75, 0xBE,
+ 0xA3, 0x04, 0xCA, 0x78, 0x7B, 0xAC, 0xD8, 0x70,
+ 0xD3, 0xC1, 0x25, 0x6F, 0x03, 0x6C, 0x14, 0x45,
+ 0xE5, 0x2B, 0x87, 0x83, 0xAA, 0x77, 0x5F, 0x4A,
+ 0x9C, 0x27, 0x0C, 0x10, 0xAE, 0x56, 0x85, 0x0D,
+ 0xE3, 0xFA, 0x71, 0xEE, 0x9F, 0x21, 0xC0, 0xCD,
+ 0xFD, 0xDC, 0x5B, 0x11, 0x02, 0x0F, 0x96, 0x3D,
+ 0x3C, 0x26, 0xEB, 0x08, 0x7A, 0x82, 0xA7, 0x19,
+ 0xD7, 0xC5, 0xF6, 0x52, 0x57, 0x88, 0xFF, 0x47,
+ 0x8F, 0xC6, 0x33, 0xB5, 0x2E, 0x8C, 0x81, 0x91,
+ 0x44, 0xA6, 0x17, 0xF0, 0x4B, 0x9D, 0x34, 0x73,
+ 0x72, 0x67, 0xD2, 0x0E, 0xA0, 0x99, 0xA5, 0xAF,
+ 0xFE, 0x9E, 0x6E, 0xDA, 0x3B, 0xE2, 0x23, 0xD6,
+ 0xD0, 0x13, 0x89, 0x5A, 0x42, 0x98, 0x5C, 0xD1,
+ 0x86, 0x24, 0xDF, 0x37, 0xF9, 0xCC, 0xF5, 0xA9,
+ 0x2D, 0xBF, 0x5D, 0xF1, 0x69, 0xE8, 0xA2, 0x06,
+ 0x48, 0xC7, 0xDB, 0x29, 0xE4, 0xAD, 0x3E, 0xA1,
+ 0xC9, 0x4C, 0x1A, 0xCE, 0x62, 0x4F, 0x7C, 0xC8,
+ 0x05, 0xC4, 0xB1, 0x1E, 0x79, 0x55, 0x84, 0xB2,
+ 0x20, 0x31, 0x9B, 0xEC, 0xB4, 0xCF, 0x54, 0x22,
+ 0x1C, 0xE0, 0x51, 0x16, 0x43, 0x07, 0x0A, 0x3F };
+
+static const unsigned char table_167[256] = {
+ 0x91, 0xEA, 0x4F, 0x6A, 0x6E, 0x2D, 0x27, 0x22,
+ 0x44, 0xA5, 0x6D, 0xE3, 0x45, 0x06, 0xE2, 0x87,
+ 0x9A, 0xC9, 0x2C, 0x4A, 0x93, 0x6F, 0x00, 0xEB,
+ 0x7C, 0x7F, 0xA2, 0xFE, 0x40, 0x3C, 0x3F, 0xC0,
+ 0xC7, 0xFB, 0x8B, 0xDF, 0xA3, 0x28, 0x78, 0x48,
+ 0x46, 0xD5, 0x70, 0x5C, 0x35, 0x4E, 0xD7, 0x3A,
+ 0x42, 0x47, 0x5B, 0x26, 0x8E, 0xE0, 0x21, 0xB1,
+ 0x77, 0x1E, 0x53, 0x4B, 0xCC, 0xE5, 0x65, 0xF6,
+ 0x66, 0x2A, 0xA0, 0x5E, 0x3E, 0xAD, 0xA8, 0x95,
+ 0x1B, 0x0D, 0x8A, 0x05, 0x68, 0x59, 0x0C, 0x38,
+ 0x18, 0xC3, 0x81, 0xA4, 0xFD, 0x13, 0x50, 0xCA,
+ 0xE8, 0xDD, 0xD9, 0x76, 0x8C, 0xC5, 0xF4, 0x17,
+ 0xB4, 0x3D, 0xEC, 0x0B, 0x67, 0xC6, 0x8D, 0xE1,
+ 0xBB, 0x7E, 0xCB, 0x10, 0x99, 0xE9, 0x39, 0xF3,
+ 0x75, 0xFA, 0xAC, 0x16, 0x54, 0x51, 0xBC, 0x24,
+ 0x58, 0x08, 0xA7, 0x0F, 0x5D, 0xBF, 0xBA, 0xE7,
+ 0x9D, 0x2B, 0xB5, 0x29, 0xE4, 0xCD, 0x37, 0x30,
+ 0x55, 0xAE, 0x1D, 0x4D, 0x94, 0x34, 0x92, 0x1C,
+ 0x6B, 0xBE, 0x52, 0x7B, 0x33, 0xB0, 0x0A, 0x5A,
+ 0x03, 0x23, 0x41, 0x49, 0x61, 0x64, 0x73, 0x97,
+ 0xC2, 0x9F, 0x5F, 0x07, 0x04, 0xF8, 0xC1, 0xFC,
+ 0x74, 0x02, 0x0E, 0x60, 0x9E, 0xD4, 0x85, 0x88,
+ 0xC4, 0xF5, 0x90, 0x31, 0xF7, 0xEE, 0x9B, 0xB9,
+ 0x20, 0xE6, 0xA6, 0x63, 0x79, 0x56, 0x62, 0xF0,
+ 0x2F, 0xD8, 0x4C, 0x83, 0xF9, 0x36, 0x3B, 0x84,
+ 0xDE, 0x57, 0xB8, 0xB7, 0x11, 0xF2, 0xC8, 0xD3,
+ 0xD1, 0x96, 0x19, 0x2E, 0x72, 0x9C, 0xDB, 0xB3,
+ 0xA1, 0xAA, 0xCE, 0x09, 0x98, 0xED, 0xA9, 0xDA,
+ 0xAF, 0x86, 0xD0, 0x12, 0xFF, 0xDC, 0x1F, 0xD6,
+ 0x01, 0xF1, 0xD2, 0x80, 0x43, 0x7A, 0x71, 0x82,
+ 0xB6, 0xAB, 0x89, 0xBD, 0x8F, 0xEF, 0x7D, 0xB2,
+ 0x14, 0x15, 0x25, 0x32, 0x6C, 0x69, 0x1A, 0xCF };
+
+static const unsigned char table_168[256] = {
+ 0x28, 0xEE, 0xB1, 0xFD, 0xB3, 0xEF, 0x36, 0x8E,
+ 0x85, 0x5D, 0x1C, 0x53, 0x1E, 0xDA, 0xBA, 0x3C,
+ 0xA8, 0x90, 0x99, 0x49, 0x45, 0xE0, 0x27, 0x8D,
+ 0x22, 0xE4, 0x51, 0x3E, 0xAB, 0xE8, 0x70, 0xF5,
+ 0x81, 0xE6, 0x34, 0x29, 0xF3, 0x11, 0x46, 0x5F,
+ 0x5C, 0xA0, 0xD1, 0xE3, 0x15, 0x68, 0x3A, 0x01,
+ 0xE9, 0xD7, 0x24, 0x5A, 0x18, 0x16, 0x88, 0x3B,
+ 0x64, 0xA1, 0xDB, 0xBF, 0xAA, 0x43, 0xEA, 0x19,
+ 0xA2, 0xD5, 0x7B, 0xBD, 0x2A, 0x0E, 0x4F, 0xB5,
+ 0x4B, 0xB7, 0x5B, 0x73, 0xC9, 0xAC, 0x1B, 0x67,
+ 0xC7, 0xB4, 0x69, 0x00, 0xBC, 0x6D, 0xC1, 0x04,
+ 0xF4, 0x74, 0xD6, 0xD0, 0x60, 0xAE, 0x17, 0xFE,
+ 0x63, 0xB6, 0x89, 0x41, 0x7C, 0x44, 0x8B, 0xDC,
+ 0x50, 0xE5, 0x79, 0x77, 0x47, 0x9F, 0xA6, 0x3D,
+ 0x09, 0x8A, 0x2F, 0xC0, 0x0F, 0xCD, 0x2B, 0x4D,
+ 0x0D, 0xC2, 0x5E, 0xB0, 0x57, 0x62, 0xAF, 0x1A,
+ 0x21, 0x82, 0x48, 0x9E, 0x38, 0xB9, 0xB8, 0xF2,
+ 0x37, 0x07, 0xCA, 0xC5, 0x84, 0xDF, 0xF9, 0xEC,
+ 0x42, 0x6B, 0x8F, 0x6C, 0x3F, 0xC4, 0x94, 0xED,
+ 0x7A, 0x2D, 0xA3, 0x83, 0xD9, 0x55, 0x02, 0x9A,
+ 0xA9, 0x75, 0x10, 0x2C, 0xCB, 0x95, 0xBB, 0x6E,
+ 0x23, 0x65, 0x35, 0x97, 0x56, 0xAD, 0xCE, 0xF8,
+ 0xF0, 0x0C, 0xE2, 0x52, 0x05, 0x91, 0xCC, 0xC8,
+ 0x78, 0x06, 0x96, 0x4E, 0x03, 0xD3, 0x98, 0xA7,
+ 0x13, 0x58, 0x93, 0xD4, 0xDD, 0xC6, 0xFC, 0x25,
+ 0x9C, 0x86, 0x1F, 0xCF, 0x76, 0xA4, 0x6A, 0xFA,
+ 0x0B, 0x4A, 0x54, 0x40, 0x59, 0xD8, 0x61, 0xFF,
+ 0x7F, 0x80, 0x6F, 0x7D, 0xF1, 0x8C, 0x92, 0xDE,
+ 0x9D, 0xC3, 0xB2, 0xE7, 0xFB, 0x20, 0x31, 0x72,
+ 0x12, 0xBE, 0x1D, 0xF6, 0x9B, 0x14, 0x26, 0x0A,
+ 0xEB, 0xF7, 0x71, 0x39, 0x30, 0xA5, 0x87, 0xD2,
+ 0x66, 0x2E, 0x08, 0x32, 0x4C, 0x33, 0x7E, 0xE1 };
+
+static const unsigned char table_169[256] = {
+ 0xA4, 0x31, 0xA9, 0x3F, 0x13, 0x4D, 0x1B, 0x29,
+ 0x73, 0x43, 0xF1, 0xE7, 0x9C, 0xC2, 0xF6, 0xCD,
+ 0xA1, 0x94, 0x0D, 0x27, 0xFE, 0x7B, 0x9B, 0x0B,
+ 0x89, 0xBA, 0x23, 0xEC, 0x76, 0xC3, 0x6C, 0xD8,
+ 0x8D, 0xF8, 0xF9, 0x7D, 0x68, 0x5B, 0x61, 0x87,
+ 0x28, 0x14, 0x55, 0x0C, 0xFC, 0xD9, 0x07, 0xE8,
+ 0x36, 0x88, 0x67, 0x4C, 0xEA, 0xBD, 0xF5, 0x9D,
+ 0xB6, 0xC6, 0x24, 0x32, 0x93, 0x03, 0x79, 0x8C,
+ 0x12, 0x84, 0xFF, 0x7E, 0x42, 0xE4, 0x3C, 0xF2,
+ 0x50, 0xEB, 0x1F, 0x47, 0xB0, 0xA5, 0xB1, 0x71,
+ 0x30, 0x5F, 0x5C, 0x53, 0xF7, 0x10, 0xC5, 0x6E,
+ 0xE0, 0xDE, 0xC8, 0x58, 0xB7, 0x90, 0xA6, 0x95,
+ 0x70, 0x8F, 0xFD, 0xC1, 0x48, 0xB5, 0x19, 0x92,
+ 0xBC, 0x15, 0x4E, 0xE6, 0x11, 0xDD, 0x81, 0x0E,
+ 0xBB, 0x75, 0x5D, 0x4A, 0xAB, 0x2D, 0x02, 0x54,
+ 0x4B, 0x66, 0xD6, 0x2B, 0x2A, 0xE5, 0x26, 0xE1,
+ 0xEE, 0xE9, 0x8B, 0x6A, 0x7A, 0xF4, 0x51, 0x39,
+ 0x1C, 0xC9, 0xCF, 0x77, 0x00, 0xF3, 0x25, 0xCC,
+ 0x08, 0xFB, 0x0F, 0x3E, 0xCE, 0xED, 0x3D, 0x56,
+ 0xEF, 0x1D, 0x85, 0x96, 0x52, 0xA8, 0xD3, 0xCB,
+ 0xE3, 0x33, 0x06, 0x7C, 0xAE, 0x72, 0x09, 0x04,
+ 0x91, 0xC4, 0x5A, 0x69, 0x98, 0xB4, 0x40, 0xDF,
+ 0x7F, 0x9F, 0xAA, 0x83, 0xE2, 0x78, 0x74, 0x20,
+ 0xAD, 0x6D, 0xDC, 0xD4, 0xCA, 0x60, 0xF0, 0x35,
+ 0x37, 0xD0, 0x18, 0x1A, 0x64, 0x3A, 0x99, 0xDB,
+ 0x62, 0x44, 0x2C, 0x82, 0x8E, 0xD7, 0xD1, 0xFA,
+ 0x16, 0xD5, 0x46, 0xBF, 0xA7, 0xC0, 0x2E, 0x3B,
+ 0x01, 0x63, 0xB2, 0x1E, 0x05, 0x21, 0xB8, 0x17,
+ 0x22, 0x97, 0xAF, 0x4F, 0x86, 0x34, 0xDA, 0xC7,
+ 0xA3, 0xA0, 0xB3, 0x2F, 0xAC, 0x49, 0xD2, 0x57,
+ 0x6F, 0x9A, 0x65, 0xB9, 0x41, 0xBE, 0x8A, 0xA2,
+ 0x6B, 0x0A, 0x59, 0x9E, 0x5E, 0x38, 0x45, 0x80 };
+
+static const unsigned char table_170[256] = {
+ 0xE3, 0x00, 0x99, 0x03, 0xF6, 0xDD, 0xD1, 0x41,
+ 0x58, 0x7E, 0xD9, 0x46, 0x04, 0xAF, 0x5C, 0x43,
+ 0xDE, 0x5E, 0xFC, 0x97, 0x3D, 0x68, 0xC8, 0x37,
+ 0x3C, 0xFB, 0x0F, 0x5A, 0xBE, 0xFA, 0x4C, 0x82,
+ 0x0C, 0xA0, 0x0A, 0xD4, 0x9D, 0xCE, 0x78, 0xA8,
+ 0x55, 0x56, 0x60, 0xAA, 0xC9, 0x96, 0x62, 0xEA,
+ 0x0D, 0xB8, 0xE2, 0x84, 0x17, 0xAE, 0x2B, 0x2C,
+ 0x91, 0x57, 0x38, 0x01, 0xA9, 0xCD, 0x34, 0xBA,
+ 0x8D, 0xC0, 0xD6, 0xFF, 0xF2, 0xD3, 0x5F, 0x26,
+ 0xCA, 0x9B, 0x21, 0x75, 0x4E, 0x49, 0x20, 0x59,
+ 0x39, 0xBF, 0x90, 0x6C, 0xFE, 0x8F, 0x2F, 0x18,
+ 0x36, 0xD7, 0xB4, 0xAC, 0xBD, 0xF3, 0x1D, 0x4F,
+ 0xA3, 0x74, 0x5B, 0x44, 0x05, 0x9C, 0x6D, 0x6B,
+ 0x1E, 0xE8, 0x25, 0x16, 0x80, 0xCC, 0x29, 0xC7,
+ 0x94, 0x4A, 0xF5, 0xF4, 0x27, 0x85, 0xBB, 0x24,
+ 0xDA, 0xB5, 0x76, 0x69, 0xA5, 0x54, 0x23, 0x31,
+ 0x11, 0xA4, 0x09, 0xE4, 0x64, 0x10, 0xC5, 0xC1,
+ 0x7D, 0xE7, 0x92, 0xF8, 0x9E, 0x6A, 0x15, 0x8B,
+ 0x98, 0x42, 0x52, 0x66, 0x0B, 0xA1, 0x35, 0x1A,
+ 0x14, 0x7C, 0xE1, 0x9F, 0x28, 0xF1, 0x1B, 0xA6,
+ 0x71, 0x73, 0x81, 0xAB, 0xE6, 0x95, 0x06, 0x1F,
+ 0xC6, 0xB0, 0x51, 0x0E, 0xEE, 0x77, 0xF0, 0xD8,
+ 0xC2, 0x89, 0x7B, 0x07, 0xA2, 0xB7, 0x19, 0x67,
+ 0x2E, 0x8E, 0x47, 0xA7, 0xEF, 0x32, 0xD2, 0x93,
+ 0xDC, 0x9A, 0xB2, 0xED, 0x45, 0xC4, 0x50, 0x3F,
+ 0xE5, 0xCF, 0x88, 0x1C, 0x7A, 0x79, 0xEB, 0x70,
+ 0x2A, 0x7F, 0xBC, 0xDB, 0xD0, 0xB1, 0xCB, 0x08,
+ 0x86, 0x5D, 0x53, 0x72, 0xB6, 0x4B, 0xB3, 0x22,
+ 0xC3, 0x6F, 0xB9, 0xD5, 0x3B, 0x13, 0x2D, 0xAD,
+ 0x33, 0xFD, 0x02, 0x40, 0x8A, 0x3A, 0xF7, 0xE0,
+ 0x8C, 0x3E, 0x61, 0x6E, 0xE9, 0x63, 0xF9, 0xEC,
+ 0x48, 0x30, 0x87, 0x83, 0x12, 0x4D, 0x65, 0xDF };
+
+static const unsigned char table_171[32] = {
+ 0x07, 0x06, 0x11, 0x08, 0x0C, 0x1F, 0x19, 0x02,
+ 0x14, 0x04, 0x0D, 0x18, 0x1A, 0x05, 0x17, 0x13,
+ 0x1C, 0x1B, 0x15, 0x03, 0x01, 0x0F, 0x16, 0x1E,
+ 0x1D, 0x10, 0x00, 0x12, 0x0B, 0x0E, 0x09, 0x0A };
+
+static const unsigned char table_172[32] = {
+ 0x11, 0x01, 0x1F, 0x06, 0x1A, 0x04, 0x02, 0x09,
+ 0x05, 0x0D, 0x0B, 0x18, 0x0E, 0x12, 0x1B, 0x17,
+ 0x07, 0x08, 0x1D, 0x1E, 0x14, 0x19, 0x16, 0x15,
+ 0x03, 0x0C, 0x00, 0x10, 0x0A, 0x1C, 0x0F, 0x13 };
+
+static const unsigned char table_173[32] = {
+ 0x1F, 0x0B, 0x13, 0x00, 0x16, 0x15, 0x14, 0x0A,
+ 0x1D, 0x05, 0x1E, 0x1A, 0x0F, 0x04, 0x0E, 0x01,
+ 0x19, 0x07, 0x02, 0x12, 0x0C, 0x17, 0x08, 0x09,
+ 0x03, 0x11, 0x18, 0x10, 0x1C, 0x1B, 0x06, 0x0D };
+
+static const unsigned char table_174[32] = {
+ 0x02, 0x1B, 0x0C, 0x17, 0x1F, 0x05, 0x15, 0x1E,
+ 0x16, 0x09, 0x1A, 0x12, 0x0F, 0x1C, 0x18, 0x0A,
+ 0x19, 0x10, 0x0D, 0x13, 0x04, 0x11, 0x08, 0x14,
+ 0x1D, 0x0E, 0x06, 0x00, 0x01, 0x07, 0x0B, 0x03 };
+
+static const unsigned char table_175[32] = {
+ 0x00, 0x06, 0x0B, 0x08, 0x0C, 0x04, 0x1A, 0x1C,
+ 0x05, 0x1E, 0x14, 0x03, 0x0A, 0x18, 0x12, 0x1D,
+ 0x16, 0x1F, 0x07, 0x09, 0x0F, 0x0E, 0x17, 0x13,
+ 0x11, 0x19, 0x10, 0x0D, 0x1B, 0x02, 0x01, 0x15 };
+
+static const unsigned char table_176[32] = {
+ 0x12, 0x03, 0x1A, 0x15, 0x04, 0x19, 0x0B, 0x1B,
+ 0x17, 0x1E, 0x0D, 0x05, 0x11, 0x14, 0x1C, 0x00,
+ 0x18, 0x10, 0x0A, 0x06, 0x0E, 0x08, 0x02, 0x07,
+ 0x13, 0x09, 0x16, 0x1D, 0x0F, 0x0C, 0x01, 0x1F };
+
+static const unsigned char table_177[256] = {
+ 0x5E, 0x4D, 0x76, 0xFE, 0xB5, 0x50, 0x83, 0x23,
+ 0x72, 0xDD, 0x93, 0x08, 0x69, 0xAD, 0xEC, 0x3B,
+ 0x0B, 0x9A, 0x36, 0xC9, 0xCA, 0xBE, 0xF7, 0x30,
+ 0x19, 0x39, 0x2C, 0xAB, 0xE3, 0x7B, 0xBC, 0x32,
+ 0xA0, 0xE4, 0xA6, 0xB6, 0xCB, 0xC8, 0x37, 0x07,
+ 0xD2, 0xA1, 0xD9, 0xF6, 0xBF, 0xF5, 0x88, 0x01,
+ 0x95, 0x0F, 0x03, 0xFD, 0xE6, 0x68, 0x90, 0x61,
+ 0x21, 0x6D, 0x3C, 0x62, 0x34, 0x2B, 0x71, 0x4B,
+ 0x44, 0x64, 0x75, 0xA2, 0x6A, 0xFF, 0x29, 0xBD,
+ 0x35, 0x15, 0xF9, 0xC1, 0x09, 0x45, 0xB2, 0xF2,
+ 0x3F, 0xCE, 0xB0, 0xC0, 0xB8, 0x00, 0x05, 0xD7,
+ 0x11, 0xC6, 0x78, 0x53, 0x9E, 0xB3, 0xED, 0x56,
+ 0x22, 0x5C, 0x9D, 0x6C, 0x99, 0x43, 0x2F, 0xAE,
+ 0xEB, 0x40, 0x8C, 0x1F, 0xC2, 0xDF, 0x92, 0x65,
+ 0x6F, 0x79, 0x5D, 0x5B, 0xAA, 0xDB, 0xF1, 0x96,
+ 0xD4, 0xF4, 0x8B, 0x51, 0xD5, 0xE2, 0xBB, 0x80,
+ 0x17, 0x7C, 0x2A, 0x6E, 0xDE, 0xEA, 0x94, 0x31,
+ 0xA4, 0x2D, 0xC3, 0x8D, 0x55, 0x14, 0x9B, 0x0E,
+ 0x7D, 0xC4, 0x06, 0x33, 0x73, 0xE9, 0x7A, 0x38,
+ 0x5F, 0x89, 0x84, 0xD6, 0xA8, 0x13, 0xE8, 0xCF,
+ 0x46, 0xD0, 0x7F, 0x24, 0x8F, 0xF8, 0x87, 0x1B,
+ 0x47, 0x02, 0x0C, 0x97, 0x52, 0xFB, 0x8E, 0x20,
+ 0x70, 0x3E, 0x7E, 0xD1, 0xE5, 0xEE, 0xCC, 0x91,
+ 0x74, 0xCD, 0x42, 0x04, 0x8A, 0xEF, 0xE1, 0x10,
+ 0x4F, 0x1C, 0x28, 0x9F, 0xD8, 0x0A, 0x18, 0x49,
+ 0x9C, 0x16, 0xF3, 0x82, 0x57, 0x1D, 0x26, 0x66,
+ 0x27, 0x86, 0xE7, 0x59, 0xFA, 0x25, 0x54, 0x0D,
+ 0x98, 0xDC, 0xF0, 0x3D, 0x63, 0x1E, 0x77, 0x3A,
+ 0xDA, 0xB7, 0x6B, 0x2E, 0x48, 0x4C, 0xBA, 0xC7,
+ 0x60, 0xAC, 0x1A, 0xB9, 0xFC, 0xA3, 0xA7, 0xA5,
+ 0xB4, 0x67, 0xA9, 0x81, 0xB1, 0x12, 0xD3, 0x85,
+ 0x5A, 0xC5, 0xE0, 0x58, 0x41, 0x4E, 0x4A, 0xAF };
+
+static const unsigned char table_178[256] = {
+ 0x33, 0xBA, 0x98, 0xDA, 0x07, 0x2C, 0x22, 0x9B,
+ 0xE0, 0xED, 0xB7, 0xA1, 0x93, 0xEB, 0xDC, 0x49,
+ 0xDF, 0xE1, 0x6C, 0xC2, 0x64, 0x52, 0xD0, 0x8F,
+ 0xA2, 0x48, 0x26, 0x21, 0x6E, 0x5E, 0x0B, 0x7C,
+ 0x0D, 0x90, 0xA4, 0xCE, 0xF5, 0x5F, 0xF9, 0x1D,
+ 0x55, 0x83, 0x8D, 0xFB, 0x38, 0xB3, 0xF2, 0x67,
+ 0xDE, 0x0A, 0xBE, 0xEC, 0x5B, 0x35, 0x08, 0x50,
+ 0xE7, 0x56, 0x4A, 0x02, 0xBC, 0x5A, 0xBD, 0x43,
+ 0x6F, 0x79, 0xB2, 0xF7, 0x60, 0xE9, 0xA0, 0x1B,
+ 0xC8, 0xDD, 0x9D, 0xA3, 0x5C, 0x61, 0x77, 0x72,
+ 0x9C, 0x31, 0x0E, 0x05, 0x1E, 0x12, 0xF1, 0xC9,
+ 0x78, 0x4E, 0x15, 0x7D, 0x54, 0xCB, 0x73, 0xEA,
+ 0xC5, 0x2B, 0x0F, 0x7E, 0x42, 0x96, 0xC6, 0x74,
+ 0x09, 0x65, 0x34, 0xE6, 0x63, 0xA6, 0x70, 0xD3,
+ 0x27, 0x87, 0x3A, 0x16, 0x7B, 0x13, 0x06, 0x40,
+ 0x46, 0x69, 0xAD, 0x88, 0x81, 0xC0, 0x37, 0x58,
+ 0xD1, 0x8A, 0x8E, 0x9A, 0x5D, 0x6D, 0xC7, 0xC3,
+ 0xD2, 0xF4, 0x3F, 0x57, 0x3C, 0x4F, 0xA9, 0x6A,
+ 0x92, 0xA5, 0x97, 0x0C, 0x2A, 0x36, 0x47, 0xDB,
+ 0x8C, 0xEE, 0x03, 0x89, 0x7F, 0x91, 0x24, 0x80,
+ 0x2F, 0x62, 0xE4, 0xAF, 0x17, 0x99, 0xD6, 0xCD,
+ 0xFE, 0x76, 0x1C, 0xD4, 0x3E, 0xFF, 0xD8, 0xC4,
+ 0x39, 0x32, 0xCF, 0xE2, 0xE3, 0x53, 0xD7, 0xCC,
+ 0xD9, 0x11, 0xAA, 0x1F, 0x01, 0x3B, 0x51, 0xB5,
+ 0x94, 0x4B, 0x28, 0xF0, 0xAC, 0x44, 0x14, 0x4C,
+ 0xB9, 0xA7, 0xB8, 0x1A, 0xD5, 0xCA, 0xE8, 0x82,
+ 0x9F, 0x2D, 0xAB, 0x2E, 0x29, 0xFD, 0x68, 0xB1,
+ 0x66, 0xC1, 0x7A, 0xFA, 0x71, 0x04, 0xA8, 0xB0,
+ 0x59, 0x18, 0xAE, 0x25, 0x3D, 0xE5, 0xF6, 0x41,
+ 0x86, 0x75, 0x6B, 0xBB, 0xFC, 0x84, 0x8B, 0x85,
+ 0x10, 0x23, 0xB6, 0xF3, 0x19, 0x30, 0x20, 0x4D,
+ 0x95, 0x9E, 0xBF, 0xEF, 0xF8, 0x45, 0x00, 0xB4 };
+
+static const unsigned char table_179[256] = {
+ 0x50, 0x3D, 0x41, 0x42, 0x06, 0x5B, 0xD6, 0x34,
+ 0x9D, 0x3C, 0x7B, 0x14, 0xE2, 0x9B, 0x80, 0x15,
+ 0x51, 0x01, 0x6A, 0x30, 0xD7, 0xFC, 0x61, 0x4B,
+ 0x8A, 0xEC, 0x38, 0x71, 0x70, 0x2E, 0x1C, 0x72,
+ 0x79, 0x26, 0x4C, 0x48, 0xED, 0xAD, 0x25, 0x53,
+ 0x03, 0xD9, 0xB5, 0x0D, 0x8E, 0x19, 0xCC, 0xBE,
+ 0xE1, 0x91, 0x64, 0xA6, 0x21, 0xCE, 0x76, 0xAB,
+ 0x9F, 0xD1, 0xB6, 0x23, 0x6D, 0xB0, 0x90, 0xBD,
+ 0x09, 0x3A, 0x5E, 0xD0, 0x73, 0x10, 0x44, 0x08,
+ 0xFF, 0xB8, 0x24, 0x58, 0xDB, 0x65, 0x95, 0xAA,
+ 0xE9, 0xC4, 0x32, 0x2B, 0x84, 0xC9, 0xC7, 0xB1,
+ 0x4F, 0x0C, 0xCB, 0x11, 0x4E, 0x22, 0x4A, 0x16,
+ 0xDE, 0xBC, 0xEE, 0x68, 0x13, 0xFA, 0xC3, 0x98,
+ 0xEB, 0x29, 0x43, 0x9A, 0xA1, 0xE0, 0xF0, 0x3F,
+ 0x2F, 0x1B, 0xC2, 0x66, 0x35, 0xF5, 0xC8, 0xD8,
+ 0x5A, 0xE5, 0x87, 0x47, 0xD3, 0x7A, 0xE6, 0x39,
+ 0x77, 0x81, 0xF2, 0x0E, 0x83, 0x7E, 0x17, 0x6C,
+ 0xB3, 0x5C, 0xE8, 0xD2, 0xC0, 0xA4, 0xF9, 0x86,
+ 0xCD, 0xFB, 0x54, 0x7C, 0xBF, 0x2D, 0x82, 0xDA,
+ 0x96, 0x74, 0x97, 0xC5, 0x7D, 0x27, 0x57, 0x56,
+ 0xDC, 0xBA, 0x69, 0x8C, 0x9C, 0x88, 0xB4, 0x8D,
+ 0x37, 0xEA, 0x3B, 0x33, 0x2C, 0xB2, 0x45, 0xF7,
+ 0xC1, 0x1E, 0x46, 0x02, 0x6B, 0x3E, 0xA7, 0xD5,
+ 0x05, 0x0A, 0xA9, 0x1D, 0xA3, 0x4D, 0xAE, 0x6F,
+ 0x49, 0xDD, 0x8F, 0xEF, 0xBB, 0x67, 0x0B, 0x40,
+ 0x9E, 0xF1, 0x78, 0x28, 0xDF, 0x52, 0xF4, 0x92,
+ 0x94, 0x0F, 0xB9, 0x93, 0xF6, 0x1F, 0xAF, 0xA8,
+ 0xCA, 0xE4, 0x59, 0x7F, 0x85, 0x75, 0xC6, 0xFD,
+ 0x00, 0xB7, 0x55, 0xFE, 0x8B, 0x62, 0x5F, 0x12,
+ 0xF8, 0xD4, 0x89, 0xA0, 0x20, 0xE7, 0xCF, 0x60,
+ 0x5D, 0xAC, 0x1A, 0x36, 0x63, 0x99, 0x31, 0xF3,
+ 0x2A, 0x04, 0x18, 0xA5, 0xA2, 0x6E, 0x07, 0xE3 };
+
+static const unsigned char table_180[256] = {
+ 0xDA, 0xCC, 0x72, 0xA6, 0xE7, 0x07, 0xFD, 0x25,
+ 0x92, 0x39, 0x49, 0x02, 0xD6, 0x09, 0xA8, 0x65,
+ 0x2E, 0x6C, 0xA1, 0x19, 0xBF, 0x21, 0x11, 0xC7,
+ 0x3F, 0x9F, 0xF4, 0x51, 0xAF, 0x8C, 0xFE, 0xCD,
+ 0x7A, 0xEB, 0x5A, 0xF7, 0x18, 0x69, 0xB9, 0xED,
+ 0x37, 0x45, 0x13, 0xB4, 0xAA, 0x75, 0x47, 0x42,
+ 0xA3, 0x81, 0x88, 0x70, 0xC1, 0x36, 0x73, 0x1D,
+ 0x3B, 0x22, 0xB6, 0x35, 0xE9, 0x31, 0x56, 0x23,
+ 0xE1, 0xF5, 0xAD, 0x46, 0x99, 0x32, 0xE4, 0x40,
+ 0x00, 0x0F, 0x05, 0xC6, 0x33, 0x84, 0x7B, 0x4D,
+ 0x4B, 0x7D, 0x91, 0x3D, 0xCE, 0x64, 0x77, 0x55,
+ 0xD7, 0x2B, 0x2F, 0x2C, 0xB8, 0xD3, 0x85, 0xD1,
+ 0xB5, 0x6A, 0xF9, 0x41, 0x08, 0xBB, 0x87, 0xEC,
+ 0x78, 0xE0, 0xEE, 0x8D, 0x01, 0x58, 0x15, 0x8F,
+ 0x06, 0xF0, 0x8B, 0x27, 0x0D, 0x0B, 0x6D, 0xBD,
+ 0xCA, 0x2A, 0xA2, 0xE6, 0xDD, 0xBC, 0x4E, 0x5D,
+ 0x74, 0x04, 0x3A, 0x96, 0x66, 0x12, 0x1E, 0xF2,
+ 0xF6, 0xC4, 0xAE, 0x3C, 0x0C, 0x90, 0x68, 0xD8,
+ 0x24, 0x5E, 0x79, 0x10, 0xAC, 0xDF, 0x9B, 0xC5,
+ 0x44, 0xC3, 0x50, 0x5C, 0xA5, 0x89, 0x60, 0x5F,
+ 0x48, 0x17, 0x34, 0xA7, 0xE2, 0xF3, 0xD9, 0x3E,
+ 0x9C, 0xB7, 0x7C, 0x1F, 0xA9, 0xD4, 0xA4, 0x0E,
+ 0x8E, 0x4C, 0xDC, 0xF8, 0xF1, 0x98, 0xDE, 0x2D,
+ 0x61, 0xCB, 0xD5, 0x43, 0x86, 0x26, 0xB0, 0x7F,
+ 0x7E, 0xFF, 0xAB, 0x83, 0x14, 0x9A, 0x80, 0x16,
+ 0x30, 0xA0, 0x53, 0x97, 0x52, 0x9E, 0xB1, 0x1B,
+ 0xD0, 0x1A, 0xC8, 0x57, 0xBA, 0x6E, 0xFA, 0x94,
+ 0xE8, 0x63, 0x5B, 0x29, 0xEF, 0x71, 0x8A, 0x03,
+ 0xB3, 0x76, 0xC9, 0xD2, 0xBE, 0xE5, 0x82, 0x1C,
+ 0x95, 0x9D, 0x4A, 0x28, 0xEA, 0x0A, 0xC0, 0xE3,
+ 0x6F, 0x20, 0x54, 0xFB, 0x93, 0xFC, 0x6B, 0x38,
+ 0x62, 0x4F, 0xCF, 0xB2, 0xC2, 0x59, 0xDB, 0x67 };
+
+static const unsigned char table_181[256] = {
+ 0x2B, 0xED, 0x14, 0x05, 0x80, 0xCC, 0x5A, 0xF8,
+ 0x43, 0xB7, 0x86, 0xC6, 0xEE, 0xA6, 0xD7, 0xD6,
+ 0xA0, 0xC4, 0x21, 0x34, 0xB1, 0x8C, 0xF9, 0xF4,
+ 0x7C, 0x53, 0x06, 0xD4, 0x6B, 0x3F, 0xE1, 0x12,
+ 0x6A, 0xCE, 0xCF, 0xBF, 0x74, 0x3E, 0xD5, 0xCB,
+ 0x97, 0x01, 0xA2, 0x2D, 0xAE, 0xF7, 0x17, 0x29,
+ 0x47, 0x03, 0x0E, 0xE9, 0x82, 0x46, 0x94, 0xAF,
+ 0x2A, 0x90, 0xFE, 0x4A, 0x7E, 0x0C, 0x71, 0xB6,
+ 0xA5, 0xF2, 0x67, 0x41, 0xBA, 0xC2, 0x8A, 0x9D,
+ 0x36, 0xFF, 0x50, 0x2E, 0xC3, 0x91, 0x9C, 0x37,
+ 0x66, 0xAD, 0xB2, 0x1F, 0xE4, 0xE3, 0x9F, 0xDD,
+ 0x87, 0xC0, 0xE6, 0xEF, 0x13, 0x70, 0x5B, 0xDE,
+ 0x5C, 0x75, 0x7F, 0x4F, 0x44, 0xCA, 0x55, 0x57,
+ 0xF0, 0x26, 0xA7, 0xC7, 0x10, 0x51, 0x00, 0xB3,
+ 0x5D, 0x99, 0x81, 0x3B, 0xB9, 0x1C, 0x64, 0x7B,
+ 0xFB, 0xD9, 0x8D, 0x4E, 0xAC, 0x25, 0xBB, 0x69,
+ 0xDF, 0x02, 0x9E, 0x2C, 0xAB, 0xF3, 0x65, 0x09,
+ 0xA3, 0x6C, 0xC1, 0x76, 0x52, 0x30, 0xD8, 0x3A,
+ 0x40, 0x18, 0x59, 0xD0, 0xE5, 0xB4, 0x5F, 0x33,
+ 0x68, 0x92, 0x2F, 0xB8, 0x93, 0xD1, 0xEB, 0xA4,
+ 0xFC, 0x77, 0x19, 0x62, 0xC9, 0x49, 0x84, 0x1A,
+ 0x9A, 0xE7, 0x31, 0xE8, 0xE2, 0x58, 0xF1, 0x4B,
+ 0x1E, 0x0B, 0x39, 0xFD, 0x42, 0x7A, 0x89, 0x38,
+ 0x11, 0x98, 0x63, 0x08, 0xE0, 0xEA, 0xBE, 0xB0,
+ 0x45, 0x1B, 0x4C, 0x54, 0xC8, 0x27, 0x3D, 0x73,
+ 0x04, 0x8F, 0x79, 0xBC, 0x6F, 0x0D, 0x0F, 0xA1,
+ 0x60, 0xDC, 0xC5, 0xFA, 0x8E, 0xDA, 0x15, 0x96,
+ 0xD3, 0x07, 0xF5, 0x3C, 0x88, 0x72, 0x1D, 0x4D,
+ 0x8B, 0x61, 0x0A, 0xDB, 0xAA, 0x20, 0x23, 0xEC,
+ 0x6E, 0x22, 0x48, 0x28, 0xBD, 0xA9, 0x56, 0x5E,
+ 0x85, 0xA8, 0x95, 0x6D, 0x16, 0x78, 0xB5, 0xF6,
+ 0x32, 0x24, 0x7D, 0x9B, 0xD2, 0x83, 0x35, 0xCD };
+
+static const unsigned char table_182[256] = {
+ 0x06, 0x7F, 0x66, 0xB5, 0xBA, 0x1E, 0xFD, 0x51,
+ 0x81, 0x8D, 0x28, 0xA3, 0x15, 0x37, 0xDC, 0x58,
+ 0xE6, 0x3D, 0xB4, 0xB9, 0x2E, 0xA0, 0x2F, 0xC4,
+ 0xCB, 0xB1, 0x25, 0xBF, 0xC1, 0x4E, 0x5A, 0xE4,
+ 0x0F, 0x10, 0x7C, 0x52, 0xA7, 0x29, 0x76, 0x55,
+ 0xAA, 0x70, 0x62, 0x54, 0x43, 0x93, 0x3A, 0x7D,
+ 0x5B, 0x56, 0x33, 0x64, 0x74, 0x2A, 0xD9, 0x9B,
+ 0x88, 0xC0, 0x3C, 0x63, 0xDE, 0xF4, 0x73, 0xDF,
+ 0x9E, 0xB2, 0xA8, 0x4F, 0x04, 0x57, 0x47, 0x87,
+ 0x14, 0xFC, 0x27, 0x53, 0x83, 0xDB, 0xD7, 0x20,
+ 0x96, 0x31, 0xD0, 0xCF, 0x30, 0x19, 0x69, 0x1A,
+ 0xAE, 0x3B, 0x11, 0x0C, 0xA6, 0x95, 0x8A, 0xF2,
+ 0x1B, 0xCC, 0x78, 0xEF, 0xB3, 0x71, 0x84, 0xA2,
+ 0xF1, 0x7A, 0x92, 0x61, 0xCA, 0x90, 0x94, 0x89,
+ 0x68, 0xEE, 0x97, 0x38, 0x0D, 0xF9, 0x1F, 0x8E,
+ 0xE9, 0x26, 0xBD, 0xC9, 0xFF, 0x4C, 0x44, 0x1D,
+ 0x98, 0xE5, 0x86, 0xF3, 0x18, 0xB6, 0x09, 0xD2,
+ 0x7E, 0xC5, 0xE7, 0x2B, 0x8C, 0x8B, 0x60, 0x3F,
+ 0x2C, 0x6A, 0x08, 0x0E, 0x50, 0x32, 0x9F, 0xF0,
+ 0x9A, 0xC2, 0x39, 0xBE, 0xEA, 0x12, 0x16, 0xBB,
+ 0x5E, 0x67, 0xE3, 0xB8, 0x79, 0x46, 0xDA, 0x00,
+ 0xD3, 0xBC, 0xCE, 0x1C, 0x80, 0xFA, 0xAB, 0x65,
+ 0x4A, 0xF8, 0xAC, 0x72, 0x01, 0xC6, 0x35, 0x85,
+ 0x3E, 0x5C, 0xA1, 0x05, 0xA5, 0xA9, 0xE1, 0x40,
+ 0xEB, 0xE8, 0x5F, 0xF5, 0xC3, 0xD1, 0x34, 0xFB,
+ 0xEC, 0xF7, 0x9C, 0xC7, 0xDD, 0x6C, 0x36, 0x9D,
+ 0x42, 0x59, 0x99, 0x5D, 0xD8, 0x82, 0x07, 0x24,
+ 0x6D, 0xAD, 0x13, 0x48, 0x6B, 0x6E, 0x75, 0x4D,
+ 0xD5, 0x02, 0xED, 0xFE, 0x91, 0xCD, 0x77, 0xB0,
+ 0xF6, 0xC8, 0x6F, 0x23, 0xAF, 0xB7, 0x2D, 0xD6,
+ 0xA4, 0xE2, 0x45, 0x8F, 0x21, 0xE0, 0x49, 0x22,
+ 0x7B, 0x17, 0x0B, 0x0A, 0x41, 0x03, 0xD4, 0x4B };
+
+static const unsigned char table_183[32] = {
+ 0x1E, 0x1B, 0x11, 0x07, 0x08, 0x06, 0x18, 0x17,
+ 0x0D, 0x0F, 0x12, 0x03, 0x1D, 0x04, 0x0A, 0x1A,
+ 0x0C, 0x13, 0x14, 0x1F, 0x0B, 0x19, 0x10, 0x01,
+ 0x16, 0x05, 0x1C, 0x0E, 0x02, 0x00, 0x09, 0x15 };
+
+static const unsigned char table_184[32] = {
+ 0x0F, 0x1D, 0x17, 0x16, 0x0D, 0x05, 0x13, 0x1F,
+ 0x1B, 0x09, 0x1C, 0x1E, 0x15, 0x01, 0x06, 0x08,
+ 0x0C, 0x10, 0x0B, 0x02, 0x04, 0x0A, 0x07, 0x1A,
+ 0x18, 0x0E, 0x03, 0x11, 0x12, 0x14, 0x19, 0x00 };
+
+static const unsigned char table_185[256] = {
+ 0xA5, 0xEE, 0x2E, 0x28, 0xA7, 0xAC, 0xD9, 0xB2,
+ 0x6E, 0x04, 0xB4, 0x03, 0xE8, 0x92, 0x5F, 0x4D,
+ 0x73, 0x20, 0x71, 0xE0, 0x43, 0x53, 0x3F, 0xF8,
+ 0x96, 0xA1, 0x24, 0x97, 0xAD, 0x7B, 0xE5, 0xE6,
+ 0xF2, 0xCE, 0xE3, 0x76, 0x2F, 0xA2, 0x48, 0x0E,
+ 0x4B, 0x4A, 0x8B, 0x5A, 0x81, 0x2C, 0xBF, 0xD7,
+ 0xFB, 0x7D, 0x4C, 0x16, 0xF4, 0x00, 0xF5, 0x40,
+ 0x64, 0x74, 0xA9, 0x37, 0x86, 0xD3, 0x1B, 0xCD,
+ 0xF1, 0x1A, 0x90, 0x9F, 0x54, 0x79, 0x29, 0xC3,
+ 0x77, 0x85, 0x02, 0xB1, 0x70, 0xFE, 0x5B, 0xDA,
+ 0x6B, 0x01, 0x0C, 0x07, 0xB8, 0x58, 0x47, 0x42,
+ 0x09, 0xE4, 0x27, 0xDD, 0xF3, 0x1E, 0x10, 0x9E,
+ 0x49, 0x30, 0x05, 0xBE, 0x59, 0xEB, 0xD2, 0xAA,
+ 0xC8, 0x9D, 0x8C, 0x5E, 0x14, 0x56, 0x8E, 0xF7,
+ 0x38, 0x55, 0x87, 0xA3, 0x5D, 0x41, 0x4F, 0x1F,
+ 0xF6, 0x0F, 0x57, 0x91, 0xAE, 0xBA, 0xB3, 0x95,
+ 0x9B, 0x69, 0xC1, 0x11, 0xD0, 0x25, 0x7F, 0x3B,
+ 0x62, 0xCF, 0xC0, 0xA0, 0xFC, 0xB6, 0x12, 0x6C,
+ 0xF0, 0x13, 0x93, 0xAB, 0xC6, 0x78, 0x6D, 0x88,
+ 0x22, 0x08, 0x2A, 0xE2, 0xB7, 0x65, 0x31, 0x3A,
+ 0xA6, 0x7C, 0xF9, 0xDC, 0xE7, 0xA4, 0xC9, 0x63,
+ 0xA8, 0x0B, 0xED, 0x50, 0x36, 0xD8, 0x3E, 0xB0,
+ 0x6A, 0x5C, 0x45, 0x4E, 0x23, 0x84, 0x34, 0x9A,
+ 0xCC, 0x3D, 0xB5, 0xEA, 0xDE, 0x75, 0xD6, 0xFF,
+ 0x6F, 0xC2, 0xDB, 0x8D, 0x7A, 0x1C, 0xE9, 0x61,
+ 0x0A, 0x1D, 0x32, 0x52, 0x3C, 0x19, 0xFA, 0xD1,
+ 0xD4, 0x68, 0xC7, 0x0D, 0x99, 0x83, 0xEF, 0x80,
+ 0x82, 0xBD, 0xD5, 0x7E, 0x39, 0x72, 0x51, 0xAF,
+ 0x8A, 0x2D, 0xB9, 0x89, 0xC4, 0x67, 0x35, 0xE1,
+ 0x44, 0x06, 0xEC, 0xCB, 0x8F, 0x17, 0xDF, 0x94,
+ 0x60, 0xCA, 0x26, 0xFD, 0x33, 0x46, 0x21, 0xBB,
+ 0x2B, 0xC5, 0x98, 0x18, 0x66, 0x15, 0x9C, 0xBC };
+
+static const unsigned char table_186[256] = {
+ 0xB7, 0xFA, 0x03, 0x7C, 0x76, 0x43, 0xA7, 0x15,
+ 0x4B, 0x4F, 0x04, 0xAA, 0x4E, 0xD2, 0x52, 0xC8,
+ 0x79, 0x16, 0xF6, 0x61, 0x01, 0x5D, 0xD6, 0x47,
+ 0xDE, 0xC5, 0x4D, 0x2F, 0xF5, 0x29, 0x21, 0xE6,
+ 0x97, 0x35, 0xDC, 0x0E, 0x8B, 0xF4, 0x0F, 0xBE,
+ 0x30, 0x07, 0x1D, 0x46, 0x75, 0xCE, 0x56, 0x42,
+ 0x28, 0x93, 0x84, 0x20, 0xA5, 0xC2, 0x87, 0x45,
+ 0x1C, 0x6B, 0x55, 0x06, 0xEB, 0xB0, 0xF9, 0x14,
+ 0x23, 0xF1, 0xFC, 0xD7, 0x98, 0xD1, 0xA4, 0xED,
+ 0x5B, 0xB1, 0x12, 0x7A, 0xD5, 0x5F, 0x53, 0x88,
+ 0x95, 0x71, 0xE7, 0x5C, 0xF8, 0x83, 0xC7, 0x49,
+ 0xDD, 0xDA, 0x0B, 0xC1, 0x70, 0xEC, 0x67, 0xE2,
+ 0xEA, 0x72, 0x4C, 0x92, 0xA6, 0xE5, 0x59, 0xA9,
+ 0x3C, 0xFE, 0x0A, 0x65, 0x6E, 0xF3, 0xA3, 0x22,
+ 0x24, 0x81, 0xF2, 0xCC, 0xD3, 0xA0, 0xDF, 0xDB,
+ 0xAB, 0x09, 0x13, 0x96, 0x36, 0x9C, 0xEE, 0xD4,
+ 0x33, 0x5E, 0x26, 0xAE, 0x48, 0x38, 0xFF, 0x08,
+ 0x1F, 0x6D, 0x02, 0xEF, 0x7E, 0x57, 0x2A, 0x8A,
+ 0xBA, 0x90, 0xAF, 0xA8, 0x37, 0x8E, 0x9B, 0xC0,
+ 0x69, 0x32, 0x86, 0xBD, 0x73, 0x6C, 0xB9, 0x31,
+ 0x66, 0xBF, 0x1B, 0x44, 0x9E, 0xB2, 0xD0, 0xE0,
+ 0xF0, 0x2C, 0x3F, 0xE1, 0x91, 0x18, 0x19, 0x50,
+ 0xCA, 0x8F, 0x54, 0xB5, 0x8D, 0x0C, 0x17, 0x39,
+ 0x8C, 0x00, 0x7F, 0x41, 0xE3, 0x2E, 0x1A, 0x9D,
+ 0x27, 0xA1, 0x10, 0x34, 0x1E, 0x3A, 0x60, 0x77,
+ 0xBB, 0xB6, 0x0D, 0x4A, 0x3E, 0x6A, 0xB4, 0xA2,
+ 0xB3, 0xFD, 0xCD, 0x80, 0x51, 0xAD, 0xCF, 0xBC,
+ 0x40, 0x74, 0x6F, 0x68, 0x2B, 0xC3, 0xF7, 0x63,
+ 0xB8, 0x25, 0xC4, 0x62, 0xE9, 0xFB, 0x58, 0x85,
+ 0x78, 0xCB, 0x9A, 0x3D, 0xE4, 0xC9, 0x89, 0x2D,
+ 0x64, 0x82, 0xC6, 0x05, 0xD8, 0xAC, 0x99, 0x9F,
+ 0x11, 0x3B, 0x94, 0xE8, 0x7D, 0x7B, 0xD9, 0x5A };
+
+static const unsigned char table_187[32] = {
+ 0x0F, 0x04, 0x1D, 0x1B, 0x15, 0x10, 0x01, 0x0B,
+ 0x00, 0x17, 0x13, 0x07, 0x1E, 0x1F, 0x08, 0x0A,
+ 0x19, 0x09, 0x05, 0x06, 0x0C, 0x1A, 0x14, 0x16,
+ 0x0E, 0x18, 0x03, 0x1C, 0x12, 0x11, 0x0D, 0x02 };
+
+static const struct yahoo_fn yahoo_fntable[5][96] =
+ {{{ IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 },
+ { IDENT, 0, 0 }},
+ {{ MULADD, 0x36056CD7, 0x4387 },
+ { LOOKUP, (long)table_0, 0 },
+ { LOOKUP, (long)table_1, 0 },
+ { BITFLD, (long)table_2, 0 },
+ { LOOKUP, (long)table_3, 0 },
+ { BITFLD, (long)table_4, 0 },
+ { MULADD, 0x4ABB534D, 0x3769 },
+ { XOR, 0x1D242DA5, 0 },
+ { MULADD, 0x3C23132D, 0x339B },
+ { XOR, 0x0191265C, 0 },
+ { XOR, 0x3DB979DB, 0 },
+ { LOOKUP, (long)table_5, 0 },
+ { XOR, 0x1A550E1E, 0 },
+ { XOR, 0x2F140A2D, 0 },
+ { MULADD, 0x7C466A4B, 0x29BF },
+ { XOR, 0x2D3F30D3, 0 },
+ { MULADD, 0x7E823B21, 0x6BB3 },
+ { BITFLD, (long)table_6, 0 },
+ { LOOKUP, (long)table_7, 0 },
+ { BITFLD, (long)table_8, 0 },
+ { LOOKUP, (long)table_9, 0 },
+ { BITFLD, (long)table_10, 0 },
+ { LOOKUP, (long)table_11, 0 },
+ { BITFLD, (long)table_12, 0 },
+ { LOOKUP, (long)table_13, 0 },
+ { BITFLD, (long)table_14, 0 },
+ { MULADD, 0x5B756AB9, 0x7E9B },
+ { LOOKUP, (long)table_15, 0 },
+ { XOR, 0x1D1C4911, 0 },
+ { LOOKUP, (long)table_16, 0 },
+ { LOOKUP, (long)table_17, 0 },
+ { XOR, 0x46BD7771, 0 },
+ { XOR, 0x51AE2B42, 0 },
+ { MULADD, 0x2417591B, 0x177B },
+ { MULADD, 0x57F27C5F, 0x2433 },
+ { LOOKUP, (long)table_18, 0 },
+ { LOOKUP, (long)table_19, 0 },
+ { XOR, 0x71422261, 0 },
+ { BITFLD, (long)table_20, 0 },
+ { MULADD, 0x58E937F9, 0x1075 },
+ { LOOKUP, (long)table_21, 0 },
+ { BITFLD, (long)table_22, 0 },
+ { LOOKUP, (long)table_23, 0 },
+ { LOOKUP, (long)table_24, 0 },
+ { MULADD, 0x0B4C3D13, 0x1597 },
+ { BITFLD, (long)table_25, 0 },
+ { XOR, 0x0FE07D38, 0 },
+ { MULADD, 0x689B4017, 0x3CFB },
+ { BITFLD, (long)table_26, 0 },
+ { LOOKUP, (long)table_27, 0 },
+ { XOR, 0x35413DF3, 0 },
+ { MULADD, 0x05B611AB, 0x570B },
+ { MULADD, 0x0DA5334F, 0x3AC7 },
+ { XOR, 0x47706008, 0 },
+ { BITFLD, (long)table_28, 0 },
+ { LOOKUP, (long)table_29, 0 },
+ { BITFLD, (long)table_30, 0 },
+ { XOR, 0x57611B36, 0 },
+ { MULADD, 0x314C2CD1, 0x2B5B },
+ { XOR, 0x1EF33946, 0 },
+ { MULADD, 0x28EA041F, 0x638F },
+ { LOOKUP, (long)table_31, 0 },
+ { LOOKUP, (long)table_32, 0 },
+ { LOOKUP, (long)table_33, 0 },
+ { MULADD, 0x511537CB, 0x7135 },
+ { MULADD, 0x1CF71007, 0x5E17 },
+ { XOR, 0x583D4BCF, 0 },
+ { LOOKUP, (long)table_34, 0 },
+ { XOR, 0x373E6856, 0 },
+ { MULADD, 0x4D595519, 0x1A7D },
+ { LOOKUP, (long)table_35, 0 },
+ { LOOKUP, (long)table_36, 0 },
+ { XOR, 0x0E2A36A7, 0 },
+ { LOOKUP, (long)table_37, 0 },
+ { LOOKUP, (long)table_38, 0 },
+ { BITFLD, (long)table_39, 0 },
+ { BITFLD, (long)table_40, 0 },
+ { XOR, 0x53F3604F, 0 },
+ { BITFLD, (long)table_41, 0 },
+ { BITFLD, (long)table_42, 0 },
+ { MULADD, 0x1EDC0BA3, 0x7531 },
+ { LOOKUP, (long)table_43, 0 },
+ { XOR, 0x10DF1038, 0 },
+ { BITFLD, (long)table_44, 0 },
+ { LOOKUP, (long)table_45, 0 },
+ { XOR, 0x4EDE0CAC, 0 },
+ { MULADD, 0x2F076EEB, 0x5BCF },
+ { XOR, 0x6D86030F, 0 },
+ { XOR, 0x3F331713, 0 },
+ { LOOKUP, (long)table_46, 0 },
+ { MULADD, 0x41CD726F, 0x3F79 },
+ { BITFLD, (long)table_47, 0 },
+ { XOR, 0x0ECE0054, 0 },
+ { MULADD, 0x19B32B03, 0x4AD1 },
+ { BITFLD, (long)table_48, 0 },
+ { BITFLD, (long)table_49, 0 }},
+ {{ MULADD, 0x39731111, 0x419B },
+ { XOR, 0x54F7757A, 0 },
+ { BITFLD, (long)table_50, 0 },
+ { BITFLD, (long)table_51, 0 },
+ { LOOKUP, (long)table_52, 0 },
+ { LOOKUP, (long)table_53, 0 },
+ { MULADD, 0x3CC0256B, 0x7CE7 },
+ { XOR, 0x79991847, 0 },
+ { MULADD, 0x228F7FB5, 0x472D },
+ { MULADD, 0x32DA290B, 0x7745 },
+ { XOR, 0x7A28180D, 0 },
+ { BITFLD, (long)table_54, 0 },
+ { BITFLD, (long)table_55, 0 },
+ { MULADD, 0x5C814F8B, 0x227F },
+ { LOOKUP, (long)table_56, 0 },
+ { MULADD, 0x0B496F6D, 0x412D },
+ { XOR, 0x6F4B62DA, 0 },
+ { LOOKUP, (long)table_57, 0 },
+ { XOR, 0x64973977, 0 },
+ { LOOKUP, (long)table_58, 0 },
+ { LOOKUP, (long)table_59, 0 },
+ { BITFLD, (long)table_60, 0 },
+ { LOOKUP, (long)table_61, 0 },
+ { LOOKUP, (long)table_62, 0 },
+ { XOR, 0x6DD14C92, 0 },
+ { LOOKUP, (long)table_63, 0 },
+ { BITFLD, (long)table_64, 0 },
+ { BITFLD, (long)table_65, 0 },
+ { BITFLD, (long)table_66, 0 },
+ { LOOKUP, (long)table_67, 0 },
+ { XOR, 0x5E6324D8, 0 },
+ { LOOKUP, (long)table_68, 0 },
+ { LOOKUP, (long)table_69, 0 },
+ { LOOKUP, (long)table_70, 0 },
+ { BITFLD, (long)table_71, 0 },
+ { XOR, 0x62745ED0, 0 },
+ { MULADD, 0x102C215B, 0x0581 },
+ { LOOKUP, (long)table_72, 0 },
+ { LOOKUP, (long)table_73, 0 },
+ { LOOKUP, (long)table_74, 0 },
+ { MULADD, 0x19511111, 0x12C1 },
+ { LOOKUP, (long)table_75, 0 },
+ { MULADD, 0x2A6E2953, 0x6977 },
+ { LOOKUP, (long)table_76, 0 },
+ { XOR, 0x55CD5445, 0 },
+ { BITFLD, (long)table_77, 0 },
+ { BITFLD, (long)table_78, 0 },
+ { MULADD, 0x646C21EB, 0x43E5 },
+ { XOR, 0x71DC4898, 0 },
+ { XOR, 0x167519CB, 0 },
+ { XOR, 0x6D3158F8, 0 },
+ { XOR, 0x7EA95BEA, 0 },
+ { BITFLD, (long)table_79, 0 },
+ { XOR, 0x47377587, 0 },
+ { XOR, 0x2D8B6E8F, 0 },
+ { MULADD, 0x5E6105DB, 0x1605 },
+ { XOR, 0x65B543C8, 0 },
+ { LOOKUP, (long)table_80, 0 },
+ { BITFLD, (long)table_81, 0 },
+ { MULADD, 0x48AF73CB, 0x0A67 },
+ { XOR, 0x4FB96154, 0 },
+ { LOOKUP, (long)table_82, 0 },
+ { BITFLD, (long)table_83, 0 },
+ { XOR, 0x622C4954, 0 },
+ { BITFLD, (long)table_84, 0 },
+ { XOR, 0x20D220F3, 0 },
+ { XOR, 0x361D4F0D, 0 },
+ { XOR, 0x2B2000D1, 0 },
+ { XOR, 0x6FB8593E, 0 },
+ { LOOKUP, (long)table_85, 0 },
+ { BITFLD, (long)table_86, 0 },
+ { XOR, 0x2B7F7DFC, 0 },
+ { MULADD, 0x5FC41A57, 0x0693 },
+ { MULADD, 0x17154387, 0x2489 },
+ { BITFLD, (long)table_87, 0 },
+ { BITFLD, (long)table_88, 0 },
+ { BITFLD, (long)table_89, 0 },
+ { LOOKUP, (long)table_90, 0 },
+ { XOR, 0x7E221470, 0 },
+ { XOR, 0x7A600061, 0 },
+ { BITFLD, (long)table_91, 0 },
+ { BITFLD, (long)table_92, 0 },
+ { LOOKUP, (long)table_93, 0 },
+ { BITFLD, (long)table_94, 0 },
+ { MULADD, 0x00E813A5, 0x2CE5 },
+ { MULADD, 0x3D707E25, 0x3827 },
+ { MULADD, 0x77A53E07, 0x6A5F },
+ { BITFLD, (long)table_95, 0 },
+ { LOOKUP, (long)table_96, 0 },
+ { LOOKUP, (long)table_97, 0 },
+ { XOR, 0x43A73788, 0 },
+ { LOOKUP, (long)table_98, 0 },
+ { BITFLD, (long)table_99, 0 },
+ { LOOKUP, (long)table_100, 0 },
+ { XOR, 0x55F4606B, 0 },
+ { BITFLD, (long)table_101, 0 }},
+ {{ BITFLD, (long)table_102, 0 },
+ { MULADD, 0x32CA58E3, 0x04F9 },
+ { XOR, 0x11756B30, 0 },
+ { MULADD, 0x218B2569, 0x5DB1 },
+ { XOR, 0x77D64B90, 0 },
+ { BITFLD, (long)table_103, 0 },
+ { LOOKUP, (long)table_104, 0 },
+ { MULADD, 0x7D1428CB, 0x3D },
+ { XOR, 0x6F872C49, 0 },
+ { XOR, 0x2E484655, 0 },
+ { MULADD, 0x1E3349F7, 0x41F5 },
+ { LOOKUP, (long)table_105, 0 },
+ { BITFLD, (long)table_106, 0 },
+ { XOR, 0x61640311, 0 },
+ { BITFLD, (long)table_107, 0 },
+ { LOOKUP, (long)table_108, 0 },
+ { LOOKUP, (long)table_109, 0 },
+ { LOOKUP, (long)table_110, 0 },
+ { XOR, 0x007044D3, 0 },
+ { BITFLD, (long)table_111, 0 },
+ { MULADD, 0x5C221625, 0x576F },
+ { LOOKUP, (long)table_112, 0 },
+ { LOOKUP, (long)table_113, 0 },
+ { XOR, 0x2D406BB1, 0 },
+ { MULADD, 0x680B1F17, 0x12CD },
+ { BITFLD, (long)table_114, 0 },
+ { MULADD, 0x12564D55, 0x32B9 },
+ { MULADD, 0x21A67897, 0x6BAB },
+ { LOOKUP, (long)table_115, 0 },
+ { MULADD, 0x06405119, 0x7143 },
+ { XOR, 0x351D01ED, 0 },
+ { MULADD, 0x46356F6B, 0x0A49 },
+ { MULADD, 0x32C77969, 0x72F3 },
+ { BITFLD, (long)table_116, 0 },
+ { LOOKUP, (long)table_117, 0 },
+ { LOOKUP, (long)table_118, 0 },
+ { BITFLD, (long)table_119, 0 },
+ { LOOKUP, (long)table_120, 0 },
+ { BITFLD, (long)table_121, 0 },
+ { MULADD, 0x74D52C55, 0x5F43 },
+ { XOR, 0x26201CA8, 0 },
+ { XOR, 0x7AEB3255, 0 },
+ { LOOKUP, (long)table_122, 0 },
+ { MULADD, 0x578F1047, 0x640B },
+ { LOOKUP, (long)table_123, 0 },
+ { LOOKUP, (long)table_124, 0 },
+ { BITFLD, (long)table_125, 0 },
+ { BITFLD, (long)table_126, 0 },
+ { XOR, 0x4A1352CF, 0 },
+ { MULADD, 0x4BFB6EF3, 0x704F },
+ { MULADD, 0x1B4C7FE7, 0x5637 },
+ { MULADD, 0x04091A3B, 0x4917 },
+ { XOR, 0x270C2F52, 0 },
+ { LOOKUP, (long)table_127, 0 },
+ { BITFLD, (long)table_128, 0 },
+ { LOOKUP, (long)table_129, 0 },
+ { BITFLD, (long)table_130, 0 },
+ { MULADD, 0x127549D5, 0x579B },
+ { MULADD, 0x0AB54121, 0x7A47 },
+ { BITFLD, (long)table_131, 0 },
+ { XOR, 0x751E6E49, 0 },
+ { LOOKUP, (long)table_132, 0 },
+ { LOOKUP, (long)table_133, 0 },
+ { XOR, 0x670C3F74, 0 },
+ { MULADD, 0x6B080851, 0x7E8B },
+ { XOR, 0x71CD789E, 0 },
+ { XOR, 0x3EB20B7B, 0 },
+ { BITFLD, (long)table_134, 0 },
+ { LOOKUP, (long)table_135, 0 },
+ { MULADD, 0x58A67753, 0x272B },
+ { MULADD, 0x1AB54AD7, 0x4D33 },
+ { MULADD, 0x07D30A45, 0x0569 },
+ { MULADD, 0x737616BF, 0x70C7 },
+ { LOOKUP, (long)table_136, 0 },
+ { MULADD, 0x45C4485D, 0x2063 },
+ { BITFLD, (long)table_137, 0 },
+ { XOR, 0x2598043D, 0 },
+ { MULADD, 0x223A4FE3, 0x49A7 },
+ { XOR, 0x1EED619F, 0 },
+ { BITFLD, (long)table_138, 0 },
+ { XOR, 0x6F477561, 0 },
+ { BITFLD, (long)table_139, 0 },
+ { BITFLD, (long)table_140, 0 },
+ { LOOKUP, (long)table_141, 0 },
+ { MULADD, 0x4BC13C4F, 0x45C1 },
+ { XOR, 0x3B547BFB, 0 },
+ { LOOKUP, (long)table_142, 0 },
+ { MULADD, 0x71406AB3, 0x7A5F },
+ { XOR, 0x2F1467E9, 0 },
+ { MULADD, 0x009366D1, 0x22D1 },
+ { MULADD, 0x587D1B75, 0x2CA5 },
+ { MULADD, 0x213A4BE7, 0x4499 },
+ { MULADD, 0x62653E89, 0x2D5D },
+ { BITFLD, (long)table_143, 0 },
+ { MULADD, 0x4F5F3257, 0x444F },
+ { MULADD, 0x4C0E2B2B, 0x19D3 }},
+ {{ MULADD, 0x3F867B35, 0x7B3B },
+ { MULADD, 0x32D25CB1, 0x3D6D },
+ { BITFLD, (long)table_144, 0 },
+ { MULADD, 0x50FA1C51, 0x5F4F },
+ { LOOKUP, (long)table_145, 0 },
+ { XOR, 0x05FE7AF1, 0 },
+ { MULADD, 0x14067C29, 0x10C5 },
+ { LOOKUP, (long)table_146, 0 },
+ { MULADD, 0x4A5558C5, 0x271F },
+ { XOR, 0x3C0861B1, 0 },
+ { BITFLD, (long)table_147, 0 },
+ { LOOKUP, (long)table_148, 0 },
+ { MULADD, 0x18837C9D, 0x6335 },
+ { BITFLD, (long)table_149, 0 },
+ { XOR, 0x7DAB5033, 0 },
+ { LOOKUP, (long)table_150, 0 },
+ { MULADD, 0x03B87321, 0x7225 },
+ { XOR, 0x7F906745, 0 },
+ { LOOKUP, (long)table_151, 0 },
+ { BITFLD, (long)table_152, 0 },
+ { XOR, 0x21C46C2C, 0 },
+ { MULADD, 0x2B36757D, 0x028D },
+ { BITFLD, (long)table_153, 0 },
+ { LOOKUP, (long)table_154, 0 },
+ { XOR, 0x106B4A85, 0 },
+ { XOR, 0x17640F11, 0 },
+ { LOOKUP, (long)table_155, 0 },
+ { XOR, 0x69E60486, 0 },
+ { LOOKUP, (long)table_156, 0 },
+ { MULADD, 0x3782017D, 0x05BF },
+ { BITFLD, (long)table_157, 0 },
+ { LOOKUP, (long)table_158, 0 },
+ { XOR, 0x6BCA53B0, 0 },
+ { LOOKUP, (long)table_159, 0 },
+ { LOOKUP, (long)table_160, 0 },
+ { LOOKUP, (long)table_161, 0 },
+ { LOOKUP, (long)table_162, 0 },
+ { XOR, 0x0B8236E3, 0 },
+ { BITFLD, (long)table_163, 0 },
+ { MULADD, 0x5EE51C43, 0x4553 },
+ { BITFLD, (long)table_164, 0 },
+ { LOOKUP, (long)table_165, 0 },
+ { LOOKUP, (long)table_166, 0 },
+ { LOOKUP, (long)table_167, 0 },
+ { MULADD, 0x42B14C6F, 0x5531 },
+ { XOR, 0x4A2548E8, 0 },
+ { MULADD, 0x5C071D85, 0x2437 },
+ { LOOKUP, (long)table_168, 0 },
+ { MULADD, 0x29195861, 0x108B },
+ { XOR, 0x24012258, 0 },
+ { LOOKUP, (long)table_169, 0 },
+ { XOR, 0x63CC2377, 0 },
+ { XOR, 0x08D04B59, 0 },
+ { MULADD, 0x3FD30CF5, 0x7027 },
+ { XOR, 0x7C3E0478, 0 },
+ { MULADD, 0x457776B7, 0x24B3 },
+ { XOR, 0x086652BC, 0 },
+ { MULADD, 0x302F5B13, 0x371D },
+ { LOOKUP, (long)table_170, 0 },
+ { MULADD, 0x58692D47, 0x0671 },
+ { XOR, 0x6601178E, 0 },
+ { MULADD, 0x0F195B9B, 0x1369 },
+ { XOR, 0x07BA21D8, 0 },
+ { BITFLD, (long)table_171, 0 },
+ { BITFLD, (long)table_172, 0 },
+ { XOR, 0x13AC3D21, 0 },
+ { MULADD, 0x5BCF3275, 0x6E1B },
+ { MULADD, 0x62725C5B, 0x16B9 },
+ { MULADD, 0x5B950FDF, 0x2D35 },
+ { BITFLD, (long)table_173, 0 },
+ { BITFLD, (long)table_174, 0 },
+ { MULADD, 0x73BA5335, 0x1C13 },
+ { BITFLD, (long)table_175, 0 },
+ { BITFLD, (long)table_176, 0 },
+ { XOR, 0x3E144154, 0 },
+ { MULADD, 0x4EED7B27, 0x38AB },
+ { LOOKUP, (long)table_177, 0 },
+ { MULADD, 0x627C7E0F, 0x7F01 },
+ { MULADD, 0x5D7E1F73, 0x2C0F },
+ { LOOKUP, (long)table_178, 0 },
+ { MULADD, 0x55C9525F, 0x4659 },
+ { XOR, 0x3765334C, 0 },
+ { MULADD, 0x5DF66DDF, 0x7C25 },
+ { LOOKUP, (long)table_179, 0 },
+ { LOOKUP, (long)table_180, 0 },
+ { XOR, 0x16AE5776, 0 },
+ { LOOKUP, (long)table_181, 0 },
+ { LOOKUP, (long)table_182, 0 },
+ { BITFLD, (long)table_183, 0 },
+ { BITFLD, (long)table_184, 0 },
+ { LOOKUP, (long)table_185, 0 },
+ { MULADD, 0x4392327B, 0x7E0D },
+ { LOOKUP, (long)table_186, 0 },
+ { MULADD, 0x3D8B0CB5, 0x640D },
+ { MULADD, 0x32865601, 0x4D43 },
+ { BITFLD, (long)table_187, 0 }}};
+
+#define A( x ) (( x ) & 0xFF )
+#define B( x ) (( x ) >> 8 & 0xFF )
+#define C( x ) (( x ) >> 16 & 0xFF )
+#define D( x ) (( x ) >> 24 & 0xFF )
+
+int yahoo_xfrm( int table, int depth, int seed )
+{
+ const struct yahoo_fn *xfrm;
+ int i, j, z;
+ unsigned int n = seed;
+ unsigned char *arg;
+
+ for( i = 0; i < depth; i++ )
+ {
+ xfrm = &yahoo_fntable[table][n % 96];
+ switch( xfrm->type )
+ {
+ case IDENT:
+ return seed;
+ case XOR:
+ seed ^= xfrm->arg1;
+ break;
+ case MULADD:
+ seed = seed * xfrm->arg1 + xfrm->arg2;
+ break;
+ case LOOKUP:
+ arg = (unsigned char *)xfrm->arg1;
+ seed = arg[A( seed )] | arg[B( seed )] << 8 | arg[C( seed )] << 16
+ | arg[D( seed )] << 24;
+ break;
+ case BITFLD:
+ arg = (unsigned char *)xfrm->arg1;
+ for( j = 0, z = 0; j < 32; j++ )
+ z = ((( seed >> j ) & 1 ) << arg[j] ) | ( ~( 1 << arg[j] ) & z );
+ seed = z;
+ break;
+ }
+ if( depth - i == 1 )
+ return seed;
+ z = (((((( A( seed ) * 0x9E3779B1 ) ^ B( seed )) * 0x9E3779B1 )
+ ^ C( seed )) * 0x9E3779B1 ) ^ D( seed )) * 0x9E3779B1;
+ n = (((( z ^ ( z >> 8 )) >> 16 ) ^ z ) ^ ( z >> 8 )) & 0xFF;
+ seed *= 0x00010DCD;
+ }
+ return seed;
+}
diff --git a/protocols/yahoo/yahoo_fn.h b/protocols/yahoo/yahoo_fn.h
new file mode 100644
index 00000000..5400e5d0
--- /dev/null
+++ b/protocols/yahoo/yahoo_fn.h
@@ -0,0 +1,32 @@
+/*
+ * libyahoo2 - originally from gaim patches by Amatus
+ *
+ * Copyright (C) 2003-2004
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define IDENT 1 /* identify function */
+#define XOR 2 /* xor with arg1 */
+#define MULADD 3 /* multipy by arg1 then add arg2 */
+#define LOOKUP 4 /* lookup each byte in the table pointed to by arg1 */
+#define BITFLD 5 /* reorder bits according to table pointed to by arg1 */
+
+struct yahoo_fn {
+ int type;
+ long arg1, arg2;
+};
+
+int yahoo_xfrm(int table, int depth, int seed);
diff --git a/protocols/yahoo/yahoo_httplib.c b/protocols/yahoo/yahoo_httplib.c
new file mode 100644
index 00000000..6bb8923d
--- /dev/null
+++ b/protocols/yahoo/yahoo_httplib.c
@@ -0,0 +1,404 @@
+/*
+ * libyahoo2: yahoo_httplib.c
+ *
+ * Copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#if STDC_HEADERS
+# include <string.h>
+#else
+# if !HAVE_STRCHR
+# define strchr index
+# define strrchr rindex
+# endif
+char *strchr(), *strrchr();
+# if !HAVE_MEMCPY
+# define memcpy(d, s, n) bcopy ((s), (d), (n))
+# define memmove(d, s, n) bcopy ((s), (d), (n))
+# endif
+#endif
+
+#include <errno.h>
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+#include <ctype.h>
+#include "yahoo2.h"
+#include "yahoo2_callbacks.h"
+#include "yahoo_httplib.h"
+#include "yahoo_util.h"
+
+#include "yahoo_debug.h"
+#ifdef __MINGW32__
+# include <winsock2.h>
+# define snprintf _snprintf
+#endif
+
+#ifdef USE_STRUCT_CALLBACKS
+extern struct yahoo_callbacks *yc;
+#define YAHOO_CALLBACK(x) yc->x
+#else
+#define YAHOO_CALLBACK(x) x
+#endif
+
+extern enum yahoo_log_level log_level;
+
+int yahoo_tcp_readline(char *ptr, int maxlen, void *fd)
+{
+ int n, rc;
+ char c;
+
+ for (n = 1; n < maxlen; n++) {
+
+ do {
+ rc = YAHOO_CALLBACK(ext_yahoo_read) (fd, &c, 1);
+ } while (rc == -1 && (errno == EINTR || errno == EAGAIN)); /* this is bad - it should be done asynchronously */
+
+ if (rc == 1) {
+ if (c == '\r') /* get rid of \r */
+ continue;
+ *ptr = c;
+ if (c == '\n')
+ break;
+ ptr++;
+ } else if (rc == 0) {
+ if (n == 1)
+ return (0); /* EOF, no data */
+ else
+ break; /* EOF, w/ data */
+ } else {
+ return -1;
+ }
+ }
+
+ *ptr = 0;
+ return (n);
+}
+
+static int url_to_host_port_path(const char *url,
+ char *host, int *port, char *path, int *ssl)
+{
+ char *urlcopy = NULL;
+ char *slash = NULL;
+ char *colon = NULL;
+
+ /*
+ * http://hostname
+ * http://hostname/
+ * http://hostname/path
+ * http://hostname/path:foo
+ * http://hostname:port
+ * http://hostname:port/
+ * http://hostname:port/path
+ * http://hostname:port/path:foo
+ * and https:// variants of the above
+ */
+
+ if (strstr(url, "http://") == url) {
+ urlcopy = strdup(url + 7);
+ } else if (strstr(url, "https://") == url) {
+ urlcopy = strdup(url + 8);
+ *ssl = 1;
+ } else {
+ WARNING(("Weird url - unknown protocol: %s", url));
+ return 0;
+ }
+
+ slash = strchr(urlcopy, '/');
+ colon = strchr(urlcopy, ':');
+
+ if (!colon || (slash && slash < colon)) {
+ if (*ssl)
+ *port = 443;
+ else
+ *port = 80;
+ } else {
+ *colon = 0;
+ *port = atoi(colon + 1);
+ }
+
+ if (!slash) {
+ strcpy(path, "/");
+ } else {
+ strcpy(path, slash);
+ *slash = 0;
+ }
+
+ strcpy(host, urlcopy);
+
+ FREE(urlcopy);
+
+ return 1;
+}
+
+static int isurlchar(unsigned char c)
+{
+ return (isalnum(c));
+}
+
+char *yahoo_urlencode(const char *instr)
+{
+ int ipos = 0, bpos = 0;
+ char *str = NULL;
+ int len = strlen(instr);
+
+ if (!(str = y_new(char, 3 *len + 1)))
+ return "";
+
+ while (instr[ipos]) {
+ while (isurlchar(instr[ipos]))
+ str[bpos++] = instr[ipos++];
+ if (!instr[ipos])
+ break;
+
+ snprintf(&str[bpos], 4, "%%%02x", instr[ipos] & 0xff);
+ bpos += 3;
+ ipos++;
+ }
+ str[bpos] = '\0';
+
+ /* free extra alloc'ed mem. */
+ len = strlen(str);
+ str = y_renew(char, str, len + 1);
+
+ return (str);
+}
+
+char *yahoo_urldecode(const char *instr)
+{
+ int ipos = 0, bpos = 0;
+ char *str = NULL;
+ char entity[3] = { 0, 0, 0 };
+ unsigned dec;
+ int len = strlen(instr);
+
+ if (!(str = y_new(char, len + 1)))
+ return "";
+
+ while (instr[ipos]) {
+ while (instr[ipos] && instr[ipos] != '%')
+ if (instr[ipos] == '+') {
+ str[bpos++] = ' ';
+ ipos++;
+ } else
+ str[bpos++] = instr[ipos++];
+ if (!instr[ipos])
+ break;
+
+ if (instr[ipos + 1] && instr[ipos + 2]) {
+ ipos++;
+ entity[0] = instr[ipos++];
+ entity[1] = instr[ipos++];
+ sscanf(entity, "%2x", &dec);
+ str[bpos++] = (char)dec;
+ } else {
+ str[bpos++] = instr[ipos++];
+ }
+ }
+ str[bpos] = '\0';
+
+ /* free extra alloc'ed mem. */
+ len = strlen(str);
+ str = y_renew(char, str, len + 1);
+
+ return (str);
+}
+
+char *yahoo_xmldecode(const char *instr)
+{
+ int ipos = 0, bpos = 0, epos = 0;
+ char *str = NULL;
+ char entity[4] = { 0, 0, 0, 0 };
+ char *entitymap[5][2] = {
+ {"amp;", "&"},
+ {"quot;", "\""},
+ {"lt;", "<"},
+ {"gt;", "<"},
+ {"nbsp;", " "}
+ };
+ unsigned dec;
+ int len = strlen(instr);
+
+ if (!(str = y_new(char, len + 1)))
+ return "";
+
+ while (instr[ipos]) {
+ while (instr[ipos] && instr[ipos] != '&')
+ if (instr[ipos] == '+') {
+ str[bpos++] = ' ';
+ ipos++;
+ } else
+ str[bpos++] = instr[ipos++];
+ if (!instr[ipos] || !instr[ipos + 1])
+ break;
+ ipos++;
+
+ if (instr[ipos] == '#') {
+ ipos++;
+ epos = 0;
+ while (instr[ipos] != ';')
+ entity[epos++] = instr[ipos++];
+ sscanf(entity, "%u", &dec);
+ str[bpos++] = (char)dec;
+ ipos++;
+ } else {
+ int i;
+ for (i = 0; i < 5; i++)
+ if (!strncmp(instr + ipos, entitymap[i][0],
+ strlen(entitymap[i][0]))) {
+ str[bpos++] = entitymap[i][1][0];
+ ipos += strlen(entitymap[i][0]);
+ break;
+ }
+ }
+ }
+ str[bpos] = '\0';
+
+ /* free extra alloc'ed mem. */
+ len = strlen(str);
+ str = y_renew(char, str, len + 1);
+
+ return (str);
+}
+
+typedef void (*http_connected) (int id, void *fd, int error);
+
+struct callback_data {
+ int id;
+ yahoo_get_fd_callback callback;
+ char *request;
+ void *user_data;
+};
+
+static void connect_complete(void *fd, int error, void *data)
+{
+ struct callback_data *ccd = data;
+ if (error == 0)
+ YAHOO_CALLBACK(ext_yahoo_write) (fd, ccd->request,
+ strlen(ccd->request));
+ free(ccd->request);
+ ccd->callback(ccd->id, fd, error, ccd->user_data);
+ FREE(ccd);
+}
+
+static void yahoo_send_http_request(int id, char *host, int port, char *request,
+ yahoo_get_fd_callback callback, void *data, int use_ssl)
+{
+ struct callback_data *ccd = y_new0(struct callback_data, 1);
+ ccd->callback = callback;
+ ccd->id = id;
+ ccd->request = strdup(request);
+ ccd->user_data = data;
+
+ YAHOO_CALLBACK(ext_yahoo_connect_async) (id, host, port,
+ connect_complete, ccd, use_ssl);
+}
+
+void yahoo_http_post(int id, const char *url, const char *cookies,
+ long content_length, yahoo_get_fd_callback callback, void *data)
+{
+ char host[255];
+ int port = 80;
+ char path[255];
+ char buff[1024];
+ int ssl = 0;
+
+ if (!url_to_host_port_path(url, host, &port, path, &ssl))
+ return;
+
+ /* thanks to kopete dumpcap */
+ snprintf(buff, sizeof(buff),
+ "POST %s HTTP/1.1\r\n"
+ "Cookie: %s\r\n"
+ "User-Agent: Mozilla/5.0\r\n"
+ "Host: %s\r\n"
+ "Content-Length: %ld\r\n"
+ "Cache-Control: no-cache\r\n"
+ "\r\n", path, cookies, host, content_length);
+
+ yahoo_send_http_request(id, host, port, buff, callback, data, ssl);
+}
+
+void yahoo_http_get(int id, const char *url, const char *cookies, int http11,
+ int keepalive, yahoo_get_fd_callback callback, void *data)
+{
+ char host[255];
+ int port = 80;
+ char path[255];
+ char buff[2048];
+ char cookiebuff[1024];
+ int ssl = 0;
+
+ if (!url_to_host_port_path(url, host, &port, path, &ssl))
+ return;
+
+ /* Allow cases when we don't need to send a cookie */
+ if (cookies)
+ snprintf(cookiebuff, sizeof(cookiebuff), "Cookie: %s\r\n",
+ cookies);
+ else
+ cookiebuff[0] = '\0';
+
+ snprintf(buff, sizeof(buff),
+ "GET %s HTTP/1.%s\r\n"
+ "%sHost: %s\r\n"
+ "User-Agent: Mozilla/4.5 [en] (" PACKAGE "/" VERSION ")\r\n"
+ "Accept: */*\r\n"
+ "%s" "\r\n", path, http11?"1":"0", cookiebuff, host,
+ keepalive? "Connection: Keep-Alive\r\n":"Connection: close\r\n");
+
+ yahoo_send_http_request(id, host, port, buff, callback, data, ssl);
+}
+
+void yahoo_http_head(int id, const char *url, const char *cookies, int len,
+ char *payload, yahoo_get_fd_callback callback, void *data)
+{
+ char host[255];
+ int port = 80;
+ char path[255];
+ char buff[2048];
+ char cookiebuff[1024];
+ int ssl = 0;
+
+ if (!url_to_host_port_path(url, host, &port, path, &ssl))
+ return;
+
+ /* Allow cases when we don't need to send a cookie */
+ if (cookies)
+ snprintf(cookiebuff, sizeof(cookiebuff), "Cookie: %s\r\n",
+ cookies);
+ else
+ cookiebuff[0] = '\0';
+
+ snprintf(buff, sizeof(buff),
+ "HEAD %s HTTP/1.0\r\n"
+ "Accept: */*\r\n"
+ "Host: %s:%d\r\n"
+ "User-Agent: Mozilla/4.5 [en] (" PACKAGE "/" VERSION ")\r\n"
+ "%s"
+ "Content-Length: %d\r\n"
+ "Cache-Control: no-cache\r\n"
+ "\r\n%s", path, host, port, cookiebuff, len,
+ payload?payload:"");
+
+ yahoo_send_http_request(id, host, port, buff, callback, data, ssl);
+}
+
diff --git a/protocols/yahoo/yahoo_httplib.h b/protocols/yahoo/yahoo_httplib.h
new file mode 100644
index 00000000..ab699b20
--- /dev/null
+++ b/protocols/yahoo/yahoo_httplib.h
@@ -0,0 +1,48 @@
+/*
+ * libyahoo2: yahoo_httplib.h
+ *
+ * Copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef YAHOO_HTTPLIB_H
+#define YAHOO_HTTPLIB_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "yahoo2_types.h"
+
+ char *yahoo_urlencode(const char *instr);
+ char *yahoo_urldecode(const char *instr);
+ char *yahoo_xmldecode(const char *instr);
+
+ int yahoo_tcp_readline(char *ptr, int maxlen, void *fd);
+ void yahoo_http_post(int id, const char *url, const char *cookies,
+ long size, yahoo_get_fd_callback callback, void *data);
+ void yahoo_http_get(int id, const char *url, const char *cookies,
+ int http11, int keepalive, yahoo_get_fd_callback callback,
+ void *data);
+ void yahoo_http_head(int id, const char *url, const char *cookies,
+ int size, char *payload, yahoo_get_fd_callback callback,
+ void *data);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/protocols/yahoo/yahoo_list.h b/protocols/yahoo/yahoo_list.h
new file mode 100644
index 00000000..c2e5ad18
--- /dev/null
+++ b/protocols/yahoo/yahoo_list.h
@@ -0,0 +1,48 @@
+/*
+ * yahoo_list.h: linked list routines
+ *
+ * Some code copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.net>
+ * Other code copyright Meredydd Luff <meredydd AT everybuddy.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef __YLIST_H__
+#define __YLIST_H__
+
+/* BitlBee already uses GLib so use it. */
+
+typedef GList YList;
+
+#define y_list_append g_list_append
+#define y_list_concat g_list_concat
+#define y_list_copy g_list_copy
+#define y_list_empty g_list_empty
+#define y_list_find g_list_find
+#define y_list_find_custom g_list_find_custom
+#define y_list_foreach g_list_foreach
+#define y_list_free g_list_free
+#define y_list_free_1 g_list_free_1
+#define y_list_insert_sorted g_list_insert_sorted
+#define y_list_length g_list_length
+#define y_list_next g_list_next
+#define y_list_nth g_list_nth
+#define y_list_prepend g_list_prepend
+#define y_list_remove g_list_remove
+#define y_list_remove_link g_list_remove_link
+#define y_list_singleton g_list_singleton
+
+#endif
diff --git a/protocols/yahoo/yahoo_util.c b/protocols/yahoo/yahoo_util.c
new file mode 100644
index 00000000..33a12674
--- /dev/null
+++ b/protocols/yahoo/yahoo_util.c
@@ -0,0 +1,107 @@
+/*
+ * libyahoo2: yahoo_util.c
+ *
+ * Copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#if STDC_HEADERS
+# include <string.h>
+#else
+# if !HAVE_STRCHR
+# define strchr index
+# define strrchr rindex
+# endif
+char *strchr (), *strrchr ();
+# if !HAVE_MEMCPY
+# define memcpy(d, s, n) bcopy ((s), (d), (n))
+# define memmove(d, s, n) bcopy ((s), (d), (n))
+# endif
+#endif
+
+#include "yahoo_util.h"
+
+char *y_string_append(char *string, char *append)
+{
+ int size = strlen(string) + strlen(append) + 1;
+ char *new_string = y_renew(char, string, size);
+
+ if (new_string == NULL) {
+ new_string = y_new(char, size);
+ strcpy(new_string, string);
+ FREE(string);
+ }
+
+ strcat(new_string, append);
+
+ return new_string;
+}
+
+#if !HAVE_GLIB
+
+void y_strfreev(char ** vector)
+{
+ char **v;
+ for(v = vector; *v; v++) {
+ FREE(*v);
+ }
+ FREE(vector);
+}
+
+char ** y_strsplit(char * str, char * sep, int nelem)
+{
+ char ** vector;
+ char *s, *p;
+ int i=0;
+ int l = strlen(sep);
+ if(nelem <= 0) {
+ char * s;
+ nelem=0;
+ if (*str) {
+ for(s=strstr(str, sep); s; s=strstr(s+l, sep),nelem++)
+ ;
+ if(strcmp(str+strlen(str)-l, sep))
+ nelem++;
+ }
+ }
+
+ vector = y_new(char *, nelem + 1);
+
+ for(p=str, s=strstr(p,sep); i<nelem && s; p=s+l, s=strstr(p,sep), i++) {
+ int len = s-p;
+ vector[i] = y_new(char, len+1);
+ strncpy(vector[i], p, len);
+ vector[i][len] = '\0';
+ }
+
+ if(i<nelem && *str) /* str didn't end with sep, and str isn't empty */
+ vector[i++] = strdup(p);
+
+ vector[i] = NULL;
+
+ return vector;
+}
+
+void * y_memdup(const void * addr, int n)
+{
+ void * new_chunk = malloc(n);
+ if(new_chunk)
+ memcpy(new_chunk, addr, n);
+ return new_chunk;
+}
+
+#endif
diff --git a/protocols/yahoo/yahoo_util.h b/protocols/yahoo/yahoo_util.h
new file mode 100644
index 00000000..8cb721c1
--- /dev/null
+++ b/protocols/yahoo/yahoo_util.h
@@ -0,0 +1,104 @@
+/*
+ * libyahoo2: yahoo_util.h
+ *
+ * Copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef __YAHOO_UTIL_H__
+#define __YAHOO_UTIL_H__
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#if HAVE_GLIB
+# include <glib.h>
+
+# define FREE(x) if(x) {g_free(x); x=NULL;}
+
+# define y_new g_new
+# define y_new0 g_new0
+# define y_renew g_renew
+
+# define y_memdup g_memdup
+# define y_strsplit g_strsplit
+# define y_strfreev g_strfreev
+# ifndef strdup
+# define strdup g_strdup
+# endif
+# ifndef strncasecmp
+# define strncasecmp g_strncasecmp
+# define strcasecmp g_strcasecmp
+# endif
+
+# define snprintf g_snprintf
+# define vsnprintf g_vsnprintf
+
+#else
+
+# include <stdlib.h>
+# include <stdarg.h>
+
+# define FREE(x) if(x) {free(x); x=NULL;}
+
+# define y_new(type, n) (type *)malloc(sizeof(type) * (n))
+# define y_new0(type, n) (type *)calloc((n), sizeof(type))
+# define y_renew(type, mem, n) (type *)realloc(mem, n)
+
+void *y_memdup(const void *addr, int n);
+char **y_strsplit(char *str, char *sep, int nelem);
+void y_strfreev(char **vector);
+
+#ifndef _WIN32
+int strncasecmp(const char *s1, const char *s2, size_t n);
+int strcasecmp(const char *s1, const char *s2);
+
+char *strdup(const char *s);
+
+int snprintf(char *str, size_t size, const char *format, ...);
+int vsnprintf(char *str, size_t size, const char *format, va_list ap);
+#endif
+
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef MIN
+#define MIN(x,y) ((x)<(y)?(x):(y))
+#endif
+
+#ifndef MAX
+#define MAX(x,y) ((x)>(y)?(x):(y))
+#endif
+
+/*
+ * The following three functions return newly allocated memory.
+ * You must free it yourself
+ */
+char *y_string_append(char *str, char *append);
+char *y_str_to_utf8(const char *in);
+char *y_utf8_to_str(const char *in);
+
+#endif
+