diff options
Diffstat (limited to 'protocols')
57 files changed, 11593 insertions, 3861 deletions
diff --git a/protocols/Makefile b/protocols/Makefile index 18d79e8d..1c7816bc 100644 --- a/protocols/Makefile +++ b/protocols/Makefile @@ -7,9 +7,13 @@ ### DEFINITIONS -include ../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/ +endif # [SH] Program variables -objects = nogaim.o +objects = account.o bee.o bee_chat.o bee_ft.o bee_user.o nogaim.o + # [SH] The next two lines should contain the directory name (in $(subdirs)) # and the name of the object file, which should be linked into @@ -20,7 +24,6 @@ subdirobjs = $(PROTOOBJS) # Expansion of variables subdirobjs := $(join $(subdirs),$(addprefix /,$(subdirobjs))) -CFLAGS += -Wall LFLAGS += -r # [SH] Phony targets @@ -48,6 +51,6 @@ protocols.o: $(objects) $(subdirs) $(objects): ../Makefile.settings Makefile -$(objects): %.o: %.c +$(objects): %.o: $(SRCDIR)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $< -o $@ diff --git a/protocols/account.c b/protocols/account.c new file mode 100644 index 00000000..50b7be8e --- /dev/null +++ b/protocols/account.c @@ -0,0 +1,444 @@ + /********************************************************************\ + * 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; + + 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; + } +} + +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 ); +} + +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 ); + } +} + +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..c5eeee17 --- /dev/null +++ b/protocols/bee.c @@ -0,0 +1,95 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Some IM-core stuff */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" + +static char *set_eval_away_status( set_t *set, char *value ); + +bee_t *bee_new() +{ + bee_t *b = g_new0( bee_t, 1 ); + set_t *s; + + s = set_add( &b->set, "away", NULL, set_eval_away_status, b ); + s->flags |= SET_NULL_OK; + s = set_add( &b->set, "auto_connect", "true", set_eval_bool, b ); + s = set_add( &b->set, "auto_reconnect", "true", set_eval_bool, b ); + s = set_add( &b->set, "auto_reconnect_delay", "5*3<900", set_eval_account_reconnect_delay, b ); + s = set_add( &b->set, "debug", "false", set_eval_bool, b ); + s = set_add( &b->set, "save_on_quit", "true", set_eval_bool, b ); + s = set_add( &b->set, "status", NULL, set_eval_away_status, b ); + s->flags |= SET_NULL_OK; + s = set_add( &b->set, "strip_html", "true", NULL, b ); + + b->user = g_malloc( 1 ); + + return b; +} + +void bee_free( bee_t *b ) +{ + while( b->accounts ) + { + if( b->accounts->ic ) + imc_logout( b->accounts->ic, FALSE ); + else if( b->accounts->reconnect ) + cancel_auto_reconnect( b->accounts ); + + if( b->accounts->ic == NULL ) + account_del( b, b->accounts ); + else + /* Nasty hack, but account_del() doesn't work in this + case and we don't want infinite loops, do we? ;-) */ + b->accounts = b->accounts->next; + } + + while( b->set ) + set_del( &b->set, b->set->key ); + + bee_group_free( b ); + + g_free( b->user ); + g_free( b ); +} + +static char *set_eval_away_status( set_t *set, char *value ) +{ + bee_t *bee = set->data; + account_t *a; + + g_free( set->value ); + set->value = g_strdup( value ); + + for( a = bee->accounts; a; a = a->next ) + { + struct im_connection *ic = a->ic; + + if( ic && ic->flags & OPT_LOGGED_IN ) + imc_away_send_update( ic ); + } + + return value; +} diff --git a/protocols/bee.h b/protocols/bee.h new file mode 100644 index 00000000..5792e988 --- /dev/null +++ b/protocols/bee.h @@ -0,0 +1,176 @@ + /********************************************************************\ + * 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 ); + +typedef enum +{ + BEE_USER_ONLINE = 1, /* Compatibility with old OPT_LOGGED_IN flag */ + BEE_USER_AWAY = 4, /* Compatibility with old OPT_AWAY flag */ + BEE_USER_LOCAL = 256, /* Locally-added contacts (not in real contact list) */ +} bee_user_flags_t; + +typedef struct bee_user +{ + struct im_connection *ic; + char *handle; + char *fullname; + char *nick; + struct bee_group *group; + + bee_user_flags_t flags; + char *status; /* 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 ); + + /* 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 ); + + struct file_transfer* (*ft_in_start)( bee_t *bee, bee_user_t *bu, const char *file_name, size_t file_size ); + gboolean (*ft_out_start)( struct im_connection *ic, struct file_transfer *ft ); + void (*ft_close)( struct im_connection *ic, struct file_transfer *ft ); + void (*ft_finished)( struct im_connection *ic, struct file_transfer *ft ); +} bee_ui_funcs_t; + + +/* bee.c */ +bee_t *bee_new(); +void bee_free( bee_t *b ); + +/* bee_user.c */ +bee_user_t *bee_user_new( bee_t *bee, struct im_connection *ic, const char *handle, bee_user_flags_t flags ); +int bee_user_free( bee_t *bee, bee_user_t *bu ); +bee_user_t *bee_user_by_handle( bee_t *bee, struct im_connection *ic, const char *handle ); +int bee_user_msg( bee_t *bee, bee_user_t *bu, const char *msg, int flags ); +bee_group_t *bee_group_by_name( bee_t *bee, const char *name, gboolean creat ); +void bee_group_free( bee_t *bee ); + +/* Callbacks from IM modules to core: */ +/* Buddy activity */ +/* To manipulate the status of a handle. + * - flags can be |='d with OPT_* constants. You will need at least: + * OPT_LOGGED_IN and OPT_AWAY. + * - 'state' and 'message' can be NULL */ +G_MODULE_EXPORT void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags, const char *state, const char *message ); +G_MODULE_EXPORT void imcb_buddy_times( struct im_connection *ic, const char *handle, time_t login, time_t idle ); +/* Call when a handle says something. 'flags' and 'sent_at may be just 0. */ +G_MODULE_EXPORT void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, guint32 flags, time_t sent_at ); + +/* bee_chat.c */ +/* 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 ); + +#endif /* __BEE_H__ */ diff --git a/protocols/bee_chat.c b/protocols/bee_chat.c new file mode 100644 index 00000000..3be6f189 --- /dev/null +++ b/protocols/bee_chat.c @@ -0,0 +1,234 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Stuff to handle rooms */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" + +struct groupchat *imcb_chat_new( struct im_connection *ic, const char *handle ) +{ + struct groupchat *c = g_new0( struct groupchat, 1 ); + bee_t *bee = ic->bee; + + /* This one just creates the conversation structure, user won't see + anything yet until s/he is joined to the conversation. (This + allows you to add other already present participants first.) */ + + ic->groupchats = g_slist_prepend( ic->groupchats, c ); + c->ic = ic; + c->title = g_strdup( handle ); + c->topic = g_strdup_printf( "BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", c->title ); + + if( set_getbool( &ic->bee->set, "debug" ) ) + imcb_log( ic, "Creating new conversation: (id=%p,handle=%s)", c, handle ); + + if( bee->ui->chat_new ) + bee->ui->chat_new( bee, c ); + + return c; +} + +void imcb_chat_name_hint( struct groupchat *c, const char *name ) +{ + bee_t *bee = c->ic->bee; + + if( bee->ui->chat_name_hint ) + bee->ui->chat_name_hint( bee, c, name ); +} + +void imcb_chat_free( struct groupchat *c ) +{ + struct im_connection *ic = c->ic; + bee_t *bee = ic->bee; + GList *ir; + + if( bee->ui->chat_free ) + bee->ui->chat_free( bee, c ); + + if( set_getbool( &ic->bee->set, "debug" ) ) + imcb_log( ic, "You were removed from conversation %p", c ); + + ic->groupchats = g_slist_remove( ic->groupchats, c ); + + for( ir = c->in_room; ir; ir = ir->next ) + g_free( ir->data ); + g_list_free( c->in_room ); + g_free( c->title ); + g_free( c->topic ); + g_free( c ); +} + +void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, uint32_t flags, time_t sent_at ) +{ + struct im_connection *ic = c->ic; + bee_t *bee = ic->bee; + bee_user_t *bu; + char *s; + + /* Gaim sends own messages through this too. IRC doesn't want this, so kill them */ + if( g_strcasecmp( who, ic->acc->user ) == 0 ) + return; + + bu = bee_user_by_handle( bee, ic, who ); + + s = set_getstr( &ic->bee->set, "strip_html" ); + if( ( g_strcasecmp( s, "always" ) == 0 ) || + ( ( ic->flags & OPT_DOES_HTML ) && s ) ) + strip_html( msg ); + + if( bu && bee->ui->chat_msg ) + bee->ui->chat_msg( bee, c, bu, msg, sent_at ); + else + imcb_chat_log( c, "Message from unknown participant %s: %s", who, msg ); +} + +void imcb_chat_log( struct groupchat *c, char *format, ... ) +{ + struct im_connection *ic = c->ic; + bee_t *bee = ic->bee; + va_list params; + char *text; + + if( !bee->ui->chat_log ) + return; + + va_start( params, format ); + text = g_strdup_vprintf( format, params ); + va_end( params ); + + bee->ui->chat_log( bee, c, text ); + g_free( text ); +} + +void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at ) +{ + struct im_connection *ic = c->ic; + bee_t *bee = ic->bee; + bee_user_t *bu; + + if( !bee->ui->chat_topic ) + return; + + if( who == NULL) + bu = NULL; + else if( g_strcasecmp( who, ic->acc->user ) == 0 ) + bu = bee->user; + else + bu = bee_user_by_handle( bee, ic, who ); + + if( ( g_strcasecmp( set_getstr( &ic->bee->set, "strip_html" ), "always" ) == 0 ) || + ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->bee->set, "strip_html" ) ) ) + strip_html( topic ); + + bee->ui->chat_topic( bee, c, topic, bu ); +} + +void imcb_chat_add_buddy( struct groupchat *c, const char *handle ) +{ + struct im_connection *ic = c->ic; + bee_t *bee = ic->bee; + bee_user_t *bu = bee_user_by_handle( bee, ic, handle ); + gboolean me; + + if( set_getbool( &c->ic->bee->set, "debug" ) ) + imcb_log( c->ic, "User %s added to conversation %p", handle, c ); + + me = ic->acc->prpl->handle_cmp( handle, ic->acc->user ) == 0; + + /* Most protocols allow people to join, even when they're not in + your contact list. Try to handle that here */ + if( !me && !bu ) + bu = bee_user_new( bee, ic, handle, BEE_USER_LOCAL ); + + /* Add the handle to the room userlist */ + /* TODO: Use bu instead of a string */ + c->in_room = g_list_append( c->in_room, g_strdup( handle ) ); + + if( bee->ui->chat_add_user ) + bee->ui->chat_add_user( bee, c, me ? bee->user : bu ); + + if( me ) + c->joined = 1; +} + +void imcb_chat_remove_buddy( struct groupchat *c, const char *handle, const char *reason ) +{ + struct im_connection *ic = c->ic; + bee_t *bee = ic->bee; + bee_user_t *bu = NULL; + + if( set_getbool( &bee->set, "debug" ) ) + imcb_log( ic, "User %s removed from conversation %p (%s)", handle, c, reason ? reason : "" ); + + /* It might be yourself! */ + if( g_strcasecmp( handle, ic->acc->user ) == 0 ) + { + if( c->joined == 0 ) + return; + + bu = bee->user; + c->joined = 0; + } + else + { + bu = bee_user_by_handle( bee, ic, handle ); + } + + if( bee->ui->chat_remove_user ) + bee->ui->chat_remove_user( bee, c, bu ); +} + +int bee_chat_msg( bee_t *bee, struct groupchat *c, const char *msg, int flags ) +{ + struct im_connection *ic = c->ic; + char *buf = NULL; + + if( ( ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<html>", 6 ) != 0 ) ) + { + buf = escape_html( msg ); + msg = buf; + } + else + buf = g_strdup( msg ); + + ic->acc->prpl->chat_msg( c, buf, flags ); + g_free( buf ); + + return 1; +} + +struct groupchat *bee_chat_by_title( bee_t *bee, struct im_connection *ic, const char *title ) +{ + struct groupchat *c; + GSList *l; + + for( l = ic->groupchats; l; l = l->next ) + { + c = l->data; + if( strcmp( c->title, title ) == 0 ) + return c; + } + + return NULL; +} diff --git a/protocols/bee_ft.c b/protocols/bee_ft.c new file mode 100644 index 00000000..1026eab3 --- /dev/null +++ b/protocols/bee_ft.c @@ -0,0 +1,66 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2010 Wilmer van der Gaast <wilmer@gaast.net> * +\********************************************************************/ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" +#include "ft.h" + +file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *handle, char *file_name, size_t file_size ) +{ + bee_t *bee = ic->bee; + bee_user_t *bu = bee_user_by_handle( bee, ic, handle ); + + if( bee->ui->ft_in_start ) + return bee->ui->ft_in_start( bee, bu, file_name, file_size ); + else + return NULL; +} + +gboolean imcb_file_recv_start( struct im_connection *ic, file_transfer_t *ft ) +{ + bee_t *bee = ic->bee; + + if( bee->ui->ft_out_start ) + return bee->ui->ft_out_start( ic, ft ); + else + return FALSE; +} + +void imcb_file_canceled( struct im_connection *ic, file_transfer_t *file, char *reason ) +{ + bee_t *bee = ic->bee; + + if( file->canceled ) + file->canceled( file, reason ); + + if( bee->ui->ft_close ) + bee->ui->ft_close( ic, file ); +} + +void imcb_file_finished( struct im_connection *ic, file_transfer_t *file ) +{ + bee_t *bee = ic->bee; + + if( bee->ui->ft_finished ) + bee->ui->ft_finished( ic, file ); +} diff --git a/protocols/bee_user.c b/protocols/bee_user.c new file mode 100644 index 00000000..5acd5b83 --- /dev/null +++ b/protocols/bee_user.c @@ -0,0 +1,252 @@ + /********************************************************************\ + * 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 = g_strdup( ( flags & OPT_AWAY ) && state == NULL ? "Away" : state ); + bu->status_msg = g_strdup( message ); + + if( bee->ui->user_status ) + bee->ui->user_status( bee, bu, old ); + + g_free( old->status_msg ); + g_free( old->status ); + g_free( old ); +} + +void imcb_buddy_times( struct im_connection *ic, const char *handle, time_t login, time_t idle ) +{ + bee_t *bee = ic->bee; + bee_user_t *bu; + + if( !( bu = bee_user_by_handle( bee, ic, handle ) ) ) + return; + + bu->login_time = login; + bu->idle_time = idle; +} + +void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, uint32_t flags, time_t sent_at ) +{ + bee_t *bee = ic->bee; + bee_user_t *bu; + + bu = bee_user_by_handle( bee, ic, handle ); + + if( !bu ) + { + char *h = set_getstr( &bee->set, "handle_unknown" ); + + if( g_strcasecmp( h, "ignore" ) == 0 ) + { + return; + } + else if( g_strncasecmp( h, "add", 3 ) == 0 ) + { + bu = bee_user_new( bee, ic, handle, BEE_USER_LOCAL ); + } + } + + if( ( g_strcasecmp( set_getstr( &ic->bee->set, "strip_html" ), "always" ) == 0 ) || + ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->bee->set, "strip_html" ) ) ) + strip_html( msg ); + + if( bee->ui->user_msg && bu ) + bee->ui->user_msg( bee, bu, msg, sent_at ); + else + imcb_log( ic, "Message from unknown handle %s:\n%s", handle, msg ); +} + +void imcb_buddy_typing( struct im_connection *ic, char *handle, uint32_t flags ) +{ + bee_user_t *bu; + + if( ic->bee->ui->user_typing && + ( bu = bee_user_by_handle( ic->bee, ic, handle ) ) ) + { + ic->bee->ui->user_typing( ic->bee, bu, flags ); + } +} diff --git a/protocols/ft.h b/protocols/ft.h new file mode 100644 index 00000000..159f16f2 --- /dev/null +++ b/protocols/ft.h @@ -0,0 +1,176 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2006 Marijn Kruisselbrink and others * +\********************************************************************/ + +/* Generic file transfer header */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _FT_H +#define _FT_H + +/* + * One buffer is needed for each transfer. The receiver stores a message + * in it and gives it to the sender. The sender will stall the receiver + * till the buffer has been sent out. + */ +#define FT_BUFFER_SIZE 2048 + +typedef enum { + FT_STATUS_LISTENING = 1, + FT_STATUS_TRANSFERRING = 2, + FT_STATUS_FINISHED = 4, + FT_STATUS_CANCELED = 8, + FT_STATUS_CONNECTING = 16 +} file_status_t; + +/* + * This structure holds all irc specific information regarding an incoming (from the point of view of + * the irc client) file transfer. New instances of this struct should only be created by calling the + * imcb_file_send_start() method, which will initialize most of the fields. The data field and the various + * methods are zero-initialized. Instances will automatically be deleted once the transfer is completed, + * canceled, or the connection to the irc client has been lost (note that also if only the irc connection + * and not the file transfer connection is lost, the file transfer will still be canceled and freed). + * + * The following (poor ascii-art) diagram illustrates what methods are called for which status-changes: + * + * /-----------\ /----------\ + * -------> | LISTENING | -----------------> | CANCELED | + * \-----------/ [canceled,]free \----------/ + * | + * | accept + * V + * /------ /-------------\ /------------------------\ + * out_of_data | | TRANSFERING | -----------------> | TRANSFERING | CANCELED | + * \-----> \-------------/ [canceled,]free \------------------------/ + * | + * | finished,free + * V + * /------------------------\ + * | TRANSFERING | FINISHED | + * \------------------------/ + */ +typedef struct file_transfer { + + /* Are we sending something? */ + int sending; + + /* + * The current status of this file transfer. + */ + file_status_t status; + + /* + * file size + */ + size_t file_size; + + /* + * Number of bytes that have been successfully transferred. + */ + size_t bytes_transferred; + + /* + * Time started. Used to calculate kb/s. + */ + time_t started; + + /* + * file name + */ + char *file_name; + + /* + * A unique local ID for this file transfer. + */ + unsigned int local_id; + + /* + * IM-protocol specific data associated with this file transfer. + */ + gpointer data; + struct im_connection *ic; + + /* + * Private data. + */ + gpointer priv; + + /* + * If set, called after succesful connection setup. + */ + void (*accept) ( struct file_transfer *file ); + + /* + * If set, called when the transfer is canceled or finished. + * Subsequently, this structure will be freed. + * + */ + void (*free) ( struct file_transfer *file ); + + /* + * If set, called when the transfer is finished and successful. + */ + void (*finished) ( struct file_transfer *file ); + + /* + * If set, called when the transfer is canceled. + * ( canceled either by the transfer implementation or by + * a call to imcb_file_canceled ) + */ + void (*canceled) ( struct file_transfer *file, char *reason ); + + /* + * called by the sending side to indicate that it is writable. + * The callee should check if data is available and call the + * function(as seen below) if that is the case. + */ + gboolean (*write_request) ( struct file_transfer *file ); + + /* + * When sending files, protocols register this function to receive data. + * This should only be called once after write_request is called. The caller + * should not read more data until write_request is called again. This technique + * avoids buffering. + */ + gboolean (*write) (struct file_transfer *file, char *buffer, unsigned int len ); + + /* The send buffer associated with this transfer. + * Since receivers always wait for a write_request call one is enough. + */ + char buffer[FT_BUFFER_SIZE]; + +} file_transfer_t; + +/* + * This starts a file transfer from bitlbee to the user. + */ +file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *user_nick, char *file_name, size_t file_size ); + +/* + * This should be called by a protocol when the transfer is canceled. Note that + * the canceled() and free() callbacks given in file will be called by this function. + */ +void imcb_file_canceled( struct im_connection *ic, file_transfer_t *file, char *reason ); + +gboolean imcb_file_recv_start( struct im_connection *ic, file_transfer_t *ft ); + +void imcb_file_finished( struct im_connection *ic, file_transfer_t *file ); +#endif diff --git a/protocols/jabber/Makefile b/protocols/jabber/Makefile index e7a505ba..efbd81fb 100644 --- a/protocols/jabber/Makefile +++ b/protocols/jabber/Makefile @@ -7,11 +7,13 @@ ### DEFINITIONS -include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/jabber/ +endif # [SH] Program variables -objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o sasl.o +objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o s5bytestream.o sasl.o si.o -CFLAGS += -Wall LFLAGS += -r # [SH] Phony targets @@ -32,7 +34,7 @@ distclean: clean $(objects): ../../Makefile.settings Makefile -$(objects): %.o: %.c +$(objects): %.o: $(SRCDIR)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $< -o $@ diff --git a/protocols/jabber/conference.c b/protocols/jabber/conference.c index f434c58a..e04b9792 100644 --- a/protocols/jabber/conference.c +++ b/protocols/jabber/conference.c @@ -91,18 +91,20 @@ static xt_status jabber_chat_join_failed( struct im_connection *ic, struct xt_no struct groupchat *jabber_chat_by_jid( struct im_connection *ic, const char *name ) { char *normalized = jabber_normalize( name ); + GSList *l; struct groupchat *ret; struct jabber_chat *jc; - for( ret = ic->groupchats; ret; ret = ret->next ) + for( l = ic->groupchats; l; l = l->next ) { + ret = l->data; jc = ret->data; if( strcmp( normalized, jc->name ) == 0 ) break; } g_free( normalized ); - return ret; + return l ? ret : NULL; } void jabber_chat_free( struct groupchat *c ) @@ -271,8 +273,10 @@ void jabber_chat_pkt_presence( struct im_connection *ic, struct jabber_buddy *bu bud->flags |= JBFLAG_IS_ANONYMOUS; } - if( bud != jc->me ) + 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 ); } diff --git a/protocols/jabber/io.c b/protocols/jabber/io.c index a14ad21c..d6f92a5f 100644 --- a/protocols/jabber/io.c +++ b/protocols/jabber/io.c @@ -63,7 +63,7 @@ int jabber_write( struct im_connection *ic, char *buf, int len ) it via the event handler. If not, add the handler. (In most cases it probably won't be necessary.) */ if( ( ret = jabber_write_queue( ic ) ) && jd->tx_len > 0 ) - jd->w_inpa = b_input_add( jd->fd, GAIM_INPUT_WRITE, jabber_write_callback, ic ); + jd->w_inpa = b_input_add( jd->fd, B_EV_IO_WRITE, jabber_write_callback, ic ); } else { @@ -503,7 +503,7 @@ gboolean jabber_start_stream( struct im_connection *ic ) jd->xt = xt_new( jabber_handlers, ic ); if( jd->r_inpa <= 0 ) - jd->r_inpa = b_input_add( jd->fd, GAIM_INPUT_READ, jabber_read_callback, ic ); + jd->r_inpa = b_input_add( jd->fd, B_EV_IO_READ, jabber_read_callback, ic ); greet = g_strdup_printf( "%s<stream:stream to=\"%s\" xmlns=\"jabber:client\" " "xmlns:stream=\"http://etherx.jabber.org/streams\" version=\"1.0\">", diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c index 1b76a761..82c90d39 100644 --- a/protocols/jabber/iq.c +++ b/protocols/jabber/iq.c @@ -64,7 +64,7 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) /* Of course this is a very essential query to support. ;-) */ if( strcmp( s, XMLNS_VERSION ) == 0 ) { - xt_add_child( reply, xt_new_node( "name", "BitlBee", NULL ) ); + 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 ) ); } @@ -90,21 +90,24 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) xt_add_attr( reply, "id", s ); pack = 0; } - else if( strcmp( s, XMLNS_DISCOVER ) == 0 ) + else if( strcmp( s, XMLNS_DISCO_INFO ) == 0 ) { - const char *features[] = { XMLNS_DISCOVER, + const char *features[] = { XMLNS_DISCO_INFO, XMLNS_VERSION, XMLNS_TIME, XMLNS_CHATSTATES, XMLNS_MUC, XMLNS_PING, + XMLNS_SI, + XMLNS_BYTESTREAMS, + XMLNS_FILETRANSFER, NULL }; const char **f; c = xt_new_node( "identity", NULL, NULL ); xt_add_attr( c, "category", "client" ); xt_add_attr( c, "type", "pc" ); - xt_add_attr( c, "name", "BitlBee" ); + xt_add_attr( c, "name", set_getstr( &ic->acc->set, "user_agent" ) ); xt_add_child( reply, c ); for( f = features; *f; f ++ ) @@ -117,24 +120,28 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) else { xt_free_node( reply ); - reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel" ); + reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL ); pack = 0; } } else if( strcmp( type, "set" ) == 0 ) { - if( !( c = xt_find_node( node->children, "query" ) ) || - !( s = xt_find_attr( c, "xmlns" ) ) ) + if( ( c = xt_find_node( node->children, "si" ) ) && + ( s = xt_find_attr( c, "xmlns" ) ) && + ( strcmp( s, XMLNS_SI ) == 0 ) ) + { + return jabber_si_handle_request( ic, node, c ); + } + else if( !( c = xt_find_node( node->children, "query" ) ) || + !( s = xt_find_attr( c, "xmlns" ) ) ) { - imcb_log( ic, "Warning: Received incomplete IQ-%s packet", type ); return XT_HANDLED; } - + else if( strcmp( s, XMLNS_ROSTER ) == 0 ) + { /* This is a roster push. XMPP servers send this when someone was added to (or removed from) the buddy list. AFAIK they're sent even if we added this buddy in our own session. */ - if( strcmp( s, XMLNS_ROSTER ) == 0 ) - { int bare_len = strlen( ic->acc->user ); if( ( s = xt_find_attr( node, "from" ) ) == NULL || @@ -151,14 +158,19 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) imcb_log( ic, "Warning: %s tried to fake a roster push!", s ? s : "(unknown)" ); xt_free_node( reply ); - reply = jabber_make_error_packet( node, "not-allowed", "cancel" ); + reply = jabber_make_error_packet( node, "not-allowed", "cancel", NULL ); pack = 0; } } + else if( strcmp( s, XMLNS_BYTESTREAMS ) == 0 ) + { + /* Bytestream Request (stage 2 of file transfer) */ + return jabber_bs_recv_request( ic, node, c ); + } else { xt_free_node( reply ); - reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel" ); + reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL ); pack = 0; } } @@ -369,7 +381,7 @@ static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node * c = query->children; while( ( c = xt_find_node( c, "item" ) ) ) { - struct xt_node *group = xt_find_node( node->children, "group" ); + struct xt_node *group = xt_find_node( c->children, "group" ); char *jid = xt_find_attr( c, "jid" ); char *name = xt_find_attr( c, "name" ); char *sub = xt_find_attr( c, "subscription" ); @@ -378,9 +390,8 @@ static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node * { if( ( strcmp( sub, "both" ) == 0 || strcmp( sub, "to" ) == 0 ) ) { - if( initial || imcb_find_buddy( ic, jid ) == NULL ) - imcb_add_buddy( ic, jid, ( group && group->text_len ) ? - group->text : NULL ); + imcb_add_buddy( ic, jid, ( group && group->text_len ) ? + group->text : NULL ); if( name ) imcb_rename_buddy( ic, jid, name ); @@ -543,7 +554,7 @@ static xt_status jabber_iq_display_vcard( struct im_connection *ic, struct xt_no static xt_status jabber_add_to_roster_callback( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); -int jabber_add_to_roster( struct im_connection *ic, char *handle, char *name ) +int jabber_add_to_roster( struct im_connection *ic, const char *handle, const char *name, const char *group ) { struct xt_node *node; int st; @@ -553,6 +564,8 @@ int jabber_add_to_roster( struct im_connection *ic, char *handle, char *name ) xt_add_attr( node, "jid", handle ); if( name ) xt_add_attr( node, "name", name ); + if( group ) + xt_add_child( node, xt_new_node( "group", group, NULL ) ); /* And pack it into a roster-add packet */ node = xt_new_node( "query", NULL, node ); @@ -576,7 +589,7 @@ static xt_status jabber_add_to_roster_callback( struct im_connection *ic, struct ( s = xt_find_attr( node, "type" ) ) && strcmp( s, "result" ) == 0 ) { - if( imcb_find_buddy( ic, jid ) == NULL ) + if( bee_user_by_handle( ic->bee, ic, jid ) == NULL ) imcb_add_buddy( ic, jid, NULL ); } else @@ -608,3 +621,175 @@ int jabber_remove_from_roster( struct im_connection *ic, char *handle ) xt_free_node( node ); return st; } + +xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid ) +{ + struct xt_node *node, *query; + struct jabber_buddy *bud; + + if( ( bud = jabber_buddy_by_jid( ic, bare_jid , 0 ) ) == NULL ) + { + /* Who cares about the unknown... */ + imcb_log( ic, "Couldn't find buddy: %s", bare_jid); + return XT_HANDLED; + } + + if( bud->features ) /* been here already */ + return XT_HANDLED; + + node = xt_new_node( "query", NULL, NULL ); + xt_add_attr( node, "xmlns", XMLNS_DISCO_INFO ); + + if( !( query = jabber_make_packet( "iq", "get", bare_jid, node ) ) ) + { + imcb_log( ic, "WARNING: Couldn't generate feature query" ); + xt_free_node( node ); + return XT_HANDLED; + } + + jabber_cache_add( ic, query, jabber_iq_parse_features ); + + return jabber_write_packet( ic, query ) ? XT_HANDLED : XT_ABORT; +} + +xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + struct xt_node *c; + struct jabber_buddy *bud; + char *feature, *xmlns, *from; + + if( !( from = xt_find_attr( node, "from" ) ) || + !( c = xt_find_node( node->children, "query" ) ) || + !( xmlns = xt_find_attr( c, "xmlns" ) ) || + !( strcmp( xmlns, XMLNS_DISCO_INFO ) == 0 ) ) + { + imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" ); + return XT_HANDLED; + } + if( ( bud = jabber_buddy_by_jid( ic, from, 0 ) ) == NULL ) + { + /* Who cares about the unknown... */ + imcb_log( ic, "Couldn't find buddy: %s", from ); + return XT_HANDLED; + } + + c = c->children; + while( ( c = xt_find_node( c, "feature" ) ) ) + { + feature = xt_find_attr( c, "var" ); + if( feature ) + bud->features = g_slist_append( bud->features, g_strdup( feature ) ); + c = c->next; + } + + return XT_HANDLED; +} + +xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns ) +{ + struct xt_node *node, *query; + struct jabber_data *jd = ic->proto_data; + + node = xt_new_node( "query", NULL, NULL ); + xt_add_attr( node, "xmlns", xmlns ); + + if( !( query = jabber_make_packet( "iq", "get", jid, node ) ) ) + { + imcb_log( ic, "WARNING: Couldn't generate server query" ); + xt_free_node( node ); + } + + jd->have_streamhosts--; + jabber_cache_add( ic, query, jabber_iq_parse_server_features ); + + return jabber_write_packet( ic, query ) ? XT_HANDLED : XT_ABORT; +} + +/* + * Query the server for "items", query each "item" for identities, query each "item" that's a proxy for it's bytestream info + */ +xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + struct xt_node *c; + struct jabber_data *jd = ic->proto_data; + char *xmlns, *from; + + if( !( c = xt_find_node( node->children, "query" ) ) || + !( from = xt_find_attr( node, "from" ) ) || + !( xmlns = xt_find_attr( c, "xmlns" ) ) ) + { + imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" ); + return XT_HANDLED; + } + + jd->have_streamhosts++; + + if( strcmp( xmlns, XMLNS_DISCO_ITEMS ) == 0 ) + { + char *itemjid; + + /* answer from server */ + + c = c->children; + while( ( c = xt_find_node( c, "item" ) ) ) + { + itemjid = xt_find_attr( c, "jid" ); + + if( itemjid ) + jabber_iq_query_server( ic, itemjid, XMLNS_DISCO_INFO ); + + c = c->next; + } + } + else if( strcmp( xmlns, XMLNS_DISCO_INFO ) == 0 ) + { + char *category, *type; + + /* answer from potential proxy */ + + c = c->children; + while( ( c = xt_find_node( c, "identity" ) ) ) + { + category = xt_find_attr( c, "category" ); + type = xt_find_attr( c, "type" ); + + if( type && ( strcmp( type, "bytestreams" ) == 0 ) && + category && ( strcmp( category, "proxy" ) == 0 ) ) + jabber_iq_query_server( ic, from, XMLNS_BYTESTREAMS ); + + c = c->next; + } + } + else if( strcmp( xmlns, XMLNS_BYTESTREAMS ) == 0 ) + { + char *host, *jid, *port_s; + int port; + + /* answer from proxy */ + + if( ( c = xt_find_node( c->children, "streamhost" ) ) && + ( host = xt_find_attr( c, "host" ) ) && + ( port_s = xt_find_attr( c, "port" ) ) && + ( sscanf( port_s, "%d", &port ) == 1 ) && + ( jid = xt_find_attr( c, "jid" ) ) ) + { + jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 ); + + sh->jid = g_strdup( jid ); + sh->host = g_strdup( host ); + g_snprintf( sh->port, sizeof( sh->port ), "%u", port ); + + imcb_log( ic, "Proxy found: jid %s host %s port %u", jid, host, port ); + jd->streamhosts = g_slist_append( jd->streamhosts, sh ); + } + } + + if( jd->have_streamhosts == 0 ) + jd->have_streamhosts++; + + return XT_HANDLED; +} diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c index 86320ada..229e35bf 100644 --- a/protocols/jabber/jabber.c +++ b/protocols/jabber/jabber.c @@ -64,6 +64,8 @@ static void jabber_init( account_t *acc ) s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add( &acc->set, "priority", "0", set_eval_priority, acc ); + + s = set_add( &acc->set, "proxy", "<local>;<auto>", NULL, acc ); s = set_add( &acc->set, "resource", "BitlBee", NULL, acc ); s->flags |= ACC_SET_OFFLINE_ONLY; @@ -79,6 +81,8 @@ static void jabber_init( account_t *acc ) 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; @@ -263,11 +267,23 @@ static void jabber_logout( struct im_connection *ic ) { struct jabber_data *jd = ic->proto_data; + while( jd->filetransfers ) + imcb_file_canceled( ic, ( ( struct jabber_transfer *) jd->filetransfers->data )->ft, "Logging out" ); + + while( jd->streamhosts ) + { + jabber_streamhost_t *sh = jd->streamhosts->data; + jd->streamhosts = g_slist_remove( jd->streamhosts, sh ); + g_free( sh->jid ); + g_free( sh->host ); + g_free( sh ); + } + if( jd->fd >= 0 ) jabber_end_stream( ic ); while( ic->groupchats ) - jabber_chat_free( ic->groupchats ); + jabber_chat_free( ic->groupchats->data ); if( jd->r_inpa >= 0 ) b_event_remove( jd->r_inpa ); @@ -285,6 +301,8 @@ static void jabber_logout( struct im_connection *ic ) 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 ); @@ -362,7 +380,7 @@ static void jabber_get_info( struct im_connection *ic, char *who ) 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 ? : "(none)" ); + imcb_log( ic, "Status message: %s", bud->away_message ? bud->away_message : "(none)" ); bud = bud->next; } @@ -376,8 +394,10 @@ static void jabber_set_away( struct im_connection *ic, char *state_txt, char *me /* state_txt == NULL -> Not away. Unknown state -> fall back to the first defined away state. */ - jd->away_state = state_txt ? jabber_away_state_by_name( state_txt ) - ? : jabber_away_state_list : NULL; + 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; @@ -396,7 +416,7 @@ static void jabber_add_buddy( struct im_connection *ic, char *who, char *group ) return; } - if( jabber_add_to_roster( ic, who, NULL ) ) + if( jabber_add_to_roster( ic, who, NULL, group ) ) presence_send_request( ic, who, "subscribe" ); } @@ -422,7 +442,7 @@ static void jabber_remove_buddy( struct im_connection *ic, char *who, char *grou presence_send_request( ic, who, "unsubscribe" ); } -static struct groupchat *jabber_chat_join_( struct im_connection *ic, const char *room, const char *nick, const char *password ) +static struct groupchat *jabber_chat_join_( struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets ) { if( strchr( room, '@' ) == NULL ) imcb_error( ic, "Invalid room name: %s", room ); @@ -469,7 +489,8 @@ static void jabber_chat_invite_( struct groupchat *c, char *who, char *msg ) static void jabber_keepalive( struct im_connection *ic ) { /* Just any whitespace character is enough as a keepalive for XMPP sessions. */ - jabber_write( ic, "\n", 1 ); + 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). */ @@ -543,6 +564,7 @@ void jabber_initmodule() ret->keepalive = jabber_keepalive; ret->send_typing = jabber_send_typing; ret->handle_cmp = g_strcasecmp; + ret->transfer_request = jabber_si_transfer_request; register_protocol( ret ); } diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h index 40cf3957..45a1c5c1 100644 --- a/protocols/jabber/jabber.h +++ b/protocols/jabber/jabber.h @@ -60,6 +60,14 @@ typedef enum have a real JID. */ } jabber_buddy_flags_t; +/* Stores a streamhost's (a.k.a. proxy) data */ +typedef struct +{ + char *jid; + char *host; + char port[6]; +} jabber_streamhost_t; + typedef enum { JCFLAG_MESSAGE_SENT = 1, /* Set this after sending the first message, so @@ -90,6 +98,10 @@ struct jabber_data md5_state_t cached_id_prefix; GHashTable *node_cache; GHashTable *buddies; + + GSList *filetransfers; + GSList *streamhosts; + int have_streamhosts; }; struct jabber_away_state @@ -126,6 +138,7 @@ struct jabber_buddy int priority; struct jabber_away_state *away_state; char *away_message; + GSList *features; time_t last_msg; jabber_buddy_flags_t flags; @@ -141,6 +154,36 @@ struct jabber_chat struct jabber_buddy *me; }; +struct jabber_transfer +{ + /* bitlbee's handle for this transfer */ + file_transfer_t *ft; + + /* the stream's private handle */ + gpointer streamhandle; + + /* timeout for discover queries */ + gint disco_timeout; + gint disco_timeout_fired; + + struct im_connection *ic; + + struct jabber_buddy *bud; + + int watch_in; + int watch_out; + + char *ini_jid; + char *tgt_jid; + char *iq_id; + char *sid; + int accepted; + + size_t bytesread, byteswritten; + int fd; + struct sockaddr_storage saddr; +}; + #define JABBER_XMLCONSOLE_HANDLE "xmlconsole" /* Prefixes to use for packet IDs (mainly for IQ packets ATM). Usually the @@ -166,17 +209,24 @@ struct jabber_chat #define XMLNS_ROSTER "jabber:iq:roster" /* Some supported extensions/legacy stuff */ -#define XMLNS_AUTH "jabber:iq:auth" /* XEP-0078 */ -#define XMLNS_VERSION "jabber:iq:version" /* XEP-0092 */ -#define XMLNS_TIME "jabber:iq:time" /* XEP-0090 */ -#define XMLNS_PING "urn:xmpp:ping" /* XEP-0199 */ -#define XMLNS_VCARD "vcard-temp" /* XEP-0054 */ -#define XMLNS_DELAY "jabber:x:delay" /* XEP-0091 */ -#define XMLNS_CHATSTATES "http://jabber.org/protocol/chatstates" /* 0085 */ -#define XMLNS_DISCOVER "http://jabber.org/protocol/disco#info" /* 0030 */ -#define XMLNS_MUC "http://jabber.org/protocol/muc" /* XEP-0045 */ -#define XMLNS_MUC_USER "http://jabber.org/protocol/muc#user"/* XEP-0045 */ -#define XMLNS_CAPS "http://jabber.org/protocol/caps" /* XEP-0115 */ +#define XMLNS_AUTH "jabber:iq:auth" /* XEP-0078 */ +#define XMLNS_VERSION "jabber:iq:version" /* XEP-0092 */ +#define XMLNS_TIME "jabber:iq:time" /* XEP-0090 */ +#define XMLNS_PING "urn:xmpp:ping" /* XEP-0199 */ +#define XMLNS_VCARD "vcard-temp" /* XEP-0054 */ +#define XMLNS_DELAY "jabber:x:delay" /* XEP-0091 */ +#define XMLNS_XDATA "jabber:x:data" /* XEP-0004 */ +#define XMLNS_CHATSTATES "http://jabber.org/protocol/chatstates" /* XEP-0085 */ +#define XMLNS_DISCO_INFO "http://jabber.org/protocol/disco#info" /* XEP-0030 */ +#define XMLNS_DISCO_ITEMS "http://jabber.org/protocol/disco#items" /* XEP-0030 */ +#define XMLNS_MUC "http://jabber.org/protocol/muc" /* XEP-0045 */ +#define XMLNS_MUC_USER "http://jabber.org/protocol/muc#user" /* XEP-0045 */ +#define XMLNS_CAPS "http://jabber.org/protocol/caps" /* XEP-0115 */ +#define XMLNS_FEATURE "http://jabber.org/protocol/feature-neg" /* XEP-0020 */ +#define XMLNS_SI "http://jabber.org/protocol/si" /* XEP-0095 */ +#define XMLNS_FILETRANSFER "http://jabber.org/protocol/si/profile/file-transfer" /* XEP-0096 */ +#define XMLNS_BYTESTREAMS "http://jabber.org/protocol/bytestreams" /* XEP-0065 */ +#define XMLNS_IBB "http://jabber.org/protocol/ibb" /* XEP-0047 */ /* iq.c */ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ); @@ -184,8 +234,20 @@ int jabber_init_iq_auth( struct im_connection *ic ); xt_status jabber_pkt_bind_sess( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); int jabber_get_roster( struct im_connection *ic ); int jabber_get_vcard( struct im_connection *ic, char *bare_jid ); -int jabber_add_to_roster( struct im_connection *ic, char *handle, char *name ); +int jabber_add_to_roster( struct im_connection *ic, const char *handle, const char *name, const char *group ); int jabber_remove_from_roster( struct im_connection *ic, char *handle ); +xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid ); +xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns ); + +/* si.c */ +int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode ); +void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ); +void jabber_si_free_transfer( file_transfer_t *ft); + +/* s5bytestream.c */ +int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode); +gboolean jabber_bs_send_start( struct jabber_transfer *tf ); +gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int len ); /* message.c */ xt_status jabber_pkt_message( struct xt_node *node, gpointer data ); @@ -199,7 +261,7 @@ int presence_send_request( struct im_connection *ic, char *handle, char *request char *set_eval_priority( set_t *set, char *value ); char *set_eval_tls( set_t *set, char *value ); struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_node *children ); -struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type ); +struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code ); void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func ); struct xt_node *jabber_cache_get( struct im_connection *ic, char *id ); void jabber_cache_entry_free( gpointer entry ); @@ -229,6 +291,7 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid, g 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 ); diff --git a/protocols/jabber/jabber_util.c b/protocols/jabber/jabber_util.c index db5944bc..ab3e6c38 100644 --- a/protocols/jabber/jabber_util.c +++ b/protocols/jabber/jabber_util.c @@ -98,7 +98,7 @@ struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_ return node; } -struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type ) +struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code ) { struct xt_node *node, *c; char *to; @@ -111,6 +111,10 @@ struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, c = xt_new_node( "error", NULL, c ); xt_add_attr( c, "type", err_type ); + /* Add the error code, if present */ + if (err_code) + xt_add_attr( c, "code", err_code ); + /* To make the actual error packet, we copy the original packet and add our <error>/type="error" tag. Including the original packet is recommended, so let's just do it. */ @@ -274,8 +278,7 @@ static void jabber_buddy_ask_yes( void *data ) presence_send_request( bla->ic, bla->handle, "subscribed" ); - if( imcb_find_buddy( bla->ic, bla->handle ) == NULL ) - imcb_ask_add( bla->ic, bla->handle, NULL ); + imcb_ask_add( bla->ic, bla->handle, NULL ); g_free( bla->handle ); g_free( bla ); @@ -457,7 +460,7 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_, } if( bud == NULL && ( flags & GET_BUDDY_CREAT ) && - ( bare_exists || imcb_find_buddy( ic, jid ) ) ) + ( bare_exists || bee_user_by_handle( ic->bee, ic, jid ) ) ) { *s = '/'; bud = jabber_buddy_add( ic, jid ); @@ -478,7 +481,8 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_, if( bud == NULL ) /* No match. Create it now? */ - return ( ( flags & GET_BUDDY_CREAT ) && imcb_find_buddy( ic, jid_ ) ) ? + return ( ( flags & GET_BUDDY_CREAT ) && + bee_user_by_handle( ic->bee, ic, jid_ ) ) ? jabber_buddy_add( ic, jid_ ) : NULL; else if( bud->resource && ( flags & GET_BUDDY_EXACT ) ) /* We want an exact match, so in thise case there shouldn't be a /resource. */ @@ -664,12 +668,37 @@ int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid ) } } +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 tm tp, utc; struct xt_node *c; - time_t res, tres; char *s = NULL; + struct tm tp; for( c = xt->children; ( c = xt_find_node( c, "x" ) ); c = c->next ) { @@ -687,30 +716,8 @@ time_t jabber_get_timestamp( struct xt_node *xt ) tp.tm_year -= 1900; tp.tm_mon --; - tp.tm_isdst = -1; /* GRRRRRRRRRRR */ - - res = mktime( &tp ); - /* Problem is, mktime() just gave us the GMT timestamp for the - given local time... While the given time WAS NOT local. So - we should fix this now. - - Now I could choose between messing with environment variables - (kludgy) or using timegm() (not portable)... Or doing the - following, which I actually prefer... */ - gmtime_r( &res, &utc ); - utc.tm_isdst = -1; /* Once more: GRRRRRRRRRRRRRRRRRR!!! */ - if( utc.tm_hour == tp.tm_hour && utc.tm_min == tp.tm_min ) - /* Sweet! We're in UTC right now... */ - return res; - - tres = mktime( &utc ); - res += res - tres; - - /* Yes, this is a hack. And it will go wrong around DST changes. - BUT this is more likely to be threadsafe than messing with - environment variables, and possibly more portable... */ - - return res; + + return mktime_utc( &tp ); } struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns ) diff --git a/protocols/jabber/message.c b/protocols/jabber/message.c index a226a225..ce5017fb 100644 --- a/protocols/jabber/message.c +++ b/protocols/jabber/message.c @@ -54,11 +54,12 @@ xt_status jabber_pkt_message( struct xt_node *node, gpointer data ) char *ns = xt_find_attr( c, "xmlns" ), *room; struct xt_node *inv, *reason; - if( strcmp( ns, XMLNS_MUC_USER ) == 0 && + if( ns && strcmp( ns, XMLNS_MUC_USER ) == 0 && ( inv = xt_find_node( c->children, "invite" ) ) ) { room = from; - from = xt_find_attr( inv, "from" ) ? : from; + if( ( from = xt_find_attr( inv, "from" ) ) == NULL ) + from = room; g_string_append_printf( fullmsg, "<< \002BitlBee\002 - Invitation to chatroom %s >>\n", room ); if( ( reason = xt_find_node( inv->children, "reason" ) ) && reason->text_len > 0 ) @@ -71,7 +72,7 @@ xt_status jabber_pkt_message( struct xt_node *node, gpointer data ) if( bud ) { bud->last_msg = time( NULL ); - from = bud->ext_jid ? : bud->bare_jid; + from = bud->ext_jid ? bud->ext_jid : bud->bare_jid; } else *s = 0; /* We need to generate a bare JID now. */ @@ -79,8 +80,8 @@ xt_status jabber_pkt_message( struct xt_node *node, gpointer data ) if( type && strcmp( type, "headline" ) == 0 ) { - c = xt_find_node( node->children, "subject" ); - g_string_append_printf( fullmsg, "Headline: %s\n", c && c->text_len > 0 ? c->text : "" ); + 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 ) diff --git a/protocols/jabber/presence.c b/protocols/jabber/presence.c index 006eeead..2875d23e 100644 --- a/protocols/jabber/presence.c +++ b/protocols/jabber/presence.c @@ -204,7 +204,7 @@ int presence_send_update( struct im_connection *ic ) { struct jabber_data *jd = ic->proto_data; struct xt_node *node, *cap; - struct groupchat *c; + GSList *l; int st; node = jabber_make_packet( "presence", NULL, NULL, NULL ); @@ -228,8 +228,9 @@ int presence_send_update( struct im_connection *ic ) /* Have to send this update to all groupchats too, the server won't do this automatically. */ - for( c = ic->groupchats; c && st; c = c->next ) + for( l = ic->groupchats; l && st; l = l->next ) { + struct groupchat *c = l->data; struct jabber_chat *jc = c->data; xt_add_attr( node, "to", jc->my_full_jid ); diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c new file mode 100644 index 00000000..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/si.c b/protocols/jabber/si.c new file mode 100644 index 00000000..58c0e17f --- /dev/null +++ b/protocols/jabber/si.c @@ -0,0 +1,529 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Jabber module - SI packets * +* * +* Copyright 2007 Uli Meis <a.sporto+bee@gmail.com> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#include "jabber.h" +#include "sha1.h" + +void jabber_si_answer_request( file_transfer_t *ft ); +int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf ); + +/* file_transfer free() callback */ +void jabber_si_free_transfer( file_transfer_t *ft) +{ + struct jabber_transfer *tf = ft->data; + struct jabber_data *jd = tf->ic->proto_data; + + if ( tf->watch_in ) + b_event_remove( tf->watch_in ); + + jd->filetransfers = g_slist_remove( jd->filetransfers, tf ); + + if( tf->fd != -1 ) + { + closesocket( tf->fd ); + tf->fd = -1; + } + + if( tf->disco_timeout ) + b_event_remove( tf->disco_timeout ); + + g_free( tf->ini_jid ); + g_free( tf->tgt_jid ); + g_free( tf->iq_id ); + g_free( tf->sid ); + g_free( tf ); +} + +/* file_transfer canceled() callback */ +void jabber_si_canceled( file_transfer_t *ft, char *reason ) +{ + struct jabber_transfer *tf = ft->data; + struct xt_node *reply, *iqnode; + + if( tf->accepted ) + return; + + iqnode = jabber_make_packet( "iq", "error", tf->ini_jid, NULL ); + xt_add_attr( iqnode, "id", tf->iq_id ); + reply = jabber_make_error_packet( iqnode, "forbidden", "cancel", "403" ); + xt_free_node( iqnode ); + + if( !jabber_write_packet( tf->ic, reply ) ) + imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" ); + xt_free_node( reply ); + +} + +int jabber_si_check_features( struct jabber_transfer *tf, GSList *features ) { + int foundft = FALSE, foundbt = FALSE, foundsi = FALSE; + + while ( features ) + { + if( !strcmp( features->data, XMLNS_FILETRANSFER ) ) + foundft = TRUE; + if( !strcmp( features->data, XMLNS_BYTESTREAMS ) ) + foundbt = TRUE; + if( !strcmp( features->data, XMLNS_SI ) ) + foundsi = TRUE; + + features = g_slist_next(features); + } + + if( !foundft ) + imcb_file_canceled( tf->ic, tf->ft, "Buddy's client doesn't feature file transfers" ); + else if( !foundbt ) + imcb_file_canceled( tf->ic, tf->ft, "Buddy's client doesn't feature byte streams (required)" ); + else if( !foundsi ) + imcb_file_canceled( tf->ic, tf->ft, "Buddy's client doesn't feature stream initiation (required)" ); + + return foundft && foundbt && foundsi; +} + +void jabber_si_transfer_start( struct jabber_transfer *tf ) { + + if( !jabber_si_check_features( tf, tf->bud->features ) ) + return; + + /* send the request to our buddy */ + jabber_si_send_request( tf->ic, tf->bud->full_jid, tf ); + + /* and start the receive logic */ + imcb_file_recv_start( tf->ic, tf->ft ); + +} + +gboolean jabber_si_waitfor_disco( gpointer data, gint fd, b_input_condition cond ) +{ + struct jabber_transfer *tf = data; + struct jabber_data *jd = tf->ic->proto_data; + + tf->disco_timeout_fired++; + + if( tf->bud->features && jd->have_streamhosts==1 ) { + tf->disco_timeout = 0; + jabber_si_transfer_start( tf ); + return FALSE; + } + + /* 8 seconds should be enough for server and buddy to respond */ + if ( tf->disco_timeout_fired < 16 ) + return TRUE; + + if( !tf->bud->features && jd->have_streamhosts!=1 ) + imcb_log( tf->ic, "Couldn't get buddy's features nor discover all services of the server" ); + else if( !tf->bud->features ) + imcb_log( tf->ic, "Couldn't get buddy's features" ); + else + imcb_log( tf->ic, "Couldn't discover some of the server's services" ); + + tf->disco_timeout = 0; + jabber_si_transfer_start( tf ); + return FALSE; +} + +void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ) +{ + struct jabber_transfer *tf; + struct jabber_data *jd = ic->proto_data; + struct jabber_buddy *bud; + char *server = jd->server, *s; + + if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) ) + bud = jabber_buddy_by_ext_jid( ic, who, 0 ); + else + bud = jabber_buddy_by_jid( ic, who, 0 ); + + if( bud == NULL ) + { + imcb_file_canceled( ic, ft, "Couldn't find buddy (BUG?)" ); + return; + } + + imcb_log( ic, "Trying to send %s(%zd bytes) to %s", ft->file_name, ft->file_size, who ); + + tf = g_new0( struct jabber_transfer, 1 ); + + tf->ic = ic; + tf->ft = ft; + tf->fd = -1; + tf->ft->data = tf; + tf->ft->free = jabber_si_free_transfer; + tf->bud = bud; + ft->write = jabber_bs_send_write; + + jd->filetransfers = g_slist_prepend( jd->filetransfers, tf ); + + /* query buddy's features and server's streaming proxies if neccessary */ + + if( !tf->bud->features ) + jabber_iq_query_features( ic, bud->full_jid ); + + /* If <auto> is not set don't check for proxies */ + if( ( jd->have_streamhosts!=1 ) && ( jd->streamhosts==NULL ) && + ( strstr( set_getstr( &ic->acc->set, "proxy" ), "<auto>" ) != NULL ) ) { + jd->have_streamhosts = 0; + jabber_iq_query_server( ic, server, XMLNS_DISCO_ITEMS ); + } else if ( jd->streamhosts!=NULL ) + jd->have_streamhosts = 1; + + /* if we had to do a query, wait for the result. + * Otherwise fire away. */ + if( !tf->bud->features || jd->have_streamhosts!=1 ) + tf->disco_timeout = b_timeout_add( 500, jabber_si_waitfor_disco, tf ); + else + jabber_si_transfer_start( tf ); +} + +/* + * First function that gets called when a file transfer request comes in. + * A lot to parse. + * + * We choose a stream type from the options given by the initiator. + * Then we wait for imcb to call the accept or cancel callbacks. + */ +int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode) +{ + struct xt_node *c, *d, *reply; + char *sid, *ini_jid, *tgt_jid, *iq_id, *s, *ext_jid, *size_s; + struct jabber_buddy *bud; + int requestok = FALSE; + char *name, *cmp; + size_t size; + struct jabber_transfer *tf; + struct jabber_data *jd = ic->proto_data; + file_transfer_t *ft; + + /* All this means we expect something like this: ( I think ) + * <iq from=... to=... id=...> + * <si id=id xmlns=si profile=ft> + * <file xmlns=ft/> + * <feature xmlns=feature> + * <x xmlns=xdata type=submit> + * <field var=stream-method> + * + */ + if( !( ini_jid = xt_find_attr( node, "from" ) ) || + !( tgt_jid = xt_find_attr( node, "to" ) ) || + !( iq_id = xt_find_attr( node, "id" ) ) || + !( sid = xt_find_attr( sinode, "id" ) ) || + !( cmp = xt_find_attr( sinode, "profile" ) ) || + !( 0 == strcmp( cmp, XMLNS_FILETRANSFER ) ) || + !( d = xt_find_node( sinode->children, "file" ) ) || + !( cmp = xt_find_attr( d, "xmlns" ) ) || + !( 0 == strcmp( cmp, XMLNS_FILETRANSFER ) ) || + !( name = xt_find_attr( d, "name" ) ) || + !( size_s = xt_find_attr( d, "size" ) ) || + !( 1 == sscanf( size_s, "%zd", &size ) ) || + !( d = xt_find_node( sinode->children, "feature" ) ) || + !( cmp = xt_find_attr( d, "xmlns" ) ) || + !( 0 == strcmp( cmp, XMLNS_FEATURE ) ) || + !( d = xt_find_node( d->children, "x" ) ) || + !( cmp = xt_find_attr( d, "xmlns" ) ) || + !( 0 == strcmp( cmp, XMLNS_XDATA ) ) || + !( cmp = xt_find_attr( d, "type" ) ) || + !( 0 == strcmp( cmp, "form" ) ) || + !( d = xt_find_node( d->children, "field" ) ) || + !( cmp = xt_find_attr( d, "var" ) ) || + !( 0 == strcmp( cmp, "stream-method" ) ) ) + { + imcb_log( ic, "WARNING: Received incomplete Stream Initiation request" ); + } + else + { + /* Check if we support one of the options */ + + c = d->children; + while( ( c = xt_find_node( c, "option" ) ) ) + if( ( d = xt_find_node( c->children, "value" ) ) && + ( d->text != NULL ) && + ( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) + { + requestok = TRUE; + break; + } + + if ( !requestok ) + imcb_log( ic, "WARNING: Unsupported file transfer request from %s", ini_jid); + } + + if( requestok ) + { + /* Figure out who the transfer should come frome... */ + + ext_jid = ini_jid; + if( ( s = strchr( ini_jid, '/' ) ) ) + { + if( ( bud = jabber_buddy_by_jid( ic, ini_jid, GET_BUDDY_EXACT ) ) ) + { + bud->last_msg = time( NULL ); + ext_jid = bud->ext_jid ? : bud->bare_jid; + } + else + *s = 0; /* We need to generate a bare JID now. */ + } + + if( !( ft = imcb_file_send_start( ic, ext_jid, name, size ) ) ) + { + imcb_log( ic, "WARNING: Error handling transfer request from %s", ini_jid); + requestok = FALSE; + } + + *s = '/'; + } + else + { + reply = jabber_make_error_packet( node, "item-not-found", "cancel", NULL ); + if (!jabber_write_packet( ic, reply )) + imcb_log( ic, "WARNING: Error generating reply to file transfer request" ); + xt_free_node( reply ); + return XT_HANDLED; + } + + /* Request is fine. */ + + tf = g_new0( struct jabber_transfer, 1 ); + + tf->ini_jid = g_strdup( ini_jid ); + tf->tgt_jid = g_strdup( tgt_jid ); + tf->iq_id = g_strdup( iq_id ); + tf->sid = g_strdup( sid ); + tf->ic = ic; + tf->ft = ft; + tf->fd = -1; + tf->ft->data = tf; + tf->ft->accept = jabber_si_answer_request; + tf->ft->free = jabber_si_free_transfer; + tf->ft->canceled = jabber_si_canceled; + + jd->filetransfers = g_slist_prepend( jd->filetransfers, tf ); + + return XT_HANDLED; +} + +/* + * imc called the accept callback which probably means that the user accepted this file transfer. + * We send our response to the initiator. + * In the next step, the initiator will send us a request for the given stream type. + * (currently that can only be a SOCKS5 bytestream) + */ +void jabber_si_answer_request( file_transfer_t *ft ) { + struct jabber_transfer *tf = ft->data; + struct xt_node *node, *sinode, *reply; + + /* generate response, start with the SI tag */ + sinode = xt_new_node( "si", NULL, NULL ); + xt_add_attr( sinode, "xmlns", XMLNS_SI ); + xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER ); + xt_add_attr( sinode, "id", tf->sid ); + + /* now the file tag */ + node = xt_new_node( "file", NULL, NULL ); + xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER ); + + xt_add_child( sinode, node ); + + /* and finally the feature tag */ + node = xt_new_node( "field", NULL, NULL ); + xt_add_attr( node, "var", "stream-method" ); + xt_add_attr( node, "type", "list-single" ); + + /* Currently all we can do. One could also implement in-band (IBB) */ + xt_add_child( node, xt_new_node( "value", XMLNS_BYTESTREAMS, NULL ) ); + + node = xt_new_node( "x", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_XDATA ); + xt_add_attr( node, "type", "submit" ); + + node = xt_new_node( "feature", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_FEATURE ); + + xt_add_child( sinode, node ); + + reply = jabber_make_packet( "iq", "result", tf->ini_jid, sinode ); + xt_add_attr( reply, "id", tf->iq_id ); + + if( !jabber_write_packet( tf->ic, reply ) ) + imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" ); + else + tf->accepted = TRUE; + xt_free_node( reply ); +} + +static xt_status jabber_si_handle_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + struct xt_node *c, *d; + char *ini_jid, *tgt_jid, *iq_id, *cmp; + GSList *tflist; + struct jabber_transfer *tf=NULL; + struct jabber_data *jd = ic->proto_data; + + if( !( tgt_jid = xt_find_attr( node, "from" ) ) || + !( ini_jid = xt_find_attr( node, "to" ) ) ) + { + imcb_log( ic, "Invalid SI response from=%s to=%s", tgt_jid, ini_jid ); + return XT_HANDLED; + } + + /* All this means we expect something like this: ( I think ) + * <iq from=... to=... id=...> + * <si xmlns=si> + * [ <file xmlns=ft/> ] <-- not neccessary + * <feature xmlns=feature> + * <x xmlns=xdata type=submit> + * <field var=stream-method> + * <value> + */ + if( !( tgt_jid = xt_find_attr( node, "from" ) ) || + !( ini_jid = xt_find_attr( node, "to" ) ) || + !( iq_id = xt_find_attr( node, "id" ) ) || + !( c = xt_find_node( node->children, "si" ) ) || + !( cmp = xt_find_attr( c, "xmlns" ) ) || + !( strcmp( cmp, XMLNS_SI ) == 0 ) || + !( d = xt_find_node( c->children, "feature" ) ) || + !( cmp = xt_find_attr( d, "xmlns" ) ) || + !( strcmp( cmp, XMLNS_FEATURE ) == 0 ) || + !( d = xt_find_node( d->children, "x" ) ) || + !( cmp = xt_find_attr( d, "xmlns" ) ) || + !( strcmp( cmp, XMLNS_XDATA ) == 0 ) || + !( cmp = xt_find_attr( d, "type" ) ) || + !( strcmp( cmp, "submit" ) == 0 ) || + !( d = xt_find_node( d->children, "field" ) ) || + !( cmp = xt_find_attr( d, "var" ) ) || + !( strcmp( cmp, "stream-method" ) == 0 ) || + !( d = xt_find_node( d->children, "value" ) ) ) + { + imcb_log( ic, "WARNING: Received incomplete Stream Initiation response" ); + return XT_HANDLED; + } + + if( !( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) { + /* since we should only have advertised what we can do and the peer should + * only have chosen what we offered, this should never happen */ + imcb_log( ic, "WARNING: Received invalid Stream Initiation response, method %s", d->text ); + + return XT_HANDLED; + } + + /* Let's see if we can find out what this bytestream should be for... */ + + for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) + { + struct jabber_transfer *tft = tflist->data; + if( ( strcmp( tft->iq_id, iq_id ) == 0 ) ) + { + tf = tft; + break; + } + } + + if (!tf) + { + imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid ); + return XT_HANDLED; + } + + tf->ini_jid = g_strdup( ini_jid ); + tf->tgt_jid = g_strdup( tgt_jid ); + + imcb_log( ic, "File %s: %s accepted the transfer!", tf->ft->file_name, tgt_jid ); + + jabber_bs_send_start( tf ); + + return XT_HANDLED; +} + +int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf ) +{ + struct xt_node *node, *sinode; + struct jabber_buddy *bud; + + /* who knows how many bits the future holds :) */ + char filesizestr[ 1 + ( int ) ( 0.301029995663981198f * sizeof( size_t ) * 8 ) ]; + + const char *methods[] = + { + XMLNS_BYTESTREAMS, + //XMLNS_IBB, + NULL + }; + const char **m; + char *s; + + /* Maybe we should hash this? */ + tf->sid = g_strdup_printf( "BitlBeeJabberSID%d", tf->ft->local_id ); + + if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) ) + bud = jabber_buddy_by_ext_jid( ic, who, 0 ); + else + bud = jabber_buddy_by_jid( ic, who, 0 ); + + /* start with the SI tag */ + sinode = xt_new_node( "si", NULL, NULL ); + xt_add_attr( sinode, "xmlns", XMLNS_SI ); + xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER ); + xt_add_attr( sinode, "id", tf->sid ); + +/* if( mimetype ) + xt_add_attr( node, "mime-type", mimetype ); */ + + /* now the file tag */ +/* if( desc ) + node = xt_new_node( "desc", descr, NULL ); */ + node = xt_new_node( "range", NULL, NULL ); + + sprintf( filesizestr, "%zd", tf->ft->file_size ); + node = xt_new_node( "file", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER ); + xt_add_attr( node, "name", tf->ft->file_name ); + xt_add_attr( node, "size", filesizestr ); +/* if (hash) + xt_add_attr( node, "hash", hash ); + if (date) + xt_add_attr( node, "date", date ); */ + + xt_add_child( sinode, node ); + + /* and finally the feature tag */ + node = xt_new_node( "field", NULL, NULL ); + xt_add_attr( node, "var", "stream-method" ); + xt_add_attr( node, "type", "list-single" ); + + for ( m = methods ; *m ; m ++ ) + xt_add_child( node, xt_new_node( "option", NULL, xt_new_node( "value", (char *)*m, NULL ) ) ); + + node = xt_new_node( "x", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_XDATA ); + xt_add_attr( node, "type", "form" ); + + node = xt_new_node( "feature", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_FEATURE ); + + xt_add_child( sinode, node ); + + /* and we are there... */ + node = jabber_make_packet( "iq", "set", bud ? bud->full_jid : who, sinode ); + jabber_cache_add( ic, node, jabber_si_handle_response ); + tf->iq_id = g_strdup( xt_find_attr( node, "id" ) ); + + return jabber_write_packet( ic, node ); +} diff --git a/protocols/msn/Makefile b/protocols/msn/Makefile index 911f47bd..b9c7ed28 100644 --- a/protocols/msn/Makefile +++ b/protocols/msn/Makefile @@ -7,11 +7,13 @@ ### DEFINITIONS -include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/msn/ +endif # [SH] Program variables objects = msn.o msn_util.o ns.o passport.o sb.o soap.o tables.o -CFLAGS += -Wall LFLAGS += -r # [SH] Phony targets @@ -32,7 +34,7 @@ distclean: clean $(objects): ../../Makefile.settings Makefile -$(objects): %.o: %.c +$(objects): %.o: $(SRCDIR)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $< -o $@ diff --git a/protocols/msn/invitation.c b/protocols/msn/invitation.c new file mode 100644 index 00000000..9f8b9a6e --- /dev/null +++ b/protocols/msn/invitation.c @@ -0,0 +1,622 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2008 Uli Meis * +* Copyright 2006 Marijn Kruisselbrink and others * +\********************************************************************/ + +/* MSN module - File transfer support */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA + */ + +#include "bitlbee.h" +#include "invitation.h" +#include "msn.h" +#include "lib/ftutil.h" + +#ifdef debug +#undef debug +#endif +#define debug(msg...) log_message( LOGLVL_INFO, msg ) + +static void msn_ftp_free( file_transfer_t *file ); +static void msn_ftpr_accept( file_transfer_t *file ); +static void msn_ftp_finished( file_transfer_t *file ); +static void msn_ftp_canceled( file_transfer_t *file, char *reason ); +static gboolean msn_ftpr_write_request( file_transfer_t *file ); + +static gboolean msn_ftp_connected( gpointer data, gint fd, b_input_condition cond ); +static gboolean msn_ftp_read( gpointer data, gint fd, b_input_condition cond ); +gboolean msn_ftps_write( file_transfer_t *file, char *buffer, unsigned int len ); + +/* + * Vararg wrapper for imcb_file_canceled(). + */ +gboolean msn_ftp_abort( file_transfer_t *file, char *format, ... ) +{ + va_list params; + va_start( params, format ); + char error[128]; + + if( vsnprintf( error, 128, format, params ) < 0 ) + sprintf( error, "internal error parsing error string (BUG)" ); + va_end( params ); + imcb_file_canceled( file, error ); + return FALSE; +} + +/* very useful */ +#define ASSERTSOCKOP(op, msg) \ + if( (op) == -1 ) \ + return msn_ftp_abort( file , msg ": %s", strerror( errno ) ); + +void msn_ftp_invitation_cmd( struct im_connection *ic, char *who, int cookie, char *icmd, + char *trailer ) +{ + struct msn_message *m = g_new0( struct msn_message, 1 ); + + m->text = g_strdup_printf( "%s" + "Invitation-Command: %s\r\n" + "Invitation-Cookie: %u\r\n" + "%s", + MSN_INVITE_HEADERS, + icmd, + cookie, + trailer); + + m->who = g_strdup( who ); + + msn_sb_write_msg( ic, m ); +} + +void msn_ftp_cancel_invite( struct im_connection *ic, char *who, int cookie, char *code ) +{ + char buf[64]; + + g_snprintf( buf, sizeof( buf ), "Cancel-Code: %s\r\n", code ); + msn_ftp_invitation_cmd( ic, who, cookie, "CANCEL", buf ); +} + +void msn_ftp_transfer_request( struct im_connection *ic, file_transfer_t *file, char *who ) +{ + unsigned int cookie = time( NULL ); /* TODO: randomize */ + char buf[2048]; + + msn_filetransfer_t *msn_file = g_new0( msn_filetransfer_t, 1 ); + file->data = msn_file; + file->free = msn_ftp_free; + file->canceled = msn_ftp_canceled; + file->write = msn_ftps_write; + msn_file->md = ic->proto_data; + msn_file->invite_cookie = cookie; + msn_file->handle = g_strdup( who ); + msn_file->dcc = file; + msn_file->md->filetransfers = g_slist_prepend( msn_file->md->filetransfers, msn_file->dcc ); + msn_file->fd = -1; + msn_file->sbufpos = 3; + + g_snprintf( buf, sizeof( buf ), + "Application-Name: File Transfer\r\n" + "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n" + "Application-File: %s\r\n" + "Application-FileSize: %zd\r\n", + file->file_name, + file->file_size); + + msn_ftp_invitation_cmd( msn_file->md->ic, msn_file->handle, cookie, "INVITE", buf ); + + imcb_file_recv_start( file ); +} + +void msn_invitation_invite( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + char *itype = msn_findheader( body, "Application-GUID:", blen ); + char *name, *size, *invitecookie, *reject = NULL; + user_t *u; + size_t isize; + file_transfer_t *file; + + if( !itype || strcmp( itype, "{5D3E02AB-6190-11d3-BBBB-00C04F795683}" ) != 0 ) { + /* Don't know what that is - don't care */ + char *iname = msn_findheader( body, "Application-Name:", blen ); + imcb_log( sb->ic, "Received unknown MSN invitation %s (%s) from %s", + itype ? : "with no GUID", iname ? iname : "no application name", handle ); + g_free( iname ); + reject = "REJECT_NOT_INSTALLED"; + } else if ( + !( name = msn_findheader( body, "Application-File:", blen )) || + !( size = msn_findheader( body, "Application-FileSize:", blen )) || + !( invitecookie = msn_findheader( body, "Invitation-Cookie:", blen)) || + !( isize = atoll( size ) ) ) { + imcb_log( sb->ic, "Received corrupted transfer request from %s" + "(name=%s, size=%s, invitecookie=%s)", + handle, name, size, invitecookie ); + reject = "REJECT"; + } else if ( !( u = user_findhandle( sb->ic, handle ) ) ) { + imcb_log( sb->ic, "Error in parsing transfer request, User '%s'" + "is not in contact list", handle ); + reject = "REJECT"; + } else if ( !( file = imcb_file_send_start( sb->ic, handle, name, isize ) ) ) { + imcb_log( sb->ic, "Error initiating transfer for request from %s for %s", + handle, name ); + reject = "REJECT"; + } else { + msn_filetransfer_t *msn_file = g_new0( msn_filetransfer_t, 1 ); + file->data = msn_file; + file->accept = msn_ftpr_accept; + file->free = msn_ftp_free; + file->finished = msn_ftp_finished; + file->canceled = msn_ftp_canceled; + file->write_request = msn_ftpr_write_request; + msn_file->md = sb->ic->proto_data; + msn_file->invite_cookie = cookie; + msn_file->handle = g_strdup( handle ); + msn_file->dcc = file; + msn_file->md->filetransfers = g_slist_prepend( msn_file->md->filetransfers, msn_file->dcc ); + msn_file->fd = -1; + } + + if( reject ) + msn_ftp_cancel_invite( sb->ic, sb->who, cookie, reject ); + + g_free( name ); + g_free( size ); + g_free( invitecookie ); + g_free( itype ); +} + +msn_filetransfer_t* msn_find_filetransfer( struct msn_data *md, unsigned int cookie, char *handle ) +{ + GSList *l; + + for( l = md->filetransfers; l; l = l->next ) { + msn_filetransfer_t *file = ( (file_transfer_t*) l->data )->data; + if( file->invite_cookie == cookie && strcmp( handle, file->handle ) == 0 ) { + return file; + } + } + return NULL; +} + +gboolean msn_ftps_connected( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + struct sockaddr_storage clt_addr; + socklen_t ssize = sizeof( clt_addr ); + + debug( "Connected to MSNFTP client" ); + + ASSERTSOCKOP( msn_file->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" ); + + closesocket( fd ); + fd = msn_file->fd; + sock_make_nonblocking( fd ); + + msn_file->r_event_id = b_input_add( fd, B_EV_IO_READ, msn_ftp_read, file ); + + return FALSE; +} + +void msn_invitations_accept( msn_filetransfer_t *msn_file, struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + file_transfer_t *file = msn_file->dcc; + char buf[1024]; + unsigned int acookie = time ( NULL ); + char host[HOST_NAME_MAX+1]; + char port[6]; + char *errmsg; + + msn_file->auth_cookie = acookie; + + if( ( msn_file->fd = ft_listen( NULL, host, port, FALSE, &errmsg ) ) == -1 ) { + msn_ftp_abort( file, "Failed to listen locally, check your ft_listen setting in bitlbee.conf: %s", errmsg ); + return; + } + + msn_file->r_event_id = b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftps_connected, file ); + + g_snprintf( buf, sizeof( buf ), + "IP-Address: %s\r\n" + "Port: %s\r\n" + "AuthCookie: %d\r\n" + "Launch-Application: FALSE\r\n" + "Request-Data: IP-Address:\r\n\r\n", + host, + port, + msn_file->auth_cookie ); + + msn_ftp_invitation_cmd( msn_file->md->ic, handle, msn_file->invite_cookie, "ACCEPT", buf ); +} + +void msn_invitationr_accept( msn_filetransfer_t *msn_file, struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) { + file_transfer_t *file = msn_file->dcc; + char *authcookie, *ip, *port; + + if( !( authcookie = msn_findheader( body, "AuthCookie:", blen ) ) || + !( ip = msn_findheader( body, "IP-Address:", blen ) ) || + !( port = msn_findheader( body, "Port:", blen ) ) ) { + msn_ftp_abort( file, "Received invalid accept reply" ); + } else if( + ( msn_file->fd = proxy_connect( ip, atoi( port ), msn_ftp_connected, file ) ) + < 0 ) { + msn_ftp_abort( file, "Error connecting to MSN client" ); + } else + msn_file->auth_cookie = strtoul( authcookie, NULL, 10 ); + + g_free( authcookie ); + g_free( ip ); + g_free( port ); +} + +void msn_invitation_accept( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + msn_filetransfer_t *msn_file = msn_find_filetransfer( sb->ic->proto_data, cookie, handle ); + file_transfer_t *file = msn_file ? msn_file->dcc : NULL; + + if( !msn_file ) + imcb_log( sb->ic, "Received invitation ACCEPT message for unknown invitation (already aborted?)" ); + else if( file->sending ) + msn_invitations_accept( msn_file, sb, handle, cookie, body, blen ); + else + msn_invitationr_accept( msn_file, sb, handle, cookie, body, blen ); +} + +void msn_invitation_cancel( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + msn_filetransfer_t *msn_file = msn_find_filetransfer( sb->ic->proto_data, cookie, handle ); + + if( !msn_file ) + imcb_log( sb->ic, "Received invitation CANCEL message for unknown invitation (already aborted?)" ); + else + msn_ftp_abort( msn_file->dcc, msn_findheader( body, "Cancel-Code:", blen ) ); +} + +int msn_ftp_write( file_transfer_t *file, char *format, ... ) +{ + msn_filetransfer_t *msn_file = file->data; + va_list params; + int st; + char *s; + + va_start( params, format ); + s = g_strdup_vprintf( format, params ); + va_end( params ); + + st = write( msn_file->fd, s, strlen( s ) ); + if( st != strlen( s ) ) + return msn_ftp_abort( file, "Error sending data over MSNFTP connection: %s", + strerror( errno ) ); + + g_free( s ); + return 1; +} + +gboolean msn_ftp_connected( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + + debug( "Connected to MSNFTP server, starting authentication" ); + if( !msn_ftp_write( file, "VER MSNFTP\r\n" ) ) + return FALSE; + + sock_make_nonblocking( msn_file->fd ); + msn_file->r_event_id = b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftp_read, file ); + + return FALSE; +} + +gboolean msn_ftp_handle_command( file_transfer_t *file, char* line ) +{ + msn_filetransfer_t *msn_file = file->data; + char **cmd = msn_linesplit( line ); + int count = 0; + if( cmd[0] ) while( cmd[++count] ); + + if( count < 1 ) + return msn_ftp_abort( file, "Missing command in MSNFTP communication" ); + + if( strcmp( cmd[0], "VER" ) == 0 ) { + if( strcmp( cmd[1], "MSNFTP" ) != 0 ) + return msn_ftp_abort( file, "Unsupported filetransfer protocol: %s", cmd[1] ); + if( file->sending ) + msn_ftp_write( file, "VER MSNFTP\r\n" ); + else + msn_ftp_write( file, "USR %s %u\r\n", msn_file->md->ic->acc->user, msn_file->auth_cookie ); + } else if( strcmp( cmd[0], "FIL" ) == 0 ) { + if( strtoul( cmd[1], NULL, 10 ) != file->file_size ) + return msn_ftp_abort( file, "FIL reply contains a different file size than the size in the invitation" ); + msn_ftp_write( file, "TFR\r\n" ); + msn_file->status |= MSN_TRANSFER_RECEIVING; + } else if( strcmp( cmd[0], "USR" ) == 0 ) { + if( ( strcmp( cmd[1], msn_file->handle ) != 0 ) || + ( strtoul( cmd[2], NULL, 10 ) != msn_file->auth_cookie ) ) + msn_ftp_abort( file, "Authentication failed. " + "Expected handle: %s (got %s), cookie: %u (got %s)", + msn_file->handle, cmd[1], + msn_file->auth_cookie, cmd[2] ); + msn_ftp_write( file, "FIL %zu\r\n", file->file_size); + } else if( strcmp( cmd[0], "TFR" ) == 0 ) { + file->write_request( file ); + } else if( strcmp( cmd[0], "BYE" ) == 0 ) { + unsigned int retcode = count > 1 ? atoi(cmd[1]) : 1; + + if( ( retcode==16777989 ) || ( retcode==16777987 ) ) + imcb_file_finished( file ); + else if( retcode==2147942405 ) + imcb_file_canceled( file, "Failure: receiver is out of disk space" ); + else if( retcode==2164261682 ) + imcb_file_canceled( file, "Failure: receiver cancelled the transfer" ); + else if( retcode==2164261683 ) + imcb_file_canceled( file, "Failure: sender has cancelled the transfer" ); + else if( retcode==2164261694 ) + imcb_file_canceled( file, "Failure: connection is blocked" ); + else { + char buf[128]; + + sprintf( buf, "Failure: unknown BYE code: %d", retcode); + imcb_file_canceled( file, buf ); + } + } else if( strcmp( cmd[0], "CCL" ) == 0 ) { + imcb_file_canceled( file, "Failure: receiver cancelled the transfer" ); + } else { + msn_ftp_abort( file, "Received invalid command %s from msn client", cmd[0] ); + } + return TRUE; +} + +gboolean msn_ftp_send( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + + msn_file->w_event_id = 0; + + file->write_request( file ); + + return FALSE; +} + +/* + * This should only be called if we can write, so just do it. + * Add a write watch so we can write more during the next cycle (if possible). + * This got a bit complicated because (at least) amsn expects packets of size 2045. + */ +gboolean msn_ftps_write( file_transfer_t *file, char *buffer, unsigned int len ) +{ + msn_filetransfer_t *msn_file = file->data; + int ret, overflow; + + /* what we can't send now */ + overflow = msn_file->sbufpos + len - MSNFTP_PSIZE; + + /* append what we can do the send buffer */ + memcpy( msn_file->sbuf + msn_file->sbufpos, buffer, MIN( len, MSNFTP_PSIZE - msn_file->sbufpos ) ); + msn_file->sbufpos += MIN( len, MSNFTP_PSIZE - msn_file->sbufpos ); + + /* if we don't have enough for a full packet and there's more wait for it */ + if( ( msn_file->sbufpos < MSNFTP_PSIZE ) && + ( msn_file->data_sent + msn_file->sbufpos - 3 < file->file_size ) ) { + if( !msn_file->w_event_id ) + msn_file->w_event_id = b_input_add( msn_file->fd, B_EV_IO_WRITE, msn_ftp_send, file ); + return TRUE; + } + + /* Accumulated enough data, lets send something out */ + + msn_file->sbuf[0] = 0; + msn_file->sbuf[1] = ( msn_file->sbufpos - 3 ) & 0xff; + msn_file->sbuf[2] = ( ( msn_file->sbufpos - 3 ) >> 8 ) & 0xff; + + ASSERTSOCKOP( ret = send( msn_file->fd, msn_file->sbuf, msn_file->sbufpos, 0 ), "Sending" ); + + msn_file->data_sent += ret - 3; + + /* TODO: this should really not be fatal */ + if( ret < msn_file->sbufpos ) + return msn_ftp_abort( file, "send() sent %d instead of %d (send buffer full!)", ret, msn_file->sbufpos ); + + msn_file->sbufpos = 3; + + if( overflow > 0 ) { + while( overflow > ( MSNFTP_PSIZE - 3 ) ) { + if( !msn_ftps_write( file, buffer + len - overflow, MSNFTP_PSIZE - 3 ) ) + return FALSE; + overflow -= MSNFTP_PSIZE - 3; + } + return msn_ftps_write( file, buffer + len - overflow, overflow ); + } + + if( msn_file->data_sent == file->file_size ) { + if( msn_file->w_event_id ) { + b_event_remove( msn_file->w_event_id ); + msn_file->w_event_id = 0; + } + } else { + /* we might already be listening if this is data from an overflow */ + if( !msn_file->w_event_id ) + msn_file->w_event_id = b_input_add( msn_file->fd, B_EV_IO_WRITE, msn_ftp_send, file ); + } + + return TRUE; +} + +/* Binary part of the file transfer protocol */ +gboolean msn_ftpr_read( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + int st; + unsigned char buf[3]; + + if( msn_file->data_remaining ) { + msn_file->r_event_id = 0; + + ASSERTSOCKOP( st = read( msn_file->fd, file->buffer, MIN( sizeof( file->buffer ), msn_file->data_remaining ) ), "Receiving" ); + + if( st == 0 ) + return msn_ftp_abort( file, "Remote end closed connection"); + + msn_file->data_sent += st; + + msn_file->data_remaining -= st; + + file->write( file, file->buffer, st ); + + if( msn_file->data_sent >= file->file_size ) + imcb_file_finished( file ); + + return FALSE; + } else { + ASSERTSOCKOP( st = read( msn_file->fd, buf, 1 ), "Receiving" ); + if( st == 0 ) { + return msn_ftp_abort( file, "read returned EOF while reading data header from msn client" ); + } else if( buf[0] == '\r' || buf[0] == '\n' ) { + debug( "Discarding extraneous newline" ); + } else if( buf[0] != 0 ) { + msn_ftp_abort( file, "Remote end canceled the transfer"); + /* don't really care about these last 2 (should be 0,0) */ + read( msn_file->fd, buf, 2 ); + return FALSE; + } else { + unsigned int size; + ASSERTSOCKOP( st = read( msn_file->fd, buf, 2 ), "Receiving" ); + if( st < 2 ) + return msn_ftp_abort( file, "read returned EOF while reading data header from msn client" ); + + size = buf[0] + ((unsigned int) buf[1] << 8); + msn_file->data_remaining = size; + } + } + return TRUE; +} + +/* Text mode part of the file transfer protocol */ +gboolean msn_ftp_txtproto( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + int i = msn_file->tbufpos, st; + char *tbuf = msn_file->tbuf; + + ASSERTSOCKOP( st = read( msn_file->fd, + tbuf + msn_file->tbufpos, + sizeof( msn_file->tbuf ) - msn_file->tbufpos ), + "Receiving" ); + + if( st == 0 ) + return msn_ftp_abort( file, "read returned EOF while reading text from msn client" ); + + msn_file->tbufpos += st; + + do { + for( ;i < msn_file->tbufpos; i++ ) { + if( tbuf[i] == '\n' || tbuf[i] == '\r' ) { + tbuf[i] = '\0'; + if( i > 0 ) + msn_ftp_handle_command( file, tbuf ); + else + while( tbuf[i] == '\n' || tbuf[i] == '\r' ) i++; + memmove( tbuf, tbuf + i + 1, msn_file->tbufpos - i - 1 ); + msn_file->tbufpos -= i + 1; + i = 0; + break; + } + } + } while ( i < msn_file->tbufpos ); + + if( msn_file->tbufpos == sizeof( msn_file->tbuf ) ) + return msn_ftp_abort( file, + "Line exceeded %d bytes in text protocol", + sizeof( msn_file->tbuf ) ); + return TRUE; +} + +gboolean msn_ftp_read( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + + if( msn_file->status & MSN_TRANSFER_RECEIVING ) + return msn_ftpr_read( file ); + else + return msn_ftp_txtproto( file ); +} + +void msn_ftp_free( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + + if( msn_file->r_event_id ) + b_event_remove( msn_file->r_event_id ); + + if( msn_file->w_event_id ) + b_event_remove( msn_file->w_event_id ); + + if( msn_file->fd != -1 ) + closesocket( msn_file->fd ); + + msn_file->md->filetransfers = g_slist_remove( msn_file->md->filetransfers, msn_file->dcc ); + + g_free( msn_file->handle ); + + g_free( msn_file ); +} + +void msn_ftpr_accept( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + + msn_ftp_invitation_cmd( msn_file->md->ic, msn_file->handle, msn_file->invite_cookie, "ACCEPT", + "Launch-Application: FALSE\r\n" + "Request-Data: IP-Address:\r\n"); +} + +void msn_ftp_finished( file_transfer_t *file ) +{ + msn_ftp_write( file, "BYE 16777989\r\n" ); +} + +void msn_ftp_canceled( file_transfer_t *file, char *reason ) +{ + msn_filetransfer_t *msn_file = file->data; + + msn_ftp_cancel_invite( msn_file->md->ic, msn_file->handle, + msn_file->invite_cookie, + file->status & FT_STATUS_TRANSFERRING ? + "FTTIMEOUT" : + "FAIL" ); + + imcb_log( msn_file->md->ic, "File transfer aborted: %s", reason ); +} + +gboolean msn_ftpr_write_request( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + if( msn_file->r_event_id != 0 ) { + msn_ftp_abort( file, + "BUG in MSN file transfer:" + "write_request called when" + "already watching for input" ); + return FALSE; + } + + msn_file->r_event_id = + b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftp_read, file ); + + return TRUE; +} diff --git a/protocols/msn/invitation.h b/protocols/msn/invitation.h new file mode 100644 index 00000000..289efd7b --- /dev/null +++ b/protocols/msn/invitation.h @@ -0,0 +1,82 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2006 Marijn Kruisselbrink and others * +\********************************************************************/ + +/* MSN module - File transfer support */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _MSN_INVITATION_H +#define _MSN_INVITATION_H + +#include "msn.h" + +#define MSN_INVITE_HEADERS "MIME-Version: 1.0\r\n" \ + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n" \ + "\r\n" + +#define MSNFTP_PSIZE 2048 + +typedef enum { + MSN_TRANSFER_RECEIVING = 1, + MSN_TRANSFER_SENDING = 2 +} msn_filetransfer_status_t; + +typedef struct msn_filetransfer +{ +/* Generic invitation data */ + /* msn_data instance this invitation was received with. */ + struct msn_data *md; + /* Cookie specifying this invitation. */ + unsigned int invite_cookie; + /* Handle of user that started this invitation. */ + char *handle; + +/* File transfer specific data */ + /* Current status of the file transfer. */ + msn_filetransfer_status_t status; + /* Pointer to the dcc structure for this transfer. */ + file_transfer_t *dcc; + /* Socket the transfer is taking place over. */ + int fd; + /* Cookie received in the original invitation, this must be sent as soon as + a connection has been established. */ + unsigned int auth_cookie; + /* Data remaining to be received in the current packet. */ + unsigned int data_remaining; + /* Buffer containing received, but unprocessed text. */ + char tbuf[256]; + unsigned int tbufpos; + + unsigned int data_sent; + + gint r_event_id; + gint w_event_id; + + unsigned char sbuf[2048]; + int sbufpos; + +} msn_filetransfer_t; + +void msn_invitation_invite( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ); +void msn_invitation_accept( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ); +void msn_invitation_cancel( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ); + +#endif diff --git a/protocols/msn/msn.c b/protocols/msn/msn.c index 4d859346..6222e1b6 100644 --- a/protocols/msn/msn.c +++ b/protocols/msn/msn.c @@ -30,16 +30,14 @@ int msn_chat_id; GSList *msn_connections; GSList *msn_switchboards; -static char *msn_set_display_name( set_t *set, char *value ); +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, msn_set_display_name, acc ); - s->flags |= ACC_SET_NOSAVE | ACC_SET_ONLINE_ONLY; - - s = set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc ); + set_add( &acc->set, "display_name", NULL, set_eval_display_name, acc ); + set_add( &acc->set, "local_display_name", "false", set_eval_bool, acc ); + set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc ); + set_add( &acc->set, "switchboard_keepalives", "false", set_eval_bool, acc ); } static void msn_login( account_t *acc ) @@ -80,6 +78,12 @@ static void msn_logout( struct im_connection *ic ) if( md ) { + /** Disabling MSN ft support for now. + while( md->filetransfers ) { + imcb_file_canceled( md->filetransfers->data, "Closing connection" ); + } + */ + if( md->fd >= 0 ) closesocket( md->fd ); @@ -98,10 +102,18 @@ static void msn_logout( struct im_connection *ic ) while( md->groupcount > 0 ) g_free( md->grouplist[--md->groupcount] ); g_free( md->grouplist ); - g_free( md->passport_token ); g_free( md->lock_key ); + 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 ); } @@ -120,6 +132,14 @@ static int msn_buddy_msg( struct im_connection *ic, char *who, char *message, in { struct msn_switchboard *sb; +#ifdef DEBUG + if( strcmp( who, "raw" ) == 0 ) + { + msn_write( ic, message, strlen( message ) ); + msn_write( ic, "\r\n", 2 ); + } + else +#endif if( ( sb = msn_sb_by_handle( ic, who ) ) ) { return( msn_sb_sendmessage( sb, message ) ); @@ -157,11 +177,10 @@ static void msn_set_away( struct im_connection *ic, char *state, char *message ) char buf[1024]; struct msn_data *md = ic->proto_data; - if( state ) - md->away_state = msn_away_state_by_name( state ) ? : - msn_away_state_list + 1; - else + 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; g_snprintf( buf, sizeof( buf ), "CHG %d %s\r\n", ++md->trId, md->away_state->code ); msn_write( ic, buf, strlen( buf ) ); @@ -169,7 +188,7 @@ static void msn_set_away( struct im_connection *ic, char *state, char *message ) static void msn_set_my_name( struct im_connection *ic, char *info ) { - msn_set_display_name( set_find( &ic->acc->set, "display_name" ), info ); + msn_set_display_name( ic, info ); } static void msn_get_info(struct im_connection *ic, char *who) @@ -180,12 +199,16 @@ static void msn_get_info(struct im_connection *ic, char *who) static void msn_add_buddy( struct im_connection *ic, char *who, char *group ) { - msn_buddy_list_add( ic, "FL", who, who ); + struct bee_user *bu = bee_user_by_handle( ic->bee, ic, who ); + + msn_buddy_list_add( ic, "FL", who, who, group ); + if( bu && bu->group ) + msn_buddy_list_remove( ic, "FL", who, bu->group->name ); } static void msn_remove_buddy( struct im_connection *ic, char *who, char *group ) { - msn_buddy_list_remove( ic, "FL", who ); + msn_buddy_list_remove( ic, "FL", who, NULL ); } static void msn_chat_msg( struct groupchat *c, char *message, int flags ) @@ -221,6 +244,7 @@ static void msn_chat_leave( struct groupchat *c ) static struct groupchat *msn_chat_with( struct im_connection *ic, char *who ) { struct msn_switchboard *sb; + struct groupchat *c = imcb_chat_new( ic, who ); if( ( sb = msn_sb_by_handle( ic, who ) ) ) { @@ -238,10 +262,8 @@ static struct groupchat *msn_chat_with( struct im_connection *ic, char *who ) msn_sb_write_msg( ic, m ); - return NULL; + return c; } - - return NULL; } static void msn_keepalive( struct im_connection *ic ) @@ -251,19 +273,19 @@ static void msn_keepalive( struct im_connection *ic ) static void msn_add_permit( struct im_connection *ic, char *who ) { - msn_buddy_list_add( ic, "AL", who, who ); + msn_buddy_list_add( ic, "AL", who, who, NULL ); } static void msn_rem_permit( struct im_connection *ic, char *who ) { - msn_buddy_list_remove( ic, "AL", who ); + msn_buddy_list_remove( ic, "AL", who, NULL ); } static void msn_add_deny( struct im_connection *ic, char *who ) { struct msn_switchboard *sb; - msn_buddy_list_add( ic, "BL", who, who ); + msn_buddy_list_add( ic, "BL", who, who, NULL ); /* If there's still a conversation with this person, close it. */ if( ( sb = msn_sb_by_handle( ic, who ) ) ) @@ -274,29 +296,29 @@ static void msn_add_deny( struct im_connection *ic, char *who ) static void msn_rem_deny( struct im_connection *ic, char *who ) { - msn_buddy_list_remove( ic, "BL", who ); + msn_buddy_list_remove( ic, "BL", who, NULL ); } static int msn_send_typing( struct im_connection *ic, char *who, int typing ) { - if( typing & OPT_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 ); + return 1; } -static char *msn_set_display_name( set_t *set, char *value ) +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; - char buf[1024], *fn; - /* Double-check. */ + /* Allow any name if we're offline. */ if( ic == NULL ) - return NULL; - - md = ic->proto_data; + return value; if( strlen( value ) > 129 ) { @@ -304,16 +326,10 @@ static char *msn_set_display_name( set_t *set, char *value ) return NULL; } - fn = msn_http_encode( value ); - - g_snprintf( buf, sizeof( buf ), "REA %d %s %s\r\n", ++md->trId, ic->acc->user, fn ); - msn_write( ic, buf, strlen( buf ) ); - g_free( fn ); - /* Returning NULL would be better, because the server still has to confirm the name change. However, it looks a bit confusing to the user. */ - return value; + return msn_set_display_name( ic, value ) ? value : NULL; } void msn_initmodule() @@ -342,6 +358,7 @@ void msn_initmodule() ret->rem_deny = msn_rem_deny; ret->send_typing = msn_send_typing; ret->handle_cmp = g_strcasecmp; + //ret->transfer_request = msn_ftp_transfer_request; register_protocol(ret); } diff --git a/protocols/msn/msn.h b/protocols/msn/msn.h index d57ff796..f060000a 100644 --- a/protocols/msn/msn.h +++ b/protocols/msn/msn.h @@ -30,6 +30,7 @@ */ #define TYPING_NOTIFICATION_MESSAGE "\r\r\rBEWARE, ME R TYPINK MESSAGE!!!!\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 ); @@ -60,6 +61,10 @@ "TypingUser: %s\r\n" \ "\r\n\r\n" +#define 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/" struct msn_data @@ -73,10 +78,11 @@ struct msn_data char *passport_token; char *lock_key; - GSList *msgq; + GSList *msgq, *grpq; GSList *switchboards; int sb_failures; time_t first_sb_failure; + GSList *filetransfers; const struct msn_away_state *away_state; int buddycount; @@ -91,6 +97,7 @@ struct msn_switchboard int fd; gint inp; struct msn_handler_data *handler; + gint keepalive; int trId; int ready; @@ -122,6 +129,12 @@ struct msn_message char *text; }; +struct msn_groupadd +{ + char *who; + char *group; +}; + struct msn_handler_data { int fd; @@ -161,14 +174,15 @@ gboolean msn_ns_connected( gpointer data, gint source, b_input_condition cond ); /* msn_util.c */ int msn_write( struct im_connection *ic, char *s, int len ); int msn_logged_in( struct im_connection *ic ); -int msn_buddy_list_add( struct im_connection *ic, char *list, char *who, char *realname ); -int msn_buddy_list_remove( struct im_connection *ic, char *list, char *who ); +int msn_buddy_list_add( struct im_connection *ic, const char *list, const char *who, const char *realname_, const char *group ); +int msn_buddy_list_remove( struct im_connection *ic, char *list, const char *who, const char *group ); void msn_buddy_ask( struct im_connection *ic, char *handle, char *realname ); char *msn_findheader( char *text, char *header, int len ); char **msn_linesplit( char *line ); int msn_handler( struct msn_handler_data *h ); char *msn_http_encode( const char *input ); void msn_msgq_purge( struct im_connection *ic, GSList **list ); +gboolean msn_set_display_name( struct im_connection *ic, const char *rawname ); char *msn_p11_challenge( char *challenge ); /* tables.c */ @@ -188,5 +202,10 @@ 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 ); + +/* invitation.c */ +void msn_ftp_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ); #endif //_MSN_H diff --git a/protocols/msn/msn_util.c b/protocols/msn/msn_util.c index 59363439..954ee716 100644 --- a/protocols/msn/msn_util.c +++ b/protocols/msn/msn_util.c @@ -38,10 +38,10 @@ int msn_write( struct im_connection *ic, char *s, int len ) { imcb_error( ic, "Short write() to main server" ); imc_logout( ic, TRUE ); - return( 0 ); + return 0; } - return( 1 ); + return 1; } int msn_logged_in( struct im_connection *ic ) @@ -51,32 +51,82 @@ int msn_logged_in( struct im_connection *ic ) return( 0 ); } -int msn_buddy_list_add( struct im_connection *ic, char *list, char *who, char *realname_ ) +int msn_buddy_list_add( struct im_connection *ic, const char *list, const char *who, const char *realname_, const char *group ) { struct msn_data *md = ic->proto_data; - char buf[1024], *realname; - - realname = msn_http_encode( realname_ ); + char buf[1024], *realname, groupid[8]; - g_snprintf( buf, sizeof( buf ), "ADD %d %s %s %s\r\n", ++md->trId, list, who, realname ); - if( msn_write( ic, buf, strlen( buf ) ) ) + *groupid = '\0'; + if( group ) { - g_free( realname ); + int i; + for( i = 0; i < md->groupcount; i ++ ) + if( g_strcasecmp( md->grouplist[i], group ) == 0 ) + { + g_snprintf( groupid, sizeof( groupid ), " %d", i ); + break; + } - return( 1 ); + if( *groupid == '\0' ) + { + /* Have to create this group, it doesn't exist yet. */ + struct msn_groupadd *ga; + GSList *l; + + for( l = md->grpq; l; l = l->next ) + { + ga = l->data; + if( g_strcasecmp( ga->group, group ) == 0 ) + break; + } + + ga = g_new0( struct msn_groupadd, 1 ); + ga->who = g_strdup( who ); + ga->group = g_strdup( group ); + md->grpq = g_slist_prepend( md->grpq, ga ); + + if( l == NULL ) + { + char *groupname = msn_http_encode( group ); + g_snprintf( buf, sizeof( buf ), "ADG %d %s %d\r\n", ++md->trId, groupname, 0 ); + g_free( groupname ); + return msn_write( ic, buf, strlen( buf ) ); + } + else + { + /* This can happen if the user's doing lots of adds to a + new group at once; we're still waiting for the server + to confirm group creation. */ + return 1; + } + } } + realname = msn_http_encode( realname_ ); + g_snprintf( buf, sizeof( buf ), "ADD %d %s %s %s%s\r\n", ++md->trId, list, who, realname, groupid ); g_free( realname ); - return( 0 ); + return msn_write( ic, buf, strlen( buf ) ); } -int msn_buddy_list_remove( struct im_connection *ic, char *list, char *who ) +int msn_buddy_list_remove( struct im_connection *ic, char *list, const char *who, const char *group ) { struct msn_data *md = ic->proto_data; - char buf[1024]; + char buf[1024], groupid[8]; + + *groupid = '\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; + } + } - g_snprintf( buf, sizeof( buf ), "REM %d %s %s\r\n", ++md->trId, list, who ); + g_snprintf( buf, sizeof( buf ), "REM %d %s %s%s\r\n", ++md->trId, list, who, groupid ); if( msn_write( ic, buf, strlen( buf ) ) ) return( 1 ); @@ -94,10 +144,9 @@ static void msn_buddy_ask_yes( void *data ) { struct msn_buddy_ask_data *bla = data; - msn_buddy_list_add( bla->ic, "AL", bla->handle, bla->realname ); + msn_buddy_list_add( bla->ic, "AL", bla->handle, bla->realname, NULL ); - if( imcb_find_buddy( bla->ic, bla->handle ) == NULL ) - imcb_ask_add( bla->ic, bla->handle, NULL ); + imcb_ask_add( bla->ic, bla->handle, NULL ); g_free( bla->handle ); g_free( bla->realname ); @@ -108,7 +157,7 @@ static void msn_buddy_ask_no( void *data ) { struct msn_buddy_ask_data *bla = data; - msn_buddy_list_add( bla->ic, "BL", bla->handle, bla->realname ); + msn_buddy_list_add( bla->ic, "BL", bla->handle, bla->realname, NULL ); g_free( bla->handle ); g_free( bla->realname ); @@ -349,6 +398,7 @@ 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 ) @@ -363,7 +413,11 @@ void msn_msgq_purge( struct im_connection *ic, GSList **list ) { m = l->data; - g_string_append_printf( ret, "\n%s", m->text ); + 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 ); @@ -374,10 +428,23 @@ void msn_msgq_purge( struct im_connection *ic, GSList **list ) g_slist_free( *list ); *list = NULL; - imcb_log( ic, "%s", ret->str ); + if( n > 0 ) + imcb_log( ic, "%s", ret->str ); g_string_free( ret, TRUE ); } +gboolean msn_set_display_name( struct im_connection *ic, const char *rawname ) +{ + char *fn = msn_http_encode( rawname ); + struct msn_data *md = ic->proto_data; + char buf[1024]; + + g_snprintf( buf, sizeof( buf ), "REA %d %s %s\r\n", ++md->trId, ic->acc->user, fn ); + g_free( fn ); + + return msn_write( ic, buf, strlen( buf ) ) != 0; +} + unsigned int little_endian( unsigned int dw ) { #if defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN @@ -400,7 +467,6 @@ unsigned int little_endian( unsigned int dw ) } /* Copied and heavily modified from http://tmsnc.sourceforge.net/chl.c */ - char *msn_p11_challenge( char *challenge ) { char *output, buf[256]; diff --git a/protocols/msn/ns.c b/protocols/msn/ns.c index f9c8ab29..40c4cdec 100644 --- a/protocols/msn/ns.c +++ b/protocols/msn/ns.c @@ -34,6 +34,7 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts ); static int msn_ns_message( gpointer data, char *msg, int msglen, char **cmd, int num_parts ); static void msn_auth_got_passport_token( struct msn_auth_data *mad ); +static gboolean msn_ns_got_display_name( struct im_connection *ic, char *name ); gboolean msn_ns_connected( gpointer data, gint source, b_input_condition cond ) { @@ -74,7 +75,7 @@ gboolean msn_ns_connected( gpointer data, gint source, b_input_condition cond ) g_snprintf( s, sizeof( s ), "VER %d MSNP8 CVR0\r\n", ++md->trId ); if( msn_write( ic, s, strlen( s ) ) ) { - ic->inpa = b_input_add( md->fd, GAIM_INPUT_READ, msn_ns_callback, ic ); + ic->inpa = b_input_add( md->fd, B_EV_IO_READ, msn_ns_callback, ic ); imcb_log( ic, "Connected to server, waiting for reply" ); } @@ -230,25 +231,10 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts ) } else if( num_parts >= 7 && strcmp( cmd[2], "OK" ) == 0 ) { - set_t *s; - if( num_parts == 7 ) - { - http_decode( cmd[4] ); - - strncpy( ic->displayname, cmd[4], sizeof( ic->displayname ) ); - ic->displayname[sizeof(ic->displayname)-1] = 0; - - if( ( s = set_find( &ic->acc->set, "display_name" ) ) ) - { - g_free( s->value ); - s->value = g_strdup( cmd[4] ); - } - } + msn_ns_got_display_name( ic, cmd[4] ); else - { imcb_log( ic, "Warning: Friendly name in server response was corrupted" ); - } imcb_log( ic, "Authenticated, getting buddy list" ); @@ -435,8 +421,12 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts ) } else if( strcmp( cmd[0], "FLN" ) == 0 ) { - if( cmd[1] ) - imcb_buddy_status( ic, cmd[1], 0, NULL, NULL ); + 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 ) { @@ -462,6 +452,8 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts ) imcb_buddy_status( ic, cmd[2], OPT_LOGGED_IN | ( st != msn_away_state_list ? OPT_AWAY : 0 ), st->name, NULL ); + + msn_sb_stop_keepalives( msn_sb_by_handle( ic, cmd[2] ) ); } else if( strcmp( cmd[0], "RNG" ) == 0 ) { @@ -540,8 +532,14 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts ) } else if( num_parts >= 6 && strcmp( cmd[2], "FL" ) == 0 ) { + const char *group = NULL; + int num; + + if( cmd[6] != NULL && sscanf( cmd[6], "%d", &num ) == 1 && num < md->groupcount ) + group = md->grouplist[num]; + http_decode( cmd[5] ); - imcb_add_buddy( ic, cmd[4], NULL ); + imcb_add_buddy( ic, cmd[4], group ); imcb_rename_buddy( ic, cmd[4], cmd[5] ); } } @@ -566,6 +564,9 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts ) imc_logout( ic, allow_reconnect ); return( 0 ); } +#if 0 + /* Discard this one completely for now since I don't care about the ack + and since MSN servers can apparently screw up the formatting. */ else if( strcmp( cmd[0], "REA" ) == 0 ) { if( num_parts != 5 ) @@ -596,6 +597,7 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts ) imcb_rename_buddy( ic, cmd[3], cmd[4] ); } } +#endif else if( strcmp( cmd[0], "IPG" ) == 0 ) { imcb_error( ic, "Received IPG command, we don't handle them yet." ); @@ -609,6 +611,50 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts ) return( 0 ); } } + else if( strcmp( cmd[0], "ADG" ) == 0 ) + { + char *group = g_strdup( cmd[3] ); + int groupnum, i; + GSList *l, *next; + + http_decode( group ); + if( sscanf( cmd[4], "%d", &groupnum ) == 1 ) + { + if( groupnum >= md->groupcount ) + { + md->grouplist = g_renew( char *, md->grouplist, groupnum + 1 ); + for( i = md->groupcount; i <= groupnum; i ++ ) + md->grouplist[i] = NULL; + md->groupcount = groupnum + 1; + } + g_free( md->grouplist[groupnum] ); + md->grouplist[groupnum] = group; + } + else + { + /* Shouldn't happen, but if it does, give up on the group. */ + g_free( group ); + imcb_error( ic, "Syntax error" ); + imc_logout( ic, TRUE ); + return 0; + } + + for( l = md->grpq; l; l = next ) + { + struct msn_groupadd *ga = l->data; + next = l->next; + if( g_strcasecmp( ga->group, group ) == 0 ) + { + if( !msn_buddy_list_add( ic, "FL", ga->who, ga->who, group ) ) + return 0; + + g_free( ga->group ); + g_free( ga->who ); + g_free( ga ); + md->grpq = g_slist_remove( md->grpq, ga ); + } + } + } else if( isdigit( cmd[0][0] ) ) { int num = atoi( cmd[0] ); @@ -747,3 +793,48 @@ static void msn_auth_got_passport_token( struct msn_auth_data *mad ) imc_logout( ic, TRUE ); } } + +static gboolean msn_ns_got_display_name( struct im_connection *ic, char *name ) +{ + set_t *s; + + if( ( s = set_find( &ic->acc->set, "display_name" ) ) == NULL ) + return FALSE; /* Shouldn't happen.. */ + + http_decode( name ); + + if( s->value && strcmp( s->value, name ) == 0 ) + { + return TRUE; + /* The names match, nothing to worry about. */ + } + else if( s->value != NULL && + ( strcmp( name, ic->acc->user ) == 0 || + set_getbool( &ic->acc->set, "local_display_name" ) ) ) + { + /* The server thinks our display name is our e-mail address + which is probably wrong, or the user *wants* us to do this: + Always use the locally set display_name. */ + return msn_set_display_name( ic, s->value ); + } + else + { + if( s->value && *s->value ) + imcb_log( ic, "BitlBee thinks your display name is `%s' but " + "the MSN server says it's `%s'. Using the MSN " + "server's name. Set local_display_name to true " + "to use the local name.", s->value, name ); + + if( g_utf8_validate( name, -1, NULL ) ) + { + g_free( s->value ); + s->value = g_strdup( name ); + } + else + { + imcb_log( ic, "Warning: Friendly name in server response was corrupted" ); + } + + return TRUE; + } +} diff --git a/protocols/msn/passport.c b/protocols/msn/passport.c index 565d15f3..7c896db1 100644 --- a/protocols/msn/passport.c +++ b/protocols/msn/passport.c @@ -144,7 +144,9 @@ static xt_status passport_xt_extract_token( struct xt_node *node, gpointer data struct msn_auth_data *mad = data; char *s; - if( ( s = xt_find_attr( node, "Id" ) ) && strcmp( s, "PPToken1" ) == 0 ) + if( ( s = xt_find_attr( node, "Id" ) ) && + ( strncmp( s, "Compact", 7 ) == 0 || + strncmp( s, "PPToken", 7 ) == 0 ) ) mad->token = g_memdup( node->text, node->text_len + 1 ); return XT_HANDLED; diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c index 461bd483..10425708 100644 --- a/protocols/msn/sb.c +++ b/protocols/msn/sb.c @@ -168,7 +168,23 @@ int msn_sb_sendmessage( struct msn_switchboard *sb, char *text ) int i, j; /* Build the message. Convert LF to CR-LF for normal messages. */ - if( strcmp( text, TYPING_NOTIFICATION_MESSAGE ) != 0 ) + 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, SB_KEEPALIVE_MESSAGE ) == 0 ) + { + buf = g_strdup( SB_KEEPALIVE_HEADERS ); + i = strlen( buf ); + } + else if( strncmp( text, MSN_INVITE_HEADERS, sizeof( MSN_INVITE_HEADERS ) - 1 ) == 0 ) + { + buf = g_strdup( text ); + i = strlen( buf ); + } + else { buf = g_new0( char, sizeof( MSN_MESSAGE_HEADERS ) + strlen( text ) * 2 + 1 ); i = strlen( MSN_MESSAGE_HEADERS ); @@ -182,12 +198,6 @@ int msn_sb_sendmessage( struct msn_switchboard *sb, char *text ) buf[i++] = text[j]; } } - else - { - 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 ); - } /* Build the final packet (MSG command + the message). */ packet = g_strdup_printf( "MSG %d N %d\r\n%s", ++sb->trId, i, buf ); @@ -222,11 +232,17 @@ int msn_sb_sendmessage( struct msn_switchboard *sb, char *text ) struct groupchat *msn_sb_to_chat( struct msn_switchboard *sb ) { struct im_connection *ic = sb->ic; + struct groupchat *c = NULL; char buf[1024]; /* Create the groupchat structure. */ g_snprintf( buf, sizeof( buf ), "MSN groupchat session %d", sb->session ); - sb->chat = imcb_chat_new( ic, buf ); + if( sb->who ) + c = bee_chat_by_title( ic->bee, ic, sb->who ); + if( c && !msn_sb_by_chat( c ) ) + sb->chat = c; + else + sb->chat = imcb_chat_new( ic, buf ); /* Populate the channel. */ if( sb->who ) imcb_chat_add_buddy( sb->chat, sb->who ); @@ -250,6 +266,7 @@ void msn_sb_destroy( struct msn_switchboard *sb ) 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 ); @@ -309,7 +326,7 @@ gboolean msn_sb_connected( gpointer data, gint source, b_input_condition cond ) g_snprintf( buf, sizeof( buf ), "ANS %d %s %s %d\r\n", ++sb->trId, ic->acc->user, sb->key, sb->session ); if( msn_sb_write( sb, buf, strlen( buf ) ) ) - sb->inp = b_input_add( sb->fd, GAIM_INPUT_READ, msn_sb_callback, sb ); + sb->inp = b_input_add( sb->fd, B_EV_IO_READ, msn_sb_callback, sb ); else debug( "Error %d while connecting to switchboard server", 2 ); @@ -322,9 +339,13 @@ static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition c struct im_connection *ic = sb->ic; struct msn_data *md = ic->proto_data; - if( msn_handler( sb->handler ) == -1 ) + if( msn_handler( sb->handler ) != -1 ) + return TRUE; + + if( sb->msgq != NULL ) { time_t now = time( NULL ); + char buf[1024]; if( now - md->first_sb_failure > 600 ) { @@ -341,37 +362,28 @@ static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition c imcb_log( ic, "Warning: Many switchboard failures on MSN connection. " "There might be problems delivering your messages." ); - if( sb->msgq != NULL ) + if( md->msgq == NULL ) { - char buf[1024]; - - 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; + md->msgq = sb->msgq; + } + else + { + GSList *l; - debug( "Moved queued messages back to the main queue, creating a new switchboard to retry." ); - g_snprintf( buf, sizeof( buf ), "XFR %d SB\r\n", ++md->trId ); - if( !msn_write( ic, buf, strlen( buf ) ) ) - return FALSE; + for( l = md->msgq; l->next; l = l->next ); + l->next = sb->msgq; } + sb->msgq = NULL; - msn_sb_destroy( sb ); - - return FALSE; - } - else - { - return TRUE; + debug( "Moved queued messages back to the main queue, " + "creating a new switchboard to retry." ); + g_snprintf( buf, sizeof( buf ), "XFR %d SB\r\n", ++md->trId ); + if( !msn_write( ic, buf, strlen( buf ) ) ) + return FALSE; } + + msn_sb_destroy( sb ); + return FALSE; } static int msn_sb_command( gpointer data, char **cmd, int num_parts ) @@ -471,6 +483,8 @@ static int msn_sb_command( gpointer data, char **cmd, int num_parts ) } sb->ready = 1; + + msn_sb_start_keepalives( sb, FALSE ); } else if( strcmp( cmd[0], "CAL" ) == 0 ) { @@ -520,6 +534,8 @@ static int msn_sb_command( gpointer data, char **cmd, int num_parts ) sb->msgq = g_slist_remove( sb->msgq, m ); } + msn_sb_start_keepalives( sb, FALSE ); + return( st ); } else if( sb->who ) @@ -581,6 +597,8 @@ static int msn_sb_command( gpointer data, char **cmd, int num_parts ) 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; @@ -690,64 +708,46 @@ static int msn_sb_message( gpointer data, char *msg, int msglen, char **cmd, int /* PANIC! */ } } +#if 0 + // Disable MSN ft support for now. else if( g_strncasecmp( ct, "text/x-msmsgsinvite", 19 ) == 0 ) { - char *itype = msn_findheader( body, "Application-GUID:", blen ); - char buf[1024]; + char *command = msn_findheader( body, "Invitation-Command:", blen ); + char *cookie = msn_findheader( body, "Invitation-Cookie:", blen ); + unsigned int icookie; g_free( ct ); - *buf = 0; - - if( !itype ) - return( 1 ); - - /* File transfer. */ - if( strcmp( itype, "{5D3E02AB-6190-11d3-BBBB-00C04F795683}" ) == 0 ) - { - char *name = msn_findheader( body, "Application-File:", blen ); - char *size = msn_findheader( body, "Application-FileSize:", blen ); - - if( name && size ) - { - g_snprintf( buf, sizeof( buf ), "<< \x02""BitlBee\x02"" - Filetransfer: `%s', %s bytes >>\n" - "Filetransfers are not supported by BitlBee for now...", name, size ); - } - else - { - strcpy( buf, "<< \x02""BitlBee\x02"" - Corrupted MSN filetransfer invitation message >>" ); - } - - if( name ) g_free( name ); - if( size ) g_free( size ); + /* 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; } - else - { - char *iname = msn_findheader( body, "Application-Name:", blen ); - - g_snprintf( buf, sizeof( buf ), "<< \x02""BitlBee\x02"" - Unknown MSN invitation - %s (%s) >>", - itype, iname ? iname : "no name" ); - - if( iname ) g_free( iname ); - } - - g_free( itype ); - if( !*buf ) - return( 1 ); + icookie = strtoul( cookie, NULL, 10 ); + g_free( cookie ); - if( sb->who ) - { - imcb_buddy_msg( ic, cmd[1], buf, 0, 0 ); - } - else if( sb->chat ) - { - imcb_chat_msg( sb->chat, cmd[1], buf, 0, 0 ); - } - else - { - /* PANIC! */ + if( g_strncasecmp( command, "INVITE", 6 ) == 0 ) { + msn_invitation_invite( sb, cmd[1], icookie, body, blen ); + } else if( g_strncasecmp( command, "ACCEPT", 6 ) == 0 ) { + msn_invitation_accept( sb, cmd[1], icookie, body, blen ); + } else if( g_strncasecmp( command, "CANCEL", 6 ) == 0 ) { + msn_invitation_cancel( sb, cmd[1], icookie, body, blen ); + } else { + imcb_log( ic, "Warning: Received invalid invitation with " + "command %s from %s", command, sb->who ); } + + g_free( command ); + } +#endif + else if( g_strncasecmp( ct, "application/x-msnmsgrp2p", 24 ) == 0 ) + { + imcb_error( sb->ic, "Cannot receive file from %s: BitlBee does not " + "support msnmsgrp2p yet.", sb->who ); + g_free( ct ); } else if( g_strncasecmp( ct, "text/x-msmsgscontrol", 20 ) == 0 ) { @@ -769,3 +769,34 @@ static int msn_sb_message( gpointer data, char *msg, int msglen, char **cmd, int 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/nogaim.c b/protocols/nogaim.c index c326e378..7943e026 100644 --- a/protocols/nogaim.c +++ b/protocols/nogaim.c @@ -35,9 +35,6 @@ #include <ctype.h> #include "nogaim.h" -#include "chat.h" - -static int remove_chat_buddy_silent( struct groupchat *b, const char *handle ); GSList *connections; @@ -91,8 +88,6 @@ void load_plugins(void) } #endif -/* nogaim.c */ - GList *protocols = NULL; void register_protocol (struct prpl *p) @@ -115,22 +110,26 @@ void register_protocol (struct prpl *p) struct prpl *find_protocol(const char *name) { GList *gl; - for (gl = protocols; gl; gl = gl->next) + + for( gl = protocols; gl; gl = gl->next ) { struct prpl *proto = gl->data; - if(!g_strcasecmp(proto->name, name)) + + if( g_strcasecmp( proto->name, name ) == 0 ) return proto; } + return NULL; } -/* nogaim.c */ void nogaim_init() { extern void msn_initmodule(); 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(); @@ -148,6 +147,14 @@ void nogaim_init() jabber_initmodule(); #endif +#ifdef WITH_TWITTER + twitter_initmodule(); +#endif + +#ifdef WITH_PURPLE + purple_initmodule(); +#endif + #ifdef WITH_PLUGINS load_plugins(); #endif @@ -155,15 +162,13 @@ void nogaim_init() GSList *get_connections() { return connections; } -/* multi.c */ - struct im_connection *imcb_new( account_t *acc ) { struct im_connection *ic; ic = g_new0( struct im_connection, 1 ); - ic->irc = acc->irc; + ic->bee = acc->bee; ic->acc = acc; acc->ic = ic; @@ -177,7 +182,7 @@ void imc_free( struct im_connection *ic ) account_t *a; /* Destroy the pointer to this connection from the account list */ - for( a = ic->irc->accounts; a; a = a->next ) + for( a = ic->bee->accounts; a; a = a->next ) if( a->ic == ic ) { a->ic = NULL; @@ -198,20 +203,21 @@ static void serv_got_crap( struct im_connection *ic, char *format, ... ) text = g_strdup_vprintf( format, params ); va_end( params ); - if( ( g_strcasecmp( set_getstr( &ic->irc->set, "strip_html" ), "always" ) == 0 ) || - ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->irc->set, "strip_html" ) ) ) + if( ( g_strcasecmp( set_getstr( &ic->bee->set, "strip_html" ), "always" ) == 0 ) || + ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->bee->set, "strip_html" ) ) ) strip_html( text ); /* Try to find a different connection on the same protocol. */ - for( a = ic->irc->accounts; a; a = a->next ) + for( a = ic->bee->accounts; a; a = a->next ) if( a->prpl == ic->acc->prpl && a->ic != ic ) break; /* If we found one, include the screenname in the message. */ if( a ) - irc_usermsg( ic->irc, "%s(%s) - %s", ic->acc->prpl->name, ic->acc->user, text ); + /* FIXME(wilmer): ui_log callback or so */ + irc_usermsg( ic->bee->ui_data, "%s(%s) - %s", ic->acc->prpl->name, ic->acc->user, text ); else - irc_usermsg( ic->irc, "%s - %s", ic->acc->prpl->name, text ); + irc_usermsg( ic->bee->ui_data, "%s - %s", ic->acc->prpl->name, text ); g_free( text ); } @@ -262,18 +268,12 @@ static gboolean send_keepalive( gpointer d, gint fd, b_input_condition cond ) void imcb_connected( struct im_connection *ic ) { - irc_t *irc = ic->irc; - struct chat *c; - user_t *u; - /* MSN servers sometimes redirect you to a different server and do the whole login sequence again, so these "late" calls to this function should be handled correctly. (IOW, ignored) */ if( ic->flags & OPT_LOGGED_IN ) return; - u = user_find( ic->irc, ic->irc->nick ); - imcb_log( ic, "Logged in" ); ic->keepalive = b_timeout_add( 60000, send_keepalive, ic ); @@ -286,14 +286,8 @@ void imcb_connected( struct im_connection *ic ) exponential backoff timer. */ ic->acc->auto_reconnect_delay = 0; - for( c = irc->chatrooms; c; c = c->next ) - { - if( c->acc != ic->acc ) - continue; - - if( set_getbool( &c->set, "auto_join" ) ) - chat_join( irc, c, NULL ); - } + if( ic->bee->ui->imc_connected ) + ic->bee->ui->imc_connected( ic ); } gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond ) @@ -301,7 +295,7 @@ gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond ) account_t *a = data; a->reconnect = 0; - account_on( a->irc, a ); + account_on( a->bee, a ); return( FALSE ); /* Only have to run the timeout once */ } @@ -314,9 +308,9 @@ void cancel_auto_reconnect( account_t *a ) void imc_logout( struct im_connection *ic, int allow_reconnect ) { - irc_t *irc = ic->irc; - user_t *t, *u; + bee_t *bee = ic->bee; account_t *a; + GSList *l; int delay; /* Nested calls might happen sometimes, this is probably the best @@ -326,6 +320,9 @@ void imc_logout( struct im_connection *ic, int allow_reconnect ) else ic->flags |= OPT_LOGGING_OUT; + if( ic->bee->ui->imc_disconnected ) + ic->bee->ui->imc_disconnected( ic ); + imcb_log( ic, "Signing off.." ); b_event_remove( ic->keepalive ); @@ -336,22 +333,20 @@ void imc_logout( struct im_connection *ic, int allow_reconnect ) g_free( ic->away ); ic->away = NULL; - u = irc->users; - while( u ) + for( l = bee->users; l; ) { - if( u->ic == ic ) - { - t = u->next; - user_del( irc, u->nick ); - u = t; - } - else - u = u->next; + bee_user_t *bu = l->data; + GSList *next = l->next; + + if( bu->ic == ic ) + bee_user_free( bee, bu ); + + l = next; } - query_del_by_conn( ic->irc, ic ); + query_del_by_conn( (irc_t*) ic->bee->ui_data, ic ); - for( a = irc->accounts; a; a = a->next ) + for( a = bee->accounts; a; a = a->next ) if( a->ic == ic ) break; @@ -359,7 +354,7 @@ void imc_logout( struct im_connection *ic, int allow_reconnect ) { /* Uhm... This is very sick. */ } - else if( allow_reconnect && set_getbool( &irc->set, "auto_reconnect" ) && + else if( allow_reconnect && set_getbool( &bee->set, "auto_reconnect" ) && set_getbool( &a->set, "auto_reconnect" ) && ( delay = account_reconnect_delay( a ) ) > 0 ) { @@ -370,170 +365,70 @@ void imc_logout( struct im_connection *ic, int allow_reconnect ) imc_free( ic ); } - -/* dialogs.c */ - void imcb_ask( struct im_connection *ic, char *msg, void *data, query_callback doit, query_callback dont ) { - query_add( ic->irc, ic, msg, doit, dont, data ); + query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, g_free, data ); } - -/* list.c */ - -void imcb_add_buddy( struct im_connection *ic, const char *handle, const char *group ) +void imcb_ask_with_free( struct im_connection *ic, char *msg, void *data, + query_callback doit, query_callback dont, query_callback myfree ) { - user_t *u; - char nick[MAX_NICK_LENGTH+1], *s; - irc_t *irc = ic->irc; - - if( user_findhandle( ic, handle ) ) - { - if( set_getbool( &irc->set, "debug" ) ) - imcb_log( ic, "User already exists, ignoring add request: %s", handle ); - - return; - - /* Buddy seems to exist already. Let's ignore this request then... - Eventually subsequent calls to this function *should* be possible - when a buddy is in multiple groups. But for now BitlBee doesn't - even support groups so let's silently ignore this for now. */ - } - - memset( nick, 0, MAX_NICK_LENGTH + 1 ); - strcpy( nick, nick_get( ic->acc, handle ) ); - - u = user_add( ic->irc, nick ); - -// if( !realname || !*realname ) realname = nick; -// u->realname = g_strdup( realname ); - - if( ( s = strchr( handle, '@' ) ) ) - { - u->host = g_strdup( s + 1 ); - u->user = g_strndup( handle, s - handle ); - } - else if( ic->acc->server ) - { - u->host = g_strdup( ic->acc->server ); - u->user = g_strdup( handle ); - - /* s/ /_/ ... important for AOL screennames */ - for( s = u->user; *s; s ++ ) - if( *s == ' ' ) - *s = '_'; - } - else - { - u->host = g_strdup( ic->acc->prpl->name ); - u->user = g_strdup( handle ); - } - - u->ic = ic; - u->handle = g_strdup( handle ); - if( group ) u->group = g_strdup( group ); - u->send_handler = buddy_send_handler; - u->last_typing_notice = 0; + query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, myfree, data ); } -struct buddy *imcb_find_buddy( struct im_connection *ic, char *handle ) +void imcb_add_buddy( struct im_connection *ic, const char *handle, const char *group ) { - static struct buddy b[1]; - user_t *u; + bee_user_t *bu; + bee_t *bee = ic->bee; + bee_group_t *oldg; - u = user_findhandle( ic, handle ); + if( !( bu = bee_user_by_handle( bee, ic, handle ) ) ) + bu = bee_user_new( bee, ic, handle, 0 ); - if( !u ) - return( NULL ); + oldg = bu->group; + bu->group = bee_group_by_name( bee, group, TRUE ); - memset( b, 0, sizeof( b ) ); - strncpy( b->name, handle, 80 ); - strncpy( b->show, u->realname, BUDDY_ALIAS_MAXLEN ); - b->present = u->online; - b->ic = u->ic; - - return( b ); + if( bee->ui->user_group && bu->group != oldg ) + bee->ui->user_group( bee, bu ); } -void imcb_rename_buddy( struct im_connection *ic, const char *handle, const char *realname ) +void imcb_rename_buddy( struct im_connection *ic, const char *handle, const char *fullname ) { - user_t *u = user_findhandle( ic, handle ); - char *set; - - if( !u || !realname ) return; + bee_t *bee = ic->bee; + bee_user_t *bu = bee_user_by_handle( bee, ic, handle ); - if( g_strcasecmp( u->realname, realname ) != 0 ) - { - if( u->realname != u->nick ) g_free( u->realname ); - - u->realname = g_strdup( realname ); - - if( ( ic->flags & OPT_LOGGED_IN ) && set_getbool( &ic->irc->set, "display_namechanges" ) ) - imcb_log( ic, "User `%s' changed name to `%s'", u->nick, u->realname ); - } + if( !bu || !fullname ) return; - set = set_getstr( &ic->acc->set, "nick_source" ); - if( strcmp( set, "handle" ) != 0 ) + if( !bu->fullname || strcmp( bu->fullname, fullname ) != 0 ) { - char *name = g_strdup( realname ); - - if( strcmp( set, "first_name" ) == 0 ) - { - int i; - for( i = 0; name[i] && !isspace( name[i] ); i ++ ) {} - name[i] = '\0'; - } - - imcb_buddy_nick_hint( ic, handle, name ); + g_free( bu->fullname ); + bu->fullname = g_strdup( fullname ); - g_free( name ); + if( bee->ui->user_fullname ) + bee->ui->user_fullname( bee, bu ); } } void imcb_remove_buddy( struct im_connection *ic, const char *handle, char *group ) { - user_t *u; - - if( ( u = user_findhandle( ic, handle ) ) ) - user_del( ic->irc, u->nick ); + bee_user_free( ic->bee, bee_user_by_handle( ic->bee, ic, handle ) ); } /* Mainly meant for ICQ (and now also for Jabber conferences) to allow IM modules to suggest a nickname for a handle. */ void imcb_buddy_nick_hint( struct im_connection *ic, const char *handle, const char *nick ) { - user_t *u = user_findhandle( ic, handle ); - char newnick[MAX_NICK_LENGTH+1], *orig_nick; + bee_t *bee = ic->bee; + bee_user_t *bu = bee_user_by_handle( bee, ic, handle ); - if( u && !u->online && !nick_saved( ic->acc, handle ) ) - { - /* Only do this if the person isn't online yet (which should - be the case if we just added it) and if the user hasn't - assigned a nickname to this buddy already. */ - - strncpy( newnick, nick, MAX_NICK_LENGTH ); - newnick[MAX_NICK_LENGTH] = 0; - - /* Some processing to make sure this string is a valid IRC nickname. */ - nick_strip( newnick ); - if( set_getbool( &ic->irc->set, "lcnicks" ) ) - nick_lc( newnick ); - - if( strcmp( u->nick, newnick ) != 0 ) - { - /* Only do this if newnick is different from the current one. - If rejoining a channel, maybe we got this nick already - (and dedupe would only add an underscore. */ - nick_dedupe( ic->acc, handle, newnick ); - - /* u->nick will be freed halfway the process, so it can't be - passed as an argument. */ - orig_nick = g_strdup( u->nick ); - user_rename( ic->irc, orig_nick, newnick ); - g_free( orig_nick ); - } - } + if( !bu || !nick ) return; + + g_free( bu->nick ); + bu->nick = g_strdup( nick ); + + if( bee->ui->user_nick_hint ) + bee->ui->user_nick_hint( bee, bu, nick ); } @@ -572,13 +467,14 @@ void imcb_ask_auth( struct im_connection *ic, const char *handle, const char *re 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_ ?: "" ); + handle, realname_ ? realname_ : "" ); g_free( realname_ ); data->ic = ic; data->handle = g_strdup( handle ); - query_add( ic->irc, ic, s, imcb_ask_auth_cb_yes, imcb_ask_auth_cb_no, data ); + query_add( (irc_t *) ic->bee->ui_data, ic, s, + imcb_ask_auth_cb_yes, imcb_ask_auth_cb_no, g_free, data ); } @@ -594,7 +490,7 @@ static void imcb_ask_add_cb_yes( void *data ) cbd->ic->acc->prpl->add_buddy( cbd->ic, cbd->handle, NULL ); - return imcb_ask_add_cb_no( data ); + imcb_ask_add_cb_no( data ); } void imcb_ask_add( struct im_connection *ic, const char *handle, const char *realname ) @@ -603,486 +499,25 @@ void imcb_ask_add( struct im_connection *ic, const char *handle, const char *rea char *s; /* TODO: Make a setting for this! */ - if( user_findhandle( ic, handle ) != NULL ) + if( bee_user_by_handle( ic->bee, ic, handle ) != NULL ) return; s = g_strdup_printf( "The user %s is not in your buddy list yet. Do you want to add him/her now?", handle ); data->ic = ic; data->handle = g_strdup( handle ); - query_add( ic->irc, ic, s, imcb_ask_add_cb_yes, imcb_ask_add_cb_no, data ); + query_add( (irc_t *) ic->bee->ui_data, ic, s, + imcb_ask_add_cb_yes, imcb_ask_add_cb_no, g_free, data ); } - -/* server.c */ - -void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags, const char *state, const char *message ) +struct bee_user *imcb_buddy_by_handle( struct im_connection *ic, const char *handle ) { - user_t *u; - int oa, oo; - - u = user_findhandle( ic, (char*) handle ); - - if( !u ) - { - if( g_strcasecmp( set_getstr( &ic->irc->set, "handle_unknown" ), "add" ) == 0 ) - { - imcb_add_buddy( ic, (char*) handle, NULL ); - u = user_findhandle( ic, (char*) handle ); - } - else - { - if( set_getbool( &ic->irc->set, "debug" ) || g_strcasecmp( set_getstr( &ic->irc->set, "handle_unknown" ), "ignore" ) != 0 ) - { - imcb_log( ic, "imcb_buddy_status() for unknown handle %s:", handle ); - imcb_log( ic, "flags = %d, state = %s, message = %s", flags, - state ? state : "NULL", message ? message : "NULL" ); - } - - return; - } - } - - oa = u->away != NULL; - oo = u->online; - - g_free( u->away ); - g_free( u->status_msg ); - u->away = u->status_msg = NULL; - - if( ( flags & OPT_LOGGED_IN ) && !u->online ) - { - irc_spawn( ic->irc, u ); - u->online = 1; - } - else if( !( flags & OPT_LOGGED_IN ) && u->online ) - { - struct groupchat *c; - - irc_kill( ic->irc, u ); - u->online = 0; - - /* Remove him/her from the groupchats to prevent PART messages after he/she QUIT already */ - for( c = ic->groupchats; c; c = c->next ) - remove_chat_buddy_silent( c, handle ); - } - - if( flags & OPT_AWAY ) - { - if( state && message ) - { - u->away = g_strdup_printf( "%s (%s)", state, message ); - } - else if( state ) - { - u->away = g_strdup( state ); - } - else if( message ) - { - u->away = g_strdup( message ); - } - else - { - u->away = g_strdup( "Away" ); - } - } - else - { - u->status_msg = g_strdup( message ); - } - - /* LISPy... */ - if( ( set_getbool( &ic->irc->set, "away_devoice" ) ) && /* Don't do a thing when user doesn't want it */ - ( u->online ) && /* Don't touch offline people */ - ( ( ( u->online != oo ) && !u->away ) || /* Voice joining people */ - ( ( u->online == oo ) && ( oa == !u->away ) ) ) ) /* (De)voice people changing state */ - { - char *from; - - if( set_getbool( &ic->irc->set, "simulate_netsplit" ) ) - { - from = g_strdup( ic->irc->myhost ); - } - else - { - from = g_strdup_printf( "%s!%s@%s", ic->irc->mynick, ic->irc->mynick, - ic->irc->myhost ); - } - irc_write( ic->irc, ":%s MODE %s %cv %s", from, ic->irc->channel, - u->away?'-':'+', u->nick ); - g_free( from ); - } -} - -void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, uint32_t flags, time_t sent_at ) -{ - irc_t *irc = ic->irc; - char *wrapped; - user_t *u; - - u = user_findhandle( ic, handle ); - - if( !u ) - { - char *h = set_getstr( &irc->set, "handle_unknown" ); - - if( g_strcasecmp( h, "ignore" ) == 0 ) - { - if( set_getbool( &irc->set, "debug" ) ) - imcb_log( ic, "Ignoring message from unknown handle %s", handle ); - - return; - } - else if( g_strncasecmp( h, "add", 3 ) == 0 ) - { - int private = set_getbool( &irc->set, "private" ); - - if( h[3] ) - { - if( g_strcasecmp( h + 3, "_private" ) == 0 ) - private = 1; - else if( g_strcasecmp( h + 3, "_channel" ) == 0 ) - private = 0; - } - - imcb_add_buddy( ic, handle, NULL ); - u = user_findhandle( ic, handle ); - u->is_private = private; - } - else - { - imcb_log( ic, "Message from unknown handle %s:", handle ); - u = user_find( irc, irc->mynick ); - } - } - - if( ( g_strcasecmp( set_getstr( &ic->irc->set, "strip_html" ), "always" ) == 0 ) || - ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->irc->set, "strip_html" ) ) ) - strip_html( msg ); - - wrapped = word_wrap( msg, 425 ); - irc_msgfrom( irc, u->nick, wrapped ); - g_free( wrapped ); + return bee_user_by_handle( ic->bee, ic, handle ); } -void imcb_buddy_typing( struct im_connection *ic, char *handle, uint32_t flags ) -{ - user_t *u; - - if( !set_getbool( &ic->irc->set, "typing_notice" ) ) - return; - - if( ( u = user_findhandle( ic, handle ) ) ) - { - char buf[256]; - - g_snprintf( buf, 256, "\1TYPING %d\1", ( flags >> 8 ) & 3 ); - irc_privmsg( ic->irc, u, "PRIVMSG", ic->irc->nick, NULL, buf ); - } -} - -struct groupchat *imcb_chat_new( struct im_connection *ic, const char *handle ) -{ - struct groupchat *c; - - /* This one just creates the conversation structure, user won't see anything yet */ - - if( ic->groupchats ) - { - for( c = ic->groupchats; c->next; c = c->next ); - c = c->next = g_new0( struct groupchat, 1 ); - } - else - ic->groupchats = c = g_new0( struct groupchat, 1 ); - - c->ic = ic; - c->title = g_strdup( handle ); - c->channel = g_strdup_printf( "&chat_%03d", ic->irc->c_id++ ); - c->topic = g_strdup_printf( "BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", c->title ); - - if( set_getbool( &ic->irc->set, "debug" ) ) - imcb_log( ic, "Creating new conversation: (id=%p,handle=%s)", c, handle ); - - return c; -} - -void imcb_chat_free( struct groupchat *c ) -{ - struct im_connection *ic = c->ic; - struct groupchat *l; - GList *ir; - - if( set_getbool( &ic->irc->set, "debug" ) ) - imcb_log( ic, "You were removed from conversation %p", c ); - - if( c ) - { - if( c->joined ) - { - user_t *u, *r; - - r = user_find( ic->irc, ic->irc->mynick ); - irc_privmsg( ic->irc, r, "PRIVMSG", c->channel, "", "Cleaning up channel, bye!" ); - - u = user_find( ic->irc, ic->irc->nick ); - irc_kick( ic->irc, u, c->channel, r ); - /* irc_part( ic->irc, u, c->channel ); */ - } - - /* Find the previous chat in the linked list. */ - for( l = ic->groupchats; l && l->next != c; l = l->next ); - - if( l ) - l->next = c->next; - else - ic->groupchats = c->next; - - for( ir = c->in_room; ir; ir = ir->next ) - g_free( ir->data ); - g_list_free( c->in_room ); - g_free( c->channel ); - g_free( c->title ); - g_free( c->topic ); - g_free( c ); - } -} - -void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, uint32_t flags, time_t sent_at ) -{ - struct im_connection *ic = c->ic; - char *wrapped; - user_t *u; - - /* Gaim sends own messages through this too. IRC doesn't want this, so kill them */ - if( g_strcasecmp( who, ic->acc->user ) == 0 ) - return; - - u = user_findhandle( ic, who ); - - if( ( g_strcasecmp( set_getstr( &ic->irc->set, "strip_html" ), "always" ) == 0 ) || - ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->irc->set, "strip_html" ) ) ) - strip_html( msg ); - - wrapped = word_wrap( msg, 425 ); - if( c && u ) - { - irc_privmsg( ic->irc, u, "PRIVMSG", c->channel, "", wrapped ); - } - else - { - imcb_log( ic, "Message from/to conversation %s@%p (unknown conv/user): %s", who, c, wrapped ); - } - g_free( wrapped ); -} - -void imcb_chat_log( struct groupchat *c, char *format, ... ) -{ - irc_t *irc = c->ic->irc; - va_list params; - char *text; - user_t *u; - - va_start( params, format ); - text = g_strdup_vprintf( format, params ); - va_end( params ); - - u = user_find( irc, irc->mynick ); - - irc_privmsg( irc, u, "PRIVMSG", c->channel, "System message: ", text ); - - g_free( text ); -} - -void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at ) -{ - struct im_connection *ic = c->ic; - user_t *u = NULL; - - if( who == NULL) - u = user_find( ic->irc, ic->irc->mynick ); - else if( g_strcasecmp( who, ic->acc->user ) == 0 ) - u = user_find( ic->irc, ic->irc->nick ); - else - u = user_findhandle( ic, who ); - - if( ( g_strcasecmp( set_getstr( &ic->irc->set, "strip_html" ), "always" ) == 0 ) || - ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->irc->set, "strip_html" ) ) ) - strip_html( topic ); - - g_free( c->topic ); - c->topic = g_strdup( topic ); - - if( c->joined && u ) - irc_write( ic->irc, ":%s!%s@%s TOPIC %s :%s", u->nick, u->user, u->host, c->channel, topic ); -} - - -/* buddy_chat.c */ - -void imcb_chat_add_buddy( struct groupchat *b, const char *handle ) -{ - user_t *u = user_findhandle( b->ic, handle ); - int me = 0; - - if( set_getbool( &b->ic->irc->set, "debug" ) ) - imcb_log( b->ic, "User %s added to conversation %p", handle, b ); - - /* It might be yourself! */ - if( b->ic->acc->prpl->handle_cmp( handle, b->ic->acc->user ) == 0 ) - { - u = user_find( b->ic->irc, b->ic->irc->nick ); - if( !b->joined ) - irc_join( b->ic->irc, u, b->channel ); - b->joined = me = 1; - } - - /* Most protocols allow people to join, even when they're not in - your contact list. Try to handle that here */ - if( !u ) - { - imcb_add_buddy( b->ic, handle, NULL ); - u = user_findhandle( b->ic, handle ); - } - - /* Add the handle to the room userlist, if it's not 'me' */ - if( !me ) - { - if( b->joined ) - irc_join( b->ic->irc, u, b->channel ); - b->in_room = g_list_append( b->in_room, g_strdup( handle ) ); - } -} - -/* This function is one BIG hack... :-( EREWRITE */ -void imcb_chat_remove_buddy( struct groupchat *b, const char *handle, const char *reason ) -{ - user_t *u; - int me = 0; - - if( set_getbool( &b->ic->irc->set, "debug" ) ) - imcb_log( b->ic, "User %s removed from conversation %p (%s)", handle, b, reason ? reason : "" ); - - /* It might be yourself! */ - if( g_strcasecmp( handle, b->ic->acc->user ) == 0 ) - { - if( b->joined == 0 ) - return; - - u = user_find( b->ic->irc, b->ic->irc->nick ); - b->joined = 0; - me = 1; - } - else - { - u = user_findhandle( b->ic, handle ); - } - - if( me || ( remove_chat_buddy_silent( b, handle ) && b->joined && u ) ) - irc_part( b->ic->irc, u, b->channel ); -} - -static int remove_chat_buddy_silent( struct groupchat *b, const char *handle ) -{ - GList *i; - - /* Find the handle in the room userlist and shoot it */ - i = b->in_room; - while( i ) - { - if( g_strcasecmp( handle, i->data ) == 0 ) - { - g_free( i->data ); - b->in_room = g_list_remove( b->in_room, i->data ); - return( 1 ); - } - - i = i->next; - } - - return( 0 ); -} - - -/* Misc. BitlBee stuff which shouldn't really be here */ - -char *set_eval_away_devoice( set_t *set, char *value ) -{ - irc_t *irc = set->data; - int st; - - if( !is_bool( value ) ) - return SET_INVALID; - - st = bool2int( value ); - - /* Horror.... */ - - if( st != set_getbool( &irc->set, "away_devoice" ) ) - { - char list[80] = ""; - user_t *u = irc->users; - int i = 0, count = 0; - char pm; - char v[80]; - - if( st ) - pm = '+'; - else - pm = '-'; - - while( u ) - { - if( u->ic && u->online && !u->away ) - { - if( ( strlen( list ) + strlen( u->nick ) ) >= 79 ) - { - for( i = 0; i < count; v[i++] = 'v' ); v[i] = 0; - irc_write( irc, ":%s MODE %s %c%s%s", - irc->myhost, - irc->channel, pm, v, list ); - - *list = 0; - count = 0; - } - - sprintf( list + strlen( list ), " %s", u->nick ); - count ++; - } - u = u->next; - } - - /* $v = 'v' x $i */ - for( i = 0; i < count; v[i++] = 'v' ); v[i] = 0; - irc_write( irc, ":%s MODE %s %c%s%s", irc->myhost, - irc->channel, pm, v, list ); - } - - return value; -} - - - - /* The plan is to not allow straight calls to prpl functions anymore, but do them all from some wrappers. We'll start to define some down here: */ -int imc_buddy_msg( struct im_connection *ic, char *handle, char *msg, int flags ) -{ - char *buf = NULL; - int st; - - if( ( ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<html>", 6 ) != 0 ) ) - { - buf = escape_html( msg ); - msg = buf; - } - - st = ic->acc->prpl->buddy_msg( ic, handle, msg, flags ); - g_free( buf ); - - return st; -} - int imc_chat_msg( struct groupchat *c, char *msg, int flags ) { char *buf = NULL; @@ -1105,8 +540,12 @@ 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->irc->set, "away" ); + : set_getstr( &ic->bee->set, "away" ); if( away && *away ) { GList *m = ic->acc->prpl->away_states( ic ); @@ -1117,7 +556,7 @@ int imc_away_send_update( struct im_connection *ic ) { away = NULL; msg = set_getstr( &ic->acc->set, "status" ) ? - : set_getstr( &ic->irc->set, "status" ); + : set_getstr( &ic->bee->set, "status" ); } ic->acc->prpl->set_away( ic, away, msg ); diff --git a/protocols/nogaim.h b/protocols/nogaim.h index a523a3a5..be67bb24 100644 --- a/protocols/nogaim.h +++ b/protocols/nogaim.h @@ -1,7 +1,7 @@ /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * - * Copyright 2002-2004 Wilmer van der Gaast and others * + * Copyright 2002-2010 Wilmer van der Gaast and others * \********************************************************************/ /* @@ -38,12 +38,20 @@ #ifndef _NOGAIM_H #define _NOGAIM_H +#if(__sun) +#include <inttypes.h> +#else #include <stdint.h> +#endif + +#include "lib/events.h" #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 */ @@ -84,9 +92,9 @@ struct im_connection int evil; /* BitlBee */ - irc_t *irc; + bee_t *bee; - struct groupchat *groupchats; + GSList *groupchats; }; struct groupchat { @@ -97,10 +105,9 @@ struct groupchat { * "nick list". This is how you can check who is in the group chat * already, for example to avoid adding somebody two times. */ GList *in_room; - GList *ignored; + //GList *ignored; - struct groupchat *next; - char *channel; + //struct groupchat *next; /* The title variable contains the ID you gave when you created the * chat using imcb_chat_new(). */ char *title; @@ -111,6 +118,7 @@ struct groupchat { /* This is for you, you can add your own structure here to extend this * structure for your protocol's needs. */ void *data; + void *ui_data; }; struct buddy { @@ -131,6 +139,7 @@ struct prpl { /* You should set this to the name of your protocol. * - The user sees this name ie. when imcb_log() is used. */ const char *name; + void *data; /* Added this one to be able to add per-account settings, don't think * it should be used for anything else. You are supposed to use the @@ -208,13 +217,19 @@ struct prpl { * your protocol does not support publicly named group chats, then do * not implement this. */ struct groupchat * - (* chat_join) (struct im_connection *, const char *room, const char *nick, const char *password); + (* chat_join) (struct im_connection *, const char *room, + const char *nick, const char *password, set_t **sets); /* Change the topic, if supported. Note that BitlBee expects the IM server to confirm the topic change with a regular topic change event. If it doesn't do that, you have to fake it to make it visible to the user. */ void (* chat_topic) (struct groupchat *, char *topic); + /* If your protocol module needs any special info for joining chatrooms + other than a roomname + nickname, add them here. */ + void (* chat_add_settings) (account_t *acc, set_t **head); + void (* chat_free_settings) (account_t *acc, set_t **head); + /* You can tell what away states your protocol supports, so that * BitlBee will try to map the IRC away reasons to them. If your * protocol doesn't have any, just return one generic "Away". */ @@ -227,6 +242,19 @@ struct prpl { /* Implement these callbacks if you want to use imcb_ask_auth() */ void (* auth_allow) (struct im_connection *, const char *who); void (* auth_deny) (struct im_connection *, const char *who); + + /* Incoming transfer request */ + void (* transfer_request) (struct im_connection *, file_transfer_t *ft, char *handle ); + + void (* buddy_data_add) (struct bee_user *bu); + void (* buddy_data_free) (struct bee_user *bu); + + /* Some placeholders so eventually older plugins may cooperate with newer BitlBees. */ + void *resv1; + void *resv2; + void *resv3; + void *resv4; + void *resv5; }; /* im_api core stuff. */ @@ -262,6 +290,7 @@ G_MODULE_EXPORT void imcb_error( struct im_connection *ic, char *format, ... ) G * - 'doit' or 'dont' will be called depending of the answer of the user. */ G_MODULE_EXPORT void imcb_ask( struct im_connection *ic, char *msg, void *data, query_callback doit, query_callback dont ); +G_MODULE_EXPORT void imcb_ask_with_free( struct im_connection *ic, char *msg, void *data, query_callback doit, query_callback dont, query_callback myfree ); /* Two common questions you may want to ask: * - X added you to his contact list, allow? @@ -280,41 +309,12 @@ G_MODULE_EXPORT struct buddy *imcb_find_buddy( struct im_connection *ic, char *h G_MODULE_EXPORT void imcb_rename_buddy( struct im_connection *ic, const char *handle, const char *realname ); G_MODULE_EXPORT void imcb_buddy_nick_hint( struct im_connection *ic, const char *handle, const char *nick ); -/* Buddy activity */ -/* To manipulate the status of a handle. - * - flags can be |='d with OPT_* constants. You will need at least: - * OPT_LOGGED_IN and OPT_AWAY. - * - 'state' and 'message' can be NULL */ -G_MODULE_EXPORT void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags, const char *state, const char *message ); -/* Not implemented yet! */ G_MODULE_EXPORT void imcb_buddy_times( struct im_connection *ic, const char *handle, time_t login, time_t idle ); -/* Call when a handle says something. 'flags' and 'sent_at may be just 0. */ -G_MODULE_EXPORT void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, uint32_t flags, time_t sent_at ); G_MODULE_EXPORT void imcb_buddy_typing( struct im_connection *ic, char *handle, uint32_t flags ); +G_MODULE_EXPORT struct bee_user *imcb_buddy_by_handle( struct im_connection *ic, const char *handle ); G_MODULE_EXPORT void imcb_clean_handle( struct im_connection *ic, char *handle ); -/* Groupchats */ -G_MODULE_EXPORT void imcb_chat_invited( struct im_connection *ic, char *handle, char *who, char *msg, GList *data ); -/* 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_add_buddy( struct groupchat *b, 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 *b, const char *handle, const char *reason ); -/* 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, uint32_t 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, ... ) G_GNUC_PRINTF( 2, 3 ); -/* 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_free( struct groupchat *c ); - /* Actions, or whatever. */ int imc_away_send_update( struct im_connection *ic ); -int imc_buddy_msg( struct im_connection *ic, char *handle, char *msg, int flags ); int imc_chat_msg( struct groupchat *c, char *msg, int flags ); void imc_add_allow( struct im_connection *ic, char *handle ); @@ -323,7 +323,7 @@ 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_away_devoice( set_t *set, char *value ); +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 ); diff --git a/protocols/oscar/Makefile b/protocols/oscar/Makefile index 2792f22a..c1a966ad 100644 --- a/protocols/oscar/Makefile +++ b/protocols/oscar/Makefile @@ -7,11 +7,14 @@ ### 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 -CFLAGS += -Wall LFLAGS += -r # [SH] Phony targets @@ -32,7 +35,7 @@ distclean: clean $(objects): ../../Makefile.settings Makefile -$(objects): %.o: %.c +$(objects): %.o: $(SRCDIR)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $< -o $@ diff --git a/protocols/oscar/auth.c b/protocols/oscar/auth.c index eb6a9d64..0f7c8d0f 100644 --- a/protocols/oscar/auth.c +++ b/protocols/oscar/auth.c @@ -119,11 +119,12 @@ 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 ((sn[0] >= '0') && (sn[0] <= '9')) + if (isdigit(sn[0]) && set_getbool(&ic->acc->set, "old_icq_auth")) return goddamnicq(sess, conn, sn); sess->flags |= AIM_SESS_FLAGS_SNACLOGIN; diff --git a/protocols/oscar/oscar.c b/protocols/oscar/oscar.c index f0e65f9a..3f5272cd 100644 --- a/protocols/oscar/oscar.c +++ b/protocols/oscar/oscar.c @@ -204,7 +204,6 @@ 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_memrequest (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 *, ...); @@ -254,8 +253,6 @@ static char *normalize(const char *s) g_return_val_if_fail((s != NULL), NULL); u = t = g_strdup(s); - - strcpy(t, s); g_strdown(t); while (*t && (x < BUF_LEN - 1)) { @@ -290,7 +287,7 @@ static gboolean oscar_callback(gpointer data, gint source, odata = (struct oscar_data *)ic->proto_data; - if (condition & GAIM_INPUT_READ) { + if (condition & B_EV_IO_READ) { if (aim_get_command(odata->sess, conn) >= 0) { aim_rxdispatch(odata->sess); if (odata->killme) @@ -362,7 +359,7 @@ static gboolean oscar_login_connect(gpointer data, gint source, b_input_conditio } aim_conn_completeconnect(sess, conn); - ic->inpa = b_input_add(conn->fd, GAIM_INPUT_READ, + ic->inpa = b_input_add(conn->fd, B_EV_IO_READ, oscar_callback, conn); return FALSE; @@ -372,11 +369,16 @@ static void oscar_init(account_t *acc) { set_t *s; - s = set_add( &acc->set, "server", AIM_DEFAULT_LOGIN_SERVER, set_eval_account, acc ); + if (isdigit(acc->user[0])) { + 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", AIM_DEFAULT_LOGIN_SERVER, set_eval_account, acc); s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY; - if (isdigit(acc->user[0])) { - s = set_add( &acc->set, "web_aware", "false", set_eval_bool, acc ); + if(isdigit(acc->user[0])) { + s = set_add(&acc->set, "web_aware", "false", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; } @@ -488,7 +490,7 @@ static gboolean oscar_bos_connect(gpointer data, gint source, b_input_condition } aim_conn_completeconnect(sess, bosconn); - ic->inpa = b_input_add(bosconn->fd, GAIM_INPUT_READ, + ic->inpa = b_input_add(bosconn->fd, B_EV_IO_READ, oscar_callback, bosconn); imcb_log(ic, _("Connection established, cookie sent")); @@ -565,7 +567,6 @@ static int gaim_parse_auth_resp(aim_session_t *sess, aim_frame_t *fr, ...) { 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, 0x1f, gaim_memrequest, 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); @@ -599,143 +600,9 @@ static int gaim_parse_auth_resp(aim_session_t *sess, aim_frame_t *fr, ...) { return 1; } -struct pieceofcrap { - struct im_connection *ic; - unsigned long offset; - unsigned long len; - char *modname; - int fd; - aim_conn_t *conn; - unsigned int inpa; -}; - -static gboolean damn_you(gpointer data, gint source, b_input_condition c) -{ - struct pieceofcrap *pos = data; - struct oscar_data *od = pos->ic->proto_data; - char in = '\0'; - int x = 0; - unsigned char m[17]; - - while (read(pos->fd, &in, 1) == 1) { - if (in == '\n') - x++; - else if (in != '\r') - x = 0; - if (x == 2) - break; - in = '\0'; - } - if (in != '\n') { - imcb_error(pos->ic, "Gaim was unable to get a valid hash for logging into AIM." - " You may be disconnected shortly."); - b_event_remove(pos->inpa); - closesocket(pos->fd); - g_free(pos); - return FALSE; - } - /* [WvG] Wheeeee! Who needs error checking anyway? ;-) */ - read(pos->fd, m, 16); - m[16] = '\0'; - b_event_remove(pos->inpa); - closesocket(pos->fd); - aim_sendmemblock(od->sess, pos->conn, 0, 16, m, AIM_SENDMEMBLOCK_FLAG_ISHASH); - g_free(pos); - - return FALSE; -} - -static gboolean straight_to_hell(gpointer data, gint source, b_input_condition cond) { - struct pieceofcrap *pos = data; - char buf[BUF_LONG]; - - if (source < 0) { - imcb_error(pos->ic, "Gaim was unable to get a valid hash for logging into AIM." - " You may be disconnected shortly."); - if (pos->modname) - g_free(pos->modname); - g_free(pos); - return FALSE; - } - - g_snprintf(buf, sizeof(buf), "GET " AIMHASHDATA - "?offset=%ld&len=%ld&modname=%s HTTP/1.0\n\n", - pos->offset, pos->len, pos->modname ? pos->modname : ""); - write(pos->fd, buf, strlen(buf)); - if (pos->modname) - g_free(pos->modname); - pos->inpa = b_input_add(pos->fd, GAIM_INPUT_READ, damn_you, pos); - return FALSE; -} - /* size of icbmui.ocm, the largest module in AIM 3.5 */ #define AIM_MAX_FILE_SIZE 98304 -int gaim_memrequest(aim_session_t *sess, aim_frame_t *fr, ...) { - va_list ap; - struct pieceofcrap *pos; - guint32 offset, len; - char *modname; - int fd; - - va_start(ap, fr); - offset = (guint32)va_arg(ap, unsigned long); - len = (guint32)va_arg(ap, unsigned long); - modname = va_arg(ap, char *); - va_end(ap); - - if (len == 0) { - aim_sendmemblock(sess, fr->conn, offset, len, NULL, - AIM_SENDMEMBLOCK_FLAG_ISREQUEST); - return 1; - } - /* uncomment this when you're convinced it's right. remember, it's been wrong before. - if (offset > AIM_MAX_FILE_SIZE || len > AIM_MAX_FILE_SIZE) { - char *buf; - int i = 8; - if (modname) - i += strlen(modname); - buf = g_malloc(i); - i = 0; - if (modname) { - memcpy(buf, modname, strlen(modname)); - i += strlen(modname); - } - buf[i++] = offset & 0xff; - buf[i++] = (offset >> 8) & 0xff; - buf[i++] = (offset >> 16) & 0xff; - buf[i++] = (offset >> 24) & 0xff; - buf[i++] = len & 0xff; - buf[i++] = (len >> 8) & 0xff; - buf[i++] = (len >> 16) & 0xff; - buf[i++] = (len >> 24) & 0xff; - aim_sendmemblock(sess, command->conn, offset, i, buf, AIM_SENDMEMBLOCK_FLAG_ISREQUEST); - g_free(buf); - return 1; - } - */ - - pos = g_new0(struct pieceofcrap, 1); - pos->ic = sess->aux_data; - pos->conn = fr->conn; - - pos->offset = offset; - pos->len = len; - pos->modname = modname ? g_strdup(modname) : NULL; - - fd = proxy_connect("gaim.sourceforge.net", 80, straight_to_hell, pos); - if (fd < 0) { - if (pos->modname) - g_free(pos->modname); - g_free(pos); - imcb_error(sess->aux_data, "Gaim was unable to get a valid hash for logging into AIM." - " You may be disconnected shortly."); - } - pos->fd = fd; - - return 1; -} - 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}; @@ -782,6 +649,7 @@ static int gaim_parse_logout(aim_session_t *sess, aim_frame_t *fr, ...) { static int conninitdone_chat(aim_session_t *sess, aim_frame_t *fr, ...) { struct im_connection *ic = sess->aux_data; struct chat_connection *chatcon; + struct groupchat *c = NULL; static int id = 1; aim_conn_addhandler(sess, fr->conn, 0x000e, 0x0001, gaim_parse_genericerr, 0); @@ -794,7 +662,12 @@ static int conninitdone_chat(aim_session_t *sess, aim_frame_t *fr, ...) { chatcon = find_oscar_chat_by_conn(ic, fr->conn); chatcon->id = id; - chatcon->cnv = imcb_chat_new(ic, chatcon->show); + + c = bee_chat_by_title(ic->bee, ic, chatcon->show); + if (c && !c->data) + chatcon->cnv = c; + else + chatcon->cnv = imcb_chat_new(ic, chatcon->show); chatcon->cnv->data = chatcon; return 1; @@ -833,7 +706,7 @@ static gboolean oscar_chatnav_connect(gpointer data, gint source, b_input_condit } aim_conn_completeconnect(sess, tstconn); - odata->cnpa = b_input_add(tstconn->fd, GAIM_INPUT_READ, + odata->cnpa = b_input_add(tstconn->fd, B_EV_IO_READ, oscar_callback, tstconn); return FALSE; @@ -861,7 +734,7 @@ static gboolean oscar_auth_connect(gpointer data, gint source, b_input_condition } aim_conn_completeconnect(sess, tstconn); - odata->paspa = b_input_add(tstconn->fd, GAIM_INPUT_READ, + odata->paspa = b_input_add(tstconn->fd, B_EV_IO_READ, oscar_callback, tstconn); return FALSE; @@ -897,7 +770,7 @@ static gboolean oscar_chat_connect(gpointer data, gint source, b_input_condition aim_conn_completeconnect(sess, ccon->conn); ccon->inpa = b_input_add(tstconn->fd, - GAIM_INPUT_READ, + B_EV_IO_READ, oscar_callback, tstconn); odata->oscar_chats = g_slist_append(odata->oscar_chats, ccon); @@ -1064,7 +937,7 @@ static int gaim_parse_oncoming(aim_session_t *sess, aim_frame_t *fr, ...) { tmp = normalize(info->sn); imcb_buddy_status(ic, tmp, flags, state_string, NULL); - /* imcb_buddy_times(ic, tmp, signon, time_idle); */ + imcb_buddy_times(ic, tmp, signon, time_idle); return 1; @@ -1161,16 +1034,21 @@ static int incomingim_chan2(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_ *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 ); + 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); + 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; @@ -1185,8 +1063,7 @@ static void gaim_icq_authgrant(void *data_) { message = 0; aim_ssi_auth_reply(od->sess, od->conn, uin, 1, ""); // aim_send_im_ch4(od->sess, uin, AIM_ICQMSG_AUTHGRANTED, &message); - if(imcb_find_buddy(data->ic, uin) == NULL) - imcb_ask_add(data->ic, uin, NULL); + imcb_ask_add(data->ic, uin, NULL); g_free(uin); g_free(data); @@ -1211,10 +1088,15 @@ static void gaim_icq_authdeny(void *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 = g_new(struct icq_auth, 1); + 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; @@ -1942,11 +1824,13 @@ static void oscar_get_info(struct im_connection *g, char *name) { static void oscar_get_away(struct im_connection *g, char *who) { struct oscar_data *odata = (struct oscar_data *)g->proto_data; if (odata->icq) { + /** FIXME(wilmer): Hmm, lost the ability to get away msgs here, do we care to get that back? struct buddy *budlight = imcb_find_buddy(g, who); if (budlight) if ((budlight->uc & 0xff80) >> 7) if (budlight->caps & AIM_CAPS_ICQSERVERRELAY) aim_send_im_ch2_geticqmessage(odata->sess, who, (budlight->uc & 0xff80) >> 7); + */ } else aim_getinfo(odata->sess, odata->conn, who, AIM_GETINFO_AWAYMESSAGE); } @@ -2058,7 +1942,12 @@ static void oscar_set_away(struct im_connection *ic, char *state, char *message) static void oscar_add_buddy(struct im_connection *g, char *name, char *group) { struct oscar_data *odata = (struct oscar_data *)g->proto_data; - aim_ssi_addbuddies(odata->sess, odata->conn, OSCAR_GROUP, &name, 1, 0); + 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) { @@ -2073,7 +1962,7 @@ static int gaim_ssi_parserights(aim_session_t *sess, aim_frame_t *fr, ...) { static int gaim_ssi_parselist(aim_session_t *sess, aim_frame_t *fr, ...) { struct im_connection *ic = sess->aux_data; - struct aim_ssi_item *curitem; + struct aim_ssi_item *curitem, *curgroup = NULL; int tmp; char *nrm; @@ -2084,13 +1973,13 @@ static int gaim_ssi_parselist(aim_session_t *sess, aim_frame_t *fr, ...) { switch (curitem->type) { case 0x0000: /* Buddy */ - if ((curitem->name) && (!imcb_find_buddy(ic, nrm))) { + if ((curitem->name) && (!imcb_buddy_by_handle(ic, nrm))) { char *realname = NULL; if (curitem->data && aim_gettlv(curitem->data, 0x0131, 1)) realname = aim_gettlv_str(curitem->data, 0x0131, 1); - - imcb_add_buddy(ic, nrm, NULL); + + imcb_add_buddy(ic, nrm, curgroup ? (curgroup->gid == curitem->gid ? curgroup->name : NULL) : NULL); if (realname) { imcb_buddy_nick_hint(ic, nrm, realname); @@ -2100,6 +1989,10 @@ static int gaim_ssi_parselist(aim_session_t *sess, aim_frame_t *fr, ...) { } break; + case 0x0001: /* Group */ + curgroup = curitem; + break; + case 0x0002: /* Permit buddy */ if (curitem->name) { GSList *list; @@ -2178,10 +2071,13 @@ static int gaim_ssi_parseack( aim_session_t *sess, aim_frame_t *fr, ... ) 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), NULL ); + imcb_add_buddy( sess->aux_data, normalize(list), group ); } else if( st == 0x0E ) { @@ -2329,7 +2225,7 @@ static int gaim_icqinfo(aim_session_t *sess, aim_frame_t *fr, ...) g_string_append_printf(str, "\n%s: %s", _("Email Address"), info->email2[i]); } } - if ((ip = (long) g_hash_table_lookup(od->ips, &info->uin)) != 0) { + 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); } @@ -2542,7 +2438,8 @@ void oscar_chat_msg(struct groupchat *c, char *message, int msgflags) guint16 flags; char *s; - ccon = c->data; + if (!(ccon = c->data)) + return; for (s = message; *s; s++) if (*s & 128) @@ -2583,7 +2480,10 @@ void oscar_chat_invite(struct groupchat *c, char *who, char *message) { struct im_connection *ic = c->ic; struct oscar_data * od = (struct oscar_data *)ic->proto_data; - struct chat_connection *ccon = c->data; + struct chat_connection *ccon; + + if (!(ccon = c->data)) + return; aim_chat_invite(od->sess, od->conn, who, message ? message : "", ccon->exchange, ccon->name, 0x0); @@ -2608,43 +2508,59 @@ void oscar_chat_kill(struct im_connection *ic, struct chat_connection *cc) void oscar_chat_leave(struct groupchat *c) { + if (!c->data) + return; oscar_chat_kill(c->ic, c->data); } -struct groupchat *oscar_chat_join(struct im_connection * ic, const char * room, const char * nick, const char * password ) +struct groupchat *oscar_chat_join_internal(struct im_connection *ic, const char *room, + const char *nick, const char *password, int exchange_number) { struct oscar_data * od = (struct oscar_data *)ic->proto_data; + struct groupchat *ret = imcb_chat_new(ic, room); aim_conn_t * cur; if((cur = aim_getconn_type(od->sess, AIM_CONN_TYPE_CHATNAV))) { int st; - st = aim_chatnav_createroom(od->sess, cur, room, 4); + st = aim_chatnav_createroom(od->sess, cur, room, exchange_number); - return NULL; + return ret; } else { struct create_room * cr = g_new0(struct create_room, 1); - cr->exchange = 4; + cr->exchange = exchange_number; cr->name = g_strdup(room); od->create_rooms = g_slist_append(od->create_rooms, cr); aim_reqservice(od->sess, od->conn, AIM_CONN_TYPE_CHATNAV); - return NULL; + return ret; } } +struct groupchat *oscar_chat_join(struct im_connection *ic, const char *room, + const char *nick, const char *password, set_t **sets) +{ + return oscar_chat_join_internal(ic, room, nick, password, set_getint(sets, "exchange_number")); +} + struct groupchat *oscar_chat_with(struct im_connection * ic, char *who) { struct oscar_data * od = (struct oscar_data *)ic->proto_data; struct groupchat *ret; static int chat_id = 0; - char * chatname; + char * chatname, *s; + struct groupchat *c; - chatname = g_strdup_printf("%s%d", ic->acc->user, chat_id++); - - ret = oscar_chat_join(ic, chatname, NULL, NULL); - + 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); @@ -2656,7 +2572,7 @@ void oscar_accept_chat(void *data) { struct aim_chat_invitation * inv = data; - oscar_chat_join(inv->ic, inv->name, NULL, NULL); + oscar_chat_join_internal(inv->ic, inv->name, NULL, NULL, 4); g_free(inv->name); g_free(inv); } @@ -2669,6 +2585,16 @@ void oscar_reject_chat(void *data) g_free(inv); } +void oscar_chat_add_settings(account_t *acc, set_t **head) +{ + set_add(head, "exchange_number", "4", set_eval_int, NULL); +} + +void oscar_chat_free_settings(account_t *acc, set_t **head) +{ + set_del(head, "exchange_number"); +} + void oscar_initmodule() { struct prpl *ret = g_new0(struct prpl, 1); @@ -2689,6 +2615,8 @@ void oscar_initmodule() ret->chat_leave = oscar_chat_leave; ret->chat_with = oscar_chat_with; ret->chat_join = oscar_chat_join; + ret->chat_add_settings = oscar_chat_add_settings; + ret->chat_free_settings = oscar_chat_free_settings; ret->add_permit = oscar_add_permit; ret->add_deny = oscar_add_deny; ret->rem_permit = oscar_rem_permit; diff --git a/protocols/purple/Makefile b/protocols/purple/Makefile new file mode 100644 index 00000000..97a5bb6a --- /dev/null +++ b/protocols/purple/Makefile @@ -0,0 +1,44 @@ +########################### +## Makefile for BitlBee ## +## ## +## Copyright 2002 Lintux ## +########################### + +### DEFINITIONS + +-include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/purple/ +endif + +# [SH] Program variables +objects = ft.o purple.o + +CFLAGS += -Wall $(PURPLE_CFLAGS) +LFLAGS += -r + +# [SH] Phony targets +all: purple_mod.o +check: all +lcov: check +gcov: + gcov *.c + +.PHONY: all clean distclean + +clean: + rm -f *.o core + +distclean: clean + +### MAIN PROGRAM + +$(objects): ../../Makefile.settings Makefile + +$(objects): %.o: $(SRCDIR)%.c + @echo '*' Compiling $< + @$(CC) -c $(CFLAGS) $< -o $@ + +purple_mod.o: $(objects) + @echo '*' Linking purple_mod.o + $(LD) $(LFLAGS) $(objects) -o purple_mod.o diff --git a/protocols/purple/ft-direct.c b/protocols/purple/ft-direct.c new file mode 100644 index 00000000..98a16d75 --- /dev/null +++ b/protocols/purple/ft-direct.c @@ -0,0 +1,239 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* libpurple module - File transfer stuff * +* * +* Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +/* This code tries to do direct file transfers, i.e. without caching the file + locally on disk first. Since libpurple can only do this since version 2.6.0 + and even then very unreliably (and not with all IM modules), I'm canning + this code for now. */ + +#include "bitlbee.h" + +#include <stdarg.h> + +#include <glib.h> +#include <purple.h> + +struct prpl_xfer_data +{ + PurpleXfer *xfer; + file_transfer_t *ft; + gint ready_timer; + char *buf; + int buf_len; +}; + +static file_transfer_t *next_ft; + +struct im_connection *purple_ic_by_pa( PurpleAccount *pa ); + +/* Glorious hack: We seem to have to remind at least some libpurple plugins + that we're ready because this info may get lost if we give it too early. + So just do it ten times a second. :-/ */ +static gboolean prplcb_xfer_write_request_cb( gpointer data, gint fd, b_input_condition cond ) +{ + struct prpl_xfer_data *px = data; + + purple_xfer_ui_ready( px->xfer ); + + return purple_xfer_get_type( px->xfer ) == PURPLE_XFER_RECEIVE; +} + +static gboolean prpl_xfer_write_request( struct file_transfer *ft ) +{ + struct prpl_xfer_data *px = ft->data; + px->ready_timer = b_timeout_add( 100, prplcb_xfer_write_request_cb, px ); + return TRUE; +} + +static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len ) +{ + struct prpl_xfer_data *px = ft->data; + + px->buf = g_memdup( buffer, len ); + px->buf_len = len; + + //purple_xfer_ui_ready( px->xfer ); + px->ready_timer = b_timeout_add( 0, prplcb_xfer_write_request_cb, px ); + + return TRUE; +} + +static void prpl_xfer_accept( struct file_transfer *ft ) +{ + struct prpl_xfer_data *px = ft->data; + purple_xfer_request_accepted( px->xfer, NULL ); + prpl_xfer_write_request( ft ); +} + +static void prpl_xfer_canceled( struct file_transfer *ft, char *reason ) +{ + struct prpl_xfer_data *px = ft->data; + purple_xfer_request_denied( px->xfer ); +} + +static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond ) +{ + PurpleXfer *xfer = data; + struct im_connection *ic = purple_ic_by_pa( xfer->account ); + struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 ); + PurpleBuddy *buddy; + const char *who; + + buddy = purple_find_buddy( xfer->account, xfer->who ); + who = buddy ? purple_buddy_get_name( buddy ) : xfer->who; + + /* TODO(wilmer): After spreading some more const goodness in BitlBee, + remove the evil cast below. */ + px->ft = imcb_file_send_start( ic, (char*) who, xfer->filename, xfer->size ); + px->ft->data = px; + px->xfer = data; + px->xfer->ui_data = px; + + px->ft->accept = prpl_xfer_accept; + px->ft->canceled = prpl_xfer_canceled; + px->ft->write_request = prpl_xfer_write_request; + + return FALSE; +} + +static void prplcb_xfer_new( PurpleXfer *xfer ) +{ + if( purple_xfer_get_type( xfer ) == PURPLE_XFER_RECEIVE ) + { + /* This should suppress the stupid file dialog. */ + purple_xfer_set_local_filename( xfer, "/tmp/wtf123" ); + + /* Sadly the xfer struct is still empty ATM so come back after + the caller is done. */ + b_timeout_add( 0, prplcb_xfer_new_send_cb, xfer ); + } + else + { + struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 ); + + px->ft = next_ft; + px->ft->data = px; + px->xfer = xfer; + px->xfer->ui_data = px; + + purple_xfer_set_filename( xfer, px->ft->file_name ); + purple_xfer_set_size( xfer, px->ft->file_size ); + + next_ft = NULL; + } +} + +static void prplcb_xfer_progress( PurpleXfer *xfer, double percent ) +{ + fprintf( stderr, "prplcb_xfer_dbg 0x%p %f\n", xfer, percent ); +} + +static void prplcb_xfer_dbg( PurpleXfer *xfer ) +{ + fprintf( stderr, "prplcb_xfer_dbg 0x%p\n", xfer ); +} + +static gssize prplcb_xfer_write( PurpleXfer *xfer, const guchar *buffer, gssize size ) +{ + struct prpl_xfer_data *px = xfer->ui_data; + gboolean st; + + fprintf( stderr, "xfer_write %d %d\n", size, px->buf_len ); + + b_event_remove( px->ready_timer ); + px->ready_timer = 0; + + st = px->ft->write( px->ft, (char*) buffer, size ); + + if( st && xfer->bytes_remaining == size ) + imcb_file_finished( px->ft ); + + return st ? size : 0; +} + +gssize prplcb_xfer_read( PurpleXfer *xfer, guchar **buffer, gssize size ) +{ + struct prpl_xfer_data *px = xfer->ui_data; + + fprintf( stderr, "xfer_read %d %d\n", size, px->buf_len ); + + if( px->buf ) + { + *buffer = px->buf; + px->buf = NULL; + + px->ft->write_request( px->ft ); + + return px->buf_len; + } + + return 0; +} + +PurpleXferUiOps bee_xfer_uiops = +{ + prplcb_xfer_new, + prplcb_xfer_dbg, + prplcb_xfer_dbg, + prplcb_xfer_progress, + prplcb_xfer_dbg, + prplcb_xfer_dbg, + prplcb_xfer_write, + prplcb_xfer_read, + prplcb_xfer_dbg, +}; + +static gboolean prplcb_xfer_send_cb( gpointer data, gint fd, b_input_condition cond ); + +void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle ) +{ + PurpleAccount *pa = ic->proto_data; + struct prpl_xfer_data *px; + + /* xfer_new() will pick up this variable. It's a hack but we're not + multi-threaded anyway. */ + next_ft = ft; + serv_send_file( purple_account_get_connection( pa ), handle, ft->file_name ); + + ft->write = prpl_xfer_write; + + px = ft->data; + imcb_file_recv_start( ft ); + + px->ready_timer = b_timeout_add( 100, prplcb_xfer_send_cb, px ); +} + +static gboolean prplcb_xfer_send_cb( gpointer data, gint fd, b_input_condition cond ) +{ + struct prpl_xfer_data *px = data; + + if( px->ft->status & FT_STATUS_TRANSFERRING ) + { + fprintf( stderr, "The ft, it is ready...\n" ); + px->ft->write_request( px->ft ); + + return FALSE; + } + + return TRUE; +} diff --git a/protocols/purple/ft.c b/protocols/purple/ft.c new file mode 100644 index 00000000..c4efc657 --- /dev/null +++ b/protocols/purple/ft.c @@ -0,0 +1,355 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* libpurple module - File transfer stuff * +* * +* Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +/* Do file transfers via disk for now, since libpurple was really designed + for straight-to/from disk fts and is only just learning how to pass the + file contents the the UI instead (2.6.0 and higher it seems, and with + varying levels of success). */ + +#include "bitlbee.h" + +#include <stdarg.h> + +#include <glib.h> +#include <purple.h> + +struct prpl_xfer_data +{ + PurpleXfer *xfer; + file_transfer_t *ft; + struct im_connection *ic; + int fd; + char *fn, *handle; + gboolean ui_wants_data; +}; + +static file_transfer_t *next_ft; + +struct im_connection *purple_ic_by_pa( PurpleAccount *pa ); +static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond ); +static gboolean prpl_xfer_write_request( struct file_transfer *ft ); + + +/* Receiving files (IM->UI): */ +static void prpl_xfer_accept( struct file_transfer *ft ) +{ + struct prpl_xfer_data *px = ft->data; + purple_xfer_request_accepted( px->xfer, NULL ); + prpl_xfer_write_request( ft ); +} + +static void prpl_xfer_canceled( struct file_transfer *ft, char *reason ) +{ + struct prpl_xfer_data *px = ft->data; + purple_xfer_request_denied( px->xfer ); +} + +static void prplcb_xfer_new( PurpleXfer *xfer ) +{ + if( purple_xfer_get_type( xfer ) == PURPLE_XFER_RECEIVE ) + { + struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 ); + + xfer->ui_data = px; + px->xfer = xfer; + px->fn = mktemp( g_strdup( "/tmp/bitlbee-purple-ft.XXXXXX" ) ); + px->fd = -1; + px->ic = purple_ic_by_pa( xfer->account ); + + purple_xfer_set_local_filename( xfer, px->fn ); + + /* Sadly the xfer struct is still empty ATM so come back after + the caller is done. */ + b_timeout_add( 0, prplcb_xfer_new_send_cb, xfer ); + } + else + { + struct file_transfer *ft = next_ft; + struct prpl_xfer_data *px = ft->data; + + xfer->ui_data = px; + px->xfer = xfer; + + next_ft = NULL; + } +} + +static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond ) +{ + PurpleXfer *xfer = data; + struct im_connection *ic = purple_ic_by_pa( xfer->account ); + struct prpl_xfer_data *px = xfer->ui_data; + PurpleBuddy *buddy; + const char *who; + + buddy = purple_find_buddy( xfer->account, xfer->who ); + who = buddy ? purple_buddy_get_name( buddy ) : xfer->who; + + /* TODO(wilmer): After spreading some more const goodness in BitlBee, + remove the evil cast below. */ + px->ft = imcb_file_send_start( ic, (char*) who, xfer->filename, xfer->size ); + px->ft->data = px; + + px->ft->accept = prpl_xfer_accept; + px->ft->canceled = prpl_xfer_canceled; + px->ft->write_request = prpl_xfer_write_request; + + return FALSE; +} + +gboolean try_write_to_ui( gpointer data, gint fd, b_input_condition cond ) +{ + struct file_transfer *ft = data; + struct prpl_xfer_data *px = ft->data; + struct stat fs; + off_t tx_bytes; + + /* If we don't have the file opened yet, there's no data so wait. */ + if( px->fd < 0 || !px->ui_wants_data ) + return FALSE; + + tx_bytes = lseek( px->fd, 0, SEEK_CUR ); + fstat( px->fd, &fs ); + + if( fs.st_size > tx_bytes ) + { + char buf[1024]; + size_t n = MIN( fs.st_size - tx_bytes, sizeof( buf ) ); + + if( read( px->fd, buf, n ) == n && ft->write( ft, buf, n ) ) + { + px->ui_wants_data = FALSE; + } + else + { + purple_xfer_cancel_local( px->xfer ); + imcb_file_canceled( px->ic, ft, "Read error" ); + } + } + + if( lseek( px->fd, 0, SEEK_CUR ) == px->xfer->size ) + { + /*purple_xfer_end( px->xfer );*/ + imcb_file_finished( px->ic, ft ); + } + + return FALSE; +} + +/* UI calls this when its buffer is empty and wants more data to send to the user. */ +static gboolean prpl_xfer_write_request( struct file_transfer *ft ) +{ + struct prpl_xfer_data *px = ft->data; + + px->ui_wants_data = TRUE; + try_write_to_ui( ft, 0, 0 ); + + return FALSE; +} + + +/* Generic (IM<>UI): */ +static void prplcb_xfer_destroy( PurpleXfer *xfer ) +{ + struct prpl_xfer_data *px = xfer->ui_data; + + g_free( px->fn ); + g_free( px->handle ); + if( px->fd >= 0 ) + close( px->fd ); + g_free( px ); +} + +static void prplcb_xfer_progress( PurpleXfer *xfer, double percent ) +{ + struct prpl_xfer_data *px = xfer->ui_data; + + if( px == NULL ) + return; + + if( purple_xfer_get_type( xfer ) == PURPLE_XFER_SEND ) + { + if( *px->fn ) + { + char *slash; + + unlink( px->fn ); + if( ( slash = strrchr( px->fn, '/' ) ) ) + { + *slash = '\0'; + rmdir( px->fn ); + } + *px->fn = '\0'; + } + + return; + } + + if( px->fd == -1 && percent > 0 ) + { + /* Weeeeeeeee, we're getting data! That means the file exists + by now so open it and start sending to the UI. */ + px->fd = open( px->fn, O_RDONLY ); + + /* Unlink it now, because we don't need it after this. */ + unlink( px->fn ); + } + + if( percent < 1 ) + try_write_to_ui( px->ft, 0, 0 ); + else + /* Another nice problem: If we have the whole file, it only + gets closed when we return. Problem: There may still be + stuff buffered and not written, we'll only see it after + the caller close()s the file. So poll the file after that. */ + b_timeout_add( 0, try_write_to_ui, px->ft ); +} + +static void prplcb_xfer_cancel_remote( PurpleXfer *xfer ) +{ + struct prpl_xfer_data *px = xfer->ui_data; + + if( px->ft ) + imcb_file_canceled( px->ic, px->ft, "Canceled by remote end" ); + else + /* px->ft == NULL for sends, because of the two stages. :-/ */ + imcb_error( px->ic, "File transfer cancelled by remote end" ); +} + +static void prplcb_xfer_dbg( PurpleXfer *xfer ) +{ + fprintf( stderr, "prplcb_xfer_dbg 0x%p\n", xfer ); +} + + +/* Sending files (UI->IM): */ +static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len ); +static gboolean purple_transfer_request_cb( gpointer data, gint fd, b_input_condition cond ); + +void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle ) +{ + struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 ); + char *dir, *basename; + + ft->data = px; + px->ft = ft; + + dir = g_strdup( "/tmp/bitlbee-purple-ft.XXXXXX" ); + if( !mkdtemp( dir ) ) + { + imcb_error( ic, "Could not create temporary file for file transfer" ); + g_free( px ); + g_free( dir ); + return; + } + + if( ( basename = strrchr( ft->file_name, '/' ) ) ) + basename++; + else + basename = ft->file_name; + px->fn = g_strdup_printf( "%s/%s", dir, basename ); + px->fd = open( px->fn, O_WRONLY | O_CREAT, 0600 ); + g_free( dir ); + + if( px->fd < 0 ) + { + imcb_error( ic, "Could not create temporary file for file transfer" ); + g_free( px ); + g_free( px->fn ); + return; + } + + px->ic = ic; + px->handle = g_strdup( handle ); + + imcb_log( ic, "Due to libpurple limitations, the file has to be cached locally before proceeding with the actual file transfer. Please wait..." ); + + b_timeout_add( 0, purple_transfer_request_cb, ft ); +} + +static void purple_transfer_forward( struct file_transfer *ft ) +{ + struct prpl_xfer_data *px = ft->data; + PurpleAccount *pa = px->ic->proto_data; + + /* xfer_new() will pick up this variable. It's a hack but we're not + multi-threaded anyway. */ + next_ft = ft; + serv_send_file( purple_account_get_connection( pa ), px->handle, px->fn ); +} + +static gboolean purple_transfer_request_cb( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *ft = data; + struct prpl_xfer_data *px = ft->data; + + if( ft->write == NULL ) + { + ft->write = prpl_xfer_write; + imcb_file_recv_start( px->ic, ft ); + } + + ft->write_request( ft ); + + return FALSE; +} + +static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len ) +{ + struct prpl_xfer_data *px = ft->data; + + if( write( px->fd, buffer, len ) != len ) + { + imcb_file_canceled( px->ic, ft, "Error while writing temporary file" ); + return FALSE; + } + + if( lseek( px->fd, 0, SEEK_CUR ) >= ft->file_size ) + { + close( px->fd ); + px->fd = -1; + + purple_transfer_forward( ft ); + imcb_file_finished( px->ic, ft ); + px->ft = NULL; + } + else + b_timeout_add( 0, purple_transfer_request_cb, ft ); + + return TRUE; +} + + + +PurpleXferUiOps bee_xfer_uiops = +{ + prplcb_xfer_new, + prplcb_xfer_destroy, + NULL, /* prplcb_xfer_add, */ + prplcb_xfer_progress, + prplcb_xfer_dbg, + prplcb_xfer_cancel_remote, + NULL, + NULL, + prplcb_xfer_dbg, +}; diff --git a/protocols/purple/purple.c b/protocols/purple/purple.c new file mode 100644 index 00000000..c7cfcfda --- /dev/null +++ b/protocols/purple/purple.c @@ -0,0 +1,1201 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* libpurple module - Main file * +* * +* Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#include "bitlbee.h" +#include "help.h" + +#include <stdarg.h> + +#include <glib.h> +#include <purple.h> + +GSList *purple_connections; + +/* This makes me VERY sad... :-( But some libpurple callbacks come in without + any context so this is the only way to get that. Don't want to support + libpurple in daemon mode anyway. */ +static bee_t *local_bee; + +static char *set_eval_display_name( set_t *set, char *value ); + +struct im_connection *purple_ic_by_pa( PurpleAccount *pa ) +{ + GSList *i; + + for( i = purple_connections; i; i = i->next ) + if( ((struct im_connection *)i->data)->proto_data == pa ) + return i->data; + + return NULL; +} + +static struct im_connection *purple_ic_by_gc( PurpleConnection *gc ) +{ + return purple_ic_by_pa( purple_connection_get_account( gc ) ); +} + +static gboolean purple_menu_cmp( const char *a, const char *b ) +{ + while( *a && *b ) + { + while( *a == '_' ) a ++; + while( *b == '_' ) b ++; + if( tolower( *a ) != tolower( *b ) ) + return FALSE; + + a ++; + b ++; + } + + return ( *a == '\0' && *b == '\0' ); +} + +static void purple_init( account_t *acc ) +{ + PurplePlugin *prpl = purple_plugins_find_with_id( (char*) acc->prpl->data ); + PurplePluginProtocolInfo *pi = prpl->info->extra_info; + PurpleAccount *pa; + GList *i, *st; + set_t *s; + char help_title[64]; + GString *help; + + help = g_string_new( "" ); + g_string_printf( help, "BitlBee libpurple module %s (%s).\n\nSupported settings:", + (char*) acc->prpl->name, prpl->info->name ); + + /* Convert all protocol_options into per-account setting variables. */ + for( i = pi->protocol_options; i; i = i->next ) + { + PurpleAccountOption *o = i->data; + const char *name; + char *def = NULL; + set_eval eval = NULL; + void *eval_data = NULL; + GList *io = NULL; + GSList *opts = NULL; + + name = purple_account_option_get_setting( o ); + + switch( purple_account_option_get_type( o ) ) + { + case PURPLE_PREF_STRING: + def = g_strdup( purple_account_option_get_default_string( o ) ); + + g_string_append_printf( help, "\n* %s (%s), %s, default: %s", + name, purple_account_option_get_text( o ), + "string", def ); + + break; + + case PURPLE_PREF_INT: + def = g_strdup_printf( "%d", purple_account_option_get_default_int( o ) ); + eval = set_eval_int; + + g_string_append_printf( help, "\n* %s (%s), %s, default: %s", + name, purple_account_option_get_text( o ), + "integer", def ); + + break; + + case PURPLE_PREF_BOOLEAN: + if( purple_account_option_get_default_bool( o ) ) + def = g_strdup( "true" ); + else + def = g_strdup( "false" ); + eval = set_eval_bool; + + g_string_append_printf( help, "\n* %s (%s), %s, default: %s", + name, purple_account_option_get_text( o ), + "boolean", def ); + + break; + + case PURPLE_PREF_STRING_LIST: + def = g_strdup( purple_account_option_get_default_list_value( o ) ); + + g_string_append_printf( help, "\n* %s (%s), %s, default: %s", + name, purple_account_option_get_text( o ), + "list", def ); + g_string_append( help, "\n Possible values: " ); + + for( io = purple_account_option_get_list( o ); io; io = io->next ) + { + PurpleKeyValuePair *kv = io->data; + opts = g_slist_append( opts, kv->value ); + /* TODO: kv->value is not a char*, WTF? */ + if( strcmp( kv->value, kv->key ) != 0 ) + g_string_append_printf( help, "%s (%s), ", (char*) kv->value, kv->key ); + else + g_string_append_printf( help, "%s, ", (char*) kv->value ); + } + g_string_truncate( help, help->len - 2 ); + eval = set_eval_list; + eval_data = opts; + + break; + + default: + /** No way to talk to the user right now, invent one when + this becomes important. + irc_usermsg( acc->irc, "Setting with unknown type: %s (%d) Expect stuff to break..\n", + name, purple_account_option_get_type( o ) ); + */ + name = NULL; + } + + if( name != NULL ) + { + s = set_add( &acc->set, name, def, eval, acc ); + s->flags |= ACC_SET_OFFLINE_ONLY; + s->eval_data = eval_data; + g_free( def ); + } + } + + g_snprintf( help_title, sizeof( help_title ), "purple %s", (char*) acc->prpl->name ); + help_add_mem( &global.help, help_title, help->str ); + g_string_free( help, TRUE ); + + s = set_add( &acc->set, "display_name", NULL, set_eval_display_name, acc ); + s->flags |= ACC_SET_ONLINE_ONLY; + + if( pi->options & OPT_PROTO_MAIL_CHECK ) + { + s = set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc ); + s->flags |= ACC_SET_OFFLINE_ONLY; + } + + /* Go through all away states to figure out if away/status messages + are possible. */ + pa = purple_account_new( acc->user, (char*) acc->prpl->data ); + for( st = purple_account_get_status_types( pa ); st; st = st->next ) + { + PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data ); + + if( prim == PURPLE_STATUS_AVAILABLE ) + { + if( purple_status_type_get_attr( st->data, "message" ) ) + acc->flags |= ACC_FLAG_STATUS_MESSAGE; + } + else if( prim != PURPLE_STATUS_OFFLINE ) + { + if( purple_status_type_get_attr( st->data, "message" ) ) + acc->flags |= ACC_FLAG_AWAY_MESSAGE; + } + } + purple_accounts_remove( pa ); +} + +static void purple_sync_settings( account_t *acc, PurpleAccount *pa ) +{ + PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id ); + PurplePluginProtocolInfo *pi = prpl->info->extra_info; + GList *i; + + for( i = pi->protocol_options; i; i = i->next ) + { + PurpleAccountOption *o = i->data; + const char *name; + set_t *s; + + name = purple_account_option_get_setting( o ); + s = set_find( &acc->set, name ); + if( s->value == NULL ) + continue; + + switch( purple_account_option_get_type( o ) ) + { + case PURPLE_PREF_STRING: + case PURPLE_PREF_STRING_LIST: + purple_account_set_string( pa, name, set_getstr( &acc->set, name ) ); + break; + + case PURPLE_PREF_INT: + purple_account_set_int( pa, name, set_getint( &acc->set, name ) ); + break; + + case PURPLE_PREF_BOOLEAN: + purple_account_set_bool( pa, name, set_getbool( &acc->set, name ) ); + break; + + default: + break; + } + } + + if( pi->options & OPT_PROTO_MAIL_CHECK ) + purple_account_set_check_mail( pa, set_getbool( &acc->set, "mail_notifications" ) ); +} + +static void purple_login( account_t *acc ) +{ + struct im_connection *ic = imcb_new( acc ); + PurpleAccount *pa; + + if( local_bee != NULL && local_bee != acc->bee ) + { + imcb_error( ic, "Daemon mode detected. Do *not* try to use libpurple in daemon mode! " + "Please use inetd or ForkDaemon mode instead." ); + imc_logout( ic, FALSE ); + return; + } + local_bee = acc->bee; + + /* For now this is needed in the _connected() handlers if using + GLib event handling, to make sure we're not handling events + on dead connections. */ + purple_connections = g_slist_prepend( purple_connections, ic ); + + ic->proto_data = pa = purple_account_new( acc->user, (char*) acc->prpl->data ); + purple_account_set_password( pa, acc->pass ); + purple_sync_settings( acc, pa ); + + purple_account_set_enabled( pa, "BitlBee", TRUE ); +} + +static void purple_logout( struct im_connection *ic ) +{ + PurpleAccount *pa = ic->proto_data; + + purple_account_set_enabled( pa, "BitlBee", FALSE ); + purple_connections = g_slist_remove( purple_connections, ic ); + purple_accounts_remove( pa ); +} + +static int purple_buddy_msg( struct im_connection *ic, char *who, char *message, int flags ) +{ + PurpleConversation *conv; + + if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM, + who, ic->proto_data ) ) == NULL ) + { + conv = purple_conversation_new( PURPLE_CONV_TYPE_IM, + ic->proto_data, who ); + } + + purple_conv_im_send( purple_conversation_get_im_data( conv ), message ); + + return 1; +} + +static GList *purple_away_states( struct im_connection *ic ) +{ + PurpleAccount *pa = ic->proto_data; + GList *st, *ret = NULL; + + for( st = purple_account_get_status_types( pa ); st; st = st->next ) + { + PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data ); + if( prim != PURPLE_STATUS_AVAILABLE && prim != PURPLE_STATUS_OFFLINE ) + ret = g_list_append( ret, (void*) purple_status_type_get_name( st->data ) ); + } + + return ret; +} + +static void purple_set_away( struct im_connection *ic, char *state_txt, char *message ) +{ + PurpleAccount *pa = ic->proto_data; + GList *status_types = purple_account_get_status_types( pa ), *st; + PurpleStatusType *pst = NULL; + GList *args = NULL; + + for( st = status_types; st; st = st->next ) + { + pst = st->data; + + if( state_txt == NULL && + purple_status_type_get_primitive( pst ) == PURPLE_STATUS_AVAILABLE ) + break; + + if( state_txt != NULL && + g_strcasecmp( state_txt, purple_status_type_get_name( pst ) ) == 0 ) + break; + } + + if( message && purple_status_type_get_attr( pst, "message" ) ) + { + args = g_list_append( args, "message" ); + args = g_list_append( args, message ); + } + + purple_account_set_status_list( pa, st ? purple_status_type_get_id( pst ) : "away", + TRUE, args ); + + g_list_free( args ); +} + +static char *set_eval_display_name( set_t *set, char *value ) +{ + account_t *acc = set->data; + struct im_connection *ic = acc->ic; + + return NULL; +} + +static void purple_add_buddy( struct im_connection *ic, char *who, char *group ) +{ + PurpleBuddy *pb; + PurpleGroup *pg = NULL; + + if( group && !( pg = purple_find_group( group ) ) ) + { + pg = purple_group_new( group ); + purple_blist_add_group( pg, NULL ); + } + + pb = purple_buddy_new( (PurpleAccount*) ic->proto_data, who, NULL ); + purple_blist_add_buddy( pb, NULL, pg, NULL ); + purple_account_add_buddy( (PurpleAccount*) ic->proto_data, pb ); +} + +static void purple_remove_buddy( struct im_connection *ic, char *who, char *group ) +{ + PurpleBuddy *pb; + + pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who ); + if( pb != NULL ) + { + PurpleGroup *group; + + group = purple_buddy_get_group( pb ); + purple_account_remove_buddy( (PurpleAccount*) ic->proto_data, pb, group ); + + purple_blist_remove_buddy( pb ); + } +} + +static void purple_add_permit( struct im_connection *ic, char *who ) +{ + PurpleAccount *pa = ic->proto_data; + + purple_privacy_permit_add( pa, who, FALSE ); +} + +static void purple_add_deny( struct im_connection *ic, char *who ) +{ + PurpleAccount *pa = ic->proto_data; + + purple_privacy_deny_add( pa, who, FALSE ); +} + +static void purple_rem_permit( struct im_connection *ic, char *who ) +{ + PurpleAccount *pa = ic->proto_data; + + purple_privacy_permit_remove( pa, who, FALSE ); +} + +static void purple_rem_deny( struct im_connection *ic, char *who ) +{ + PurpleAccount *pa = ic->proto_data; + + purple_privacy_deny_remove( pa, who, FALSE ); +} + +static void purple_get_info( struct im_connection *ic, char *who ) +{ + serv_get_info( purple_account_get_connection( ic->proto_data ), who ); +} + +static void purple_keepalive( struct im_connection *ic ) +{ +} + +static int purple_send_typing( struct im_connection *ic, char *who, int flags ) +{ + PurpleTypingState state = PURPLE_NOT_TYPING; + PurpleConversation *conv; + + if( flags & OPT_TYPING ) + state = PURPLE_TYPING; + else if( flags & OPT_THINKING ) + state = PURPLE_TYPED; + + if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM, + who, ic->proto_data ) ) == NULL ) + { + purple_conv_im_set_typing_state( purple_conversation_get_im_data( conv ), state ); + return 1; + } + else + { + return 0; + } +} + +static void purple_chat_msg( struct groupchat *gc, char *message, int flags ) +{ + PurpleConversation *pc = gc->data; + + purple_conv_chat_send( purple_conversation_get_chat_data( pc ), message ); +} + +struct groupchat *purple_chat_with( struct im_connection *ic, char *who ) +{ + /* No, "of course" this won't work this way. Or in fact, it almost + does, but it only lets you send msgs to it, you won't receive + any. Instead, we have to click the virtual menu item. + PurpleAccount *pa = ic->proto_data; + PurpleConversation *pc; + PurpleConvChat *pcc; + struct groupchat *gc; + + gc = imcb_chat_new( ic, "BitlBee-libpurple groupchat" ); + gc->data = pc = purple_conversation_new( PURPLE_CONV_TYPE_CHAT, pa, "BitlBee-libpurple groupchat" ); + pc->ui_data = gc; + + pcc = PURPLE_CONV_CHAT( pc ); + purple_conv_chat_add_user( pcc, ic->acc->user, "", 0, TRUE ); + purple_conv_chat_invite_user( pcc, who, "Please join my chat", FALSE ); + //purple_conv_chat_add_user( pcc, who, "", 0, TRUE ); + */ + + /* There went my nice afternoon. :-( */ + + PurpleAccount *pa = ic->proto_data; + PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id ); + PurplePluginProtocolInfo *pi = prpl->info->extra_info; + PurpleBuddy *pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who ); + PurpleMenuAction *mi; + GList *menu; + void (*callback)(PurpleBlistNode *, gpointer); /* FFFFFFFFFFFFFUUUUUUUUUUUUUU */ + + if( !pb || !pi || !pi->blist_node_menu ) + return NULL; + + menu = pi->blist_node_menu( &pb->node ); + while( menu ) + { + mi = menu->data; + if( purple_menu_cmp( mi->label, "initiate chat" ) || + purple_menu_cmp( mi->label, "initiate conference" ) ) + break; + menu = menu->next; + } + + if( menu == NULL ) + return NULL; + + /* Call the fucker. */ + callback = (void*) mi->callback; + callback( &pb->node, menu->data ); + + return NULL; +} + +void purple_chat_invite( struct groupchat *gc, char *who, char *message ) +{ + PurpleConversation *pc = gc->data; + PurpleConvChat *pcc = PURPLE_CONV_CHAT( pc ); + + serv_chat_invite( purple_account_get_connection( gc->ic->proto_data ), + purple_conv_chat_get_id( pcc ), + message && *message ? message : "Please join my chat", + who ); +} + +void purple_chat_leave( struct groupchat *gc ) +{ + PurpleConversation *pc = gc->data; + + purple_conversation_destroy( pc ); +} + +struct groupchat *purple_chat_join( struct im_connection *ic, const char *room, const char *nick, const char *password ) +{ + PurpleAccount *pa = ic->proto_data; + PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id ); + PurplePluginProtocolInfo *pi = prpl->info->extra_info; + GHashTable *chat_hash; + PurpleConversation *conv; + GList *info, *l; + + if( !pi->chat_info || !pi->chat_info_defaults || + !( info = pi->chat_info( purple_account_get_connection( pa ) ) ) ) + { + imcb_error( ic, "Joining chatrooms not supported by this protocol" ); + return NULL; + } + + if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_CHAT, room, pa ) ) ) + purple_conversation_destroy( conv ); + + chat_hash = pi->chat_info_defaults( purple_account_get_connection( pa ), room ); + + for( l = info; l; l = l->next ) + { + struct proto_chat_entry *pce = l->data; + + if( strcmp( pce->identifier, "handle" ) == 0 ) + g_hash_table_replace( chat_hash, "handle", g_strdup( nick ) ); + else if( strcmp( pce->identifier, "password" ) == 0 ) + g_hash_table_replace( chat_hash, "password", g_strdup( password ) ); + else if( strcmp( pce->identifier, "passwd" ) == 0 ) + g_hash_table_replace( chat_hash, "passwd", g_strdup( password ) ); + } + + serv_join_chat( purple_account_get_connection( pa ), chat_hash ); + + return NULL; +} + +void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle ); + +static void purple_ui_init(); + +GHashTable *prplcb_ui_info() +{ + static GHashTable *ret; + + if( ret == NULL ) + { + ret = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert( ret, "name", "BitlBee" ); + g_hash_table_insert( ret, "version", BITLBEE_VERSION ); + } + + return ret; +} + +static PurpleCoreUiOps bee_core_uiops = +{ + NULL, + NULL, + purple_ui_init, + NULL, + prplcb_ui_info, +}; + +static void prplcb_conn_progress( PurpleConnection *gc, const char *text, size_t step, size_t step_count ) +{ + struct im_connection *ic = purple_ic_by_gc( gc ); + + imcb_log( ic, "%s", text ); +} + +static void prplcb_conn_connected( PurpleConnection *gc ) +{ + struct im_connection *ic = purple_ic_by_gc( gc ); + const char *dn; + set_t *s; + + imcb_connected( ic ); + + if( ( dn = purple_connection_get_display_name( gc ) ) && + ( s = set_find( &ic->acc->set, "display_name" ) ) ) + { + g_free( s->value ); + s->value = g_strdup( dn ); + } + + if( gc->flags & PURPLE_CONNECTION_HTML ) + ic->flags |= OPT_DOES_HTML; +} + +static void prplcb_conn_disconnected( PurpleConnection *gc ) +{ + struct im_connection *ic = purple_ic_by_gc( gc ); + + if( ic != NULL ) + { + imc_logout( ic, !gc->wants_to_die ); + } +} + +static void prplcb_conn_notice( PurpleConnection *gc, const char *text ) +{ + struct im_connection *ic = purple_ic_by_gc( gc ); + + if( ic != NULL ) + imcb_log( ic, "%s", text ); +} + +static void prplcb_conn_report_disconnect_reason( PurpleConnection *gc, PurpleConnectionError reason, const char *text ) +{ + struct im_connection *ic = purple_ic_by_gc( gc ); + + /* PURPLE_CONNECTION_ERROR_NAME_IN_USE means concurrent login, + should probably handle that. */ + if( ic != NULL ) + imcb_error( ic, "%s", text ); +} + +static PurpleConnectionUiOps bee_conn_uiops = +{ + prplcb_conn_progress, + prplcb_conn_connected, + prplcb_conn_disconnected, + prplcb_conn_notice, + NULL, + NULL, + NULL, + prplcb_conn_report_disconnect_reason, +}; + +static void prplcb_blist_update( PurpleBuddyList *list, PurpleBlistNode *node ) +{ + if( node->type == PURPLE_BLIST_BUDDY_NODE ) + { + PurpleBuddy *bud = (PurpleBuddy*) node; + PurpleGroup *group = purple_buddy_get_group( bud ); + struct im_connection *ic = purple_ic_by_pa( bud->account ); + PurpleStatus *as; + int flags = 0; + + if( ic == NULL ) + return; + + if( bud->server_alias ) + imcb_rename_buddy( ic, bud->name, bud->server_alias ); + + if( group ) + imcb_add_buddy( ic, bud->name, purple_group_get_name( group ) ); + + flags |= purple_presence_is_online( bud->presence ) ? OPT_LOGGED_IN : 0; + flags |= purple_presence_is_available( bud->presence ) ? 0 : OPT_AWAY; + + as = purple_presence_get_active_status( bud->presence ); + + imcb_buddy_status( ic, bud->name, flags, purple_status_get_name( as ), + purple_status_get_attr_string( as, "message" ) ); + + imcb_buddy_times( ic, bud->name, + purple_presence_get_login_time( bud->presence ), + purple_presence_get_idle_time( bud->presence ) ); + } +} + +static void prplcb_blist_new( PurpleBlistNode *node ) +{ + if( node->type == PURPLE_BLIST_BUDDY_NODE ) + { + PurpleBuddy *bud = (PurpleBuddy*) node; + struct im_connection *ic = purple_ic_by_pa( bud->account ); + + if( ic == NULL ) + return; + + imcb_add_buddy( ic, bud->name, NULL ); + + prplcb_blist_update( NULL, node ); + } +} + +static void prplcb_blist_remove( PurpleBuddyList *list, PurpleBlistNode *node ) +{ +/* + PurpleBuddy *bud = (PurpleBuddy*) node; + + if( node->type == PURPLE_BLIST_BUDDY_NODE ) + { + struct im_connection *ic = purple_ic_by_pa( bud->account ); + + if( ic == NULL ) + return; + + imcb_remove_buddy( ic, bud->name, NULL ); + } +*/ +} + +static PurpleBlistUiOps bee_blist_uiops = +{ + NULL, + prplcb_blist_new, + NULL, + prplcb_blist_update, + prplcb_blist_remove, +}; + +void prplcb_conv_new( PurpleConversation *conv ) +{ + if( conv->type == PURPLE_CONV_TYPE_CHAT ) + { + struct im_connection *ic = purple_ic_by_pa( conv->account ); + struct groupchat *gc; + + gc = imcb_chat_new( ic, conv->name ); + conv->ui_data = gc; + gc->data = conv; + + /* libpurple brokenness: Whatever. Show that we join right away, + there's no clear "This is you!" signaling in _add_users so + don't even try. */ + imcb_chat_add_buddy( gc, gc->ic->acc->user ); + } +} + +void prplcb_conv_free( PurpleConversation *conv ) +{ + struct groupchat *gc = conv->ui_data; + + imcb_chat_free( gc ); +} + +void prplcb_conv_add_users( PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals ) +{ + struct groupchat *gc = conv->ui_data; + GList *b; + + for( b = cbuddies; b; b = b->next ) + { + PurpleConvChatBuddy *pcb = b->data; + + imcb_chat_add_buddy( gc, pcb->name ); + } +} + +void prplcb_conv_del_users( PurpleConversation *conv, GList *cbuddies ) +{ + struct groupchat *gc = conv->ui_data; + GList *b; + + for( b = cbuddies; b; b = b->next ) + imcb_chat_remove_buddy( gc, b->data, "" ); +} + +void prplcb_conv_chat_msg( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime ) +{ + struct groupchat *gc = conv->ui_data; + PurpleBuddy *buddy; + + /* ..._SEND means it's an outgoing message, no need to echo those. */ + if( flags & PURPLE_MESSAGE_SEND ) + return; + + buddy = purple_find_buddy( conv->account, who ); + if( buddy != NULL ) + who = purple_buddy_get_name( buddy ); + + imcb_chat_msg( gc, who, (char*) message, 0, mtime ); +} + +static void prplcb_conv_im( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime ) +{ + struct im_connection *ic = purple_ic_by_pa( conv->account ); + PurpleBuddy *buddy; + + /* ..._SEND means it's an outgoing message, no need to echo those. */ + if( flags & PURPLE_MESSAGE_SEND ) + return; + + buddy = purple_find_buddy( conv->account, who ); + if( buddy != NULL ) + who = purple_buddy_get_name( buddy ); + + imcb_buddy_msg( ic, (char*) who, (char*) message, 0, mtime ); +} + +static PurpleConversationUiOps bee_conv_uiops = +{ + prplcb_conv_new, /* create_conversation */ + prplcb_conv_free, /* destroy_conversation */ + prplcb_conv_chat_msg, /* write_chat */ + prplcb_conv_im, /* write_im */ + NULL, /* write_conv */ + prplcb_conv_add_users, /* chat_add_users */ + NULL, /* chat_rename_user */ + prplcb_conv_del_users, /* chat_remove_users */ + NULL, /* chat_update_user */ + NULL, /* present */ + NULL, /* has_focus */ + NULL, /* custom_smiley_add */ + NULL, /* custom_smiley_write */ + NULL, /* custom_smiley_close */ + NULL, /* send_confirm */ +}; + +struct prplcb_request_action_data +{ + void *user_data, *bee_data; + PurpleRequestActionCb yes, no; + int yes_i, no_i; +}; + +static void prplcb_request_action_yes( void *data ) +{ + struct prplcb_request_action_data *pqad = data; + + if( pqad->yes ) + pqad->yes( pqad->user_data, pqad->yes_i ); + g_free( pqad ); +} + +static void prplcb_request_action_no( void *data ) +{ + struct prplcb_request_action_data *pqad = data; + + if( pqad->no ) + pqad->no( pqad->user_data, pqad->no_i ); + g_free( pqad ); +} + +static void *prplcb_request_action( const char *title, const char *primary, const char *secondary, + int default_action, PurpleAccount *account, const char *who, + PurpleConversation *conv, void *user_data, size_t action_count, + va_list actions ) +{ + struct prplcb_request_action_data *pqad; + int i; + char *q; + + pqad = g_new0( struct prplcb_request_action_data, 1 ); + + for( i = 0; i < action_count; i ++ ) + { + char *caption; + void *fn; + + caption = va_arg( actions, char* ); + fn = va_arg( actions, void* ); + + if( strstr( caption, "Accept" ) || strstr( caption, "OK" ) ) + { + pqad->yes = fn; + pqad->yes_i = i; + } + else if( strstr( caption, "Reject" ) || strstr( caption, "Cancel" ) ) + { + pqad->no = fn; + pqad->no_i = i; + } + } + + pqad->user_data = user_data; + + /* TODO: IRC stuff here :-( */ + q = g_strdup_printf( "Request: %s\n\n%s\n\n%s", title, primary, secondary ); + pqad->bee_data = query_add( local_bee->ui_data, purple_ic_by_pa( account ), q, + prplcb_request_action_yes, prplcb_request_action_no, g_free, pqad ); + + g_free( q ); + + return pqad; +} + +/* +static void prplcb_request_test() +{ + fprintf( stderr, "bla\n" ); +} +*/ + +static PurpleRequestUiOps bee_request_uiops = +{ + NULL, + NULL, + prplcb_request_action, + NULL, + NULL, + NULL, + NULL, +}; + +static void prplcb_privacy_permit_added( PurpleAccount *account, const char *name ) +{ + struct im_connection *ic = purple_ic_by_pa( account ); + + if( !g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) + ic->permit = g_slist_prepend( ic->permit, g_strdup( name ) ); +} + +static void prplcb_privacy_permit_removed( PurpleAccount *account, const char *name ) +{ + struct im_connection *ic = purple_ic_by_pa( account ); + void *n; + + n = g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp ); + ic->permit = g_slist_remove( ic->permit, n ); +} + +static void prplcb_privacy_deny_added( PurpleAccount *account, const char *name ) +{ + struct im_connection *ic = purple_ic_by_pa( account ); + + if( !g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) + ic->deny = g_slist_prepend( ic->deny, g_strdup( name ) ); +} + +static void prplcb_privacy_deny_removed( PurpleAccount *account, const char *name ) +{ + struct im_connection *ic = purple_ic_by_pa( account ); + void *n; + + n = g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp ); + ic->deny = g_slist_remove( ic->deny, n ); +} + +static PurplePrivacyUiOps bee_privacy_uiops = +{ + prplcb_privacy_permit_added, + prplcb_privacy_permit_removed, + prplcb_privacy_deny_added, + prplcb_privacy_deny_removed, +}; + +static void prplcb_debug_print( PurpleDebugLevel level, const char *category, const char *arg_s ) +{ + fprintf( stderr, "DEBUG %s: %s", category, arg_s ); +} + +static PurpleDebugUiOps bee_debug_uiops = +{ + prplcb_debug_print, +}; + +static guint prplcb_ev_timeout_add( guint interval, GSourceFunc func, gpointer udata ) +{ + return b_timeout_add( interval, (b_event_handler) func, udata ); +} + +static guint prplcb_ev_input_add( int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata ) +{ + return b_input_add( fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata ); +} + +static gboolean prplcb_ev_remove( guint id ) +{ + b_event_remove( (gint) id ); + return TRUE; +} + +static PurpleEventLoopUiOps glib_eventloops = +{ + prplcb_ev_timeout_add, + prplcb_ev_remove, + prplcb_ev_input_add, + prplcb_ev_remove, +}; + +static void *prplcb_notify_email( PurpleConnection *gc, const char *subject, const char *from, + const char *to, const char *url ) +{ + struct im_connection *ic = purple_ic_by_gc( gc ); + + imcb_log( ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url ); + + return NULL; +} + +static void *prplcb_notify_userinfo( PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info ) +{ + struct im_connection *ic = purple_ic_by_gc( gc ); + GString *info = g_string_new( "" ); + GList *l = purple_notify_user_info_get_entries( user_info ); + char *key; + const char *value; + int n; + + while( l ) + { + PurpleNotifyUserInfoEntry *e = l->data; + + switch( purple_notify_user_info_entry_get_type( e ) ) + { + case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR: + case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER: + key = g_strdup( purple_notify_user_info_entry_get_label( e ) ); + value = purple_notify_user_info_entry_get_value( e ); + + if( key ) + { + strip_html( key ); + g_string_append_printf( info, "%s: ", key ); + + if( value ) + { + n = strlen( value ) - 1; + while( isspace( value[n] ) ) + n --; + g_string_append_len( info, value, n + 1 ); + } + g_string_append_c( info, '\n' ); + g_free( key ); + } + + break; + case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK: + g_string_append( info, "------------------------\n" ); + break; + } + + l = l->next; + } + + imcb_log( ic, "User %s info:\n%s", who, info->str ); + g_string_free( info, TRUE ); + + return NULL; +} + +static PurpleNotifyUiOps bee_notify_uiops = +{ + NULL, + prplcb_notify_email, + NULL, + NULL, + NULL, + NULL, + prplcb_notify_userinfo, +}; + +static void *prplcb_account_request_authorize( PurpleAccount *account, const char *remote_user, + const char *id, const char *alias, const char *message, gboolean on_list, + PurpleAccountRequestAuthorizationCb authorize_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data ) +{ + struct im_connection *ic = purple_ic_by_pa( account ); + char *q; + + if( alias ) + q = g_strdup_printf( "%s (%s) wants to add you to his/her contact " + "list. (%s)", alias, remote_user, message ); + else + q = g_strdup_printf( "%s wants to add you to his/her contact " + "list. (%s)", remote_user, message ); + + imcb_ask_with_free( ic, q, user_data, authorize_cb, deny_cb, NULL ); + g_free( q ); + + return NULL; +} + +static PurpleAccountUiOps bee_account_uiops = +{ + NULL, + NULL, + NULL, + prplcb_account_request_authorize, + NULL, +}; + +extern PurpleXferUiOps bee_xfer_uiops; + +static void purple_ui_init() +{ + purple_connections_set_ui_ops( &bee_conn_uiops ); + purple_blist_set_ui_ops( &bee_blist_uiops ); + purple_conversations_set_ui_ops( &bee_conv_uiops ); + purple_request_set_ui_ops( &bee_request_uiops ); + purple_privacy_set_ui_ops( &bee_privacy_uiops ); + purple_notify_set_ui_ops( &bee_notify_uiops ); + purple_accounts_set_ui_ops( &bee_account_uiops ); + purple_xfers_set_ui_ops( &bee_xfer_uiops ); + + if( getenv( "BITLBEE_DEBUG" ) ) + purple_debug_set_ui_ops( &bee_debug_uiops ); +} + +void purple_initmodule() +{ + struct prpl funcs; + GList *prots; + GString *help; + + if( B_EV_IO_READ != PURPLE_INPUT_READ || + B_EV_IO_WRITE != PURPLE_INPUT_WRITE ) + { + /* FIXME FIXME FIXME FIXME FIXME :-) */ + exit( 1 ); + } + + purple_util_set_user_dir( "/tmp" ); + purple_debug_set_enabled( FALSE ); + purple_core_set_ui_ops( &bee_core_uiops ); + purple_eventloop_set_ui_ops( &glib_eventloops ); + if( !purple_core_init( "BitlBee") ) + { + /* Initializing the core failed. Terminate. */ + fprintf( stderr, "libpurple initialization failed.\n" ); + abort(); + } + + /* This seems like stateful shit we don't want... */ + purple_set_blist( purple_blist_new() ); + purple_blist_load(); + + /* Meh? */ + purple_prefs_load(); + + memset( &funcs, 0, sizeof( funcs ) ); + funcs.login = purple_login; + funcs.init = purple_init; + funcs.logout = purple_logout; + funcs.buddy_msg = purple_buddy_msg; + funcs.away_states = purple_away_states; + funcs.set_away = purple_set_away; + funcs.add_buddy = purple_add_buddy; + funcs.remove_buddy = purple_remove_buddy; + funcs.add_permit = purple_add_permit; + funcs.add_deny = purple_add_deny; + funcs.rem_permit = purple_rem_permit; + funcs.rem_deny = purple_rem_deny; + funcs.get_info = purple_get_info; + funcs.keepalive = purple_keepalive; + funcs.send_typing = purple_send_typing; + funcs.handle_cmp = g_strcasecmp; + /* TODO(wilmer): Set these only for protocols that support them? */ + funcs.chat_msg = purple_chat_msg; + funcs.chat_with = purple_chat_with; + funcs.chat_invite = purple_chat_invite; + funcs.chat_leave = purple_chat_leave; + funcs.chat_join = purple_chat_join; + funcs.transfer_request = purple_transfer_request; + + help = g_string_new( "BitlBee libpurple module supports the following IM protocols:\n" ); + + /* Add a protocol entry to BitlBee's structures for every protocol + supported by this libpurple instance. */ + for( prots = purple_plugins_get_protocols(); prots; prots = prots->next ) + { + PurplePlugin *prot = prots->data; + struct prpl *ret; + + ret = g_memdup( &funcs, sizeof( funcs ) ); + ret->name = ret->data = prot->info->id; + if( strncmp( ret->name, "prpl-", 5 ) == 0 ) + ret->name += 5; + register_protocol( ret ); + + g_string_append_printf( help, "\n* %s (%s)", ret->name, prot->info->name ); + + /* libpurple doesn't define a protocol called OSCAR, but we + need it to be compatible with normal BitlBee. */ + if( g_strcasecmp( prot->info->id, "prpl-aim" ) == 0 ) + { + ret = g_memdup( &funcs, sizeof( funcs ) ); + ret->name = "oscar"; + ret->data = prot->info->id; + register_protocol( ret ); + } + } + + g_string_append( help, "\n\nFor used protocols, more information about available " + "settings can be found using \x02help purple <protocol name>\x02" ); + + /* Add a simple dynamically-generated help item listing all + the supported protocols. */ + help_add_mem( &global.help, "purple", help->str ); + g_string_free( help, TRUE ); +} diff --git a/protocols/twitter/Makefile b/protocols/twitter/Makefile new file mode 100644 index 00000000..3fa9b61e --- /dev/null +++ b/protocols/twitter/Makefile @@ -0,0 +1,45 @@ +########################### +## 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 + +### 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 + + diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c new file mode 100644 index 00000000..a2f2325c --- /dev/null +++ b/protocols/twitter/twitter.c @@ -0,0 +1,553 @@ +/***************************************************************************\ +* * +* 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" + +/** + * 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 gboolean twitter_oauth_callback( struct oauth_info *info ); + +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( &twitter_oauth, twitter_oauth_callback, ic ); +} + +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; + } + + /* 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", "one", set_eval_mode, 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, &twitter_oauth ); + + sprintf( name, "%s_%s", td->prefix, acc->user ); + imcb_add_buddy( ic, name, NULL ); + imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL ); + + 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 = 0; + + // 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 ); + } + + 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 struct groupchat *twitter_chat_with( struct im_connection *ic, char *who ) +{ + return 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 int twitter_send_typing( struct im_connection *ic, char *who, int typing ) +{ + return( 1 ); +} + +//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; + + 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 ); + + 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 ); + + td->last_status_id = 0; + if( id ) + twitter_status_retweet( ic, id ); + + g_free( cmds ); + return; + } + else if( g_strcasecmp( cmd[0], "post" ) == 0 ) + { + message += 5; + } + + { + guint64 in_reply_to = 0; + char *s, *new = NULL; + bee_user_t *bu; + + if( !twitter_length_check( ic, message ) ) + { + g_free( cmds ); + return; + } + + s = cmd[0] + strlen( cmd[0] ) - 1; + if( 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->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->chat_with = twitter_chat_with; + 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->send_typing = twitter_send_typing; + 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); + + // Initialise the twitter_connections GSList. + twitter_connections = NULL; +} diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h new file mode 100644 index 00000000..0acb3b4d --- /dev/null +++ b/protocols/twitter/twitter.h @@ -0,0 +1,75 @@ +/***************************************************************************\ +* * +* 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_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_user_data +{ + guint64 last_id; + time_t last_time; +}; + +/** + * 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. + */ +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..f9e808f7 --- /dev/null +++ b/protocols/twitter/twitter_lib.c @@ -0,0 +1,837 @@ +/***************************************************************************\ +* * +* 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; +}; + +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); +} + +/** + * 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); + g_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; +} + + +/** + * 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; + 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, "%a %b %d %H:%M:%S %z %Y", &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); + } + } + + /* 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 void twitter_groupchat_init(struct im_connection *ic) +{ + char *name_hint; + struct groupchat *gc; + struct twitter_data *td = ic->proto_data; + + 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 ); +} + +/** + * 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) ) + { + 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); + + // Say it! + if (g_strcasecmp(td->user, status->user->screen_name) == 0) + imcb_chat_log (gc, "Your Tweet: %s", status->text); + else + imcb_chat_msg (gc, status->user->screen_name, 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 allready in the list. + td->home_timeline_id = td->home_timeline_id < status->id ? status->id : td->home_timeline_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 *text = NULL; + + status = l->data; + + strip_html( status->text ); + if( mode_one ) + text = g_strdup_printf( "\002<\002%s\002>\002 %s", + status->user->screen_name, status->text ); + else + twitter_add_buddy(ic, status->user->screen_name, status->user->name); + + imcb_buddy_msg( ic, + mode_one ? from : status->user->screen_name, + mode_one ? 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 allready in the list. + td->home_timeline_id = td->home_timeline_id < status->id ? status->id : td->home_timeline_id; + + g_free( text ); + } +} + +/** + * 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 (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); + g_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); + g_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 index b4fe56e2..a8021ffb 100644 --- a/protocols/yahoo/Makefile +++ b/protocols/yahoo/Makefile @@ -7,11 +7,14 @@ ### DEFINITIONS -include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/yahoo/ +endif # [SH] Program variables objects = yahoo.o crypt.o libyahoo2.o yahoo_fn.o yahoo_httplib.o yahoo_util.o -CFLAGS += -Wall -DSTDC_HEADERS -DHAVE_STRING_H -DHAVE_STRCHR -DHAVE_MEMCPY -DHAVE_GLIB +CFLAGS += -DSTDC_HEADERS -DHAVE_STRING_H -DHAVE_STRCHR -DHAVE_MEMCPY -DHAVE_GLIB LFLAGS += -r # [SH] Phony targets @@ -32,7 +35,7 @@ distclean: clean $(objects): ../../Makefile.settings Makefile -$(objects): %.o: %.c +$(objects): %.o: $(SRCDIR)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $< -o $@ diff --git a/protocols/yahoo/libyahoo2.c b/protocols/yahoo/libyahoo2.c index 1bfc2e59..b062e7f9 100644 --- a/protocols/yahoo/libyahoo2.c +++ b/protocols/yahoo/libyahoo2.c @@ -2,6 +2,8 @@ * libyahoo2: libyahoo2.c * * Some code copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.net> + * YMSG16 code copyright (C) 2009, + * Siddhesh Poyarekar <siddhesh dot poyarekar at gmail dot com> * * Yahoo Search copyright (C) 2003, Konstantin Klyagin <konst AT konst.org.ua> * @@ -26,6 +28,8 @@ * Portions of Sylpheed copyright 2000-2002 Hiroyuki Yamamoto * <hiro-y@kcn.ne.jp> * + * YMSG16 authentication code based mostly on write-up at: + * http://www.carbonize.co.uk/ymsg16.html * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -91,9 +95,9 @@ char *strchr (), *strrchr (); #include "http_client.h" #ifdef USE_STRUCT_CALLBACKS -struct yahoo_callbacks *yc=NULL; +struct yahoo_callbacks *yc = NULL; -void yahoo_register_callbacks(struct yahoo_callbacks * tyc) +void yahoo_register_callbacks(struct yahoo_callbacks *tyc) { yc = tyc; } @@ -103,21 +107,23 @@ void yahoo_register_callbacks(struct yahoo_callbacks * tyc) #define YAHOO_CALLBACK(x) x #endif -static int yahoo_send_data(int fd, void *data, int len); +static int yahoo_send_data(void *fd, void *data, int len); +static void _yahoo_http_connected(int id, void *fd, int error, void *data); +static void yahoo_connected(void *fd, int error, void *data); -int yahoo_log_message(char * fmt, ...) +int yahoo_log_message(char *fmt, ...) { char out[1024]; va_list ap; va_start(ap, fmt); vsnprintf(out, sizeof(out), fmt, ap); va_end(ap); - return YAHOO_CALLBACK(ext_yahoo_log)("%s", out); + return YAHOO_CALLBACK(ext_yahoo_log) ("%s", out); } -int yahoo_connect(char * host, int port) +int yahoo_connect(char *host, int port) { - return YAHOO_CALLBACK(ext_yahoo_connect)(host, port); + return YAHOO_CALLBACK(ext_yahoo_connect) (host, port); } static enum yahoo_log_level log_level = YAHOO_LOG_NONE; @@ -135,97 +141,30 @@ int yahoo_set_log_level(enum yahoo_log_level level) } /* default values for servers */ -static char pager_host[] = "scs.msg.yahoo.com"; +static char *default_pager_hosts[] = { "scs.msg.yahoo.com", + "scsa.msg.yahoo.com", + "scsb.msg.yahoo.com", + "scsc.msg.yahoo.com", + NULL}; + static int pager_port = 5050; -static int fallback_ports[]={23, 25, 80, 20, 119, 8001, 8002, 5050, 0}; -static char filetransfer_host[]="filetransfer.msg.yahoo.com"; -static int filetransfer_port=80; -static char webcam_host[]="webcam.yahoo.com"; -static int webcam_port=5100; -static char webcam_description[]=""; -static char local_host[]=""; -static int conn_type=Y_WCM_DSL; +static int fallback_ports[] = { 23, 25, 80, 20, 119, 8001, 8002, 5050, 0 }; + +static char filetransfer_host[] = "filetransfer.msg.yahoo.com"; +static int filetransfer_port = 80; +static char webcam_host[] = "webcam.yahoo.com"; +static int webcam_port = 5100; +static char webcam_description[] = ""; +static char local_host[] = ""; +static int conn_type = Y_WCM_DSL; static char profile_url[] = "http://profiles.yahoo.com/"; -enum yahoo_service { /* these are easier to see in hex */ - YAHOO_SERVICE_LOGON = 1, - YAHOO_SERVICE_LOGOFF, - YAHOO_SERVICE_ISAWAY, - YAHOO_SERVICE_ISBACK, - YAHOO_SERVICE_IDLE, /* 5 (placemarker) */ - YAHOO_SERVICE_MESSAGE, - YAHOO_SERVICE_IDACT, - YAHOO_SERVICE_IDDEACT, - YAHOO_SERVICE_MAILSTAT, - YAHOO_SERVICE_USERSTAT, /* 0xa */ - YAHOO_SERVICE_NEWMAIL, - YAHOO_SERVICE_CHATINVITE, - YAHOO_SERVICE_CALENDAR, - YAHOO_SERVICE_NEWPERSONALMAIL, - YAHOO_SERVICE_NEWCONTACT, - YAHOO_SERVICE_ADDIDENT, /* 0x10 */ - YAHOO_SERVICE_ADDIGNORE, - YAHOO_SERVICE_PING, - YAHOO_SERVICE_GOTGROUPRENAME, /* < 1, 36(old), 37(new) */ - YAHOO_SERVICE_SYSMESSAGE = 0x14, - YAHOO_SERVICE_SKINNAME = 0x15, - YAHOO_SERVICE_PASSTHROUGH2 = 0x16, - YAHOO_SERVICE_CONFINVITE = 0x18, - YAHOO_SERVICE_CONFLOGON, - YAHOO_SERVICE_CONFDECLINE, - YAHOO_SERVICE_CONFLOGOFF, - YAHOO_SERVICE_CONFADDINVITE, - YAHOO_SERVICE_CONFMSG, - YAHOO_SERVICE_CHATLOGON, - YAHOO_SERVICE_CHATLOGOFF, - YAHOO_SERVICE_CHATMSG = 0x20, - YAHOO_SERVICE_GAMELOGON = 0x28, - YAHOO_SERVICE_GAMELOGOFF, - YAHOO_SERVICE_GAMEMSG = 0x2a, - YAHOO_SERVICE_FILETRANSFER = 0x46, - YAHOO_SERVICE_VOICECHAT = 0x4A, - YAHOO_SERVICE_NOTIFY, - YAHOO_SERVICE_VERIFY, - YAHOO_SERVICE_P2PFILEXFER, - YAHOO_SERVICE_PEERTOPEER = 0x4F, /* Checks if P2P possible */ - YAHOO_SERVICE_WEBCAM, - YAHOO_SERVICE_AUTHRESP = 0x54, - YAHOO_SERVICE_LIST, - YAHOO_SERVICE_AUTH = 0x57, - YAHOO_SERVICE_AUTHBUDDY = 0x6d, - YAHOO_SERVICE_ADDBUDDY = 0x83, - YAHOO_SERVICE_REMBUDDY, - YAHOO_SERVICE_IGNORECONTACT, /* > 1, 7, 13 < 1, 66, 13, 0*/ - YAHOO_SERVICE_REJECTCONTACT, - YAHOO_SERVICE_GROUPRENAME = 0x89, /* > 1, 65(new), 66(0), 67(old) */ - YAHOO_SERVICE_Y7_PING = 0x8A, /* 0 - id and that's it?? */ - YAHOO_SERVICE_CHATONLINE = 0x96, /* > 109(id), 1, 6(abcde) < 0,1*/ - YAHOO_SERVICE_CHATGOTO, - YAHOO_SERVICE_CHATJOIN, /* > 1 104-room 129-1600326591 62-2 */ - YAHOO_SERVICE_CHATLEAVE, - YAHOO_SERVICE_CHATEXIT = 0x9b, - YAHOO_SERVICE_CHATADDINVITE = 0x9d, - YAHOO_SERVICE_CHATLOGOUT = 0xa0, - YAHOO_SERVICE_CHATPING, - YAHOO_SERVICE_COMMENT = 0xa8, - YAHOO_SERVICE_STEALTH = 0xb9, - YAHOO_SERVICE_PICTURE_CHECKSUM = 0xbd, - YAHOO_SERVICE_PICTURE = 0xbe, - YAHOO_SERVICE_PICTURE_UPDATE = 0xc1, - YAHOO_SERVICE_PICTURE_UPLOAD = 0xc2, - YAHOO_SERVICE_Y6_VISIBILITY=0xc5, - YAHOO_SERVICE_Y6_STATUS_UPDATE=0xc6, - YAHOO_PHOTOSHARE_INIT=0xd2, - YAHOO_SERVICE_CONTACT_YMSG13=0xd6, - YAHOO_PHOTOSHARE_PREV=0xd7, - YAHOO_PHOTOSHARE_KEY=0xd8, - YAHOO_PHOTOSHARE_TRANS=0xda, - YAHOO_FILE_TRANSFER_INIT_YMSG13=0xdc, - YAHOO_FILE_TRANSFER_GET_YMSG13=0xdd, - YAHOO_FILE_TRANSFER_PUT_YMSG13=0xde, - YAHOO_SERVICE_YMSG15_STATUS=0xf0, - YAHOO_SERVICE_YMSG15_BUDDY_LIST=0xf1, +struct connect_callback_data { + struct yahoo_data *yd; + int tag; + int i; + int server_i; }; struct yahoo_pair { @@ -241,15 +180,15 @@ struct yahoo_packet { }; struct yahoo_search_state { - int lsearch_type; - char *lsearch_text; - int lsearch_gender; - int lsearch_agerange; - int lsearch_photo; - int lsearch_yahoo_only; - int lsearch_nstart; - int lsearch_nfound; - int lsearch_ntotal; + int lsearch_type; + char *lsearch_text; + int lsearch_gender; + int lsearch_agerange; + int lsearch_photo; + int lsearch_yahoo_only; + int lsearch_nstart; + int lsearch_nfound; + int lsearch_ntotal; }; struct data_queue { @@ -263,34 +202,52 @@ struct yahoo_input_data { struct yahoo_webcam_data *wcd; struct yahoo_search_state *ys; - int fd; + void *fd; enum yahoo_connection_type type; - - unsigned char *rxqueue; - int rxlen; - int read_tag; + + unsigned char *rxqueue; + int rxlen; + int read_tag; YList *txqueues; - int write_tag; + int write_tag; }; struct yahoo_server_settings { char *pager_host; - int pager_port; + int pager_port; char *filetransfer_host; - int filetransfer_port; + int filetransfer_port; char *webcam_host; - int webcam_port; + int webcam_port; char *webcam_description; char *local_host; - int conn_type; + int conn_type; + char **pager_host_list; }; -static void * _yahoo_default_server_settings() +static void yahoo_process_ft_connection(struct yahoo_input_data *yid, int over); + +static void yahoo_process_filetransfer(struct yahoo_input_data *yid, + struct yahoo_packet *pkt); +static void yahoo_process_filetransferinfo(struct yahoo_input_data *yid, + struct yahoo_packet *pkt); +static void yahoo_process_filetransferaccept(struct yahoo_input_data *yid, + struct yahoo_packet *pkt); + +static void yahoo_https_auth(struct yahoo_input_data *yid, const char *seed, const char *sn); + +static void *_yahoo_default_server_settings() { - struct yahoo_server_settings *yss = y_new0(struct yahoo_server_settings, 1); + struct yahoo_server_settings *yss = + y_new0(struct yahoo_server_settings, 1); + + /* Give preference to the default host list + * Make sure that only one of the two is set at any time + */ + yss->pager_host = NULL; + yss->pager_host_list = default_pager_hosts; - yss->pager_host = strdup(pager_host); yss->pager_port = pager_port; yss->filetransfer_host = strdup(filetransfer_host); yss->filetransfer_port = filetransfer_port; @@ -303,54 +260,61 @@ static void * _yahoo_default_server_settings() return yss; } -static void * _yahoo_assign_server_settings(va_list ap) +static void *_yahoo_assign_server_settings(va_list ap) { struct yahoo_server_settings *yss = _yahoo_default_server_settings(); char *key; char *svalue; - int nvalue; + int nvalue; + char **pvalue; - while(1) { + while (1) { key = va_arg(ap, char *); - if(key == NULL) + if (key == NULL) break; - if(!strcmp(key, "pager_host")) { + if (!strcmp(key, "pager_host")) { svalue = va_arg(ap, char *); free(yss->pager_host); yss->pager_host = strdup(svalue); - } else if(!strcmp(key, "pager_port")) { + yss->pager_host_list = NULL; + } else if (!strcmp(key, "pager_host_list")) { + pvalue = va_arg(ap, char **); + yss->pager_host_list = pvalue; + free(yss->pager_host); + yss->pager_host = NULL; + } else if (!strcmp(key, "pager_port")) { nvalue = va_arg(ap, int); yss->pager_port = nvalue; - } else if(!strcmp(key, "filetransfer_host")) { + } else if (!strcmp(key, "filetransfer_host")) { svalue = va_arg(ap, char *); free(yss->filetransfer_host); yss->filetransfer_host = strdup(svalue); - } else if(!strcmp(key, "filetransfer_port")) { + } else if (!strcmp(key, "filetransfer_port")) { nvalue = va_arg(ap, int); yss->filetransfer_port = nvalue; - } else if(!strcmp(key, "webcam_host")) { + } else if (!strcmp(key, "webcam_host")) { svalue = va_arg(ap, char *); free(yss->webcam_host); yss->webcam_host = strdup(svalue); - } else if(!strcmp(key, "webcam_port")) { + } else if (!strcmp(key, "webcam_port")) { nvalue = va_arg(ap, int); yss->webcam_port = nvalue; - } else if(!strcmp(key, "webcam_description")) { + } else if (!strcmp(key, "webcam_description")) { svalue = va_arg(ap, char *); free(yss->webcam_description); yss->webcam_description = strdup(svalue); - } else if(!strcmp(key, "local_host")) { + } else if (!strcmp(key, "local_host")) { svalue = va_arg(ap, char *); free(yss->local_host); yss->local_host = strdup(svalue); - } else if(!strcmp(key, "conn_type")) { + } else if (!strcmp(key, "conn_type")) { nvalue = va_arg(ap, int); yss->conn_type = nvalue; } else { WARNING(("Unknown key passed to yahoo_init, " - "perhaps you didn't terminate the list " - "with NULL")); + "perhaps you didn't terminate the list " + "with NULL")); } } @@ -359,7 +323,7 @@ static void * _yahoo_assign_server_settings(va_list ap) static void yahoo_free_server_settings(struct yahoo_server_settings *yss) { - if(!yss) + if (!yss) return; free(yss->pager_host); @@ -371,30 +335,33 @@ static void yahoo_free_server_settings(struct yahoo_server_settings *yss) free(yss); } -static YList *conns=NULL; -static YList *inputs=NULL; -static int last_id=0; +static YList *conns = NULL; +static YList *inputs = NULL; +static int last_id = 0; static void add_to_list(struct yahoo_data *yd) { conns = y_list_prepend(conns, yd); } -static struct yahoo_data * find_conn_by_id(int id) + +static struct yahoo_data *find_conn_by_id(int id) { YList *l; - for(l = conns; l; l = y_list_next(l)) { + for (l = conns; l; l = y_list_next(l)) { struct yahoo_data *yd = l->data; - if(yd->client_id == id) + if (yd->client_id == id) return yd; } return NULL; } + static void del_from_list(struct yahoo_data *yd) { conns = y_list_remove(conns, yd); } /* call repeatedly to get the next one */ +/* static struct yahoo_input_data * find_input_by_id(int id) { YList *l; @@ -405,41 +372,45 @@ static struct yahoo_input_data * find_input_by_id(int id) } return NULL; } +*/ -static struct yahoo_input_data * find_input_by_id_and_webcam_user(int id, const char * who) +static struct yahoo_input_data *find_input_by_id_and_webcam_user(int id, + const char *who) { YList *l; LOG(("find_input_by_id_and_webcam_user")); - for(l = inputs; l; l = y_list_next(l)) { + for (l = inputs; l; l = y_list_next(l)) { struct yahoo_input_data *yid = l->data; - if(yid->type == YAHOO_CONNECTION_WEBCAM && yid->yd->client_id == id - && yid->wcm && - ((who && yid->wcm->user && !strcmp(who, yid->wcm->user)) || - !(yid->wcm->user && !who))) + if (yid->type == YAHOO_CONNECTION_WEBCAM + && yid->yd->client_id == id && yid->wcm && ((who + && yid->wcm->user + && !strcmp(who, yid->wcm->user)) + || !(yid->wcm->user && !who))) return yid; } return NULL; } -static struct yahoo_input_data * find_input_by_id_and_type(int id, enum yahoo_connection_type type) +static struct yahoo_input_data *find_input_by_id_and_type(int id, + enum yahoo_connection_type type) { YList *l; LOG(("find_input_by_id_and_type")); - for(l = inputs; l; l = y_list_next(l)) { + for (l = inputs; l; l = y_list_next(l)) { struct yahoo_input_data *yid = l->data; - if(yid->type == type && yid->yd->client_id == id) + if (yid->type == type && yid->yd->client_id == id) return yid; } return NULL; } -static struct yahoo_input_data * find_input_by_id_and_fd(int id, int fd) +static struct yahoo_input_data *find_input_by_id_and_fd(int id, void *fd) { YList *l; LOG(("find_input_by_id_and_fd")); - for(l = inputs; l; l = y_list_next(l)) { + for (l = inputs; l; l = y_list_next(l)) { struct yahoo_input_data *yid = l->data; - if(yid->fd == fd && yid->yd->client_id == id) + if (yid->fd == fd && yid->yd->client_id == id) return yid; } return NULL; @@ -447,36 +418,34 @@ static struct yahoo_input_data * find_input_by_id_and_fd(int id, int fd) static int count_inputs_with_id(int id) { - int c=0; + int c = 0; YList *l; LOG(("counting %d", id)); - for(l = inputs; l; l = y_list_next(l)) { + for (l = inputs; l; l = y_list_next(l)) { struct yahoo_input_data *yid = l->data; - if(yid->yd->client_id == id) + if (yid->yd->client_id == id) c++; } LOG(("%d", c)); return c; } - extern char *yahoo_crypt(char *, char *); /* Free a buddy list */ -static void yahoo_free_buddies(YList * list) +static void yahoo_free_buddies(YList *list) { YList *l; - for(l = list; l; l = l->next) - { + for (l = list; l; l = l->next) { struct yahoo_buddy *bud = l->data; - if(!bud) + if (!bud) continue; FREE(bud->group); FREE(bud->id); FREE(bud->real_name); - if(bud->yab_entry) { + if (bud->yab_entry) { FREE(bud->yab_entry->fname); FREE(bud->yab_entry->lname); FREE(bud->yab_entry->nname); @@ -495,7 +464,7 @@ static void yahoo_free_buddies(YList * list) } /* Free an identities list */ -static void yahoo_free_identities(YList * list) +static void yahoo_free_identities(YList *list) { while (list) { YList *n = list; @@ -524,6 +493,7 @@ static void yahoo_free_data(struct yahoo_data *yd) FREE(yd->password); FREE(yd->cookie_y); FREE(yd->cookie_t); + FREE(yd->cookie_b); FREE(yd->cookie_c); FREE(yd->login_cookie); FREE(yd->login_id); @@ -539,8 +509,8 @@ static void yahoo_free_data(struct yahoo_data *yd) #define YAHOO_PACKET_HDRLEN (4 + 2 + 2 + 2 + 2 + 4 + 4) -static struct yahoo_packet *yahoo_packet_new(enum yahoo_service service, - enum yahoo_status status, int id) +static struct yahoo_packet *yahoo_packet_new(enum yahoo_service service, + enum ypacket_status status, int id) { struct yahoo_packet *pkt = y_new0(struct yahoo_packet, 1); @@ -551,7 +521,8 @@ static struct yahoo_packet *yahoo_packet_new(enum yahoo_service service, return pkt; } -static void yahoo_packet_hash(struct yahoo_packet *pkt, int key, const char *value) +static void yahoo_packet_hash(struct yahoo_packet *pkt, int key, + const char *value) { struct yahoo_pair *pair = y_new0(struct yahoo_pair, 1); pair->key = key; @@ -596,7 +567,8 @@ static int yahoo_packet_length(struct yahoo_packet *pkt) (((*((buf)+2))&0xff)<< 8) + \ (((*((buf)+3))&0xff))) -static void yahoo_packet_read(struct yahoo_packet *pkt, unsigned char *data, int len) +static void yahoo_packet_read(struct yahoo_packet *pkt, unsigned char *data, + int len) { int pos = 0; @@ -649,7 +621,8 @@ static void yahoo_packet_read(struct yahoo_packet *pkt, unsigned char *data, int pair->value = strdup(value); FREE(value); pkt->hash = y_list_append(pkt->hash, pair); - DEBUG_MSG(("Key: %d \tValue: %s", pair->key, pair->value)); + DEBUG_MSG(("Key: %d \tValue: %s", pair->key, + pair->value)); } else { FREE(pair); } @@ -689,30 +662,29 @@ static void yahoo_dump_unhandled(struct yahoo_packet *pkt) } } - static void yahoo_packet_dump(unsigned char *data, int len) { - if(yahoo_get_log_level() >= YAHOO_LOG_DEBUG) { + if (yahoo_get_log_level() >= YAHOO_LOG_DEBUG) { int i; for (i = 0; i < len; i++) { if ((i % 8 == 0) && i) - YAHOO_CALLBACK(ext_yahoo_log)(" "); + YAHOO_CALLBACK(ext_yahoo_log) (" "); if ((i % 16 == 0) && i) - YAHOO_CALLBACK(ext_yahoo_log)("\n"); - YAHOO_CALLBACK(ext_yahoo_log)("%02x ", data[i]); + YAHOO_CALLBACK(ext_yahoo_log) ("\n"); + YAHOO_CALLBACK(ext_yahoo_log) ("%02x ", data[i]); } - YAHOO_CALLBACK(ext_yahoo_log)("\n"); + YAHOO_CALLBACK(ext_yahoo_log) ("\n"); for (i = 0; i < len; i++) { if ((i % 8 == 0) && i) - YAHOO_CALLBACK(ext_yahoo_log)(" "); + YAHOO_CALLBACK(ext_yahoo_log) (" "); if ((i % 16 == 0) && i) - YAHOO_CALLBACK(ext_yahoo_log)("\n"); + YAHOO_CALLBACK(ext_yahoo_log) ("\n"); if (isprint(data[i])) - YAHOO_CALLBACK(ext_yahoo_log)(" %c ", data[i]); + YAHOO_CALLBACK(ext_yahoo_log) (" %c ", data[i]); else - YAHOO_CALLBACK(ext_yahoo_log)(" . "); + YAHOO_CALLBACK(ext_yahoo_log) (" . "); } - YAHOO_CALLBACK(ext_yahoo_log)("\n"); + YAHOO_CALLBACK(ext_yahoo_log) ("\n"); } } @@ -722,7 +694,8 @@ static void to_y64(unsigned char *out, const unsigned char *in, int inlen) base64_encode_real(in, inlen, out, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-"); } -static void yahoo_add_to_send_queue(struct yahoo_input_data *yid, void *data, int length) +static void yahoo_add_to_send_queue(struct yahoo_input_data *yid, void *data, + int length) { struct data_queue *tx = y_new0(struct data_queue, 1); tx->queue = y_new0(unsigned char, length); @@ -731,15 +704,17 @@ static void yahoo_add_to_send_queue(struct yahoo_input_data *yid, void *data, in yid->txqueues = y_list_append(yid->txqueues, tx); - if(!yid->write_tag) - yid->write_tag=YAHOO_CALLBACK(ext_yahoo_add_handler)(yid->yd->client_id, yid->fd, YAHOO_INPUT_WRITE, yid); + if (!yid->write_tag) + yid->write_tag = + YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd-> + client_id, yid->fd, YAHOO_INPUT_WRITE, yid); } -static void yahoo_send_packet(struct yahoo_input_data *yid, struct yahoo_packet *pkt, int extra_pad) +static void yahoo_send_packet(struct yahoo_input_data *yid, + struct yahoo_packet *pkt, int extra_pad) { int pktlen = yahoo_packet_length(pkt); int len = YAHOO_PACKET_HDRLEN + pktlen; - unsigned char *data; int pos = 0; @@ -748,19 +723,20 @@ static void yahoo_send_packet(struct yahoo_input_data *yid, struct yahoo_packet data = y_new0(unsigned char, len + 1); - memcpy(data + pos, "YMSG", 4); pos += 4; - pos += yahoo_put16(data + pos, YAHOO_PROTO_VER); - pos += yahoo_put16(data + pos, 0x0000); - pos += yahoo_put16(data + pos, pktlen + extra_pad); - pos += yahoo_put16(data + pos, pkt->service); - pos += yahoo_put32(data + pos, pkt->status); - pos += yahoo_put32(data + pos, pkt->id); + memcpy(data + pos, "YMSG", 4); + pos += 4; + pos += yahoo_put16(data + pos, YAHOO_PROTO_VER); /* version [latest 12 0x000c] */ + pos += yahoo_put16(data + pos, 0x0000); /* HIWORD pkt length??? */ + pos += yahoo_put16(data + pos, pktlen + extra_pad); /* LOWORD pkt length? */ + pos += yahoo_put16(data + pos, pkt->service); /* service */ + pos += yahoo_put32(data + pos, pkt->status); /* status [4bytes] */ + pos += yahoo_put32(data + pos, pkt->id); /* session [4bytes] */ yahoo_packet_write(pkt, data + pos); yahoo_packet_dump(data, len); - - if( yid->type == YAHOO_CONNECTION_FT ) + + if (yid->type == YAHOO_CONNECTION_FT) yahoo_send_data(yid->fd, data, len); else yahoo_add_to_send_queue(yid, data, len); @@ -781,92 +757,94 @@ static void yahoo_packet_free(struct yahoo_packet *pkt) FREE(pkt); } -static int yahoo_send_data(int fd, void *data, int len) +static int yahoo_send_data(void *fd, void *data, int len) { int ret; int e; - if (fd < 0) + if (fd == NULL) return -1; yahoo_packet_dump(data, len); do { - ret = write(fd, data, len); - } while(ret == -1 && errno==EINTR); - e=errno; + ret = YAHOO_CALLBACK(ext_yahoo_write) (fd, data, len); + } while (ret == -1 && errno == EINTR); + e = errno; - if (ret == -1) { + if (ret == -1) { LOG(("wrote data: ERR %s", strerror(errno))); } else { LOG(("wrote data: OK")); } - errno=e; + errno = e; return ret; } -void yahoo_close(int id) +void yahoo_close(int id) { struct yahoo_data *yd = find_conn_by_id(id); - - if(!yd) + if (!yd) return; del_from_list(yd); yahoo_free_data(yd); - if(id == last_id) + if (id == last_id) last_id--; } -static void yahoo_input_close(struct yahoo_input_data *yid) +static void yahoo_input_close(struct yahoo_input_data *yid) { inputs = y_list_remove(inputs, yid); - LOG(("yahoo_input_close(read)")); - YAHOO_CALLBACK(ext_yahoo_remove_handler)(yid->yd->client_id, yid->read_tag); - LOG(("yahoo_input_close(write)")); - YAHOO_CALLBACK(ext_yahoo_remove_handler)(yid->yd->client_id, yid->write_tag); + LOG(("yahoo_input_close(read)")); + YAHOO_CALLBACK(ext_yahoo_remove_handler) (yid->yd->client_id, + yid->read_tag); + LOG(("yahoo_input_close(write)")); + YAHOO_CALLBACK(ext_yahoo_remove_handler) (yid->yd->client_id, + yid->write_tag); yid->read_tag = yid->write_tag = 0; - if(yid->fd) - close(yid->fd); + if (yid->fd) + YAHOO_CALLBACK(ext_yahoo_close) (yid->fd); yid->fd = 0; FREE(yid->rxqueue); - if(count_inputs_with_id(yid->yd->client_id) == 0) { + if (count_inputs_with_id(yid->yd->client_id) == 0) { LOG(("closing %d", yid->yd->client_id)); yahoo_close(yid->yd->client_id); } yahoo_free_webcam(yid->wcm); - if(yid->wcd) + if (yid->wcd) FREE(yid->wcd); - if(yid->ys) { + if (yid->ys) { FREE(yid->ys->lsearch_text); FREE(yid->ys); } FREE(yid); } -static int is_same_bud(const void * a, const void * b) { +static int is_same_bud(const void *a, const void *b) +{ const struct yahoo_buddy *subject = a; const struct yahoo_buddy *object = b; return strcmp(subject->id, object->id); } -static char * getcookie(char *rawcookie) +static char *getcookie(char *rawcookie) { - char * cookie=NULL; - char * tmpcookie; - char * cookieend; + char *cookie = NULL; + char *tmpcookie; + char *cookieend; - if (strlen(rawcookie) < 2) + if (strlen(rawcookie) < 2) return NULL; - tmpcookie = strdup(rawcookie+2); + tmpcookie = strdup(rawcookie + 2); cookieend = strchr(tmpcookie, ';'); - if(cookieend) + if (cookieend) *cookieend = '\0'; cookie = strdup(tmpcookie); @@ -876,18 +854,18 @@ static char * getcookie(char *rawcookie) return cookie; } -static char * getlcookie(char *cookie) +static char *getlcookie(char *cookie) { char *tmp; char *tmpend; char *login_cookie = NULL; tmpend = strstr(cookie, "n="); - if(tmpend) { - tmp = strdup(tmpend+2); + if (tmpend) { + tmp = strdup(tmpend + 2); tmpend = strchr(tmp, '&'); - if(tmpend) - *tmpend='\0'; + if (tmpend) + *tmpend = '\0'; login_cookie = strdup(tmp); FREE(tmp); } @@ -895,7 +873,8 @@ static char * getlcookie(char *cookie) return login_cookie; } -static void yahoo_process_notify(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_notify(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *msg = NULL; @@ -926,84 +905,31 @@ static void yahoo_process_notify(struct yahoo_input_data *yid, struct yahoo_pack if (!msg) return; - - if (!strncasecmp(msg, "TYPING", strlen("TYPING"))) - YAHOO_CALLBACK(ext_yahoo_typing_notify)(yd->client_id, to, from, stat); - else if (!strncasecmp(msg, "GAME", strlen("GAME"))) - YAHOO_CALLBACK(ext_yahoo_game_notify)(yd->client_id, to, from, stat); - else if (!strncasecmp(msg, "WEBCAMINVITE", strlen("WEBCAMINVITE"))) - { + + if (!strncasecmp(msg, "TYPING", strlen("TYPING"))) + YAHOO_CALLBACK(ext_yahoo_typing_notify) (yd->client_id, to, + from, stat); + else if (!strncasecmp(msg, "GAME", strlen("GAME"))) + YAHOO_CALLBACK(ext_yahoo_game_notify) (yd->client_id, to, from, + stat, ind); + else if (!strncasecmp(msg, "WEBCAMINVITE", strlen("WEBCAMINVITE"))) { if (!strcmp(ind, " ")) { - YAHOO_CALLBACK(ext_yahoo_webcam_invite)(yd->client_id, to, from); + YAHOO_CALLBACK(ext_yahoo_webcam_invite) (yd->client_id, + to, from); } else { accept = atoi(ind); /* accept the invitation (-1 = deny 1 = accept) */ if (accept < 0) accept = 0; - YAHOO_CALLBACK(ext_yahoo_webcam_invite_reply)(yd->client_id, to, from, accept); + YAHOO_CALLBACK(ext_yahoo_webcam_invite_reply) (yd-> + client_id, to, from, accept); } - } - else + } else LOG(("Got unknown notification: %s", msg)); } -static void yahoo_process_filetransfer(struct yahoo_input_data *yid, struct yahoo_packet *pkt) -{ - struct yahoo_data *yd = yid->yd; - char *from=NULL; - char *to=NULL; - char *msg=NULL; - char *url=NULL; - long expires=0; - - char *service=NULL; - - char *filename=NULL; - unsigned long filesize=0L; - - YList *l; - for (l = pkt->hash; l; l = l->next) { - struct yahoo_pair *pair = l->data; - if (pair->key == 4) - from = pair->value; - if (pair->key == 5) - to = pair->value; - if (pair->key == 14) - msg = pair->value; - if (pair->key == 20) - url = pair->value; - if (pair->key == 38) - expires = atol(pair->value); - - if (pair->key == 27) - filename = pair->value; - if (pair->key == 28) - filesize = atol(pair->value); - - if (pair->key == 49) - service = pair->value; - } - - if(pkt->service == YAHOO_SERVICE_P2PFILEXFER) { - if(strcmp("FILEXFER", service) != 0) { - WARNING(("unhandled service 0x%02x", pkt->service)); - yahoo_dump_unhandled(pkt); - return; - } - } - - if(msg) { - char *tmp; - tmp = strchr(msg, '\006'); - if(tmp) - *tmp = '\0'; - } - if(url && from) - YAHOO_CALLBACK(ext_yahoo_got_file)(yd->client_id, to, from, url, expires, msg, filename, filesize); - -} - -static void yahoo_process_conference(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_conference(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *msg = NULL; @@ -1011,98 +937,102 @@ static void yahoo_process_conference(struct yahoo_input_data *yid, struct yahoo_ char *who = NULL; char *room = NULL; char *id = NULL; - int utf8 = 0; + int utf8 = 0; YList *members = NULL; YList *l; - + for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 50) host = pair->value; - - if (pair->key == 52) { /* invite */ + + if (pair->key == 52) { /* invite */ members = y_list_append(members, strdup(pair->value)); } - if (pair->key == 53) /* logon */ + if (pair->key == 53) /* logon */ who = pair->value; - if (pair->key == 54) /* decline */ + if (pair->key == 54) /* decline */ who = pair->value; - if (pair->key == 55) /* unavailable (status == 2) */ + if (pair->key == 55) /* unavailable (status == 2) */ who = pair->value; - if (pair->key == 56) /* logoff */ + if (pair->key == 56) /* logoff */ who = pair->value; if (pair->key == 57) room = pair->value; - if (pair->key == 58) /* join message */ + if (pair->key == 58) /* join message */ msg = pair->value; - if (pair->key == 14) /* decline/conf message */ + if (pair->key == 14) /* decline/conf message */ msg = pair->value; - if (pair->key == 13) - ; - if (pair->key == 16) /* error */ + if (pair->key == 13) ; + if (pair->key == 16) /* error */ msg = pair->value; - if (pair->key == 1) /* my id */ + if (pair->key == 1) /* my id */ id = pair->value; - if (pair->key == 3) /* message sender */ + if (pair->key == 3) /* message sender */ who = pair->value; if (pair->key == 97) utf8 = atoi(pair->value); } - if(!room) + if (!room) return; - if(host) { - for(l = members; l; l = l->next) { - char * w = l->data; - if(!strcmp(w, host)) + if (host) { + for (l = members; l; l = l->next) { + char *w = l->data; + if (!strcmp(w, host)) break; } - if(!l) + if (!l) members = y_list_append(members, strdup(host)); } /* invite, decline, join, left, message -> status == 1 */ - switch(pkt->service) { + switch (pkt->service) { case YAHOO_SERVICE_CONFINVITE: - if(pkt->status == 2) - ; - else if(members) - YAHOO_CALLBACK(ext_yahoo_got_conf_invite)(yd->client_id, id, host, room, msg, members); - else if(msg) - YAHOO_CALLBACK(ext_yahoo_error)(yd->client_id, msg, 0, E_CONFNOTAVAIL); + if (pkt->status == 2) ; + else if (members) + YAHOO_CALLBACK(ext_yahoo_got_conf_invite) (yd-> + client_id, id, host, room, msg, members); + else if (msg) + YAHOO_CALLBACK(ext_yahoo_error) (yd->client_id, msg, 0, + E_CONFNOTAVAIL); break; case YAHOO_SERVICE_CONFADDINVITE: - if(pkt->status == 2) - ; - else - YAHOO_CALLBACK(ext_yahoo_got_conf_invite)(yd->client_id, id, host, room, msg, members); + if (pkt->status == 1) + YAHOO_CALLBACK(ext_yahoo_got_conf_invite) (yd-> + client_id, id, host, room, msg, members); break; case YAHOO_SERVICE_CONFDECLINE: - if(who) - YAHOO_CALLBACK(ext_yahoo_conf_userdecline)(yd->client_id, id, who, room, msg); + if (who) + YAHOO_CALLBACK(ext_yahoo_conf_userdecline) (yd-> + client_id, id, who, room, msg); break; case YAHOO_SERVICE_CONFLOGON: - if(who) - YAHOO_CALLBACK(ext_yahoo_conf_userjoin)(yd->client_id, id, who, room); + if (who) + YAHOO_CALLBACK(ext_yahoo_conf_userjoin) (yd->client_id, + id, who, room); break; case YAHOO_SERVICE_CONFLOGOFF: - if(who) - YAHOO_CALLBACK(ext_yahoo_conf_userleave)(yd->client_id, id, who, room); + if (who) + YAHOO_CALLBACK(ext_yahoo_conf_userleave) (yd->client_id, + id, who, room); break; case YAHOO_SERVICE_CONFMSG: - if(who) - YAHOO_CALLBACK(ext_yahoo_conf_message)(yd->client_id, id, who, room, msg, utf8); + if (who) + YAHOO_CALLBACK(ext_yahoo_conf_message) (yd->client_id, + id, who, room, msg, utf8); break; } } -static void yahoo_process_chat(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_chat(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { char *msg = NULL; char *id = NULL; @@ -1111,13 +1041,13 @@ static void yahoo_process_chat(struct yahoo_input_data *yid, struct yahoo_packet char *topic = NULL; YList *members = NULL; struct yahoo_chat_member *currentmember = NULL; - int msgtype = 1; - int utf8 = 0; - int firstjoin = 0; - int membercount = 0; - int chaterr=0; + int msgtype = 1; + int utf8 = 0; + int firstjoin = 0; + int membercount = 0; + int chaterr = 0; YList *l; - + yahoo_dump_unhandled(pkt); for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; @@ -1147,7 +1077,8 @@ static void yahoo_process_chat(struct yahoo_input_data *yid, struct yahoo_packet who = pair->value; if (pkt->service == YAHOO_SERVICE_CHATJOIN) { - currentmember = y_new0(struct yahoo_chat_member, 1); + currentmember = + y_new0(struct yahoo_chat_member, 1); currentmember->id = strdup(pair->value); members = y_list_append(members, currentmember); } @@ -1177,7 +1108,6 @@ static void yahoo_process_chat(struct yahoo_input_data *yid, struct yahoo_packet currentmember->location = strdup(pair->value); } - if (pair->key == 130) { /* first join */ firstjoin = 1; @@ -1195,17 +1125,19 @@ static void yahoo_process_chat(struct yahoo_input_data *yid, struct yahoo_packet if (pair->key == 114) { /* message error not sure what all the pair values mean */ /* but -1 means no session in room */ - chaterr= atoi(pair->value); + chaterr = atoi(pair->value); } } - if(!room) { - if (pkt->service == YAHOO_SERVICE_CHATLOGOUT) { /* yahoo originated chat logout */ - YAHOO_CALLBACK(ext_yahoo_chat_yahoologout)(yid->yd->client_id, id); - return ; + if (!room) { + if (pkt->service == YAHOO_SERVICE_CHATLOGOUT) { /* yahoo originated chat logout */ + YAHOO_CALLBACK(ext_yahoo_chat_yahoologout) (yid->yd-> + client_id, id); + return; } - if (pkt->service == YAHOO_SERVICE_COMMENT && chaterr) { - YAHOO_CALLBACK(ext_yahoo_chat_yahooerror)(yid->yd->client_id, id); + if (pkt->service == YAHOO_SERVICE_COMMENT && chaterr) { + YAHOO_CALLBACK(ext_yahoo_chat_yahooerror) (yid->yd-> + client_id, id); return; } @@ -1213,103 +1145,156 @@ static void yahoo_process_chat(struct yahoo_input_data *yid, struct yahoo_packet return; } - switch(pkt->service) { + switch (pkt->service) { case YAHOO_SERVICE_CHATJOIN: - if(y_list_length(members) != membercount) { + if (y_list_length(members) != membercount) { WARNING(("Count of members doesn't match No. of members we got")); } - if(firstjoin && members) { - YAHOO_CALLBACK(ext_yahoo_chat_join)(yid->yd->client_id, id, room, topic, members, yid->fd); - } else if(who) { - if(y_list_length(members) != 1) { + if (firstjoin && members) { + YAHOO_CALLBACK(ext_yahoo_chat_join) (yid->yd->client_id, + id, room, topic, members, yid->fd); + } else if (who) { + if (y_list_length(members) != 1) { WARNING(("Got more than 1 member on a normal join")); } /* this should only ever have one, but just in case */ - while(members) { + while (members) { YList *n = members->next; currentmember = members->data; - YAHOO_CALLBACK(ext_yahoo_chat_userjoin)(yid->yd->client_id, id, room, currentmember); + YAHOO_CALLBACK(ext_yahoo_chat_userjoin) (yid-> + yd->client_id, id, room, currentmember); y_list_free_1(members); - members=n; + members = n; } } break; case YAHOO_SERVICE_CHATEXIT: - if(who) { - YAHOO_CALLBACK(ext_yahoo_chat_userleave)(yid->yd->client_id, id, room, who); + if (who) { + YAHOO_CALLBACK(ext_yahoo_chat_userleave) (yid->yd-> + client_id, id, room, who); } break; case YAHOO_SERVICE_COMMENT: - if(who) { - YAHOO_CALLBACK(ext_yahoo_chat_message)(yid->yd->client_id, id, who, room, msg, msgtype, utf8); + if (who) { + YAHOO_CALLBACK(ext_yahoo_chat_message) (yid->yd-> + client_id, id, who, room, msg, msgtype, utf8); } break; } } -static void yahoo_process_message(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_message(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; YList *l; - YList * messages = NULL; + YList *messages = NULL; struct m { - int i_31; - int i_32; + int i_31; + int i_32; char *to; char *from; long tm; char *msg; - int utf8; + int utf8; + char *gunk; } *message = y_new0(struct m, 1); for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; - if (pair->key == 1 || pair->key == 4) - { - if(!message->from) + if (pair->key == 1 || pair->key == 4) { + if (!message->from) message->from = pair->value; - } - else if (pair->key == 5) + } else if (pair->key == 5) message->to = pair->value; else if (pair->key == 15) message->tm = strtol(pair->value, NULL, 10); else if (pair->key == 97) message->utf8 = atoi(pair->value); - /* user message */ /* sys message */ + /* This comes when the official client sends us a message */ + else if (pair->key == 429) + message->gunk = pair->value; + /* user message *//* sys message */ else if (pair->key == 14 || pair->key == 16) message->msg = pair->value; else if (pair->key == 31) { - if(message->i_31) { + if (message->i_31) { messages = y_list_append(messages, message); message = y_new0(struct m, 1); } message->i_31 = atoi(pair->value); - } - else if (pair->key == 32) + } else if (pair->key == 32) message->i_32 = atoi(pair->value); else - LOG(("yahoo_process_message: status: %d, key: %d, value: %s", - pkt->status, pair->key, pair->value)); + LOG(("yahoo_process_message: status: %d, key: %d, value: %s", pkt->status, pair->key, pair->value)); } messages = y_list_append(messages, message); - for (l = messages; l; l=l->next) { + for (l = messages; l; l = l->next) { message = l->data; if (pkt->service == YAHOO_SERVICE_SYSMESSAGE) { - YAHOO_CALLBACK(ext_yahoo_system_message)(yd->client_id, message->msg); + YAHOO_CALLBACK(ext_yahoo_system_message) (yd->client_id, + message->to, message->from, message->msg); } else if (pkt->status <= 2 || pkt->status == 5) { - YAHOO_CALLBACK(ext_yahoo_got_im)(yd->client_id, message->to, message->from, message->msg, message->tm, pkt->status, message->utf8); + /* Confirm message receipt if we got the gunk */ + if(message->gunk) { + struct yahoo_packet *outpkt; + + outpkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE_CONFIRM, + YPACKET_STATUS_DEFAULT, 0); + yahoo_packet_hash(outpkt, 1, yd->user); + yahoo_packet_hash(outpkt, 5, message->from); + yahoo_packet_hash(outpkt, 302, "430"); + yahoo_packet_hash(outpkt, 430, message->gunk); + yahoo_packet_hash(outpkt, 303, "430"); + yahoo_packet_hash(outpkt, 450, "0"); + yahoo_send_packet(yid, outpkt, 0); + + yahoo_packet_free(outpkt); + } + + if (!strcmp(message->msg, "<ding>")) + YAHOO_CALLBACK(ext_yahoo_got_buzz) (yd->client_id, + message->to, message->from, message->tm); + else + YAHOO_CALLBACK(ext_yahoo_got_im) (yd->client_id, + message->to, message->from, message->msg, + message->tm, pkt->status, message->utf8); } else if (pkt->status == 0xffffffff) { - YAHOO_CALLBACK(ext_yahoo_error)(yd->client_id, message->msg, 0, E_SYSTEM); + YAHOO_CALLBACK(ext_yahoo_error) (yd->client_id, + message->msg, 0, E_SYSTEM); } - free(message); + FREE(message); } y_list_free(messages); } +/* + * Here's what multi-level packets look like. Data in brackets is the value. + * + * 3 level: + * ======= + * + * 302 (318) - Beginning level 1 + * 300 (318) - Begin level 2 + * 302 (319) - End level 2 header + * 300 (319) - Begin level 3 + * 301 (319) - End level 3 + * 303 (319) - End level 2 + * 303 (318) - End level 1 + * + * 2 level: + * ======= + * + * 302 (315) - Beginning level 1 + * 300 (315) - Begin level 2 + * 301 (315) - End level 2 + * 303 (315) - End level 1 + * + */ static void yahoo_process_status(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { @@ -1326,10 +1311,12 @@ static void yahoo_process_status(struct yahoo_input_data *yid, return; } - /* Status updates may be spread accross multiple packets and not - even on buddy boundaries, so keeping some state is important. - So, continue where we left off, and only add a user entry to - the list once it's complete (301-315 End buddy). */ + /* + * Status updates may be spread accross multiple packets and not + * even on buddy boundaries, so keeping some state is important. + * So, continue where we left off, and only add a user entry to + * the list once it's complete (301-315 End buddy). + */ u = yd->half_user; for (l = pkt->hash; l; l = l->next) { @@ -1593,17 +1580,20 @@ static void yahoo_process_list(struct yahoo_input_data *yid, YAHOO_CALLBACK(ext_yahoo_got_cookies) (yd->client_id); } -static void yahoo_process_verify(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_verify(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; - if(pkt->status != 0x01) { + if (pkt->status != 0x01) { DEBUG_MSG(("expected status: 0x01, got: %d", pkt->status)); - YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, YAHOO_LOGIN_LOCK, ""); + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, + YAHOO_LOGIN_LOCK, ""); return; } - pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YPACKET_STATUS_DEFAULT, + yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); yahoo_send_packet(yid, pkt, 0); @@ -1612,7 +1602,8 @@ static void yahoo_process_verify(struct yahoo_input_data *yid, struct yahoo_pack } -static void yahoo_process_picture_checksum( struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_picture_checksum(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *from = NULL; @@ -1620,30 +1611,30 @@ static void yahoo_process_picture_checksum( struct yahoo_input_data *yid, struct int checksum = 0; YList *l; - for(l = pkt->hash; l; l = l->next) - { + for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; - switch(pair->key) - { - case 1: - case 4: - from = pair->value; - case 5: - to = pair->value; - break; - case 212: - break; - case 192: - checksum = atoi( pair->value ); - break; + switch (pair->key) { + case 1: + case 4: + from = pair->value; + case 5: + to = pair->value; + break; + case 212: + break; + case 192: + checksum = atoi(pair->value); + break; } } - YAHOO_CALLBACK(ext_yahoo_got_buddyicon_checksum)(yd->client_id,to,from,checksum); + YAHOO_CALLBACK(ext_yahoo_got_buddyicon_checksum) (yd->client_id, to, + from, checksum); } -static void yahoo_process_picture(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_picture(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *url = NULL; @@ -1652,607 +1643,108 @@ static void yahoo_process_picture(struct yahoo_input_data *yid, struct yahoo_pac int status = 0; int checksum = 0; YList *l; - - for(l = pkt->hash; l; l = l->next) - { + + for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; - switch(pair->key) - { + switch (pair->key) { case 1: - case 4: /* sender */ + case 4: /* sender */ from = pair->value; break; - case 5: /* we */ + case 5: /* we */ to = pair->value; break; - case 13: /* request / sending */ - status = atoi( pair->value ); + case 13: /* request / sending */ + status = atoi(pair->value); break; - case 20: /* url */ + case 20: /* url */ url = pair->value; break; case 192: /*checksum */ - checksum = atoi( pair->value ); + checksum = atoi(pair->value); break; } } - switch( status ) - { - case 1: /* this is a request, ignore for now */ - YAHOO_CALLBACK(ext_yahoo_got_buddyicon_request)(yd->client_id, to, from); - break; - case 2: /* this is cool - we get a picture :) */ - YAHOO_CALLBACK(ext_yahoo_got_buddyicon)(yd->client_id,to, from, url, checksum); - break; + switch (status) { + case 1: /* this is a request, ignore for now */ + YAHOO_CALLBACK(ext_yahoo_got_buddyicon_request) (yd->client_id, + to, from); + break; + case 2: /* this is cool - we get a picture :) */ + YAHOO_CALLBACK(ext_yahoo_got_buddyicon) (yd->client_id, to, + from, url, checksum); + break; } } -static void yahoo_process_picture_upload(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_picture_upload(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; YList *l; char *url = NULL; - if ( pkt->status != 1 ) + if (pkt->status != 1) return; /* something went wrong */ - - for(l = pkt->hash; l; l = l->next) - { - struct yahoo_pair *pair = l->data; - - switch(pair->key) - { - case 5: /* we */ - break; - case 20: /* url */ - url = pair->value; - break; - case 27: /* local filename */ - break; - case 38: /* time */ - break; - } - } - - YAHOO_CALLBACK(ext_yahoo_buddyicon_uploaded)(yd->client_id, url); -} - -static void yahoo_process_auth_pre_0x0b(struct yahoo_input_data *yid, - const char *seed, const char *sn) -{ - struct yahoo_data *yd = yid->yd; - - /* So, Yahoo has stopped supporting its older clients in India, and - * undoubtedly will soon do so in the rest of the world. - * - * The new clients use this authentication method. I warn you in - * advance, it's bizzare, convoluted, inordinately complicated. - * It's also no more secure than crypt() was. The only purpose this - * scheme could serve is to prevent third part clients from connecting - * to their servers. - * - * Sorry, Yahoo. - */ - - struct yahoo_packet *pack; - - md5_byte_t result[16]; - md5_state_t ctx; - char *crypt_result; - unsigned char *password_hash = malloc(25); - unsigned char *crypt_hash = malloc(25); - unsigned char *hash_string_p = malloc(50 + strlen(sn)); - unsigned char *hash_string_c = malloc(50 + strlen(sn)); - - char checksum; - - int sv; - - unsigned char *result6 = malloc(25); - unsigned char *result96 = malloc(25); - - sv = seed[15]; - sv = (sv % 8) % 5; - - md5_init(&ctx); - md5_append(&ctx, (md5_byte_t *)yd->password, strlen(yd->password)); - md5_finish(&ctx, result); - to_y64(password_hash, result, 16); - - md5_init(&ctx); - crypt_result = yahoo_crypt(yd->password, "$1$_2S43d5f$"); - md5_append(&ctx, (md5_byte_t *)crypt_result, strlen(crypt_result)); - md5_finish(&ctx, result); - to_y64(crypt_hash, result, 16); - free(crypt_result); - - switch (sv) { - case 0: - checksum = seed[seed[7] % 16]; - snprintf((char *)hash_string_p, strlen(sn) + 50, - "%c%s%s%s", checksum, password_hash, yd->user, seed); - snprintf((char *)hash_string_c, strlen(sn) + 50, - "%c%s%s%s", checksum, crypt_hash, yd->user, seed); - break; - case 1: - checksum = seed[seed[9] % 16]; - snprintf((char *)hash_string_p, strlen(sn) + 50, - "%c%s%s%s", checksum, yd->user, seed, password_hash); - snprintf((char *)hash_string_c, strlen(sn) + 50, - "%c%s%s%s", checksum, yd->user, seed, crypt_hash); - break; - case 2: - checksum = seed[seed[15] % 16]; - snprintf((char *)hash_string_p, strlen(sn) + 50, - "%c%s%s%s", checksum, seed, password_hash, yd->user); - snprintf((char *)hash_string_c, strlen(sn) + 50, - "%c%s%s%s", checksum, seed, crypt_hash, yd->user); - break; - case 3: - checksum = seed[seed[1] % 16]; - snprintf((char *)hash_string_p, strlen(sn) + 50, - "%c%s%s%s", checksum, yd->user, password_hash, seed); - snprintf((char *)hash_string_c, strlen(sn) + 50, - "%c%s%s%s", checksum, yd->user, crypt_hash, seed); - break; - case 4: - checksum = seed[seed[3] % 16]; - snprintf((char *)hash_string_p, strlen(sn) + 50, - "%c%s%s%s", checksum, password_hash, seed, yd->user); - snprintf((char *)hash_string_c, strlen(sn) + 50, - "%c%s%s%s", checksum, crypt_hash, seed, yd->user); - break; - } - - md5_init(&ctx); - md5_append(&ctx, (md5_byte_t *)hash_string_p, strlen((char *)hash_string_p)); - md5_finish(&ctx, result); - to_y64(result6, result, 16); - - md5_init(&ctx); - md5_append(&ctx, (md5_byte_t *)hash_string_c, strlen((char *)hash_string_c)); - md5_finish(&ctx, result); - to_y64(result96, result, 16); - pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, yd->initial_status, yd->session_id); - yahoo_packet_hash(pack, 0, yd->user); - yahoo_packet_hash(pack, 6, (char *)result6); - yahoo_packet_hash(pack, 96, (char *)result96); - yahoo_packet_hash(pack, 1, yd->user); - - yahoo_send_packet(yid, pack, 0); - - FREE(result6); - FREE(result96); - FREE(password_hash); - FREE(crypt_hash); - FREE(hash_string_p); - FREE(hash_string_c); - - yahoo_packet_free(pack); - -} - -/* - * New auth protocol cracked by Cerulean Studios and sent in to Gaim - */ -static void yahoo_process_auth_0x0b(struct yahoo_input_data *yid, const char *seed, const char *sn) -{ - struct yahoo_packet *pack = NULL; - struct yahoo_data *yd = yid->yd; - - md5_byte_t result[16]; - md5_state_t ctx; - - sha1_state_t ctx1; - sha1_state_t ctx2; - - char *alphabet1 = "FBZDWAGHrJTLMNOPpRSKUVEXYChImkwQ"; - char *alphabet2 = "F0E1D2C3B4A59687abcdefghijklmnop"; - - char *challenge_lookup = "qzec2tb3um1olpar8whx4dfgijknsvy5"; - char *operand_lookup = "+|&%/*^-"; - char *delimit_lookup = ",;"; - - unsigned char *password_hash = malloc(25); - unsigned char *crypt_hash = malloc(25); - char *crypt_result = NULL; - unsigned char pass_hash_xor1[64]; - unsigned char pass_hash_xor2[64]; - unsigned char crypt_hash_xor1[64]; - unsigned char crypt_hash_xor2[64]; - unsigned char chal[7]; - char resp_6[100]; - char resp_96[100]; - - unsigned char digest1[20]; - unsigned char digest2[20]; - unsigned char magic_key_char[4]; - const unsigned char *magic_ptr; - - unsigned int magic[64]; - unsigned int magic_work=0; - - char comparison_src[20]; - - int x, j, i; - int cnt = 0; - int magic_cnt = 0; - int magic_len; - int depth =0, table =0; - - memset(&pass_hash_xor1, 0, 64); - memset(&pass_hash_xor2, 0, 64); - memset(&crypt_hash_xor1, 0, 64); - memset(&crypt_hash_xor2, 0, 64); - memset(&digest1, 0, 20); - memset(&digest2, 0, 20); - memset(&magic, 0, 64); - memset(&resp_6, 0, 100); - memset(&resp_96, 0, 100); - memset(&magic_key_char, 0, 4); - - /* - * Magic: Phase 1. Generate what seems to be a 30 - * byte value (could change if base64 - * ends up differently? I don't remember and I'm - * tired, so use a 64 byte buffer. - */ - - magic_ptr = (unsigned char *)seed; - - while (*magic_ptr != 0) { - char *loc; - - /* Ignore parentheses. */ - - if (*magic_ptr == '(' || *magic_ptr == ')') { - magic_ptr++; - continue; - } - - /* Characters and digits verify against - the challenge lookup. - */ - - if (isalpha(*magic_ptr) || isdigit(*magic_ptr)) { - loc = strchr(challenge_lookup, *magic_ptr); - if (!loc) { - /* This isn't good */ - continue; - } - - /* Get offset into lookup table and lsh 3. */ - - magic_work = loc - challenge_lookup; - magic_work <<= 3; - - magic_ptr++; - continue; - } else { - unsigned int local_store; - - loc = strchr(operand_lookup, *magic_ptr); - if (!loc) { - /* Also not good. */ - continue; - } - - local_store = loc - operand_lookup; - - /* Oops; how did this happen? */ - if (magic_cnt >= 64) - break; - - magic[magic_cnt++] = magic_work | local_store; - magic_ptr++; - continue; - } - } - - magic_len = magic_cnt; - magic_cnt = 0; - - /* Magic: Phase 2. Take generated magic value and - * sprinkle fairy dust on the values. */ - - for (magic_cnt = magic_len-2; magic_cnt >= 0; magic_cnt--) { - unsigned char byte1; - unsigned char byte2; - - /* Bad. Abort. - */ - if (magic_cnt >= magic_len) { - WARNING(("magic_cnt(%d) magic_len(%d)", magic_cnt, magic_len)) - break; - } - - byte1 = magic[magic_cnt]; - byte2 = magic[magic_cnt+1]; - - byte1 *= 0xcd; - byte1 ^= byte2; - - magic[magic_cnt+1] = byte1; - } - - /* Magic: Phase 3. This computes 20 bytes. The first 4 bytes are used as our magic - * key (and may be changed later); the next 16 bytes are an MD5 sum of the magic key - * plus 3 bytes. The 3 bytes are found by looping, and they represent the offsets - * into particular functions we'll later call to potentially alter the magic key. - * - * %-) - */ - - magic_cnt = 1; - x = 0; - - do { - unsigned int bl = 0; - unsigned int cl = magic[magic_cnt++]; - - if (magic_cnt >= magic_len) - break; - - if (cl > 0x7F) { - if (cl < 0xe0) - bl = cl = (cl & 0x1f) << 6; - else { - bl = magic[magic_cnt++]; - cl = (cl & 0x0f) << 6; - bl = ((bl & 0x3f) + cl) << 6; - } - - cl = magic[magic_cnt++]; - bl = (cl & 0x3f) + bl; - } else - bl = cl; - - comparison_src[x++] = (bl & 0xff00) >> 8; - comparison_src[x++] = bl & 0xff; - } while (x < 20); - - /* Dump magic key into a char for SHA1 action. */ - - - for(x = 0; x < 4; x++) - magic_key_char[x] = comparison_src[x]; - - /* Compute values for recursive function table! */ - memcpy( chal, magic_key_char, 4 ); - x = 1; - for( i = 0; i < 0xFFFF && x; i++ ) - { - for( j = 0; j < 5 && x; j++ ) - { - chal[4] = i; - chal[5] = i >> 8; - chal[6] = j; - md5_init( &ctx ); - md5_append( &ctx, chal, 7 ); - md5_finish( &ctx, result ); - if( memcmp( comparison_src + 4, result, 16 ) == 0 ) - { - depth = i; - table = j; - x = 0; - } - } - } - - /* Transform magic_key_char using transform table */ - x = magic_key_char[3] << 24 | magic_key_char[2] << 16 - | magic_key_char[1] << 8 | magic_key_char[0]; - x = yahoo_xfrm( table, depth, x ); - x = yahoo_xfrm( table, depth, x ); - magic_key_char[0] = x & 0xFF; - magic_key_char[1] = x >> 8 & 0xFF; - magic_key_char[2] = x >> 16 & 0xFF; - magic_key_char[3] = x >> 24 & 0xFF; - - /* Get password and crypt hashes as per usual. */ - md5_init(&ctx); - md5_append(&ctx, (md5_byte_t *)yd->password, strlen(yd->password)); - md5_finish(&ctx, result); - to_y64(password_hash, result, 16); - - md5_init(&ctx); - crypt_result = yahoo_crypt(yd->password, "$1$_2S43d5f$"); - md5_append(&ctx, (md5_byte_t *)crypt_result, strlen(crypt_result)); - md5_finish(&ctx, result); - to_y64(crypt_hash, result, 16); - free(crypt_result); - - /* Our first authentication response is based off - * of the password hash. */ - - for (x = 0; x < (int)strlen((char *)password_hash); x++) - pass_hash_xor1[cnt++] = password_hash[x] ^ 0x36; - - if (cnt < 64) - memset(&(pass_hash_xor1[cnt]), 0x36, 64-cnt); - - cnt = 0; - - for (x = 0; x < (int)strlen((char *)password_hash); x++) - pass_hash_xor2[cnt++] = password_hash[x] ^ 0x5c; - - if (cnt < 64) - memset(&(pass_hash_xor2[cnt]), 0x5c, 64-cnt); - - sha1_init(&ctx1); - sha1_init(&ctx2); - - /* The first context gets the password hash XORed - * with 0x36 plus a magic value - * which we previously extrapolated from our - * challenge. */ - - sha1_append(&ctx1, pass_hash_xor1, 64); - if (j >= 3 ) - ctx1.Length_Low = 0x1ff; - sha1_append(&ctx1, magic_key_char, 4); - sha1_finish(&ctx1, digest1); - - /* The second context gets the password hash XORed - * with 0x5c plus the SHA-1 digest - * of the first context. */ - - sha1_append(&ctx2, pass_hash_xor2, 64); - sha1_append(&ctx2, digest1, 20); - sha1_finish(&ctx2, digest2); - - /* Now that we have digest2, use it to fetch - * characters from an alphabet to construct - * our first authentication response. */ - - for (x = 0; x < 20; x += 2) { - unsigned int val = 0; - unsigned int lookup = 0; - char byte[6]; - - memset(&byte, 0, 6); - - /* First two bytes of digest stuffed - * together. - */ - - val = digest2[x]; - val <<= 8; - val += digest2[x+1]; + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; - lookup = (val >> 0x0b); - lookup &= 0x1f; - if (lookup >= strlen(alphabet1)) + switch (pair->key) { + case 5: /* we */ break; - sprintf(byte, "%c", alphabet1[lookup]); - strcat(resp_6, byte); - strcat(resp_6, "="); - - lookup = (val >> 0x06); - lookup &= 0x1f; - if (lookup >= strlen(alphabet2)) + case 20: /* url */ + url = pair->value; break; - sprintf(byte, "%c", alphabet2[lookup]); - strcat(resp_6, byte); - - lookup = (val >> 0x01); - lookup &= 0x1f; - if (lookup >= strlen(alphabet2)) + case 27: /* local filename */ break; - sprintf(byte, "%c", alphabet2[lookup]); - strcat(resp_6, byte); - - lookup = (val & 0x01); - if (lookup >= strlen(delimit_lookup)) + case 38: /* time */ break; - sprintf(byte, "%c", delimit_lookup[lookup]); - strcat(resp_6, byte); + } } - /* Our second authentication response is based off - * of the crypto hash. */ - - cnt = 0; - memset(&digest1, 0, 20); - memset(&digest2, 0, 20); - - for (x = 0; x < (int)strlen((char *)crypt_hash); x++) - crypt_hash_xor1[cnt++] = crypt_hash[x] ^ 0x36; - - if (cnt < 64) - memset(&(crypt_hash_xor1[cnt]), 0x36, 64-cnt); - - cnt = 0; - - for (x = 0; x < (int)strlen((char *)crypt_hash); x++) - crypt_hash_xor2[cnt++] = crypt_hash[x] ^ 0x5c; - - if (cnt < 64) - memset(&(crypt_hash_xor2[cnt]), 0x5c, 64-cnt); - - sha1_init(&ctx1); - sha1_init(&ctx2); - - /* The first context gets the password hash XORed - * with 0x36 plus a magic value - * which we previously extrapolated from our - * challenge. */ - - sha1_append(&ctx1, crypt_hash_xor1, 64); - if (j >= 3 ) - ctx1.Length_Low = 0x1ff; - sha1_append(&ctx1, magic_key_char, 4); - sha1_finish(&ctx1, digest1); - - /* The second context gets the password hash XORed - * with 0x5c plus the SHA-1 digest - * of the first context. */ - - sha1_append(&ctx2, crypt_hash_xor2, 64); - sha1_append(&ctx2, digest1, 20); - sha1_finish(&ctx2, digest2); - - /* Now that we have digest2, use it to fetch - * characters from an alphabet to construct - * our first authentication response. */ - - for (x = 0; x < 20; x += 2) { - unsigned int val = 0; - unsigned int lookup = 0; + YAHOO_CALLBACK(ext_yahoo_buddyicon_uploaded) (yd->client_id, url); +} - char byte[6]; +void yahoo_login(int id, int initial) +{ + struct yahoo_data *yd = find_conn_by_id(id); + struct connect_callback_data *ccd; + struct yahoo_server_settings *yss; + int tag; - memset(&byte, 0, 6); + char *host; - /* First two bytes of digest stuffed - * together. */ + struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1); + yid->yd = yd; + yid->type = YAHOO_CONNECTION_PAGER; + inputs = y_list_prepend(inputs, yid); - val = digest2[x]; - val <<= 8; - val += digest2[x+1]; + yd->initial_status = initial; + yss = yd->server_settings; - lookup = (val >> 0x0b); - lookup &= 0x1f; - if (lookup >= strlen(alphabet1)) - break; - sprintf(byte, "%c", alphabet1[lookup]); - strcat(resp_96, byte); - strcat(resp_96, "="); + ccd = y_new0(struct connect_callback_data, 1); + ccd->yd = yd; - lookup = (val >> 0x06); - lookup &= 0x1f; - if (lookup >= strlen(alphabet2)) - break; - sprintf(byte, "%c", alphabet2[lookup]); - strcat(resp_96, byte); + host = yss->pager_host; - lookup = (val >> 0x01); - lookup &= 0x1f; - if (lookup >= strlen(alphabet2)) - break; - sprintf(byte, "%c", alphabet2[lookup]); - strcat(resp_96, byte); + if (!host) + host = yss->pager_host_list[0]; - lookup = (val & 0x01); - if (lookup >= strlen(delimit_lookup)) - break; - sprintf(byte, "%c", delimit_lookup[lookup]); - strcat(resp_96, byte); - } + tag = YAHOO_CALLBACK(ext_yahoo_connect_async) (yd->client_id, + host, yss->pager_port, yahoo_connected, ccd, 0); - pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, yd->initial_status, yd->session_id); - yahoo_packet_hash(pack, 0, sn); - yahoo_packet_hash(pack, 6, resp_6); - yahoo_packet_hash(pack, 96, resp_96); - yahoo_packet_hash(pack, 1, sn); - yahoo_send_packet(yid, pack, 0); - yahoo_packet_free(pack); - - free(password_hash); - free(crypt_hash); + /* + * if tag <= 0, then callback has already been called + * so ccd will have been freed + */ + if (tag > 0) + ccd->tag = tag; + else if (tag < 0) + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, + YAHOO_LOGIN_SOCK, NULL); } struct yahoo_https_auth_data @@ -2302,7 +1794,7 @@ static enum yahoo_status yahoo_https_status_parse(int code) } } -static void yahoo_process_auth_0x10(struct yahoo_input_data *yid, const char *seed, const char *sn) +static void yahoo_https_auth(struct yahoo_input_data *yid, const char *seed, const char *sn) { struct yahoo_https_auth_data *had = g_new0(struct yahoo_https_auth_data, 1); @@ -2352,21 +1844,22 @@ static void yahoo_https_auth_token_finish(struct http_request *req) yd = yid->yd; if (req->status_code != 200) { - YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, 2000 + req->status_code, NULL); + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 2000 + req->status_code, NULL); goto fail; } if (sscanf(req->reply_body, "%d", &st) != 1 || st != 0) { - YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, yahoo_https_status_parse(st), NULL); + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, yahoo_https_status_parse(st), NULL); goto fail; } if ((had->token = yahoo_ha_find_key(req->reply_body, "ymsgr")) == NULL) { - YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, 3001, NULL); + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 3001, NULL); goto fail; } - return yahoo_https_auth_init(had); + yahoo_https_auth_init(had); + return; fail: g_free(had->token); @@ -2408,19 +1901,19 @@ static void yahoo_https_auth_finish(struct http_request *req) unsigned char yhash[32]; if (req->status_code != 200) { - YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, 2000 + req->status_code, NULL); + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 2000 + req->status_code, NULL); goto fail; } if (sscanf(req->reply_body, "%d", &st) != 1 || st != 0) { - YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, yahoo_https_status_parse(st), NULL); + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, yahoo_https_status_parse(st), NULL); goto fail; } if ((yd->cookie_y = yahoo_ha_find_key(req->reply_body, "Y")) == NULL || (yd->cookie_t = yahoo_ha_find_key(req->reply_body, "T")) == NULL || (crumb = yahoo_ha_find_key(req->reply_body, "crumb")) == NULL) { - YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, 3002, NULL); + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 3002, NULL); goto fail; } @@ -2453,52 +1946,53 @@ fail: g_free(had); } -static void yahoo_process_auth(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_auth(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { char *seed = NULL; - char *sn = NULL; + char *sn = NULL; YList *l = pkt->hash; int m = 0; + struct yahoo_data *yd = yid->yd; while (l) { struct yahoo_pair *pair = l->data; - if (pair->key == 94) + + switch (pair->key) { + case 94: seed = pair->value; - if (pair->key == 1) + break; + case 1: sn = pair->value; - if (pair->key == 13) + break; + case 13: m = atoi(pair->value); + break; + } l = l->next; } - if (!seed) + if (!seed) return; - switch (m) { - case 0: - yahoo_process_auth_pre_0x0b(yid, seed, sn); - break; - case 1: - yahoo_process_auth_0x0b(yid, seed, sn); - break; - case 2: - yahoo_process_auth_0x10(yid, seed, sn); - break; - default: - /* call error */ - WARNING(("unknown auth type %d", m)); - yahoo_process_auth_0x0b(yid, seed, sn); - break; + if (m==2) + yahoo_https_auth(yid, seed, sn); + else { + /* call error */ + WARNING(("unknown auth type %d", m)); + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, + YAHOO_LOGIN_UNKNOWN, NULL); } } -static void yahoo_process_auth_resp(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_auth_resp(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *login_id; char *handle; - char *url=NULL; - int login_status=0; + char *url = NULL; + int login_status = -1; YList *l; @@ -2514,13 +2008,15 @@ static void yahoo_process_auth_resp(struct yahoo_input_data *yid, struct yahoo_p login_status = atoi(pair->value); } - if(pkt->status == 0xffffffff) { - YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, login_status, url); - /* yahoo_logoff(yd->client_id);*/ + if (pkt->status == YPACKET_STATUS_DISCONNECTED) { + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, + login_status, url); + /* yahoo_logoff(yd->client_id); */ } } -static void yahoo_process_mail(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_mail(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *who = NULL; @@ -2546,12 +2042,46 @@ static void yahoo_process_mail(struct yahoo_input_data *yid, struct yahoo_packet if (who && email && subj) { char from[1024]; snprintf(from, sizeof(from), "%s (%s)", who, email); - YAHOO_CALLBACK(ext_yahoo_mail_notify)(yd->client_id, from, subj, count); - } else if(count > 0) - YAHOO_CALLBACK(ext_yahoo_mail_notify)(yd->client_id, NULL, NULL, count); + YAHOO_CALLBACK(ext_yahoo_mail_notify) (yd->client_id, from, + subj, count); + } else if (count > 0) + YAHOO_CALLBACK(ext_yahoo_mail_notify) (yd->client_id, NULL, + NULL, count); } -static void yahoo_process_contact(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_new_contact(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = yid->yd; + char *me = NULL; + char *who = NULL; + char *msg = NULL; + int online = -1; + + YList *l; + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + if (pair->key == 4) + who = pair->value; + else if (pair->key == 5) + me = pair->value; + else if (pair->key == 14) + msg = pair->value; + else if (pair->key == 13) + online = strtol(pair->value, NULL, 10); + } + + if (who && online < 0) + YAHOO_CALLBACK(ext_yahoo_contact_added) (yd->client_id, me, who, + msg); + else if (online == 2) + YAHOO_CALLBACK(ext_yahoo_rejected) (yd->client_id, who, msg); +} + +/* UNUSED? */ +static void yahoo_process_contact(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *id = NULL; @@ -2560,7 +2090,7 @@ static void yahoo_process_contact(struct yahoo_input_data *yid, struct yahoo_pac char *name = NULL; long tm = 0L; int state = YAHOO_STATUS_AVAILABLE; - int online = FALSE; + int online = 0; int away = 0; int idle = 0; int mobile = 0; @@ -2589,18 +2119,21 @@ static void yahoo_process_contact(struct yahoo_input_data *yid, struct yahoo_pac idle = strtol(pair->value, NULL, 10); else if (pair->key == 60) mobile = strtol(pair->value, NULL, 10); - + } if (id) - YAHOO_CALLBACK(ext_yahoo_contact_added)(yd->client_id, id, who, msg); + YAHOO_CALLBACK(ext_yahoo_contact_added) (yd->client_id, id, who, + msg); else if (name) - YAHOO_CALLBACK(ext_yahoo_status_changed)(yd->client_id, name, state, msg, away, idle, mobile); - else if(pkt->status == 0x07) - YAHOO_CALLBACK(ext_yahoo_rejected)(yd->client_id, who, msg); + YAHOO_CALLBACK(ext_yahoo_status_changed) (yd->client_id, name, + state, msg, away, idle, mobile); + else if (pkt->status == 0x07) + YAHOO_CALLBACK(ext_yahoo_rejected) (yd->client_id, who, msg); } -static void yahoo_process_buddyadd(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_buddyadd(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *who = NULL; @@ -2608,7 +2141,7 @@ static void yahoo_process_buddyadd(struct yahoo_input_data *yid, struct yahoo_pa int status = 0; char *me = NULL; - struct yahoo_buddy *bud=NULL; + struct yahoo_buddy *bud = NULL; YList *l; for (l = pkt->hash; l; l = l->next) { @@ -2623,51 +2156,30 @@ static void yahoo_process_buddyadd(struct yahoo_input_data *yid, struct yahoo_pa status = strtol(pair->value, NULL, 10); } - yahoo_dump_unhandled(pkt); - - if(!who) + if (!who) return; - if(!where) + if (!where) where = "Unknown"; - /* status: 0 == Successful, 1 == Error (does not exist), 2 == Already in list */ - if( status == 0 ) { - bud = y_new0(struct yahoo_buddy, 1); - bud->id = strdup(who); - bud->group = strdup(where); - bud->real_name = NULL; - - yd->buddies = y_list_append(yd->buddies, bud); - - /* Possibly called already, but at least the call above doesn't - seem to happen every time (not anytime I tried). */ - YAHOO_CALLBACK(ext_yahoo_contact_added)(yd->client_id, me, who, NULL); - } + bud = y_new0(struct yahoo_buddy, 1); + bud->id = strdup(who); + bud->group = strdup(where); + bud->real_name = NULL; -/* YAHOO_CALLBACK(ext_yahoo_status_changed)(yd->client_id, who, status, NULL, (status==YAHOO_STATUS_AVAILABLE?0:1)); */ -} + yd->buddies = y_list_append(yd->buddies, bud); -static void yahoo_process_contact_ymsg13(struct yahoo_input_data *yid, struct yahoo_packet *pkt) -{ - char* who=NULL; - char* me=NULL; - char* msg=NULL; - YList *l; - for (l = pkt->hash; l; l = l->next) { - struct yahoo_pair *pair = l->data; - if (pair->key == 4) - who = pair->value; - else if (pair->key == 5) - me = pair->value; - else - DEBUG_MSG(("unknown key: %d = %s", pair->key, pair->value)); + /* A non-zero status (i've seen 2) seems to mean the buddy is already + * added and is online */ + if (status) { + LOG(("Setting online see packet for info")); + yahoo_dump_unhandled(pkt); + YAHOO_CALLBACK(ext_yahoo_status_changed) (yd->client_id, who, + YAHOO_STATUS_AVAILABLE, NULL, 0, 0, 0); } - - if(pkt->status==3) - YAHOO_CALLBACK(ext_yahoo_contact_auth_request)(yid->yd->client_id, me, who, msg); } -static void yahoo_process_buddydel(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_buddydel(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *who = NULL; @@ -2690,12 +2202,13 @@ static void yahoo_process_buddydel(struct yahoo_input_data *yid, struct yahoo_pa else if (pair->key == 66) unk_66 = strtol(pair->value, NULL, 10); else - DEBUG_MSG(("unknown key: %d = %s", pair->key, pair->value)); + DEBUG_MSG(("unknown key: %d = %s", pair->key, + pair->value)); } - if(!who || !where) + if (!who || !where) return; - + bud = y_new0(struct yahoo_buddy, 1); bud->id = strdup(who); bud->group = strdup(where); @@ -2706,7 +2219,7 @@ static void yahoo_process_buddydel(struct yahoo_input_data *yid, struct yahoo_pa FREE(bud->group); FREE(bud); - if(buddy) { + if (buddy) { bud = buddy->data; yd->buddies = y_list_remove_link(yd->buddies, buddy); y_list_free_1(buddy); @@ -2716,16 +2229,17 @@ static void yahoo_process_buddydel(struct yahoo_input_data *yid, struct yahoo_pa FREE(bud->real_name); FREE(bud); - bud=NULL; + bud = NULL; } } -static void yahoo_process_ignore(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_ignore(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { char *who = NULL; - int status = 0; + int status = 0; char *me = NULL; - int un_ignore = 0; + int un_ignore = 0; YList *l; for (l = pkt->hash; l; l = l->next) { @@ -2734,27 +2248,27 @@ static void yahoo_process_ignore(struct yahoo_input_data *yid, struct yahoo_pack who = pair->value; if (pair->key == 1) me = pair->value; - if (pair->key == 13) /* 1 == ignore, 2 == unignore */ + if (pair->key == 13) /* 1 == ignore, 2 == unignore */ un_ignore = strtol(pair->value, NULL, 10); - if (pair->key == 66) + if (pair->key == 66) status = strtol(pair->value, NULL, 10); } - /* * status - * 0 - ok - * 2 - already in ignore list, could not add - * 3 - not in ignore list, could not delete - * 12 - is a buddy, could not add + * 0 - ok + * 2 - already in ignore list, could not add + * 3 - not in ignore list, could not delete + * 12 - is a buddy, could not add */ /* if(status) YAHOO_CALLBACK(ext_yahoo_error)(yd->client_id, who, 0, status); -*/ +*/ } -static void yahoo_process_voicechat(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_voicechat(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { char *who = NULL; char *me = NULL; @@ -2769,12 +2283,13 @@ static void yahoo_process_voicechat(struct yahoo_input_data *yid, struct yahoo_p if (pair->key == 5) me = pair->value; if (pair->key == 13) - voice_room=pair->value; - if (pair->key == 57) - room=pair->value; + voice_room = pair->value; + if (pair->key == 57) + room = pair->value; } - NOTICE(("got voice chat invite from %s in %s to identity %s", who, room, me)); + NOTICE(("got voice chat invite from %s in %s to identity %s", who, room, + me)); /* * send: s:0 1:me 5:who 57:room 13:1 * ???? s:4 5:who 10:99 19:-1615114531 @@ -2786,33 +2301,59 @@ static void yahoo_process_voicechat(struct yahoo_input_data *yid, struct yahoo_p */ } -static void yahoo_process_ping(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_ping(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { char *errormsg = NULL; - + YList *l; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 16) errormsg = pair->value; } - + NOTICE(("got ping packet")); - YAHOO_CALLBACK(ext_yahoo_got_ping)(yid->yd->client_id, errormsg); + YAHOO_CALLBACK(ext_yahoo_got_ping) (yid->yd->client_id, errormsg); } -static void _yahoo_webcam_get_server_connected(int fd, int error, void *d) +static void yahoo_process_buddy_change_group(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + YList *l; + char *me = NULL; + char *who = NULL; + char *old_group = NULL; + char *new_group = NULL; + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + if (pair->key == 1) + me = pair->value; + if (pair->key == 7) + who = pair->value; + if (pair->key == 224) + old_group = pair->value; + if (pair->key == 264) + new_group = pair->value; + } + + YAHOO_CALLBACK(ext_yahoo_got_buddy_change_group) (yid->yd->client_id, + me, who, old_group, new_group); +} + +static void _yahoo_webcam_get_server_connected(void *fd, int error, void *d) { struct yahoo_input_data *yid = d; char *who = yid->wcm->user; char *data = NULL; char *packet = NULL; - unsigned char magic_nr[] = {0, 1, 0}; + unsigned char magic_nr[] = { 0, 1, 0 }; unsigned char header_len = 8; unsigned int len = 0; unsigned int pos = 0; - if(error || fd <= 0) { + if (error || !fd) { FREE(who); FREE(yid); return; @@ -2820,7 +2361,7 @@ static void _yahoo_webcam_get_server_connected(int fd, int error, void *d) yid->fd = fd; inputs = y_list_prepend(inputs, yid); - + /* send initial packet */ if (who) data = strdup("<RVWCFG>"); @@ -2830,8 +2371,7 @@ static void _yahoo_webcam_get_server_connected(int fd, int error, void *d) FREE(data); /* send data */ - if (who) - { + if (who) { data = strdup("g="); data = y_string_append(data, who); data = y_string_append(data, "\r\n"); @@ -2850,10 +2390,13 @@ static void _yahoo_webcam_get_server_connected(int fd, int error, void *d) FREE(packet); FREE(data); - yid->read_tag=YAHOO_CALLBACK(ext_yahoo_add_handler)(yid->yd->client_id, fd, YAHOO_INPUT_READ, yid); + yid->read_tag = + YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd, + YAHOO_INPUT_READ, yid); } -static void yahoo_webcam_get_server(struct yahoo_input_data *y, char *who, char *key) +static void yahoo_webcam_get_server(struct yahoo_input_data *y, char *who, + char *key) { struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1); struct yahoo_server_settings *yss = y->yd->server_settings; @@ -2861,34 +2404,36 @@ static void yahoo_webcam_get_server(struct yahoo_input_data *y, char *who, char yid->type = YAHOO_CONNECTION_WEBCAM_MASTER; yid->yd = y->yd; yid->wcm = y_new0(struct yahoo_webcam, 1); - yid->wcm->user = who?strdup(who):NULL; - yid->wcm->direction = who?YAHOO_WEBCAM_DOWNLOAD:YAHOO_WEBCAM_UPLOAD; + yid->wcm->user = who ? strdup(who) : NULL; + yid->wcm->direction = who ? YAHOO_WEBCAM_DOWNLOAD : YAHOO_WEBCAM_UPLOAD; yid->wcm->key = strdup(key); - YAHOO_CALLBACK(ext_yahoo_connect_async)(yid->yd->client_id, yss->webcam_host, yss->webcam_port, - _yahoo_webcam_get_server_connected, yid); + YAHOO_CALLBACK(ext_yahoo_connect_async) (yid->yd->client_id, + yss->webcam_host, yss->webcam_port, + _yahoo_webcam_get_server_connected, yid, 0); } -static YList *webcam_queue=NULL; -static void yahoo_process_webcam_key(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static YList *webcam_queue = NULL; +static void yahoo_process_webcam_key(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { char *me = NULL; char *key = NULL; char *who = NULL; YList *l; - // yahoo_dump_unhandled(pkt); + yahoo_dump_unhandled(pkt); for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 5) me = pair->value; - if (pair->key == 61) - key=pair->value; + if (pair->key == 61) + key = pair->value; } l = webcam_queue; - if(!l) + if (!l) return; who = l->data; webcam_queue = y_list_remove_link(webcam_queue, webcam_queue); @@ -2897,12 +2442,11 @@ static void yahoo_process_webcam_key(struct yahoo_input_data *yid, struct yahoo_ FREE(who); } -static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_packet_process(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { DEBUG_MSG(("yahoo_packet_process: 0x%02x", pkt->service)); - yahoo_dump_unhandled(pkt); - switch (pkt->service) - { + switch (pkt->service) { case YAHOO_SERVICE_USERSTAT: case YAHOO_SERVICE_LOGON: case YAHOO_SERVICE_LOGOFF: @@ -2913,7 +2457,7 @@ static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_pack case YAHOO_SERVICE_IDACT: case YAHOO_SERVICE_IDDEACT: case YAHOO_SERVICE_Y6_STATUS_UPDATE: - case YAHOO_SERVICE_YMSG15_STATUS: + case YAHOO_SERVICE_Y8_STATUS: yahoo_process_status(yid, pkt); break; case YAHOO_SERVICE_NOTIFY: @@ -2927,7 +2471,9 @@ static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_pack case YAHOO_SERVICE_NEWMAIL: yahoo_process_mail(yid, pkt); break; - case YAHOO_SERVICE_REJECTCONTACT: + case YAHOO_SERVICE_Y7_AUTHORIZATION: + yahoo_process_new_contact(yid, pkt); + break; case YAHOO_SERVICE_NEWCONTACT: yahoo_process_contact(yid, pkt); break; @@ -2962,15 +2508,18 @@ static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_pack yahoo_process_chat(yid, pkt); break; case YAHOO_SERVICE_P2PFILEXFER: - case YAHOO_SERVICE_FILETRANSFER: + case YAHOO_SERVICE_Y7_FILETRANSFER: yahoo_process_filetransfer(yid, pkt); break; + case YAHOO_SERVICE_Y7_FILETRANSFERINFO: + yahoo_process_filetransferinfo(yid, pkt); + break; + case YAHOO_SERVICE_Y7_FILETRANSFERACCEPT: + yahoo_process_filetransferaccept(yid, pkt); + break; case YAHOO_SERVICE_ADDBUDDY: yahoo_process_buddyadd(yid, pkt); break; - case YAHOO_SERVICE_CONTACT_YMSG13: - yahoo_process_contact_ymsg13(yid,pkt); - break; case YAHOO_SERVICE_REMBUDDY: yahoo_process_buddydel(yid, pkt); break; @@ -2986,6 +2535,9 @@ static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_pack case YAHOO_SERVICE_PING: yahoo_process_ping(yid, pkt); break; + case YAHOO_SERVICE_Y7_CHANGE_GROUP: + yahoo_process_buddy_change_group(yid, pkt); + break; case YAHOO_SERVICE_IDLE: case YAHOO_SERVICE_MAILSTAT: case YAHOO_SERVICE_CHATINVITE: @@ -2999,6 +2551,7 @@ static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_pack case YAHOO_SERVICE_CHATLOGON: case YAHOO_SERVICE_CHATLOGOFF: case YAHOO_SERVICE_CHATMSG: + case YAHOO_SERVICE_REJECTCONTACT: case YAHOO_SERVICE_PEERTOPEER: WARNING(("unhandled service 0x%02x", pkt->service)); yahoo_dump_unhandled(pkt); @@ -3011,9 +2564,10 @@ static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_pack break; case YAHOO_SERVICE_PICTURE_UPLOAD: yahoo_process_picture_upload(yid, pkt); - break; - case YAHOO_SERVICE_YMSG15_BUDDY_LIST: /* Buddy List */ + break; + case YAHOO_SERVICE_Y8_LIST: /* Buddy List */ yahoo_process_buddy_list(yid, pkt); + break; default: WARNING(("unknown service 0x%02x", pkt->service)); yahoo_dump_unhandled(pkt); @@ -3021,14 +2575,14 @@ static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_pack } } -static struct yahoo_packet * yahoo_getdata(struct yahoo_input_data * yid) +static struct yahoo_packet *yahoo_getdata(struct yahoo_input_data *yid) { struct yahoo_packet *pkt; struct yahoo_data *yd = yid->yd; int pos = 0; int pktlen; - if(!yd) + if (!yd) return NULL; DEBUG_MSG(("rxlen is %d", yid->rxlen)); @@ -3037,13 +2591,13 @@ static struct yahoo_packet * yahoo_getdata(struct yahoo_input_data * yid) return NULL; } - pos += 4; /* YMSG */ + pos += 4; /* YMSG */ pos += 2; pos += 2; - pktlen = yahoo_get16(yid->rxqueue + pos); pos += 2; - DEBUG_MSG(("%d bytes to read, rxlen is %d", - pktlen, yid->rxlen)); + pktlen = yahoo_get16(yid->rxqueue + pos); + pos += 2; + DEBUG_MSG(("%d bytes to read, rxlen is %d", pktlen, yid->rxlen)); if (yid->rxlen < (YAHOO_PACKET_HDRLEN + pktlen)) { DEBUG_MSG(("len < YAHOO_PACKET_HDRLEN + pktlen")); @@ -3055,11 +2609,14 @@ static struct yahoo_packet * yahoo_getdata(struct yahoo_input_data * yid) pkt = yahoo_packet_new(0, 0, 0); - pkt->service = yahoo_get16(yid->rxqueue + pos); pos += 2; - pkt->status = yahoo_get32(yid->rxqueue + pos); pos += 4; + pkt->service = yahoo_get16(yid->rxqueue + pos); + pos += 2; + pkt->status = yahoo_get32(yid->rxqueue + pos); + pos += 4; DEBUG_MSG(("Yahoo Service: 0x%02x Status: %d", pkt->service, - pkt->status)); - pkt->id = yahoo_get32(yid->rxqueue + pos); pos += 4; + pkt->status)); + pkt->id = yahoo_get32(yid->rxqueue + pos); + pos += 4; yd->session_id = pkt->id; @@ -3067,12 +2624,13 @@ static struct yahoo_packet * yahoo_getdata(struct yahoo_input_data * yid) yid->rxlen -= YAHOO_PACKET_HDRLEN + pktlen; DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); - if (yid->rxlen>0) { - unsigned char *tmp = y_memdup(yid->rxqueue + YAHOO_PACKET_HDRLEN - + pktlen, yid->rxlen); + if (yid->rxlen > 0) { + unsigned char *tmp = y_memdup(yid->rxqueue + YAHOO_PACKET_HDRLEN + + pktlen, yid->rxlen); FREE(yid->rxqueue); yid->rxqueue = tmp; - DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); + DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, + yid->rxqueue)); } else { DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue)); FREE(yid->rxqueue); @@ -3081,136 +2639,166 @@ static struct yahoo_packet * yahoo_getdata(struct yahoo_input_data * yid) return pkt; } -static void yahoo_yab_read(struct yab *yab, unsigned char *d, int len) +static struct yab *yahoo_yab_read(unsigned char *d, int len) { char *st, *en; char *data = (char *)d; - data[len]='\0'; + struct yab *yab = NULL; + + data[len] = '\0'; DEBUG_MSG(("Got yab: %s", data)); - st = en = strstr(data, "userid=\""); - if(st) { - st += strlen("userid=\""); - en = strchr(st, '"'); *en++ = '\0'; - yab->id = yahoo_xmldecode(st); + st = en = strstr(data, "e0=\""); + if (st) { + yab = y_new0(struct yab, 1); + + st += strlen("e0=\""); + en = strchr(st, '"'); + *en++ = '\0'; + yab->email = yahoo_xmldecode(st); } - st = strstr(en, "fname=\""); - if(st) { - st += strlen("fname=\""); - en = strchr(st, '"'); *en++ = '\0'; + if (!en) + return NULL; + + st = strstr(en, "id=\""); + if (st) { + st += strlen("id=\""); + en = strchr(st, '"'); + *en++ = '\0'; + yab->yid = atoi(yahoo_xmldecode(st)); + } + + st = strstr(en, "fn=\""); + if (st) { + st += strlen("fn=\""); + en = strchr(st, '"'); + *en++ = '\0'; yab->fname = yahoo_xmldecode(st); } - st = strstr(en, "lname=\""); - if(st) { - st += strlen("lname=\""); - en = strchr(st, '"'); *en++ = '\0'; + st = strstr(en, "ln=\""); + if (st) { + st += strlen("ln=\""); + en = strchr(st, '"'); + *en++ = '\0'; yab->lname = yahoo_xmldecode(st); } - st = strstr(en, "nname=\""); - if(st) { - st += strlen("nname=\""); - en = strchr(st, '"'); *en++ = '\0'; + st = strstr(en, "nn=\""); + if (st) { + st += strlen("nn=\""); + en = strchr(st, '"'); + *en++ = '\0'; yab->nname = yahoo_xmldecode(st); } - st = strstr(en, "email=\""); - if(st) { - st += strlen("email=\""); - en = strchr(st, '"'); *en++ = '\0'; - yab->email = yahoo_xmldecode(st); + st = strstr(en, "yi=\""); + if (st) { + st += strlen("yi=\""); + en = strchr(st, '"'); + *en++ = '\0'; + yab->id = yahoo_xmldecode(st); } st = strstr(en, "hphone=\""); - if(st) { + if (st) { st += strlen("hphone=\""); - en = strchr(st, '"'); *en++ = '\0'; + en = strchr(st, '"'); + *en++ = '\0'; yab->hphone = yahoo_xmldecode(st); } st = strstr(en, "wphone=\""); - if(st) { + if (st) { st += strlen("wphone=\""); - en = strchr(st, '"'); *en++ = '\0'; + en = strchr(st, '"'); + *en++ = '\0'; yab->wphone = yahoo_xmldecode(st); } st = strstr(en, "mphone=\""); - if(st) { + if (st) { st += strlen("mphone=\""); - en = strchr(st, '"'); *en++ = '\0'; + en = strchr(st, '"'); + *en++ = '\0'; yab->mphone = yahoo_xmldecode(st); } st = strstr(en, "dbid=\""); - if(st) { + if (st) { st += strlen("dbid=\""); - en = strchr(st, '"'); *en++ = '\0'; + en = strchr(st, '"'); + *en++ = '\0'; yab->dbid = atoi(st); } + + return yab; } -static struct yab * yahoo_getyab(struct yahoo_input_data *yid) +static struct yab *yahoo_getyab(struct yahoo_input_data *yid) { struct yab *yab = NULL; - int pos = 0, end=0; + int pos = 0, end = 0; struct yahoo_data *yd = yid->yd; - if(!yd) - return NULL; - - DEBUG_MSG(("rxlen is %d", yid->rxlen)); - - if(yid->rxlen <= strlen("<record")) - return NULL; - - /* start with <record */ - while(pos < yid->rxlen-strlen("<record")+1 - && memcmp(yid->rxqueue + pos, "<record", strlen("<record"))) - pos++; - - if(pos >= yid->rxlen-1) + if (!yd) return NULL; - end = pos+2; - /* end with /> */ - while(end < yid->rxlen-strlen("/>")+1 && memcmp(yid->rxqueue + end, "/>", strlen("/>"))) - end++; - - if(end >= yid->rxlen-1) - return NULL; - - yab = y_new0(struct yab, 1); - yahoo_yab_read(yab, yid->rxqueue + pos, end+2-pos); - - - yid->rxlen -= end+1; - DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); - if (yid->rxlen>0) { - unsigned char *tmp = y_memdup(yid->rxqueue + end + 1, yid->rxlen); - FREE(yid->rxqueue); - yid->rxqueue = tmp; - DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); - } else { - DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue)); - FREE(yid->rxqueue); - } + do { + DEBUG_MSG(("rxlen is %d", yid->rxlen)); + + if (yid->rxlen <= strlen("<ct")) + return NULL; + + /* start with <ct */ + while (pos < yid->rxlen - strlen("<ct") + 1 + && memcmp(yid->rxqueue + pos, "<ct", strlen("<ct"))) + pos++; + + if (pos >= yid->rxlen - 1) + return NULL; + + end = pos + 2; + /* end with > */ + while (end < yid->rxlen - strlen(">") + && memcmp(yid->rxqueue + end, ">", strlen(">"))) + end++; + + if (end >= yid->rxlen - 1) + return NULL; + + yab = yahoo_yab_read(yid->rxqueue + pos, end + 2 - pos); + + yid->rxlen -= end + 1; + DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, + yid->rxqueue)); + if (yid->rxlen > 0) { + unsigned char *tmp = + y_memdup(yid->rxqueue + end + 1, yid->rxlen); + FREE(yid->rxqueue); + yid->rxqueue = tmp; + DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, + yid->rxqueue)); + } else { + DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue)); + FREE(yid->rxqueue); + } + } while (!yab && end < yid->rxlen - 1); return yab; } -static char * yahoo_getwebcam_master(struct yahoo_input_data *yid) +static char *yahoo_getwebcam_master(struct yahoo_input_data *yid) { - unsigned int pos=0; - unsigned int len=0; - unsigned int status=0; - char *server=NULL; + unsigned int pos = 0; + unsigned int len = 0; + unsigned int status = 0; + char *server = NULL; struct yahoo_data *yd = yid->yd; - if(!yid || !yd) + if (!yid || !yd) return NULL; DEBUG_MSG(("rxlen is %d", yid->rxlen)); @@ -3222,14 +2810,11 @@ static char * yahoo_getwebcam_master(struct yahoo_input_data *yid) /* extract status (0 = ok, 6 = webcam not online) */ status = yid->rxqueue[pos++]; - if (status == 0) - { - pos += 2; /* skip next 2 bytes */ - server = y_memdup(yid->rxqueue+pos, 16); + if (status == 0) { + pos += 2; /* skip next 2 bytes */ + server = y_memdup(yid->rxqueue + pos, 16); pos += 16; - } - else if (status == 6) - { + } else if (status == 6) { YAHOO_CALLBACK(ext_yahoo_webcam_closed) (yd->client_id, yid->wcm->user, 4); } @@ -3238,11 +2823,12 @@ static char * yahoo_getwebcam_master(struct yahoo_input_data *yid) yid->rxlen -= len; DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); - if (yid->rxlen>0) { + if (yid->rxlen > 0) { unsigned char *tmp = y_memdup(yid->rxqueue + pos, yid->rxlen); FREE(yid->rxqueue); yid->rxqueue = tmp; - DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); + DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, + yid->rxqueue)); } else { DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue)); FREE(yid->rxqueue); @@ -3253,35 +2839,33 @@ static char * yahoo_getwebcam_master(struct yahoo_input_data *yid) static int yahoo_get_webcam_data(struct yahoo_input_data *yid) { - unsigned char reason=0; - unsigned int pos=0; - unsigned int begin=0; - unsigned int end=0; - unsigned int closed=0; - unsigned char header_len=0; + unsigned char reason = 0; + unsigned int pos = 0; + unsigned int begin = 0; + unsigned int end = 0; + unsigned int closed = 0; + unsigned char header_len = 0; char *who; - int connect=0; + int connect = 0; struct yahoo_data *yd = yid->yd; - if(!yd) + if (!yd) return -1; - if(!yid->wcm || !yid->wcd || !yid->rxlen) + if (!yid->wcm || !yid->wcd || !yid->rxlen) return -1; DEBUG_MSG(("rxlen is %d", yid->rxlen)); /* if we are not reading part of image then read header */ - if (!yid->wcd->to_read) - { - header_len=yid->rxqueue[pos++]; - yid->wcd->packet_type=0; + if (!yid->wcd->to_read) { + header_len = yid->rxqueue[pos++]; + yid->wcd->packet_type = 0; if (yid->rxlen < header_len) return 0; - if (header_len >= 8) - { + if (header_len >= 8) { reason = yid->rxqueue[pos++]; /* next 2 bytes should always be 05 00 */ pos += 2; @@ -3289,8 +2873,7 @@ static int yahoo_get_webcam_data(struct yahoo_input_data *yid) pos += 4; yid->wcd->to_read = yid->wcd->data_size; } - if (header_len >= 13) - { + if (header_len >= 13) { yid->wcd->packet_type = yid->rxqueue[pos++]; yid->wcd->timestamp = yahoo_get32(yid->rxqueue + pos); pos += 4; @@ -3302,7 +2885,8 @@ static int yahoo_get_webcam_data(struct yahoo_input_data *yid) begin = pos; pos += yid->wcd->to_read; - if (pos > yid->rxlen) pos = yid->rxlen; + if (pos > yid->rxlen) + pos = yid->rxlen; /* if it is not an image then make sure we have the whole packet */ if (yid->wcd->packet_type != 0x02) { @@ -3315,93 +2899,95 @@ static int yahoo_get_webcam_data(struct yahoo_input_data *yid) } DEBUG_MSG(("packet type %.2X, data length %d", yid->wcd->packet_type, - yid->wcd->data_size)); + yid->wcd->data_size)); /* find out what kind of packet we got */ - switch (yid->wcd->packet_type) - { - case 0x00: - /* user requests to view webcam (uploading) */ - if (yid->wcd->data_size && - yid->wcm->direction == YAHOO_WEBCAM_UPLOAD) { - end = begin; - while (end <= yid->rxlen && - yid->rxqueue[end++] != 13); - if (end > begin) - { - who = y_memdup(yid->rxqueue + begin, end - begin); - who[end - begin - 1] = 0; - YAHOO_CALLBACK(ext_yahoo_webcam_viewer)(yd->client_id, who + 2, 2); - FREE(who); - } + switch (yid->wcd->packet_type) { + case 0x00: + /* user requests to view webcam (uploading) */ + if (yid->wcd->data_size && + yid->wcm->direction == YAHOO_WEBCAM_UPLOAD) { + end = begin; + while (end <= yid->rxlen && yid->rxqueue[end++] != 13) ; + if (end > begin) { + who = y_memdup(yid->rxqueue + begin, + end - begin); + who[end - begin - 1] = 0; + YAHOO_CALLBACK(ext_yahoo_webcam_viewer) (yd-> + client_id, who + 2, 2); + FREE(who); } + } - if (yid->wcm->direction == YAHOO_WEBCAM_DOWNLOAD) { - /* timestamp/status field */ - /* 0 = declined viewing permission */ - /* 1 = accepted viewing permission */ - if (yid->wcd->timestamp == 0) { - YAHOO_CALLBACK(ext_yahoo_webcam_closed)(yd->client_id, yid->wcm->user, 3); - } - } - break; - case 0x01: /* status packets?? */ - /* timestamp contains status info */ - /* 00 00 00 01 = we have data?? */ - break; - case 0x02: /* image data */ - YAHOO_CALLBACK(ext_yahoo_got_webcam_image)(yd->client_id, - yid->wcm->user, yid->rxqueue + begin, - yid->wcd->data_size, pos - begin, - yid->wcd->timestamp); - break; - case 0x05: /* response packets when uploading */ - if (!yid->wcd->data_size) { - YAHOO_CALLBACK(ext_yahoo_webcam_data_request)(yd->client_id, yid->wcd->timestamp); + if (yid->wcm->direction == YAHOO_WEBCAM_DOWNLOAD) { + /* timestamp/status field */ + /* 0 = declined viewing permission */ + /* 1 = accepted viewing permission */ + if (yid->wcd->timestamp == 0) { + YAHOO_CALLBACK(ext_yahoo_webcam_closed) (yd-> + client_id, yid->wcm->user, 3); } + } + break; + case 0x01: /* status packets?? */ + /* timestamp contains status info */ + /* 00 00 00 01 = we have data?? */ + break; + case 0x02: /* image data */ + YAHOO_CALLBACK(ext_yahoo_got_webcam_image) (yd->client_id, + yid->wcm->user, yid->rxqueue + begin, + yid->wcd->data_size, pos - begin, yid->wcd->timestamp); + break; + case 0x05: /* response packets when uploading */ + if (!yid->wcd->data_size) { + YAHOO_CALLBACK(ext_yahoo_webcam_data_request) (yd-> + client_id, yid->wcd->timestamp); + } + break; + case 0x07: /* connection is closing */ + switch (reason) { + case 0x01: /* user closed connection */ + closed = 1; break; - case 0x07: /* connection is closing */ - switch(reason) - { - case 0x01: /* user closed connection */ - closed = 1; - break; - case 0x0F: /* user cancelled permission */ - closed = 2; - break; - } - YAHOO_CALLBACK(ext_yahoo_webcam_closed)(yd->client_id, yid->wcm->user, closed); - break; - case 0x0C: /* user connected */ - case 0x0D: /* user disconnected */ - if (yid->wcd->data_size) { - who = y_memdup(yid->rxqueue + begin, pos - begin + 1); - who[pos - begin] = 0; - if (yid->wcd->packet_type == 0x0C) - connect=1; - else - connect=0; - YAHOO_CALLBACK(ext_yahoo_webcam_viewer)(yd->client_id, who, connect); - FREE(who); - } - break; - case 0x13: /* user data */ - /* i=user_ip (ip of the user we are viewing) */ - /* j=user_ext_ip (external ip of the user we */ - /* are viewing) */ - break; - case 0x17: /* ?? */ + case 0x0F: /* user cancelled permission */ + closed = 2; break; + } + YAHOO_CALLBACK(ext_yahoo_webcam_closed) (yd->client_id, + yid->wcm->user, closed); + break; + case 0x0C: /* user connected */ + case 0x0D: /* user disconnected */ + if (yid->wcd->data_size) { + who = y_memdup(yid->rxqueue + begin, pos - begin + 1); + who[pos - begin] = 0; + if (yid->wcd->packet_type == 0x0C) + connect = 1; + else + connect = 0; + YAHOO_CALLBACK(ext_yahoo_webcam_viewer) (yd->client_id, + who, connect); + FREE(who); + } + break; + case 0x13: /* user data */ + /* i=user_ip (ip of the user we are viewing) */ + /* j=user_ext_ip (external ip of the user we */ + /* are viewing) */ + break; + case 0x17: /* ?? */ + break; } yid->wcd->to_read -= pos - begin; yid->rxlen -= pos; DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); - if (yid->rxlen>0) { + if (yid->rxlen > 0) { unsigned char *tmp = y_memdup(yid->rxqueue + pos, yid->rxlen); FREE(yid->rxqueue); yid->rxqueue = tmp; - DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); + DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, + yid->rxqueue)); } else { DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue)); FREE(yid->rxqueue); @@ -3414,60 +3000,64 @@ static int yahoo_get_webcam_data(struct yahoo_input_data *yid) return 0; } -int yahoo_write_ready(int id, int fd, void *data) +int yahoo_write_ready(int id, void *fd, void *data) { struct yahoo_input_data *yid = data; int len; struct data_queue *tx; - LOG(("write callback: id=%d fd=%d data=%p", id, fd, data)); - if(!yid || !yid->txqueues || !find_conn_by_id(id)) + LOG(("write callback: id=%d fd=%p data=%p", id, fd, data)); + if (!yid || !yid->txqueues) return -2; - + tx = yid->txqueues->data; LOG(("writing %d bytes", tx->len)); len = yahoo_send_data(fd, tx->queue, MIN(1024, tx->len)); - if(len == -1 && errno == EAGAIN) + if (len == -1 && errno == EAGAIN) return 1; - if(len <= 0) { + if (len <= 0) { int e = errno; DEBUG_MSG(("len == %d (<= 0)", len)); - while(yid->txqueues) { - YList *l=yid->txqueues; + while (yid->txqueues) { + YList *l = yid->txqueues; tx = l->data; free(tx->queue); free(tx); - yid->txqueues = y_list_remove_link(yid->txqueues, yid->txqueues); + yid->txqueues = + y_list_remove_link(yid->txqueues, + yid->txqueues); y_list_free_1(l); } - LOG(("yahoo_write_ready(%d, %d) len < 0", id, fd)); - YAHOO_CALLBACK(ext_yahoo_remove_handler)(id, yid->write_tag); + LOG(("yahoo_write_ready(%d, %p) len < 0", id, fd)); + YAHOO_CALLBACK(ext_yahoo_remove_handler) (id, yid->write_tag); yid->write_tag = 0; - errno=e; + errno = e; return 0; } tx->len -= len; - if(tx->len > 0) { + if (tx->len > 0) { unsigned char *tmp = y_memdup(tx->queue + len, tx->len); FREE(tx->queue); tx->queue = tmp; } else { - YList *l=yid->txqueues; + YList *l = yid->txqueues; free(tx->queue); free(tx); - yid->txqueues = y_list_remove_link(yid->txqueues, yid->txqueues); + yid->txqueues = + y_list_remove_link(yid->txqueues, yid->txqueues); y_list_free_1(l); /* - if(!yid->txqueues) - LOG(("yahoo_write_ready(%d, %d) !yxqueues", id, fd)); - */ - if(!yid->txqueues) { - LOG(("yahoo_write_ready(%d, %d) !yxqueues", id, fd)); - YAHOO_CALLBACK(ext_yahoo_remove_handler)(id, yid->write_tag); + if(!yid->txqueues) + LOG(("yahoo_write_ready(%d, %d) !yxqueues", id, fd)); + */ + if (!yid->txqueues) { + LOG(("yahoo_write_ready(%d, %p) !txqueues", id, fd)); + YAHOO_CALLBACK(ext_yahoo_remove_handler) (id, + yid->write_tag); yid->write_tag = 0; } } @@ -3475,17 +3065,18 @@ int yahoo_write_ready(int id, int fd, void *data) return 1; } -static void yahoo_process_pager_connection(struct yahoo_input_data *yid, int over) +static void yahoo_process_pager_connection(struct yahoo_input_data *yid, + int over) { struct yahoo_packet *pkt; struct yahoo_data *yd = yid->yd; int id = yd->client_id; - if(over) + if (over) return; - while (find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER) - && (pkt = yahoo_getdata(yid)) != NULL) { + while (find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER) + && (pkt = yahoo_getdata(yid)) != NULL) { yahoo_packet_process(yid, pkt); @@ -3493,17 +3084,15 @@ static void yahoo_process_pager_connection(struct yahoo_input_data *yid, int ove } } -static void yahoo_process_ft_connection(struct yahoo_input_data *yid, int over) -{ -} - -static void yahoo_process_chatcat_connection(struct yahoo_input_data *yid, int over) +static void yahoo_process_chatcat_connection(struct yahoo_input_data *yid, + int over) { - if(over) + if (over) return; - if (strstr((char*)yid->rxqueue+(yid->rxlen-20), "</content>")) { - YAHOO_CALLBACK(ext_yahoo_chat_cat_xml)(yid->yd->client_id, (char*)yid->rxqueue); + if (strstr((char *)yid->rxqueue + (yid->rxlen - 20), "</content>")) { + YAHOO_CALLBACK(ext_yahoo_chat_cat_xml) (yid->yd->client_id, + (char *)yid->rxqueue); } } @@ -3512,111 +3101,143 @@ static void yahoo_process_yab_connection(struct yahoo_input_data *yid, int over) struct yahoo_data *yd = yid->yd; struct yab *yab; YList *buds; - int changed=0; + int changed = 0; int id = yd->client_id; + int yab_used = 0; - if(over) + 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) + while (find_input_by_id_and_type(id, YAHOO_CONNECTION_YAB) + && (yab = yahoo_getyab(yid)) != NULL) { + if (!yab->id) continue; - changed=1; - for(buds = yd->buddies; buds; buds=buds->next) { - struct yahoo_buddy * bud = buds->data; - if(!strcmp(bud->id, yab->id)) { + + changed = 1; + yab_used = 0; + for (buds = yd->buddies; buds; buds = buds->next) { + struct yahoo_buddy *bud = buds->data; + if (!strcmp(bud->id, yab->id)) { + yab_used = 1; bud->yab_entry = yab; - if(yab->nname) { + if (yab->nname) { bud->real_name = strdup(yab->nname); - } else if(yab->fname && yab->lname) { - bud->real_name = y_new0(char, - strlen(yab->fname)+ - strlen(yab->lname)+2 - ); + } else if (yab->fname && yab->lname) { + bud->real_name = y_new0(char, + strlen(yab->fname) + + strlen(yab->lname) + 2); sprintf(bud->real_name, "%s %s", - yab->fname, yab->lname); - } else if(yab->fname) { + yab->fname, yab->lname); + } else if (yab->fname) { bud->real_name = strdup(yab->fname); } - break; /* for */ + break; /* for */ } } + + if (!yab_used) { + FREE(yab->fname); + FREE(yab->lname); + FREE(yab->nname); + FREE(yab->id); + FREE(yab->email); + FREE(yab->hphone); + FREE(yab->wphone); + FREE(yab->mphone); + FREE(yab); + } + } - if(changed) - YAHOO_CALLBACK(ext_yahoo_got_buddies)(yd->client_id, yd->buddies); + if (changed) + YAHOO_CALLBACK(ext_yahoo_got_buddies) (yd->client_id, + yd->buddies); } -static void yahoo_process_search_connection(struct yahoo_input_data *yid, int over) +static void yahoo_process_search_connection(struct yahoo_input_data *yid, + int over) { - struct yahoo_found_contact *yct=NULL; + struct yahoo_found_contact *yct = NULL; char *p = (char *)yid->rxqueue, *np, *cp; int k, n; - int start=0, found=0, total=0; - YList *contacts=NULL; - struct yahoo_input_data *pyid = find_input_by_id_and_type(yid->yd->client_id, YAHOO_CONNECTION_PAGER); + int start = 0, found = 0, total = 0; + YList *contacts = NULL; + struct yahoo_input_data *pyid = + find_input_by_id_and_type(yid->yd->client_id, + YAHOO_CONNECTION_PAGER); - if(!over || !pyid) + if (!over || !pyid) return; - if(p && (p=strstr(p, "\r\n\r\n"))) { + if (p && (p = strstr(p, "\r\n\r\n"))) { p += 4; - for(k = 0; (p = strchr(p, 4)) && (k < 4); k++) { + for (k = 0; (p = strchr(p, 4)) && (k < 4); k++) { p++; n = atoi(p); - switch(k) { - case 0: found = pyid->ys->lsearch_nfound = n; break; - case 2: start = pyid->ys->lsearch_nstart = n; break; - case 3: total = pyid->ys->lsearch_ntotal = n; break; + switch (k) { + case 0: + found = pyid->ys->lsearch_nfound = n; + break; + case 2: + start = pyid->ys->lsearch_nstart = n; + break; + case 3: + total = pyid->ys->lsearch_ntotal = n; + break; } } - if(p) + if (p) p++; - k=0; - while(p && *p) { + k = 0; + while (p && *p) { cp = p; np = strchr(p, 4); - if(!np) + if (!np) break; *np = 0; - p = np+1; - - switch(k++) { - case 1: - if(strlen(cp) > 2 && y_list_length(contacts) < total) { - yct = y_new0(struct yahoo_found_contact, 1); - contacts = y_list_append(contacts, yct); - yct->id = cp+2; - } else { - *p = 0; - } - break; - case 2: - yct->online = !strcmp(cp, "2") ? 1 : 0; - break; - case 3: - yct->gender = cp; - break; - case 4: - yct->age = atoi(cp); - break; - case 5: - if(strcmp(cp, "5") != 0) - yct->location = cp; - k = 0; - break; + p = np + 1; + + switch (k++) { + case 1: + if (strlen(cp) > 2 + && y_list_length(contacts) < total) { + yct = y_new0(struct yahoo_found_contact, + 1); + contacts = y_list_append(contacts, yct); + yct->id = cp + 2; + } else { + *p = 0; + } + break; + case 2: + yct->online = !strcmp(cp, "2") ? 1 : 0; + break; + case 3: + yct->gender = cp; + break; + case 4: + yct->age = atoi(cp); + break; + case 5: + /* not worth the context switch for strcmp */ + if (cp[0] != '\005' || cp[1] != '\000') + yct->location = cp; + k = 0; + break; } } } - YAHOO_CALLBACK(ext_yahoo_got_search_result)(yid->yd->client_id, found, start, total, contacts); + YAHOO_CALLBACK(ext_yahoo_got_search_result) (yid->yd->client_id, found, + start, total, contacts); - while(contacts) { + while (contacts) { YList *node = contacts; contacts = y_list_remove_link(contacts, node); free(node->data); @@ -3624,20 +3245,20 @@ static void yahoo_process_search_connection(struct yahoo_input_data *yid, int ov } } -static void _yahoo_webcam_connected(int fd, int error, void *d) +static void _yahoo_webcam_connected(void *fd, int error, void *d) { struct yahoo_input_data *yid = d; struct yahoo_webcam *wcm = yid->wcm; struct yahoo_data *yd = yid->yd; char conn_type[100]; - char *data=NULL; - char *packet=NULL; - unsigned char magic_nr[] = {1, 0, 0, 0, 1}; - unsigned header_len=0; - unsigned int len=0; - unsigned int pos=0; - - if(error || fd <= 0) { + char *data = NULL; + char *packet = NULL; + unsigned char magic_nr[] = { 1, 0, 0, 0, 1 }; + unsigned header_len = 0; + unsigned int len = 0; + unsigned int pos = 0; + + if (error || !fd) { FREE(yid); return; } @@ -3647,74 +3268,70 @@ static void _yahoo_webcam_connected(int fd, int error, void *d) LOG(("Connected")); /* send initial packet */ - switch (wcm->direction) - { - case YAHOO_WEBCAM_DOWNLOAD: - data = strdup("<REQIMG>"); - break; - case YAHOO_WEBCAM_UPLOAD: - data = strdup("<SNDIMG>"); - break; - default: - return; + switch (wcm->direction) { + case YAHOO_WEBCAM_DOWNLOAD: + data = strdup("<REQIMG>"); + break; + case YAHOO_WEBCAM_UPLOAD: + data = strdup("<SNDIMG>"); + break; + default: + return; } yahoo_add_to_send_queue(yid, data, strlen(data)); FREE(data); /* send data */ - switch (wcm->direction) - { - case YAHOO_WEBCAM_DOWNLOAD: - header_len = 8; - data = strdup("a=2\r\nc=us\r\ne=21\r\nu="); - data = y_string_append(data, yd->user); - data = y_string_append(data, "\r\nt="); - data = y_string_append(data, wcm->key); - data = y_string_append(data, "\r\ni="); - data = y_string_append(data, wcm->my_ip); - data = y_string_append(data, "\r\ng="); - data = y_string_append(data, wcm->user); - data = y_string_append(data, "\r\no=w-2-5-1\r\np="); - snprintf(conn_type, sizeof(conn_type), "%d", wcm->conn_type); - data = y_string_append(data, conn_type); - data = y_string_append(data, "\r\n"); - break; - case YAHOO_WEBCAM_UPLOAD: - header_len = 13; - data = strdup("a=2\r\nc=us\r\nu="); - data = y_string_append(data, yd->user); - data = y_string_append(data, "\r\nt="); - data = y_string_append(data, wcm->key); - data = y_string_append(data, "\r\ni="); - data = y_string_append(data, wcm->my_ip); - data = y_string_append(data, "\r\no=w-2-5-1\r\np="); - snprintf(conn_type, sizeof(conn_type), "%d", wcm->conn_type); - data = y_string_append(data, conn_type); - data = y_string_append(data, "\r\nb="); - data = y_string_append(data, wcm->description); - data = y_string_append(data, "\r\n"); - break; + switch (wcm->direction) { + case YAHOO_WEBCAM_DOWNLOAD: + header_len = 8; + data = strdup("a=2\r\nc=us\r\ne=21\r\nu="); + data = y_string_append(data, yd->user); + data = y_string_append(data, "\r\nt="); + data = y_string_append(data, wcm->key); + data = y_string_append(data, "\r\ni="); + data = y_string_append(data, wcm->my_ip); + data = y_string_append(data, "\r\ng="); + data = y_string_append(data, wcm->user); + data = y_string_append(data, "\r\no=w-2-5-1\r\np="); + snprintf(conn_type, sizeof(conn_type), "%d", wcm->conn_type); + data = y_string_append(data, conn_type); + data = y_string_append(data, "\r\n"); + break; + case YAHOO_WEBCAM_UPLOAD: + header_len = 13; + data = strdup("a=2\r\nc=us\r\nu="); + data = y_string_append(data, yd->user); + data = y_string_append(data, "\r\nt="); + data = y_string_append(data, wcm->key); + data = y_string_append(data, "\r\ni="); + data = y_string_append(data, wcm->my_ip); + data = y_string_append(data, "\r\no=w-2-5-1\r\np="); + snprintf(conn_type, sizeof(conn_type), "%d", wcm->conn_type); + data = y_string_append(data, conn_type); + data = y_string_append(data, "\r\nb="); + data = y_string_append(data, wcm->description); + data = y_string_append(data, "\r\n"); + break; } len = strlen(data); packet = y_new0(char, header_len + len); packet[pos++] = header_len; packet[pos++] = 0; - switch (wcm->direction) - { - case YAHOO_WEBCAM_DOWNLOAD: - packet[pos++] = 1; - packet[pos++] = 0; - break; - case YAHOO_WEBCAM_UPLOAD: - packet[pos++] = 5; - packet[pos++] = 0; - break; + switch (wcm->direction) { + case YAHOO_WEBCAM_DOWNLOAD: + packet[pos++] = 1; + packet[pos++] = 0; + break; + case YAHOO_WEBCAM_UPLOAD: + packet[pos++] = 5; + packet[pos++] = 0; + break; } pos += yahoo_put32(packet + pos, len); - if (wcm->direction == YAHOO_WEBCAM_UPLOAD) - { + if (wcm->direction == YAHOO_WEBCAM_UPLOAD) { memcpy(packet + pos, magic_nr, sizeof(magic_nr)); pos += sizeof(magic_nr); } @@ -3723,7 +3340,9 @@ static void _yahoo_webcam_connected(int fd, int error, void *d) FREE(packet); FREE(data); - yid->read_tag=YAHOO_CALLBACK(ext_yahoo_add_handler)(yid->yd->client_id, yid->fd, YAHOO_INPUT_READ, yid); + yid->read_tag = + YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, + yid->fd, YAHOO_INPUT_READ, yid); } static void yahoo_webcam_connect(struct yahoo_input_data *y) @@ -3748,23 +3367,23 @@ static void yahoo_webcam_connect(struct yahoo_input_data *y) yid->wcd = y_new0(struct yahoo_webcam_data, 1); LOG(("Connecting to: %s:%d", wcm->server, wcm->port)); - YAHOO_CALLBACK(ext_yahoo_connect_async)(y->yd->client_id, wcm->server, wcm->port, - _yahoo_webcam_connected, yid); + YAHOO_CALLBACK(ext_yahoo_connect_async) (y->yd->client_id, wcm->server, + wcm->port, _yahoo_webcam_connected, yid, 0); } -static void yahoo_process_webcam_master_connection(struct yahoo_input_data *yid, int over) +static void yahoo_process_webcam_master_connection(struct yahoo_input_data *yid, + int over) { - char* server; + char *server; struct yahoo_server_settings *yss; - if(over) + if (over) return; server = yahoo_getwebcam_master(yid); - if (server) - { + if (server) { yss = yid->yd->server_settings; yid->wcm->server = strdup(server); yid->wcm->port = yss->webcam_port; @@ -3777,72 +3396,74 @@ static void yahoo_process_webcam_master_connection(struct yahoo_input_data *yid, } } -static void yahoo_process_webcam_connection(struct yahoo_input_data *yid, int over) +static void yahoo_process_webcam_connection(struct yahoo_input_data *yid, + int over) { int id = yid->yd->client_id; - int fd = yid->fd; + void *fd = yid->fd; - if(over) + if (over) return; /* as long as we still have packets available keep processing them */ - while (find_input_by_id_and_fd(id, fd) - && yahoo_get_webcam_data(yid) == 1); -} - -static void (*yahoo_process_connection[])(struct yahoo_input_data *, int over) = { - yahoo_process_pager_connection, - yahoo_process_ft_connection, - yahoo_process_yab_connection, - yahoo_process_webcam_master_connection, - yahoo_process_webcam_connection, - yahoo_process_chatcat_connection, - yahoo_process_search_connection, -}; + while (find_input_by_id_and_fd(id, fd) + && yahoo_get_webcam_data(yid) == 1) ; +} + +static void (*yahoo_process_connection[]) (struct yahoo_input_data *, + int over) = { +yahoo_process_pager_connection, yahoo_process_ft_connection, + yahoo_process_yab_connection, + yahoo_process_webcam_master_connection, + yahoo_process_webcam_connection, + yahoo_process_chatcat_connection, + yahoo_process_search_connection}; -int yahoo_read_ready(int id, int fd, void *data) +int yahoo_read_ready(int id, void *fd, void *data) { struct yahoo_input_data *yid = data; char buf[1024]; int len; - LOG(("read callback: id=%d fd=%d data=%p", id, fd, data)); - if(!yid) + LOG(("read callback: id=%d fd=%p data=%p", id, fd, data)); + if (!yid) return -2; - do { - len = read(fd, buf, sizeof(buf)); - } while(len == -1 && errno == EINTR); + len = YAHOO_CALLBACK(ext_yahoo_read) (fd, buf, sizeof(buf)); + } while (len == -1 && errno == EINTR); - if(len == -1 && (errno == EAGAIN||errno == EINTR)) /* we'll try again later */ + if (len == -1 && (errno == EAGAIN || errno == EINTR)) /* we'll try again later */ return 1; if (len <= 0) { int e = errno; DEBUG_MSG(("len == %d (<= 0)", len)); - if(yid->type == YAHOO_CONNECTION_PAGER) { - YAHOO_CALLBACK(ext_yahoo_error)(yid->yd->client_id, "Connection closed by server", 1, E_CONNECTION); + if (yid->type == YAHOO_CONNECTION_PAGER) { + YAHOO_CALLBACK(ext_yahoo_login_response) (yid->yd-> + client_id, YAHOO_LOGIN_SOCK, NULL); } - yahoo_process_connection[yid->type](yid, 1); + yahoo_process_connection[yid->type] (yid, 1); yahoo_input_close(yid); /* no need to return an error, because we've already fixed it */ - if(len == 0) + if (len == 0) return 1; - errno=e; + errno = e; LOG(("read error: %s", strerror(errno))); return -1; } - yid->rxqueue = y_renew(unsigned char, yid->rxqueue, len + yid->rxlen); + yid->rxqueue = + y_renew(unsigned char, yid->rxqueue, len + yid->rxlen + 1); memcpy(yid->rxqueue + yid->rxlen, buf, len); yid->rxlen += len; + yid->rxqueue[yid->rxlen] = 0; - yahoo_process_connection[yid->type](yid, 0); + yahoo_process_connection[yid->type] (yid, 0); return len; } @@ -3854,7 +3475,7 @@ int yahoo_init_with_attributes(const char *username, const char *password, ...) yd = y_new0(struct yahoo_data, 1); - if(!yd) + if (!yd) return 0; yd->user = strdup(username); @@ -3879,13 +3500,7 @@ int yahoo_init(const char *username, const char *password) return yahoo_init_with_attributes(username, password, NULL); } -struct connect_callback_data { - struct yahoo_data *yd; - int tag; - int i; -}; - -static void yahoo_connected(int fd, int error, void *data) +static void yahoo_connected(void *fd, int error, void *data) { struct connect_callback_data *ccd = data; struct yahoo_data *yd = ccd->yd; @@ -3893,114 +3508,112 @@ static void yahoo_connected(int fd, int error, void *data) struct yahoo_input_data *yid; struct yahoo_server_settings *yss = yd->server_settings; - if(error) { - if(fallback_ports[ccd->i]) { - int tag; - yss->pager_port = fallback_ports[ccd->i++]; - tag = YAHOO_CALLBACK(ext_yahoo_connect_async)(yd->client_id, yss->pager_host, - yss->pager_port, yahoo_connected, ccd); + if (error) { + int tag; + if (fallback_ports[ccd->i]) { + char *host = yss->pager_host; - if(tag > 0) - ccd->tag=tag; + 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); + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, + YAHOO_LOGIN_SOCK, NULL); } return; } FREE(ccd); - /* fd < 0 && error == 0 means connect was cancelled */ - if(fd < 0) + /* fd == NULL && error == 0 means connect was cancelled */ + if (!fd) return; - pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YPACKET_STATUS_DEFAULT, + yd->session_id); NOTICE(("Sending initial packet")); yahoo_packet_hash(pkt, 1, yd->user); - yid = y_new0(struct yahoo_input_data, 1); - yid->yd = yd; + yid = find_input_by_id_and_type(yd->client_id, YAHOO_CONNECTION_PAGER); yid->fd = fd; - inputs = y_list_prepend(inputs, yid); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); - yid->read_tag=YAHOO_CALLBACK(ext_yahoo_add_handler)(yid->yd->client_id, yid->fd, YAHOO_INPUT_READ, yid); + yid->read_tag = + YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, + yid->fd, YAHOO_INPUT_READ, yid); } -void yahoo_login(int id, int initial) +void *yahoo_get_fd(int id) { - struct yahoo_data *yd = find_conn_by_id(id); - struct connect_callback_data *ccd; - struct yahoo_server_settings *yss; - int tag; - - if(!yd) - return; - - yss = yd->server_settings; - - yd->initial_status = initial; - - ccd = y_new0(struct connect_callback_data, 1); - ccd->yd = yd; - tag = YAHOO_CALLBACK(ext_yahoo_connect_async)(yd->client_id, yss->pager_host, yss->pager_port, - yahoo_connected, ccd); - - /* - * if tag <= 0, then callback has already been called - * so ccd will have been freed - */ - if(tag > 0) - ccd->tag = tag; - else if(tag < 0) - YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, YAHOO_LOGIN_SOCK, NULL); -} - - -int yahoo_get_fd(int id) -{ - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); - if(!yid) + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + if (!yid) return 0; else return yid->fd; } -void yahoo_send_im(int id, const char *from, const char *who, const char *what, int utf8, int picture) +void yahoo_send_buzz(int id, const char *from, const char *who) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + yahoo_send_im(id, from, who, "<ding>", 1, 0); +} + +void yahoo_send_im(int id, const char *from, const char *who, const char *what, + int utf8, int picture) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_packet *pkt = NULL; struct yahoo_data *yd; char pic_str[10]; - if(!yid) + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE, YAHOO_STATUS_OFFLINE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE, YAHOO_STATUS_OFFLINE, + yd->session_id); snprintf(pic_str, sizeof(pic_str), "%d", picture); - - if(from && strcmp(from, yd->user)) + + if (from && strcmp(from, yd->user)) yahoo_packet_hash(pkt, 0, yd->user); - yahoo_packet_hash(pkt, 1, from?from:yd->user); + yahoo_packet_hash(pkt, 1, from ? from : yd->user); yahoo_packet_hash(pkt, 5, who); yahoo_packet_hash(pkt, 14, what); - if(utf8) + if (utf8) yahoo_packet_hash(pkt, 97, "1"); yahoo_packet_hash(pkt, 63, ";0"); /* imvironment name; or ;0 */ yahoo_packet_hash(pkt, 64, "0"); yahoo_packet_hash(pkt, 206, pic_str); - yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); @@ -4008,17 +3621,19 @@ void yahoo_send_im(int id, const char *from, const char *who, const char *what, void yahoo_send_typing(int id, const char *from, const char *who, int typ) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; - if(!yid) + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YAHOO_STATUS_NOTIFY, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YPACKET_STATUS_NOTIFY, + yd->session_id); yahoo_packet_hash(pkt, 5, who); - yahoo_packet_hash(pkt, 1, from?from:yd->user); + yahoo_packet_hash(pkt, 1, from ? from : yd->user); yahoo_packet_hash(pkt, 14, " "); yahoo_packet_hash(pkt, 13, typ ? "1" : "0"); yahoo_packet_hash(pkt, 49, "TYPING"); @@ -4030,22 +3645,25 @@ void yahoo_send_typing(int id, const char *from, const char *who, int typ) void yahoo_set_away(int id, enum yahoo_status state, const char *msg, int away) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; int old_status; char s[4]; - if(!yid) + if (!yid) return; yd = yid->yd; + old_status = yd->current_status; yd->current_status = state; /* Thank you libpurple :) */ if (yd->current_status == YAHOO_STATUS_INVISIBLE) { - pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBILITY, YAHOO_STATUS_AVAILABLE, 0); + pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBLE_TOGGLE, + YAHOO_STATUS_AVAILABLE, 0); yahoo_packet_hash(pkt, 13, "2"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); @@ -4053,7 +3671,8 @@ void yahoo_set_away(int id, enum yahoo_status state, const char *msg, int away) return; } - pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_STATUS_UPDATE, yd->current_status, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_STATUS_UPDATE, + yd->current_status, yd->session_id); snprintf(s, sizeof(s), "%d", yd->current_status); yahoo_packet_hash(pkt, 10, s); yahoo_packet_hash(pkt, 19, msg && state == YAHOO_STATUS_CUSTOM ? msg : ""); @@ -4061,8 +3680,9 @@ void yahoo_set_away(int id, enum yahoo_status state, const char *msg, int away) yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); - if(old_status == YAHOO_STATUS_INVISIBLE) { - pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBILITY, YAHOO_STATUS_AVAILABLE, 0); + if (old_status == YAHOO_STATUS_INVISIBLE) { + pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBLE_TOGGLE, + YAHOO_STATUS_AVAILABLE, 0); yahoo_packet_hash(pkt, 13, "1"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); @@ -4071,21 +3691,23 @@ void yahoo_set_away(int id, enum yahoo_status state, const char *msg, int away) void yahoo_logoff(int id) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; - if(!yid) + if (!yid) return; yd = yid->yd; LOG(("yahoo_logoff: current status: %d", yd->current_status)); - if(yd->current_status != -1 && 0) { + if (yd->current_status != -1 && 0) { /* Meh. Don't send this. The event handlers are not going to get to do this so it'll just leak memory. And the TCP connection reset will hopefully be clear enough. */ - pkt = yahoo_packet_new(YAHOO_SERVICE_LOGOFF, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_LOGOFF, + YPACKET_STATUS_DEFAULT, yd->session_id); yd->current_status = -1; if (pkt) { @@ -4094,22 +3716,25 @@ void yahoo_logoff(int id) } } - do { +/* do { yahoo_input_close(yid); - } while((yid = find_input_by_id(id))); + } while((yid = find_input_by_id(id)));*/ + } void yahoo_get_list(int id) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; - if(!yid) + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_LIST, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_LIST, YPACKET_STATUS_DEFAULT, + yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); if (pkt) { yahoo_send_packet(yid, pkt, 0); @@ -4117,139 +3742,136 @@ void yahoo_get_list(int id) } } -static void _yahoo_http_connected(int id, int fd, int error, void *data) +static void _yahoo_http_connected(int id, void *fd, int error, void *data) { struct yahoo_input_data *yid = data; - if(fd <= 0) { + if (fd == NULL || error) { inputs = y_list_remove(inputs, yid); FREE(yid); return; } yid->fd = fd; - yid->read_tag=YAHOO_CALLBACK(ext_yahoo_add_handler)(yid->yd->client_id, fd, YAHOO_INPUT_READ, yid); + yid->read_tag = + YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd, + YAHOO_INPUT_READ, yid); } +/* FIXME Get address book from address.yahoo.com instead */ void yahoo_get_yab(int id) { struct yahoo_data *yd = find_conn_by_id(id); struct yahoo_input_data *yid; char url[1024]; - char buff[1024]; + char buff[2048]; - if(!yd) + if (!yd) return; yid = y_new0(struct yahoo_input_data, 1); yid->yd = yd; yid->type = YAHOO_CONNECTION_YAB; - snprintf(url, 1024, "http://insider.msg.yahoo.com/ycontent/?ab2=0"); + LOG(("Sending request for Address Book")); - snprintf(buff, sizeof(buff), "Y=%s; T=%s", - yd->cookie_y, yd->cookie_t); + 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, - _yahoo_http_connected, yid); + yahoo_http_get(yid->yd->client_id, url, buff, 0, 0, + _yahoo_http_connected, yid); +} + +struct yahoo_post_data { + struct yahoo_input_data *yid; + char *data; +}; + +static void _yahoo_http_post_connected(int id, void *fd, int error, void *data) +{ + struct yahoo_post_data *yad = data; + struct yahoo_input_data *yid = yad->yid; + char *buff = yad->data; + + if (!fd) { + inputs = y_list_remove(inputs, yid); + FREE(yid); + return; + } + + YAHOO_CALLBACK(ext_yahoo_write) (fd, buff, strlen(buff)); + + yid->fd = fd; + yid->read_tag = + YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd, + YAHOO_INPUT_READ, yid); + + FREE(buff); + FREE(yad); } -void yahoo_set_yab(int id, struct yab * yab) +/* FIXME This is also likely affected */ +void yahoo_set_yab(int id, struct yab *yab) { + struct yahoo_post_data *yad = y_new0(struct yahoo_post_data, 1); struct yahoo_data *yd = find_conn_by_id(id); struct yahoo_input_data *yid; char url[1024]; char buff[1024]; - char *temp; - int size = sizeof(url)-1; + char post[1024]; + int size = 0; - if(!yd) + if (!yd) return; yid = y_new0(struct yahoo_input_data, 1); yid->type = YAHOO_CONNECTION_YAB; yid->yd = yd; - strncpy(url, "http://insider.msg.yahoo.com/ycontent/?addab2=0", size); - - if(yab->dbid) { - /* change existing yab */ - char tmp[32]; - strncat(url, "&ee=1&ow=1&id=", size - strlen(url)); - snprintf(tmp, sizeof(tmp), "%d", yab->dbid); - strncat(url, tmp, size - strlen(url)); - } - - if(yab->fname) { - strncat(url, "&fn=", size - strlen(url)); - temp = yahoo_urlencode(yab->fname); - strncat(url, temp, size - strlen(url)); - free(temp); - } - if(yab->lname) { - strncat(url, "&ln=", size - strlen(url)); - temp = yahoo_urlencode(yab->lname); - strncat(url, temp, size - strlen(url)); - free(temp); - } - strncat(url, "&yid=", size - strlen(url)); - temp = yahoo_urlencode(yab->id); - strncat(url, temp, size - strlen(url)); - free(temp); - if(yab->nname) { - strncat(url, "&nn=", size - strlen(url)); - temp = yahoo_urlencode(yab->nname); - strncat(url, temp, size - strlen(url)); - free(temp); - } - if(yab->email) { - strncat(url, "&e=", size - strlen(url)); - temp = yahoo_urlencode(yab->email); - strncat(url, temp, size - strlen(url)); - free(temp); - } - if(yab->hphone) { - strncat(url, "&hp=", size - strlen(url)); - temp = yahoo_urlencode(yab->hphone); - strncat(url, temp, size - strlen(url)); - free(temp); - } - if(yab->wphone) { - strncat(url, "&wp=", size - strlen(url)); - temp = yahoo_urlencode(yab->wphone); - strncat(url, temp, size - strlen(url)); - free(temp); - } - if(yab->mphone) { - strncat(url, "&mp=", size - strlen(url)); - temp = yahoo_urlencode(yab->mphone); - strncat(url, temp, size - strlen(url)); - free(temp); - } - strncat(url, "&pp=0", size - strlen(url)); - - snprintf(buff, sizeof(buff), "Y=%s; T=%s", - yd->cookie_y, yd->cookie_t); + if(yab->yid) + size = snprintf(post, sizeof(post), "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<ab k=\"%s\" cc=\"%d\">" + "<ct id=\"%d\" e=\"1\" yi=\"%s\" nn=\"%s\" />" + "</ab>", yd->user, 9, yab->yid, /* Don't know why */ + yab->id, yab->nname?yab->nname:""); + else + size = snprintf(post, sizeof(post), "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<ab k=\"%s\" cc=\"%d\">" + "<ct a=\"1\" yi=\"%s\" nn=\"%s\" />" + "</ab>", yd->user, 1, /* Don't know why */ + yab->id, yab->nname?yab->nname:""); + + yad->yid = yid; + yad->data = strdup(post); + + strcpy(url, "http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us" + "&sync=1&tags=short&noclear=1&useutf8=1&legenc=codepage-1252"); + + snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t); inputs = y_list_prepend(inputs, yid); - yahoo_http_get(yid->yd->client_id, url, buff, - _yahoo_http_connected, yid); + yahoo_http_post(yid->yd->client_id, url, buff, size, + _yahoo_http_post_connected, yad); } -void yahoo_set_identity_status(int id, const char * identity, int active) +void yahoo_set_identity_status(int id, const char *identity, int active) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; - if(!yid) + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(active?YAHOO_SERVICE_IDACT:YAHOO_SERVICE_IDDEACT, - YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(active ? YAHOO_SERVICE_IDACT : + YAHOO_SERVICE_IDDEACT, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 3, identity); if (pkt) { yahoo_send_packet(yid, pkt, 0); @@ -4259,15 +3881,17 @@ void yahoo_set_identity_status(int id, const char * identity, int active) void yahoo_refresh(int id) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; - if(!yid) + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_USERSTAT, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_USERSTAT, YPACKET_STATUS_DEFAULT, + yd->session_id); if (pkt) { yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); @@ -4276,54 +3900,59 @@ void yahoo_refresh(int id) void yahoo_keepalive(int id) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; - struct yahoo_packet *pkt=NULL; - if(!yid) + struct yahoo_packet *pkt = NULL; + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_PING, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_PING, YPACKET_STATUS_DEFAULT, + yd->session_id); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } -void yahoo_chat_keepalive (int id) +void yahoo_chat_keepalive(int id) { - struct yahoo_input_data *yid = find_input_by_id_and_type (id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; if (!yid) - return; + return; yd = yid->yd; - pkt = yahoo_packet_new (YAHOO_SERVICE_CHATPING, YAHOO_STATUS_AVAILABLE, yd->session_id); - yahoo_send_packet (yid, pkt, 0); - yahoo_packet_free (pkt); + pkt = yahoo_packet_new(YAHOO_SERVICE_CHATPING, YPACKET_STATUS_DEFAULT, + yd->session_id); + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); } -void yahoo_add_buddy(int id, const char *who, const char *group, const char *msg) +void yahoo_add_buddy(int id, const char *who, const char *group, + const char *msg) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - if(!yid) + if (!yid) return; yd = yid->yd; if (!yd->logged_in) return; - pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YPACKET_STATUS_DEFAULT, yd->session_id); - - if (msg != NULL) /* add message/request "it's me add me" */ + pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YPACKET_STATUS_DEFAULT, + yd->session_id); + if (msg != NULL) /* add message/request "it's me add me" */ yahoo_packet_hash(pkt, 14, msg); else - yahoo_packet_hash(pkt,14,""); - + yahoo_packet_hash(pkt, 14, ""); yahoo_packet_hash(pkt, 65, group); yahoo_packet_hash(pkt, 97, "1"); yahoo_packet_hash(pkt, 1, yd->user); @@ -4333,23 +3962,23 @@ void yahoo_add_buddy(int id, const char *who, const char *group, const char *msg yahoo_packet_hash(pkt, 334, "0"); yahoo_packet_hash(pkt, 301, "319"); yahoo_packet_hash(pkt, 303, "319"); - - yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_remove_buddy(int id, const char *who, const char *group) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; - if(!yid) + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YPACKET_STATUS_DEFAULT, + yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 7, who); @@ -4358,151 +3987,129 @@ void yahoo_remove_buddy(int id, const char *who, const char *group) yahoo_packet_free(pkt); } -void yahoo_accept_buddy_ymsg13(int id,const char* me,const char* who){ - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); - struct yahoo_data *yd; - - if(!yid) - return; - yd = yid->yd; - - struct yahoo_packet* pkt=NULL; - pkt= yahoo_packet_new(YAHOO_SERVICE_CONTACT_YMSG13,YAHOO_STATUS_AVAILABLE,0); - - yahoo_packet_hash(pkt,1,me ?: yd->user); - yahoo_packet_hash(pkt,5,who); - yahoo_packet_hash(pkt,13,"1"); - yahoo_packet_hash(pkt,334,"0"); - yahoo_send_packet(yid, pkt, 0); - yahoo_packet_free(pkt); -} - -void yahoo_reject_buddy_ymsg13(int id,const char* me,const char* who,const char* msg){ - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); - struct yahoo_data *yd; - - if(!yid) - return; - yd = yid->yd; - - struct yahoo_packet* pkt=NULL; - pkt= yahoo_packet_new(YAHOO_SERVICE_CONTACT_YMSG13,YAHOO_STATUS_AVAILABLE,0); - - yahoo_packet_hash(pkt,1,me ?: yd->user); - yahoo_packet_hash(pkt,5,who); -// yahoo_packet_hash(pkt,241,YAHOO_PROTO_VER); - yahoo_packet_hash(pkt,13,"2"); - yahoo_packet_hash(pkt,334,"0"); - yahoo_packet_hash(pkt,97,"1"); - yahoo_packet_hash(pkt,14,msg?:""); - - yahoo_send_packet(yid, pkt, 0); - yahoo_packet_free(pkt); - -} - -void yahoo_reject_buddy(int id, const char *who, const char *msg) +void yahoo_confirm_buddy(int id, const char *who, int reject, const char *msg) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - if(!yid) + if (!yid) return; yd = yid->yd; if (!yd->logged_in) return; - pkt = yahoo_packet_new(YAHOO_SERVICE_REJECTCONTACT, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_AUTHORIZATION, + YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); - yahoo_packet_hash(pkt, 7, who); - yahoo_packet_hash(pkt, 14, msg); + yahoo_packet_hash(pkt, 5, who); + if (reject) + yahoo_packet_hash(pkt, 13, "2"); + else { + yahoo_packet_hash(pkt, 241, "0"); + yahoo_packet_hash(pkt, 13, "1"); + } + + yahoo_packet_hash(pkt, 334, "0"); + + if (reject) { + yahoo_packet_hash(pkt, 14, msg ? msg : ""); + yahoo_packet_hash(pkt, 97, "1"); + } + yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_ignore_buddy(int id, const char *who, int unignore) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - if(!yid) + if (!yid) return; yd = yid->yd; if (!yd->logged_in) return; - pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, + YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 7, who); - yahoo_packet_hash(pkt, 13, unignore?"2":"1"); + yahoo_packet_hash(pkt, 13, unignore ? "2" : "1"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_stealth_buddy(int id, const char *who, int unstealth) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - if(!yid) + if (!yid) return; yd = yid->yd; if (!yd->logged_in) return; - pkt = yahoo_packet_new(YAHOO_SERVICE_STEALTH, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_STEALTH_PERM, + YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 7, who); - yahoo_packet_hash(pkt, 31, unstealth?"2":"1"); + yahoo_packet_hash(pkt, 31, unstealth ? "2" : "1"); yahoo_packet_hash(pkt, 13, "2"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } -void yahoo_change_buddy_group(int id, const char *who, const char *old_group, const char *new_group) +void yahoo_change_buddy_group(int id, const char *who, const char *old_group, + const char *new_group) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; - if(!yid) + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_CHANGE_GROUP, + YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); + yahoo_packet_hash(pkt, 302, "240"); + yahoo_packet_hash(pkt, 300, "240"); yahoo_packet_hash(pkt, 7, who); - yahoo_packet_hash(pkt, 65, new_group); - yahoo_packet_hash(pkt, 14, " "); + yahoo_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); - - pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YAHOO_STATUS_AVAILABLE, yd->session_id); - yahoo_packet_hash(pkt, 1, yd->user); - yahoo_packet_hash(pkt, 7, who); - yahoo_packet_hash(pkt, 65, old_group); - yahoo_send_packet(yid, pkt, 0); - yahoo_packet_free(pkt); } void yahoo_group_rename(int id, const char *old_group, const char *new_group) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; - if(!yid) + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_GROUPRENAME, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_GROUPRENAME, + YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 65, old_group); yahoo_packet_hash(pkt, 67, new_group); @@ -4511,24 +4118,27 @@ void yahoo_group_rename(int id, const char *old_group, const char *new_group) yahoo_packet_free(pkt); } -void yahoo_conference_addinvite(int id, const char * from, const char *who, const char *room, const YList * members, const char *msg) +void yahoo_conference_addinvite(int id, const char *from, const char *who, + const char *room, const YList *members, const char *msg) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_CONFADDINVITE, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_CONFADDINVITE, + YPACKET_STATUS_DEFAULT, yd->session_id); - yahoo_packet_hash(pkt, 1, (from?from:yd->user)); + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); yahoo_packet_hash(pkt, 51, who); yahoo_packet_hash(pkt, 57, room); yahoo_packet_hash(pkt, 58, msg); yahoo_packet_hash(pkt, 13, "0"); - for(; members; members = members->next) { + for (; members; members = members->next) { yahoo_packet_hash(pkt, 52, (char *)members->data); yahoo_packet_hash(pkt, 53, (char *)members->data); } @@ -4539,21 +4149,24 @@ void yahoo_conference_addinvite(int id, const char * from, const char *who, cons yahoo_packet_free(pkt); } -void yahoo_conference_invite(int id, const char * from, YList *who, const char *room, const char *msg) +void yahoo_conference_invite(int id, const char *from, YList *who, + const char *room, const char *msg) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_CONFINVITE, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_CONFINVITE, YPACKET_STATUS_DEFAULT, + yd->session_id); - yahoo_packet_hash(pkt, 1, (from?from:yd->user)); + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); yahoo_packet_hash(pkt, 50, yd->user); - for(; who; who = who->next) { + for (; who; who = who->next) { yahoo_packet_hash(pkt, 52, (char *)who->data); } yahoo_packet_hash(pkt, 57, room); @@ -4565,45 +4178,51 @@ void yahoo_conference_invite(int id, const char * from, YList *who, const char * yahoo_packet_free(pkt); } -void yahoo_conference_logon(int id, const char *from, YList *who, const char *room) +void yahoo_conference_logon(int id, const char *from, YList *who, + const char *room) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGON, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGON, YPACKET_STATUS_DEFAULT, + yd->session_id); - yahoo_packet_hash(pkt, 1, (from?from:yd->user)); - for(; who; who = who->next) { - yahoo_packet_hash(pkt, 3, (char *)who->data); - } + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); + yahoo_packet_hash(pkt, 3, (from ? from : yd->user)); yahoo_packet_hash(pkt, 57, room); + for (; who; who = who->next) + yahoo_packet_hash(pkt, 3, (char *)who->data); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } -void yahoo_conference_decline(int id, const char * from, YList *who, const char *room, const char *msg) +void yahoo_conference_decline(int id, const char *from, YList *who, + const char *room, const char *msg) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_CONFDECLINE, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_CONFDECLINE, + YPACKET_STATUS_DEFAULT, yd->session_id); - yahoo_packet_hash(pkt, 1, (from?from:yd->user)); - for(; who; who = who->next) { + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); + yahoo_packet_hash(pkt, 3, (from ? from : yd->user)); + for (; who; who = who->next) yahoo_packet_hash(pkt, 3, (char *)who->data); - } yahoo_packet_hash(pkt, 57, room); yahoo_packet_hash(pkt, 14, msg); @@ -4612,22 +4231,26 @@ void yahoo_conference_decline(int id, const char * from, YList *who, const char yahoo_packet_free(pkt); } -void yahoo_conference_logoff(int id, const char * from, YList *who, const char *room) +void yahoo_conference_logoff(int id, const char *from, YList *who, + const char *room) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGOFF, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGOFF, YPACKET_STATUS_DEFAULT, + yd->session_id); - yahoo_packet_hash(pkt, 1, (from?from:yd->user)); - for(; who; who = who->next) { + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); + yahoo_packet_hash(pkt, 3, (from ? from : yd->user)); + for (; who; who = who->next) yahoo_packet_hash(pkt, 3, (char *)who->data); - } + yahoo_packet_hash(pkt, 57, room); yahoo_send_packet(yid, pkt, 0); @@ -4635,26 +4258,30 @@ void yahoo_conference_logoff(int id, const char * from, YList *who, const char * yahoo_packet_free(pkt); } -void yahoo_conference_message(int id, const char * from, YList *who, const char *room, const char *msg, int utf8) +void yahoo_conference_message(int id, const char *from, YList *who, + const char *room, const char *msg, int utf8) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_CONFMSG, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_CONFMSG, YPACKET_STATUS_DEFAULT, + yd->session_id); - yahoo_packet_hash(pkt, 1, (from?from:yd->user)); - for(; who; who = who->next) { + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); + yahoo_packet_hash(pkt, 53, (from ? from : yd->user)); + for (; who; who = who->next) yahoo_packet_hash(pkt, 53, (char *)who->data); - } + yahoo_packet_hash(pkt, 57, room); yahoo_packet_hash(pkt, 14, msg); - if(utf8) + if (utf8) yahoo_packet_hash(pkt, 97, "1"); yahoo_send_packet(yid, pkt, 0); @@ -4669,7 +4296,7 @@ void yahoo_get_chatrooms(int id, int chatroomid) char url[1024]; char buff[1024]; - if(!yd) + if (!yd) return; yid = y_new0(struct yahoo_input_data, 1); @@ -4677,32 +4304,39 @@ void yahoo_get_chatrooms(int id, int chatroomid) yid->type = YAHOO_CONNECTION_CHATCAT; if (chatroomid == 0) { - snprintf(url, 1024, "http://insider.msg.yahoo.com/ycontent/?chatcat=0"); + snprintf(url, 1024, + "http://insider.msg.yahoo.com/ycontent/?chatcat=0"); } else { - snprintf(url, 1024, "http://insider.msg.yahoo.com/ycontent/?chatroom_%d=0",chatroomid); + snprintf(url, 1024, + "http://insider.msg.yahoo.com/ycontent/?chatroom_%d=0", + chatroomid); } snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t); inputs = y_list_prepend(inputs, yid); - yahoo_http_get(yid->yd->client_id, url, buff, _yahoo_http_connected, yid); + yahoo_http_get(yid->yd->client_id, url, buff, 0, 0, + _yahoo_http_connected, yid); } -void yahoo_chat_logon(int id, const char *from, const char *room, const char *roomid) +void yahoo_chat_logon(int id, const char *from, const char *room, + const char *roomid) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_CHATONLINE, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_CHATONLINE, YPACKET_STATUS_DEFAULT, + yd->session_id); - yahoo_packet_hash(pkt, 1, (from?from:yd->user)); + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); yahoo_packet_hash(pkt, 109, yd->user); yahoo_packet_hash(pkt, 6, "abcde"); @@ -4710,41 +4344,44 @@ void yahoo_chat_logon(int id, const char *from, const char *room, const char *ro yahoo_packet_free(pkt); - pkt = yahoo_packet_new(YAHOO_SERVICE_CHATJOIN, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_CHATJOIN, YPACKET_STATUS_DEFAULT, + yd->session_id); - yahoo_packet_hash(pkt, 1, (from?from:yd->user)); + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); yahoo_packet_hash(pkt, 104, room); yahoo_packet_hash(pkt, 129, roomid); - yahoo_packet_hash(pkt, 62, "2"); /* ??? */ + yahoo_packet_hash(pkt, 62, "2"); /* ??? */ yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } - -void yahoo_chat_message(int id, const char *from, const char *room, const char *msg, const int msgtype, const int utf8) +void yahoo_chat_message(int id, const char *from, const char *room, + const char *msg, const int msgtype, const int utf8) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; char buf[2]; - - if(!yid) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_COMMENT, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_COMMENT, YPACKET_STATUS_DEFAULT, + yd->session_id); - yahoo_packet_hash(pkt, 1, (from?from:yd->user)); + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); yahoo_packet_hash(pkt, 104, room); yahoo_packet_hash(pkt, 117, msg); - + snprintf(buf, sizeof(buf), "%d", msgtype); yahoo_packet_hash(pkt, 124, buf); - if(utf8) + if (utf8) yahoo_packet_hash(pkt, 97, "1"); yahoo_send_packet(yid, pkt, 0); @@ -4752,21 +4389,22 @@ void yahoo_chat_message(int id, const char *from, const char *room, const char yahoo_packet_free(pkt); } - void yahoo_chat_logoff(int id, const char *from) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_CHATLOGOUT, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_CHATLOGOUT, YPACKET_STATUS_DEFAULT, + yd->session_id); - yahoo_packet_hash(pkt, 1, (from?from:yd->user)); + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); yahoo_send_packet(yid, pkt, 0); @@ -4775,16 +4413,18 @@ void yahoo_chat_logoff(int id, const char *from) void yahoo_buddyicon_request(int id, const char *who) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - if( !yid ) + if (!yid) return; yd = yid->yd; - - pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YAHOO_STATUS_AVAILABLE, 0); + + pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YPACKET_STATUS_DEFAULT, + 0); yahoo_packet_hash(pkt, 4, yd->user); yahoo_packet_hash(pkt, 5, who); yahoo_packet_hash(pkt, 13, "1"); @@ -4793,21 +4433,24 @@ void yahoo_buddyicon_request(int id, const char *who) yahoo_packet_free(pkt); } -void yahoo_send_picture_info(int id, const char *who, const char *url, int checksum) +void yahoo_send_picture_info(int id, const char *who, const char *url, + int checksum) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; char checksum_str[10]; - if( !yid ) + if (!yid) return; yd = yid->yd; snprintf(checksum_str, sizeof(checksum_str), "%d", checksum); - pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YAHOO_STATUS_AVAILABLE, 0); + pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YPACKET_STATUS_DEFAULT, + 0); yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 4, yd->user); yahoo_packet_hash(pkt, 5, who); @@ -4821,19 +4464,21 @@ void yahoo_send_picture_info(int id, const char *who, const char *url, int check void yahoo_send_picture_update(int id, const char *who, int type) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; char type_str[10]; - if( !yid ) + if (!yid) return; yd = yid->yd; snprintf(type_str, sizeof(type_str), "%d", type); - pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_UPDATE, YAHOO_STATUS_AVAILABLE, 0); + pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_UPDATE, + YPACKET_STATUS_DEFAULT, 0); yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 5, who); yahoo_packet_hash(pkt, 206, type_str); @@ -4844,21 +4489,23 @@ void yahoo_send_picture_update(int id, const char *who, int type) void yahoo_send_picture_checksum(int id, const char *who, int checksum) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; char checksum_str[10]; - if( !yid ) + if (!yid) return; yd = yid->yd; - + snprintf(checksum_str, sizeof(checksum_str), "%d", checksum); - pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_CHECKSUM, YAHOO_STATUS_AVAILABLE, 0); + pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_CHECKSUM, + YPACKET_STATUS_DEFAULT, 0); yahoo_packet_hash(pkt, 1, yd->user); - if( who != 0 ) + if (who != 0) yahoo_packet_hash(pkt, 5, who); yahoo_packet_hash(pkt, 192, checksum_str); yahoo_packet_hash(pkt, 212, "1"); @@ -4869,19 +4516,21 @@ void yahoo_send_picture_checksum(int id, const char *who, int checksum) void yahoo_webcam_close_feed(int id, const char *who) { - struct yahoo_input_data *yid = find_input_by_id_and_webcam_user(id, who); + struct yahoo_input_data *yid = + find_input_by_id_and_webcam_user(id, who); - if(yid) + if (yid) yahoo_input_close(yid); } void yahoo_webcam_get_feed(int id, const char *who) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; /* @@ -4891,11 +4540,12 @@ void yahoo_webcam_get_feed(int id, const char *who) * order that we request them. * The queue is popped in yahoo_process_webcam_key */ - webcam_queue = y_list_append(webcam_queue, who?strdup(who):NULL); + webcam_queue = y_list_append(webcam_queue, who ? strdup(who) : NULL); yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_WEBCAM, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_WEBCAM, YPACKET_STATUS_DEFAULT, + yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); if (who != NULL) @@ -4905,9 +4555,11 @@ void yahoo_webcam_get_feed(int id, const char *who) yahoo_packet_free(pkt); } -void yahoo_webcam_send_image(int id, unsigned char *image, unsigned int length, unsigned int timestamp) +void yahoo_webcam_send_image(int id, unsigned char *image, unsigned int length, + unsigned int timestamp) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_WEBCAM); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_WEBCAM); unsigned char *packet; unsigned char header_len = 13; unsigned int pos = 0; @@ -4919,10 +4571,10 @@ void yahoo_webcam_send_image(int id, unsigned char *image, unsigned int length, packet[pos++] = header_len; packet[pos++] = 0; - packet[pos++] = 5; /* version byte?? */ + packet[pos++] = 5; /* version byte?? */ packet[pos++] = 0; pos += yahoo_put32(packet + pos, length); - packet[pos++] = 2; /* packet type, image */ + packet[pos++] = 2; /* packet type, image */ pos += yahoo_put32(packet + pos, timestamp); yahoo_add_to_send_queue(yid, packet, header_len); FREE(packet); @@ -4931,9 +4583,10 @@ void yahoo_webcam_send_image(int id, unsigned char *image, unsigned int length, yahoo_add_to_send_queue(yid, image, length); } -void yahoo_webcam_accept_viewer(int id, const char* who, int accept) +void yahoo_webcam_accept_viewer(int id, const char *who, int accept) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_WEBCAM); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_WEBCAM); char *packet = NULL; char *data = NULL; unsigned char header_len = 13; @@ -4944,17 +4597,17 @@ void yahoo_webcam_accept_viewer(int id, const char* who, int accept) return; data = strdup("u="); - data = y_string_append(data, (char*)who); + data = y_string_append(data, (char *)who); data = y_string_append(data, "\r\n"); len = strlen(data); packet = y_new0(char, header_len + len); packet[pos++] = header_len; packet[pos++] = 0; - packet[pos++] = 5; /* version byte?? */ + packet[pos++] = 5; /* version byte?? */ packet[pos++] = 0; pos += yahoo_put32(packet + pos, len); - packet[pos++] = 0; /* packet type */ + packet[pos++] = 0; /* packet type */ pos += yahoo_put32(packet + pos, accept); memcpy(packet + pos, data, len); FREE(data); @@ -4964,13 +4617,15 @@ void yahoo_webcam_accept_viewer(int id, const char* who, int accept) void yahoo_webcam_invite(int id, const char *who) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; - pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YAHOO_STATUS_NOTIFY, yid->yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YPACKET_STATUS_NOTIFY, + yid->yd->session_id); yahoo_packet_hash(pkt, 49, "WEBCAMINVITE"); yahoo_packet_hash(pkt, 14, " "); @@ -4982,7 +4637,8 @@ void yahoo_webcam_invite(int id, const char *who) yahoo_packet_free(pkt); } -static void yahoo_search_internal(int id, int t, const char *text, int g, int ar, int photo, int yahoo_only, int startpos, int total) +static void yahoo_search_internal(int id, int t, const char *text, int g, + int ar, int photo, int yahoo_only, int startpos, int total) { struct yahoo_data *yd = find_conn_by_id(id); struct yahoo_input_data *yid; @@ -4990,7 +4646,7 @@ static void yahoo_search_internal(int id, int t, const char *text, int g, int ar char buff[1024]; char *ctext, *p; - if(!yd) + if (!yd) return; yid = y_new0(struct yahoo_input_data, 1); @@ -4998,38 +4654,43 @@ static void yahoo_search_internal(int id, int t, const char *text, int g, int ar yid->type = YAHOO_CONNECTION_SEARCH; /* - age range - .ar=1 - 13-18, 2 - 18-25, 3 - 25-35, 4 - 35-50, 5 - 50-70, 6 - 70+ - */ + age range + .ar=1 - 13-18, 2 - 18-25, 3 - 25-35, 4 - 35-50, 5 - 50-70, 6 - 70+ + */ - snprintf(buff, sizeof(buff), "&.sq=%%20&.tt=%d&.ss=%d", total, startpos); + snprintf(buff, sizeof(buff), "&.sq=%%20&.tt=%d&.ss=%d", total, + startpos); ctext = strdup(text); - while((p = strchr(ctext, ' '))) + while ((p = strchr(ctext, ' '))) *p = '+'; - snprintf(url, 1024, "http://members.yahoo.com/interests?.oc=m&.kw=%s&.sb=%d&.g=%d&.ar=0%s%s%s", - ctext, t, g, photo ? "&.p=y" : "", yahoo_only ? "&.pg=y" : "", - startpos ? buff : ""); + snprintf(url, 1024, + "http://members.yahoo.com/interests?.oc=m&.kw=%s&.sb=%d&.g=%d&.ar=0%s%s%s", + ctext, t, g, photo ? "&.p=y" : "", yahoo_only ? "&.pg=y" : "", + startpos ? buff : ""); FREE(ctext); snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t); inputs = y_list_prepend(inputs, yid); - yahoo_http_get(yid->yd->client_id, url, buff, _yahoo_http_connected, yid); + yahoo_http_get(yid->yd->client_id, url, buff, 0, 0, + _yahoo_http_connected, yid); } -void yahoo_search(int id, enum yahoo_search_type t, const char *text, enum yahoo_search_gender g, enum yahoo_search_agerange ar, - int photo, int yahoo_only) +void yahoo_search(int id, enum yahoo_search_type t, const char *text, + enum yahoo_search_gender g, enum yahoo_search_agerange ar, int photo, + int yahoo_only) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_search_state *yss; - if(!yid) + if (!yid) return; - if(!yid->ys) + if (!yid->ys) yid->ys = y_new0(struct yahoo_search_state, 1); yss = yid->ys; @@ -5047,273 +4708,711 @@ void yahoo_search(int id, enum yahoo_search_type t, const char *text, enum yahoo void yahoo_search_again(int id, int start) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_search_state *yss; - if(!yid || !yid->ys) + if (!yid || !yid->ys) return; yss = yid->ys; - if(start == -1) + if (start == -1) start = yss->lsearch_nstart + yss->lsearch_nfound; - yahoo_search_internal(id, yss->lsearch_type, yss->lsearch_text, - yss->lsearch_gender, yss->lsearch_agerange, - yss->lsearch_photo, yss->lsearch_yahoo_only, - start, yss->lsearch_ntotal); + yahoo_search_internal(id, yss->lsearch_type, yss->lsearch_text, + yss->lsearch_gender, yss->lsearch_agerange, + yss->lsearch_photo, yss->lsearch_yahoo_only, + start, yss->lsearch_ntotal); +} + +void yahoo_send_picture(int id, const char *name, unsigned long size, + yahoo_get_fd_callback callback, void *data) +{ + /* Not Implemented */ } +/* File Transfer */ +static YList *active_file_transfers = NULL; + +enum { + FT_STATE_HEAD = 1, + FT_STATE_RECV, + FT_STATE_RECV_START, + FT_STATE_SEND +}; + struct send_file_data { - struct yahoo_packet *pkt; + int client_id; + char *id; + char *who; + char *filename; + char *ip_addr; + char *token; + int size; + + struct yahoo_input_data *yid; + int state; + yahoo_get_fd_callback callback; - void *user_data; + void *data; }; -static void _yahoo_send_picture_connected(int id, int fd, int error, void *data) +static char *yahoo_get_random(void) +{ + int i = 0; + int r = 0; + int c = 0; + char out[25]; + + out[24] = '\0'; + out[23] = '$'; + out[22] = '$'; + + for (i = 0; i < 22; i++) { + if(r == 0) + r = rand(); + + c = r%61; + + if(c<26) + out[i] = c + 'a'; + else if (c<52) + out[i] = c - 26 + 'A'; + else + out[i] = c - 52 + '0'; + + r /= 61; + } + + return strdup(out); +} + +static int _are_same_id(const void *sfd1, const void *id) +{ + return strcmp(((struct send_file_data *)sfd1)->id, (char *)id); +} + +static int _are_same_yid(const void *sfd1, const void *yid) +{ + if(((struct send_file_data *)sfd1)->yid == yid) + return 0; + else + return 1; +} + +static struct send_file_data *yahoo_get_active_transfer(char *id) +{ + YList *l = y_list_find_custom(active_file_transfers, id, + _are_same_id); + + if(l) + return (struct send_file_data *)l->data; + + return NULL; +} + +static struct send_file_data *yahoo_get_active_transfer_with_yid(void *yid) +{ + YList *l = y_list_find_custom(active_file_transfers, yid, + _are_same_yid); + + if(l) + return (struct send_file_data *)l->data; + + return NULL; +} + +static void yahoo_add_active_transfer(struct send_file_data *sfd) +{ + active_file_transfers = y_list_prepend(active_file_transfers, sfd); +} + +static void yahoo_remove_active_transfer(struct send_file_data *sfd) +{ + 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 yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_FT); struct send_file_data *sfd = data; - struct yahoo_packet *pkt = sfd->pkt; - unsigned char buff[1024]; + struct yahoo_input_data *yid = sfd->yid; - if(fd <= 0) { - sfd->callback(id, fd, error, sfd->user_data); - FREE(sfd); - yahoo_packet_free(pkt); + if (!fd) { inputs = y_list_remove(inputs, yid); FREE(yid); return; } + sfd->callback(id, fd, error, sfd->data); + yid->fd = fd; - yahoo_send_packet(yid, pkt, 8); - yahoo_packet_free(pkt); + yid->read_tag = + YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd, + YAHOO_INPUT_READ, yid); +} - snprintf((char *)buff, sizeof(buff), "29"); - buff[2] = 0xc0; - buff[3] = 0x80; - - write(yid->fd, buff, 4); +static void yahoo_file_transfer_upload(struct yahoo_data *yd, + struct send_file_data *sfd) +{ + char url[256]; + char buff[4096]; + char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL; - /* YAHOO_CALLBACK(ext_yahoo_add_handler)(nyd->fd, YAHOO_INPUT_READ); */ + struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1); - sfd->callback(id, fd, error, sfd->user_data); - FREE(sfd); - inputs = y_list_remove(inputs, yid); - /* - while(yahoo_tcp_readline(buff, sizeof(buff), nyd->fd) > 0) { - if(!strcmp(buff, "")) - break; + yid->yd = yd; + yid->type = YAHOO_CONNECTION_FT; + + inputs = y_list_prepend(inputs, yid); + sfd->yid = yid; + sfd->state = FT_STATE_SEND; + + token_enc = yahoo_urlencode(sfd->token); + sender_enc = yahoo_urlencode(yd->user); + recv_enc = yahoo_urlencode(sfd->who); + + snprintf(url, sizeof(url), + "http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr, + token_enc, sender_enc, recv_enc); + + snprintf(buff, sizeof(buff), "T=%s; Y=%s", yd->cookie_t, yd->cookie_y); + + yahoo_http_post(yd->client_id, url, buff, sfd->size, + _yahoo_ft_upload_connected, sfd); + + FREE(token_enc); + FREE(sender_enc); + FREE(recv_enc); } - */ - yahoo_input_close(yid); +static void yahoo_init_ft_recv(struct yahoo_data *yd, + struct send_file_data *sfd) +{ + char url[256]; + char buff[1024]; + char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL; + + struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1); + + yid->yd = yd; + yid->type = YAHOO_CONNECTION_FT; + + inputs = y_list_prepend(inputs, yid); + sfd->yid = yid; + sfd->state = FT_STATE_HEAD; + + token_enc = yahoo_urlencode(sfd->token); + sender_enc = yahoo_urlencode(sfd->who); + recv_enc = yahoo_urlencode(yd->user); + + snprintf(url, sizeof(url), + "http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr, + token_enc, sender_enc, recv_enc); + + snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t); + + yahoo_http_head(yid->yd->client_id, url, buff, 0, NULL, + _yahoo_http_connected, yid); + + FREE(token_enc); + FREE(sender_enc); + FREE(recv_enc); } -void yahoo_send_picture(int id, const char *name, unsigned long size, - yahoo_get_fd_callback callback, void *data) +static void yahoo_file_transfer_accept(struct yahoo_input_data *yid, + struct send_file_data *sfd) { - struct yahoo_data *yd = find_conn_by_id(id); - struct yahoo_input_data *yid; - struct yahoo_server_settings *yss; - struct yahoo_packet *pkt = NULL; - char size_str[10]; - char expire_str[10]; - long content_length=0; - unsigned char buff[1024]; - char url[255]; + struct yahoo_packet *pkt; + + pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFERACCEPT, + YPACKET_STATUS_DEFAULT, yid->yd->session_id); + + yahoo_packet_hash(pkt, 1, yid->yd->user); + yahoo_packet_hash(pkt, 5, sfd->who); + yahoo_packet_hash(pkt, 265, sfd->id); + yahoo_packet_hash(pkt, 27, sfd->filename); + yahoo_packet_hash(pkt, 249, "3"); + yahoo_packet_hash(pkt, 251, sfd->token); + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); + + yahoo_init_ft_recv(yid->yd, sfd); +} + +static void yahoo_process_filetransferaccept(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + YList *l; struct send_file_data *sfd; + char *who = NULL; + char *filename = NULL; + char *id = NULL; + char *token = NULL; - if(!yd) - return; + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + switch (pair->key) { + case 4: + who = pair->value; + break; + case 5: + /* Me... don't care */ + break; + case 249: + break; + case 265: + id = pair->value; + break; + case 251: + token = pair->value; + break; + case 27: + filename = pair->value; + break; + } + } - yss = yd->server_settings; + sfd = yahoo_get_active_transfer(id); - yid = y_new0(struct yahoo_input_data, 1); - yid->yd = yd; - yid->type = YAHOO_CONNECTION_FT; + if (sfd) { + sfd->token = strdup(token); - pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_UPLOAD, YAHOO_STATUS_AVAILABLE, yd->session_id); + yahoo_file_transfer_upload(yid->yd, sfd); + } + else { + YAHOO_CALLBACK(ext_yahoo_file_transfer_done) + (yid->yd->client_id, YAHOO_FILE_TRANSFER_UNKNOWN, + sfd ? sfd->data : NULL); - snprintf(size_str, sizeof(size_str), "%ld", size); - snprintf(expire_str, sizeof(expire_str), "%ld", (long)604800); + yahoo_remove_active_transfer(sfd); + } +} - yahoo_packet_hash(pkt, 0, yd->user); - yahoo_packet_hash(pkt, 1, yd->user); - yahoo_packet_hash(pkt, 14, ""); - yahoo_packet_hash(pkt, 27, name); - yahoo_packet_hash(pkt, 28, size_str); - yahoo_packet_hash(pkt, 38, expire_str); - +static void yahoo_process_filetransferinfo(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + YList *l; + char *who = NULL; + char *filename = NULL; + char *id = NULL; + char *token = NULL; + char *ip_addr = NULL; - content_length = YAHOO_PACKET_HDRLEN + yahoo_packet_length(pkt); + struct send_file_data *sfd; - snprintf(url, sizeof(url), "http://%s:%d/notifyft", - yss->filetransfer_host, yss->filetransfer_port); - snprintf((char *)buff, sizeof(buff), "Y=%s; T=%s", - yd->cookie_y, yd->cookie_t); - inputs = y_list_prepend(inputs, yid); + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + switch (pair->key) { + case 1: + case 4: + who = pair->value; + break; + case 5: + /* Me... don't care */ + break; + case 249: + break; + case 265: + id = pair->value; + break; + case 250: + ip_addr = pair->value; + break; + case 251: + token = pair->value; + break; + case 27: + filename = pair->value; + break; + } + } - sfd = y_new0(struct send_file_data, 1); - sfd->pkt = pkt; - sfd->callback = callback; - sfd->user_data = data; - yahoo_http_post(yid->yd->client_id, url, (char *)buff, content_length+4+size, - _yahoo_send_picture_connected, sfd); + sfd = yahoo_get_active_transfer(id); + + if (sfd) { + sfd->token = strdup(token); + sfd->ip_addr = strdup(ip_addr); + + yahoo_file_transfer_accept(yid, sfd); + } + else { + YAHOO_CALLBACK(ext_yahoo_file_transfer_done) + (yid->yd->client_id, YAHOO_FILE_TRANSFER_UNKNOWN, + sfd ? sfd->data : NULL); + + yahoo_remove_active_transfer(sfd); + } } -static void _yahoo_send_file_connected(int id, int fd, int error, void *data) +static void yahoo_send_filetransferinfo(struct yahoo_data *yd, + struct send_file_data *sfd) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_FT); - struct send_file_data *sfd = data; - struct yahoo_packet *pkt = sfd->pkt; - unsigned char buff[1024]; + struct yahoo_input_data *yid; + struct yahoo_packet *pkt; + + yid = find_input_by_id_and_type(yd->client_id, YAHOO_CONNECTION_PAGER); + sfd->ip_addr = YAHOO_CALLBACK(ext_yahoo_get_ip_addr)("relay.yahoo.com"); + + if (!sfd->ip_addr) { + YAHOO_CALLBACK(ext_yahoo_file_transfer_done) + (yd->client_id, YAHOO_FILE_TRANSFER_RELAY, sfd->data); + + yahoo_remove_active_transfer(sfd); - if(fd <= 0) { - sfd->callback(id, fd, error, sfd->user_data); - FREE(sfd); - yahoo_packet_free(pkt); - inputs = y_list_remove(inputs, yid); - FREE(yid); return; } - yid->fd = fd; - yahoo_send_packet(yid, pkt, 8); + pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFERINFO, + YPACKET_STATUS_DEFAULT, yd->session_id); + + yahoo_packet_hash(pkt, 1, yd->user); + yahoo_packet_hash(pkt, 5, sfd->who); + yahoo_packet_hash(pkt, 265, sfd->id); + yahoo_packet_hash(pkt, 27, sfd->filename); + yahoo_packet_hash(pkt, 249, "3"); + yahoo_packet_hash(pkt, 250, sfd->ip_addr); + + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); +} - snprintf((char *)buff, sizeof(buff), "29"); - buff[2] = 0xc0; - buff[3] = 0x80; - - write(yid->fd, buff, 4); +static void yahoo_process_filetransfer(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + YList *l; + char *who = NULL; + char *filename = NULL; + char *msg = NULL; + char *id = NULL; + int action = 0; + int size = 0; + struct yahoo_data *yd = yid->yd; -/* YAHOO_CALLBACK(ext_yahoo_add_handler)(nyd->fd, YAHOO_INPUT_READ); */ + struct send_file_data *sfd; - sfd->callback(id, fd, error, sfd->user_data); - FREE(sfd); - inputs = y_list_remove(inputs, yid); - /* - while(yahoo_tcp_readline(buff, sizeof(buff), nyd->fd) > 0) { - if(!strcmp(buff, "")) + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + switch (pair->key) { + case 4: + who = pair->value; + break; + case 5: + /* Me... don't care */ + break; + case 222: + action = atoi(pair->value); + break; + case 265: + id = pair->value; + break; + case 266: /* Don't know */ + break; + case 302: /* Start Data? */ + break; + case 300: + break; + case 27: + filename = pair->value; break; + case 28: + size = atoi(pair->value); + break; + case 14: + msg = pair->value; + case 301: /* End Data? */ + break; + case 303: + break; + + } + } + + if (action == YAHOO_FILE_TRANSFER_INIT) { + /* Received a FT request from buddy */ + sfd = y_new0(struct send_file_data, 1); + + sfd->client_id = yd->client_id; + sfd->id = strdup(id); + sfd->who = strdup(who); + sfd->filename = strdup(filename); + sfd->size = size; + + yahoo_add_active_transfer(sfd); + + YAHOO_CALLBACK(ext_yahoo_got_file) (yd->client_id, yd->user, + who, msg, filename, size, sfd->id); } + else { + /* Response to our request */ + sfd = yahoo_get_active_transfer(id); - */ - yahoo_input_close(yid); + if (sfd && action == YAHOO_FILE_TRANSFER_ACCEPT) { + yahoo_send_filetransferinfo(yd, sfd); + } + else if (!sfd || action == YAHOO_FILE_TRANSFER_REJECT) { + YAHOO_CALLBACK(ext_yahoo_file_transfer_done) + (yd->client_id, YAHOO_FILE_TRANSFER_REJECT, + sfd ? 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) +void yahoo_send_file(int id, const char *who, const char *msg, + const char *name, unsigned long size, + yahoo_get_fd_callback callback, void *data) { - struct yahoo_data *yd = find_conn_by_id(id); - struct yahoo_input_data *yid; - struct yahoo_server_settings *yss; struct yahoo_packet *pkt = NULL; char size_str[10]; - long content_length=0; - unsigned char buff[1024]; - char url[255]; + struct yahoo_input_data *yid; + struct yahoo_data *yd; struct send_file_data *sfd; + + yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + yd = find_conn_by_id(id); + sfd = y_new0(struct send_file_data, 1); - if(!yd) - return; + sfd->client_id = id; + sfd->id = yahoo_get_random(); + sfd->who = strdup(who); + sfd->filename = strdup(name); + sfd->size = size; + sfd->callback = callback; + sfd->data = data; - yss = yd->server_settings; + yahoo_add_active_transfer(sfd); - yid = y_new0(struct yahoo_input_data, 1); - yid->yd = yd; - yid->type = YAHOO_CONNECTION_FT; + if (!yd) + return; - pkt = yahoo_packet_new(YAHOO_SERVICE_FILETRANSFER, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER, + YPACKET_STATUS_DEFAULT, yd->session_id); snprintf(size_str, sizeof(size_str), "%ld", size); - yahoo_packet_hash(pkt, 0, yd->user); + yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 5, who); - yahoo_packet_hash(pkt, 14, msg); + yahoo_packet_hash(pkt, 265, sfd->id); + yahoo_packet_hash(pkt, 222, "1"); + yahoo_packet_hash(pkt, 266, "1"); + yahoo_packet_hash(pkt, 302, "268"); + yahoo_packet_hash(pkt, 300, "268"); yahoo_packet_hash(pkt, 27, name); yahoo_packet_hash(pkt, 28, size_str); + yahoo_packet_hash(pkt, 301, "268"); + yahoo_packet_hash(pkt, 303, "268"); - content_length = YAHOO_PACKET_HDRLEN + yahoo_packet_length(pkt); + yahoo_send_packet(yid, pkt, 0); - snprintf(url, sizeof(url), "http://%s:%d/notifyft", - yss->filetransfer_host, yss->filetransfer_port); - snprintf((char *)buff, sizeof(buff), "Y=%s; T=%s", - yd->cookie_y, yd->cookie_t); - inputs = y_list_prepend(inputs, yid); + yahoo_packet_free(pkt); +} - sfd = y_new0(struct send_file_data, 1); - sfd->pkt = pkt; - sfd->callback = callback; - sfd->user_data = data; - yahoo_http_post(yid->yd->client_id, url, (char *)buff, content_length+4+size, - _yahoo_send_file_connected, sfd); +void yahoo_send_file_transfer_response(int client_id, int response, char *id, void *data) +{ + struct yahoo_packet *pkt = NULL; + char resp[2]; + struct yahoo_input_data *yid; + + struct send_file_data *sfd = yahoo_get_active_transfer(id); + + sfd->data = data; + + yid = find_input_by_id_and_type(client_id, YAHOO_CONNECTION_PAGER); + + pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER, + YPACKET_STATUS_DEFAULT, yid->yd->session_id); + + snprintf(resp, sizeof(resp), "%d", response); + + yahoo_packet_hash(pkt, 1, yid->yd->user); + yahoo_packet_hash(pkt, 5, sfd->who); + yahoo_packet_hash(pkt, 265, sfd->id); + yahoo_packet_hash(pkt, 222, resp); + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); + + if(response == YAHOO_FILE_TRANSFER_REJECT) + yahoo_remove_active_transfer(sfd); +} + +static void yahoo_process_ft_connection(struct yahoo_input_data *yid, int over) +{ + struct send_file_data *sfd; + struct yahoo_data *yd = yid->yd; + + sfd = yahoo_get_active_transfer_with_yid(yid); + + if (!sfd) { + LOG(("Something funny happened. yid %p has no sfd.\n", yid)); + return; + } + + /* + * We want to handle only the complete data with HEAD since we don't + * want a situation where both the GET and HEAD are active. + * With SEND, we really can't do much with partial response + */ + if ((sfd->state == FT_STATE_HEAD || sfd->state == FT_STATE_SEND) + && !over) + return; + + if (sfd->state == FT_STATE_HEAD) { + /* Do a GET */ + char url[256]; + char buff[1024]; + char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL; + + struct yahoo_input_data *yid_ft = + y_new0(struct yahoo_input_data, 1); + + yid_ft->yd = yid->yd; + yid_ft->type = YAHOO_CONNECTION_FT; + + inputs = y_list_prepend(inputs, yid_ft); + sfd->yid = yid_ft; + sfd->state = FT_STATE_RECV; + + token_enc = yahoo_urlencode(sfd->token); + sender_enc = yahoo_urlencode(sfd->who); + recv_enc = yahoo_urlencode(yd->user); + + snprintf(url, sizeof(url), + "http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr, + token_enc, sender_enc, recv_enc); + + snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, + yd->cookie_t); + + + yahoo_http_get(yd->client_id, url, buff, 1, 1, + _yahoo_http_connected, yid_ft); + + FREE(token_enc); + FREE(sender_enc); + FREE(recv_enc); + } + else if (sfd->state == FT_STATE_RECV || + sfd->state == FT_STATE_RECV_START) { + + unsigned char *data_begin = NULL; + + if (yid->rxlen == 0) + yahoo_remove_active_transfer(sfd); + + if (sfd->state != FT_STATE_RECV_START && + (data_begin = + (unsigned char *)strstr((char *)yid->rxqueue, + "\r\n\r\n"))) { + + sfd->state = FT_STATE_RECV_START; + + yid->rxlen -= 4+(data_begin-yid->rxqueue)/sizeof(char); + data_begin += 4; + + if (yid->rxlen > 0) + YAHOO_CALLBACK(ext_yahoo_got_ft_data) + (yd->client_id, data_begin, + yid->rxlen, sfd->data); + } + else if (sfd->state == FT_STATE_RECV_START) + YAHOO_CALLBACK(ext_yahoo_got_ft_data) (yd->client_id, + yid->rxqueue, yid->rxlen, sfd->data); + + FREE(yid->rxqueue); + yid->rxqueue = NULL; + yid->rxlen = 0; + } + else if (sfd->state == FT_STATE_SEND) { + /* Sent file completed */ + int len = 0; + char *off = strstr((char *)yid->rxqueue, "Content-Length: "); + + if (off) { + off += 16; + len = atoi(off); + } + + if (len < sfd->size) + YAHOO_CALLBACK(ext_yahoo_file_transfer_done) + (yd->client_id, + YAHOO_FILE_TRANSFER_FAILED, sfd->data); + else + YAHOO_CALLBACK(ext_yahoo_file_transfer_done) + (yd->client_id, + YAHOO_FILE_TRANSFER_DONE, sfd->data); + + yahoo_remove_active_transfer(sfd); + } } +/* End File Transfer */ enum yahoo_status yahoo_current_status(int id) { struct yahoo_data *yd = find_conn_by_id(id); - if(!yd) + if (!yd) return YAHOO_STATUS_OFFLINE; return yd->current_status; } -const YList * yahoo_get_buddylist(int id) +const YList *yahoo_get_buddylist(int id) { struct yahoo_data *yd = find_conn_by_id(id); - if(!yd) + if (!yd) return NULL; return yd->buddies; } -const YList * yahoo_get_ignorelist(int id) +const YList *yahoo_get_ignorelist(int id) { struct yahoo_data *yd = find_conn_by_id(id); - if(!yd) + if (!yd) return NULL; return yd->ignore; } -const YList * yahoo_get_identities(int id) +const YList *yahoo_get_identities(int id) { struct yahoo_data *yd = find_conn_by_id(id); - if(!yd) + if (!yd) return NULL; return yd->identities; } -const char * yahoo_get_cookie(int id, const char *which) +const char *yahoo_get_cookie(int id, const char *which) { struct yahoo_data *yd = find_conn_by_id(id); - if(!yd) + if (!yd) return NULL; - if(!strncasecmp(which, "y", 1)) + if (!strncasecmp(which, "y", 1)) return yd->cookie_y; - if(!strncasecmp(which, "t", 1)) + if (!strncasecmp(which, "b", 1)) + return yd->cookie_b; + if (!strncasecmp(which, "t", 1)) return yd->cookie_t; - if(!strncasecmp(which, "c", 1)) + if (!strncasecmp(which, "c", 1)) return yd->cookie_c; - if(!strncasecmp(which, "login", 5)) + if (!strncasecmp(which, "login", 5)) return yd->login_cookie; return NULL; } -void yahoo_get_url_handle(int id, const char *url, - yahoo_get_url_handle_callback callback, void *data) -{ - struct yahoo_data *yd = find_conn_by_id(id); - if(!yd) - return; - - yahoo_get_url_fd(id, url, yd, callback, data); -} - -const char * yahoo_get_profile_url( void ) +const char *yahoo_get_profile_url(void) { return profile_url; } - diff --git a/protocols/yahoo/yahoo.c b/protocols/yahoo/yahoo.c index b61f6ff9..9f7a7896 100644 --- a/protocols/yahoo/yahoo.c +++ b/protocols/yahoo/yahoo.c @@ -1,7 +1,7 @@ /* * libyahoo2 wrapper to BitlBee * - * Mostly Copyright 2004 Wilmer van der Gaast <wilmer@gaast.net> + * Mostly Copyright 2004-2010 Wilmer van der Gaast <wilmer@gaast.net> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -137,10 +137,15 @@ 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 ); @@ -152,7 +157,7 @@ static void byahoo_logout( struct im_connection *ic ) GSList *l; while( ic->groupchats ) - imcb_chat_free( ic->groupchats ); + imcb_chat_free( ic->groupchats->data ); for( l = yd->buddygroups; l; l = l->next ) { @@ -265,8 +270,32 @@ static void byahoo_keepalive( struct im_connection *ic ) static void byahoo_add_buddy( struct im_connection *ic, char *who, char *group ) { struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; + bee_user_t *bu; - yahoo_add_buddy( yd->y2_id, who, group ? group : BYAHOO_DEFAULT_GROUP, NULL ); + if( group && ( bu = bee_user_by_handle( ic->bee, ic, who ) ) && bu->group ) + { + GSList *bgl; + + /* If the person is in our list already, this is a group change. */ + yahoo_change_buddy_group( yd->y2_id, who, bu->group->name, group ); + + /* No idea how often people have people in multiple groups and + BitlBee doesn't currently support this anyway .. but keep + this struct up-to-date for now. */ + for( bgl = yd->buddygroups; bgl; bgl = bgl->next ) + { + struct byahoo_buddygroups *bg = bgl->data; + + if( g_strcasecmp( bg->buddy, who ) == 0 && + g_strcasecmp( bg->group, bu->group->name ) == 0 ) + { + g_free( bg->group ); + bg->group = g_strdup( group ); + } + } + } + else + yahoo_add_buddy( yd->y2_id, who, group ? group : BYAHOO_DEFAULT_GROUP, NULL ); } static void byahoo_remove_buddy( struct im_connection *ic, char *who, char *group ) @@ -335,14 +364,14 @@ static void byahoo_auth_allow( struct im_connection *ic, const char *who ) { struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; - yahoo_accept_buddy_ymsg13( yd->y2_id, NULL, who ); + yahoo_confirm_buddy( yd->y2_id, who, 0, "" ); } static void byahoo_auth_deny( struct im_connection *ic, const char *who ) { struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; - yahoo_reject_buddy_ymsg13( yd->y2_id, NULL, who, NULL ); + yahoo_confirm_buddy( yd->y2_id, who, 1, "" ); } void byahoo_initmodule( ) @@ -415,7 +444,7 @@ void byahoo_connect_callback( gpointer data, gint source, b_input_condition cond return; } - d->callback( d->fd, 0, d->data ); + d->callback( NULL + d->fd, 0, d->data ); g_free( d ); } @@ -435,7 +464,7 @@ gboolean byahoo_read_ready_callback( gpointer data, gint source, b_input_conditi /* WTF doesn't libyahoo clean this up? */ return FALSE; - yahoo_read_ready( d->id, d->fd, d->data ); + yahoo_read_ready( d->id, NULL + d->fd, d->data ); return TRUE; } @@ -452,7 +481,7 @@ gboolean byahoo_write_ready_callback( gpointer data, gint source, b_input_condit { struct byahoo_write_ready_data *d = data; - return yahoo_write_ready( d->id, d->fd, d->data ); + return yahoo_write_ready( d->id, NULL + d->fd, d->data ); } void ext_yahoo_login_response( int id, int succ, const char *url ) @@ -481,7 +510,7 @@ void ext_yahoo_login_response( int id, int succ, const char *url ) else { char *errstr; - int allow_reconnect = TRUE; + int allow_reconnect = FALSE; yd->logged_in = FALSE; @@ -491,13 +520,15 @@ void ext_yahoo_login_response( int id, int succ, const char *url ) errstr = "Incorrect Yahoo! password"; else if( succ == YAHOO_LOGIN_LOCK ) errstr = "Yahoo! account locked"; + else if( succ == 1236 ) + errstr = "Yahoo! account locked or machine temporarily banned"; else if( succ == YAHOO_LOGIN_DUPL ) - { errstr = "Logged in on a different machine or device"; - allow_reconnect = FALSE; - } else if( succ == YAHOO_LOGIN_SOCK ) + { errstr = "Socket problem"; + allow_reconnect = TRUE; + } else errstr = "Unknown error"; @@ -600,17 +631,16 @@ void ext_yahoo_status_changed( int id, const char *who, int stat, const char *ms state_string = "Offline"; flags = 0; break; - case YAHOO_STATUS_NOTIFY: - state_string = "Notify"; - break; } imcb_buddy_status( ic, who, flags, state_string, msg ); - /* Not implemented yet... if( stat == YAHOO_STATUS_IDLE ) - imcb_buddy_times( ic, who, 0, away ); - */ + imcb_buddy_times( ic, who, 0, idle ); +} + +void ext_yahoo_got_buzz( int id, const char *me, const char *who, long tm ) +{ } void ext_yahoo_got_im( int id, const char *me, const char *who, const char *msg, long tm, int stat, int utf8 ) @@ -626,15 +656,22 @@ void ext_yahoo_got_im( int id, const char *me, const char *who, const char *msg, } } -void ext_yahoo_got_file( int id, - const char *ignored, - const char *who, const char *url, long expires, const char *msg, const char *fname, unsigned long fesize ) +void ext_yahoo_got_file( int id, const char *ignored, const char *who, const char *msg, + const char *fname, unsigned long fesize, char *trid ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); imcb_log( ic, "Got a file transfer (file = %s) from %s. Ignoring for now due to lack of support.", fname, who ); } +void ext_yahoo_got_ft_data( int id, const unsigned char *in, int len, void *data ) +{ +} + +void ext_yahoo_file_transfer_done( int id, int result, void *data ) +{ +} + void ext_yahoo_typing_notify( int id, const char *ignored, const char *who, int stat ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); @@ -645,7 +682,7 @@ void ext_yahoo_typing_notify( int id, const char *ignored, const char *who, int imcb_buddy_typing( ic, (char*) who, 0 ); } -void ext_yahoo_system_message( int id, const char *msg ) +void ext_yahoo_system_message( int id, const char *me, const char *who, const char *msg ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); @@ -667,9 +704,10 @@ void ext_yahoo_error( int id, const char *err, int fatal, int num ) } /* TODO: Clear up the mess of inp and d structures */ -int ext_yahoo_add_handler( int id, int fd, yahoo_input_condition cond, void *data ) +int ext_yahoo_add_handler( int id, void *fd_, yahoo_input_condition cond, void *data ) { struct byahoo_input_data *inp = g_new0( struct byahoo_input_data, 1 ); + int fd = (long) fd_; if( cond == YAHOO_INPUT_READ ) { @@ -680,7 +718,7 @@ int ext_yahoo_add_handler( int id, int fd, yahoo_input_condition cond, void *dat d->data = data; inp->d = d; - d->tag = inp->h = b_input_add( fd, GAIM_INPUT_READ, (b_event_handler) byahoo_read_ready_callback, (gpointer) d ); + d->tag = inp->h = b_input_add( fd, B_EV_IO_READ, (b_event_handler) byahoo_read_ready_callback, (gpointer) d ); } else if( cond == YAHOO_INPUT_WRITE ) { @@ -691,17 +729,17 @@ int ext_yahoo_add_handler( int id, int fd, yahoo_input_condition cond, void *dat d->data = data; inp->d = d; - d->tag = inp->h = b_input_add( fd, GAIM_INPUT_WRITE, (b_event_handler) byahoo_write_ready_callback, (gpointer) d ); + d->tag = inp->h = b_input_add( fd, B_EV_IO_WRITE, (b_event_handler) byahoo_write_ready_callback, (gpointer) d ); } else { g_free( inp ); - return( -1 ); + return -1; /* Panic... */ } byahoo_inputs = g_slist_append( byahoo_inputs, inp ); - return( inp->h ); + return inp->h; } void ext_yahoo_remove_handler( int id, int tag ) @@ -725,7 +763,7 @@ void ext_yahoo_remove_handler( int id, int tag ) b_event_remove( tag ); } -int ext_yahoo_connect_async( int id, const char *host, int port, yahoo_connect_callback callback, void *data ) +int ext_yahoo_connect_async( int id, const char *host, int port, yahoo_connect_callback callback, void *data, int use_ssl ) { struct byahoo_connect_callback_data *d; int fd; @@ -741,59 +779,56 @@ int ext_yahoo_connect_async( int id, const char *host, int port, yahoo_connect_c d->data = data; d->id = id; - return( fd ); + return fd; } -/* Because we don't want asynchronous connects in BitlBee, and because - libyahoo doesn't seem to use this one anyway, this one is now defunct. */ -int ext_yahoo_connect(const char *host, int port) +char *ext_yahoo_get_ip_addr( const char *domain ) { -#if 0 - struct sockaddr_in serv_addr; - static struct hostent *server; - static char last_host[256]; - int servfd; - char **p; + return NULL; +} - if(last_host[0] || g_strcasecmp(last_host, host)!=0) { - if(!(server = gethostbyname(host))) { - return -1; - } - strncpy(last_host, host, 255); - } +int ext_yahoo_write( void *fd, char *buf, int len ) +{ + return write( (long) fd, buf, len ); +} - if((servfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { - return -1; - } +int ext_yahoo_read( void *fd, char *buf, int len ) +{ + return read( (long) fd, buf, len ); +} - for (p = server->h_addr_list; *p; p++) - { - memset(&serv_addr, 0, sizeof(serv_addr)); - serv_addr.sin_family = AF_INET; - memcpy(&serv_addr.sin_addr.s_addr, *p, server->h_length); - serv_addr.sin_port = htons(port); - - if(connect(servfd, (struct sockaddr *) &serv_addr, - sizeof(serv_addr)) == -1) { - return -1; - } else { - return servfd; - } - } +void ext_yahoo_close( void *fd ) +{ + close( (long) fd ); +} - closesocket(servfd); -#endif +void ext_yahoo_got_buddy_change_group( int id, const char *me, const char *who, + const char *old_group, const char *new_group ) +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + + imcb_add_buddy( ic, who, new_group ); +} + +/* Because we don't want asynchronous connects in BitlBee, and because + libyahoo doesn't seem to use this one anyway, this one is now defunct. */ +int ext_yahoo_connect(const char *host, int port) +{ return -1; } static void byahoo_accept_conf( void *data ) { struct byahoo_conf_invitation *inv = data; - struct groupchat *b; + struct groupchat *b = NULL; + GSList *l; - for( b = inv->ic->groupchats; b; b = b->next ) + for( l = inv->ic->groupchats; l; l = l->next ) + { + b = l->data; if( b == inv->c ) break; + } if( b != NULL ) { @@ -827,6 +862,10 @@ void ext_yahoo_got_conf_invite( int id, const char *ignored, 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 ); @@ -855,9 +894,7 @@ void ext_yahoo_conf_userdecline( int id, const char *ignored, const char *who, c void ext_yahoo_conf_userjoin( int id, const char *ignored, const char *who, const char *room ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); - struct groupchat *c; - - for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next ); + struct groupchat *c = bee_chat_by_title( ic->bee, ic, room ); if( c ) imcb_chat_add_buddy( c, (char*) who ); @@ -867,9 +904,7 @@ void ext_yahoo_conf_userleave( int id, const char *ignored, const char *who, con { struct im_connection *ic = byahoo_get_ic_by_id( id ); - struct groupchat *c; - - for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next ); + struct groupchat *c = bee_chat_by_title( ic->bee, ic, room ); if( c ) imcb_chat_remove_buddy( c, (char*) who, "" ); @@ -879,9 +914,7 @@ void ext_yahoo_conf_message( int id, const char *ignored, const char *who, const { struct im_connection *ic = byahoo_get_ic_by_id( id ); char *m = byahoo_strip( msg ); - struct groupchat *c; - - for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next ); + struct groupchat *c = bee_chat_by_title( ic->bee, ic, room ); if( c ) imcb_chat_msg( c, (char*) who, (char*) m, 0, 0 ); @@ -892,7 +925,7 @@ void ext_yahoo_chat_cat_xml( int id, const char *xml ) { } -void ext_yahoo_chat_join( int id, const char *who, const char *room, const char *topic, YList *members, int fd ) +void ext_yahoo_chat_join( int id, const char *who, const char *room, const char *topic, YList *members, void *fd ) { } @@ -920,25 +953,18 @@ void ext_yahoo_chat_yahooerror( int id, const char *me ) { } -void ext_yahoo_contact_auth_request( int id, const char *myid, const char *who, const char *msg ) -{ - struct im_connection *ic = byahoo_get_ic_by_id( id ); - - imcb_ask_auth( ic, who, NULL ); -} - void ext_yahoo_contact_added( int id, const char *myid, const char *who, const char *msg ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); - imcb_add_buddy( ic, (char*) who, NULL ); + imcb_ask_auth( ic, who, msg ); } void ext_yahoo_rejected( int id, const char *who, const char *msg ) { } -void ext_yahoo_game_notify( int id, const char *me, const char *who, int stat ) +void ext_yahoo_game_notify( int id, const char *me, const char *who, int stat, const char *msg ) { } @@ -983,7 +1009,7 @@ void ext_yahoo_got_webcam_image( int id, const char * who, const unsigned char * { } -void ext_yahoo_got_ping( int id, const char *msg) +void ext_yahoo_got_ping( int id, const char *msg ) { } diff --git a/protocols/yahoo/yahoo2.h b/protocols/yahoo/yahoo2.h index 2184a321..589aaa5a 100644 --- a/protocols/yahoo/yahoo2.h +++ b/protocols/yahoo/yahoo2.h @@ -50,19 +50,18 @@ extern "C" { #include "yahoo2_types.h" -/* returns the socket descriptor for a given pager connection. shouldn't be needed */ -int yahoo_get_fd(int id); +/* returns the socket descriptor object for a given pager connection. shouldn't be needed */ + void *yahoo_get_fd(int id); /* says how much logging to do */ /* see yahoo2_types.h for the different values */ -int yahoo_set_log_level(enum yahoo_log_level level); -enum yahoo_log_level yahoo_get_log_level( void ); + int yahoo_set_log_level(enum yahoo_log_level level); + enum yahoo_log_level yahoo_get_log_level(void); /* these functions should be self explanatory */ /* who always means the buddy you're acting on */ /* id is the successful value returned by yahoo_init */ - /* init returns a connection id used to identify the connection hereon */ /* or 0 on failure */ /* you must call init before calling any other function */ @@ -87,101 +86,129 @@ enum yahoo_log_level yahoo_get_log_level( void ); * * You should set at least local_host if you intend to use webcams */ -int yahoo_init_with_attributes(const char *username, const char *password, ...); + int yahoo_init_with_attributes(const char *username, + const char *password, ...); /* yahoo_init does the same as yahoo_init_with_attributes, assuming defaults * for all attributes */ -int yahoo_init(const char *username, const char *password); - - + int yahoo_init(const char *username, const char *password); /* release all resources held by this session */ /* you need to call yahoo_close for a session only if * yahoo_logoff is never called for it (ie, it was never logged in) */ -void yahoo_close(int id); + void yahoo_close(int id); /* login logs in to the server */ /* initial is of type enum yahoo_status. see yahoo2_types.h */ -void yahoo_login(int id, int initial); -void yahoo_logoff(int id); + void yahoo_login(int id, int initial); + void yahoo_logoff(int id); /* reloads status of all buddies */ -void yahoo_refresh(int id); + void yahoo_refresh(int id); /* activates/deactivates an identity */ -void yahoo_set_identity_status(int id, const char * identity, int active); + void yahoo_set_identity_status(int id, const char *identity, + int active); /* regets the entire buddy list from the server */ -void yahoo_get_list(int id); + void yahoo_get_list(int id); /* download buddy contact information from your yahoo addressbook */ -void yahoo_get_yab(int id); + void yahoo_get_yab(int id); /* add/modify an address book entry. if yab->dbid is set, it will */ /* modify that entry else it creates a new entry */ -void yahoo_set_yab(int id, struct yab * yab); -void yahoo_keepalive(int id); -void yahoo_chat_keepalive(int id); + void yahoo_set_yab(int id, struct yab *yab); + void yahoo_keepalive(int id); + void yahoo_chat_keepalive(int id); /* from is the identity you're sending from. if NULL, the default is used */ /* utf8 is whether msg is a utf8 string or not. */ -void yahoo_send_im(int id, const char *from, const char *who, const char *msg, int utf8, int picture); + void yahoo_send_im(int id, const char *from, const char *who, + const char *msg, int utf8, int picture); + void yahoo_send_buzz(int id, const char *from, const char *who); /* if type is true, send typing notice, else send stopped typing notice */ -void yahoo_send_typing(int id, const char *from, const char *who, int typ); + void yahoo_send_typing(int id, const char *from, const char *who, + int typ); /* used to set away/back status. */ /* away says whether the custom message is an away message or a sig */ -void yahoo_set_away(int id, enum yahoo_status state, const char *msg, int away); - -void yahoo_add_buddy(int id, const char *who, const char *group, const char *msg); -void yahoo_remove_buddy(int id, const char *who, const char *group); -void yahoo_reject_buddy(int id, const char *who, const char *msg); -void yahoo_stealth_buddy(int id, const char *who, int unstealth); + void yahoo_set_away(int id, enum yahoo_status state, const char *msg, + int away); + + void yahoo_add_buddy(int id, const char *who, const char *group, + const char *msg); + void yahoo_remove_buddy(int id, const char *who, const char *group); + void yahoo_confirm_buddy(int id, const char *who, int reject, + const char *msg); + void yahoo_stealth_buddy(int id, const char *who, int unstealth); /* if unignore is true, unignore, else ignore */ -void yahoo_ignore_buddy(int id, const char *who, int unignore); -void yahoo_change_buddy_group(int id, const char *who, const char *old_group, const char *new_group); -void yahoo_group_rename(int id, const char *old_group, const char *new_group); - -void yahoo_conference_invite(int id, const char * from, YList *who, const char *room, const char *msg); -void yahoo_conference_addinvite(int id, const char * from, const char *who, const char *room, const YList * members, const char *msg); -void yahoo_conference_decline(int id, const char * from, YList *who, const char *room, const char *msg); -void yahoo_conference_message(int id, const char * from, YList *who, const char *room, const char *msg, int utf8); -void yahoo_conference_logon(int id, const char * from, YList *who, const char *room); -void yahoo_conference_logoff(int id, const char * from, YList *who, const char *room); + void yahoo_ignore_buddy(int id, const char *who, int unignore); + void yahoo_change_buddy_group(int id, const char *who, + const char *old_group, const char *new_group); + void yahoo_group_rename(int id, const char *old_group, + const char *new_group); + + void yahoo_conference_invite(int id, const char *from, YList *who, + const char *room, const char *msg); + void yahoo_conference_addinvite(int id, const char *from, + const char *who, const char *room, const YList *members, + const char *msg); + void yahoo_conference_decline(int id, const char *from, YList *who, + const char *room, const char *msg); + void yahoo_conference_message(int id, const char *from, YList *who, + const char *room, const char *msg, int utf8); + void yahoo_conference_logon(int id, const char *from, YList *who, + const char *room); + void yahoo_conference_logoff(int id, const char *from, YList *who, + const char *room); /* Get a list of chatrooms */ -void yahoo_get_chatrooms(int id,int chatroomid); + void yahoo_get_chatrooms(int id, int chatroomid); /* join room with specified roomname and roomid */ -void yahoo_chat_logon(int id, const char *from, const char *room, const char *roomid); + void yahoo_chat_logon(int id, const char *from, const char *room, + const char *roomid); /* Send message "msg" to room with specified roomname, msgtype is 1-normal message or 2-/me mesage */ -void yahoo_chat_message(int id, const char *from, const char *room, const char *msg, const int msgtype, const int utf8); + void yahoo_chat_message(int id, const char *from, const char *room, + const char *msg, const int msgtype, const int utf8); /* Log off chat */ -void yahoo_chat_logoff(int id, const char *from); + void yahoo_chat_logoff(int id, const char *from); /* requests a webcam feed */ /* who is the person who's webcam you would like to view */ /* if who is null, then you're the broadcaster */ -void yahoo_webcam_get_feed(int id, const char *who); -void yahoo_webcam_close_feed(int id, const char *who); + void yahoo_webcam_get_feed(int id, const char *who); + void yahoo_webcam_close_feed(int id, const char *who); /* sends an image when uploading */ /* image points to a JPEG-2000 image, length is the length of the image */ /* in bytes. The timestamp is the time in milliseconds since we started the */ /* webcam. */ -void yahoo_webcam_send_image(int id, unsigned char *image, unsigned int length, unsigned int timestamp); + void yahoo_webcam_send_image(int id, unsigned char *image, + unsigned int length, unsigned int timestamp); /* this function should be called if we want to allow a user to watch the */ /* webcam. Who is the user we want to accept. */ /* Accept user (accept = 1), decline user (accept = 0) */ -void yahoo_webcam_accept_viewer(int id, const char* who, int accept); + void yahoo_webcam_accept_viewer(int id, const char *who, int accept); /* send an invitation to a user to view your webcam */ -void yahoo_webcam_invite(int id, const char *who); + void yahoo_webcam_invite(int id, const char *who); /* will set up a connection and initiate file transfer. * callback will be called with the fd that you should write * the file data to */ -void yahoo_send_file(int id, const char *who, const char *msg, const char *name, unsigned long size, + void yahoo_send_file(int id, const char *who, const char *msg, + const char *name, unsigned long size, yahoo_get_fd_callback callback, void *data); +/* + * Respond to a file transfer request. Be sure to provide the callback data + * since that is your only chance to recognize future callbacks + */ + void yahoo_send_file_transfer_response(int client_id, int response, + char *id, void *data); + + /* send a search request */ -void yahoo_search(int id, enum yahoo_search_type t, const char *text, enum yahoo_search_gender g, enum yahoo_search_agerange ar, + void yahoo_search(int id, enum yahoo_search_type t, const char *text, + enum yahoo_search_gender g, enum yahoo_search_agerange ar, int photo, int yahoo_only); /* continue last search @@ -189,40 +216,32 @@ void yahoo_search(int id, enum yahoo_search_type t, const char *text, enum yahoo * * where the above three are passed to ext_yahoo_got_search_result */ -void yahoo_search_again(int id, int start); - -/* returns a socket fd to a url for downloading a file. */ -void yahoo_get_url_handle(int id, const char *url, - yahoo_get_url_handle_callback callback, void *data); + void yahoo_search_again(int id, int start); /* these should be called when input is available on a fd */ /* registered by ext_yahoo_add_handler */ /* if these return negative values, errno may be set */ -int yahoo_read_ready(int id, int fd, void *data); -int yahoo_write_ready(int id, int fd, void *data); + int yahoo_read_ready(int id, void *fd, void *data); + int yahoo_write_ready(int id, void *fd, void *data); /* utility functions. these do not hit the server */ -enum yahoo_status yahoo_current_status(int id); -const YList * yahoo_get_buddylist(int id); -const YList * yahoo_get_ignorelist(int id); -const YList * yahoo_get_identities(int id); + enum yahoo_status yahoo_current_status(int id); + const YList *yahoo_get_buddylist(int id); + const YList *yahoo_get_ignorelist(int id); + const YList *yahoo_get_identities(int id); /* 'which' could be y, t, c or login. This may change in later versions. */ -const char * yahoo_get_cookie(int id, const char *which); + const char *yahoo_get_cookie(int id, const char *which); /* returns the url used to get user profiles - you must append the user id */ /* as of now this is http://profiles.yahoo.com/ */ /* You'll have to do urlencoding yourself, but see yahoo_httplib.h first */ -const char * yahoo_get_profile_url( void ); - -void yahoo_buddyicon_request(int id, const char *who); + const char *yahoo_get_profile_url(void); -void yahoo_accept_buddy_ymsg13(int,const char*,const char*); -void yahoo_reject_buddy_ymsg13(int,const char*,const char*,const char*); + void yahoo_buddyicon_request(int id, const char *who); #include "yahoo_httplib.h" #ifdef __cplusplus } #endif - #endif diff --git a/protocols/yahoo/yahoo2_callbacks.h b/protocols/yahoo/yahoo2_callbacks.h index e2c8ea42..0dccf188 100644 --- a/protocols/yahoo/yahoo2_callbacks.h +++ b/protocols/yahoo/yahoo2_callbacks.h @@ -29,7 +29,6 @@ * declared in this file and defined in libyahoo2.c */ - #ifndef YAHOO2_CALLBACKS_H #define YAHOO2_CALLBACKS_H @@ -45,25 +44,26 @@ extern "C" { * Callback interface for libyahoo2 */ -typedef enum { - YAHOO_INPUT_READ = 1 << 0, - YAHOO_INPUT_WRITE = 1 << 1, - YAHOO_INPUT_EXCEPTION = 1 << 2 -} yahoo_input_condition; + typedef enum { + YAHOO_INPUT_READ = 1 << 0, + YAHOO_INPUT_WRITE = 1 << 1, + YAHOO_INPUT_EXCEPTION = 1 << 2 + } yahoo_input_condition; /* * A callback function called when an asynchronous connect completes. * * Params: - * fd - The file descriptor that has been connected, or -1 on error + * fd - The file descriptor object that has been connected, or NULL on + * error * error - The value of errno set by the call to connect or 0 if no error * Set both fd and error to 0 if the connect was cancelled by the * user * callback_data - the callback_data passed to the ext_yahoo_connect_async * function */ -typedef void (*yahoo_connect_callback)(int fd, int error, void *callback_data); - + typedef void (*yahoo_connect_callback) (void *fd, int error, + void *callback_data); /* * The following functions need to be implemented in the client @@ -93,8 +93,8 @@ struct yahoo_callbacks { * succ - enum yahoo_login_status * url - url to reactivate account if locked */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_login_response)(int id, int succ, const char *url); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_login_response) (int id, int succ, + const char *url); /* * Name: ext_yahoo_got_buddies @@ -103,8 +103,7 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_login_response)(int id, int succ, const char * id - the id that identifies the server connection * buds - the buddy list */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddies)(int id, YList * buds); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddies) (int id, YList *buds); /* * Name: ext_yahoo_got_ignore @@ -113,8 +112,7 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddies)(int id, YList * buds); * id - the id that identifies the server connection * igns - the ignore list */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ignore)(int id, YList * igns); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ignore) (int id, YList *igns); /* * Name: ext_yahoo_got_identities @@ -123,8 +121,7 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ignore)(int id, YList * igns); * id - the id that identifies the server connection * ids - the identity list */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_got_identities)(int id, YList * ids); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_identities) (int id, YList *ids); /* * Name: ext_yahoo_got_cookies @@ -132,8 +129,7 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_identities)(int id, YList * ids); * Params: * id - the id that identifies the server connection */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_got_cookies)(int id); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_cookies) (int id); /* * Name: ext_yahoo_got_ping @@ -142,8 +138,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_cookies)(int id); * id - the id that identifies the server connection * errormsg - optional error message */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ping)(int id, const char *errormsg); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ping) (int id, + const char *errormsg); /* * Name: ext_yahoo_status_changed @@ -158,8 +154,21 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ping)(int id, const char *errormsg); * mobile - this is set for mobile users/buddies * TODO: add support for pager, chat, and game states */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_status_changed)(int id, const char *who, int stat, const char *msg, int away, int idle, int mobile); + void YAHOO_CALLBACK_TYPE(ext_yahoo_status_changed) (int id, + const char *who, int stat, const char *msg, int away, int idle, + int mobile); +/* + * Name: ext_yahoo_got_buzz + * Called when remote user sends you a buzz. + * Params: + * id - the id that identifies the server connection + * me - the identity the message was sent to + * who - the handle of the remote user + * tm - timestamp of message if offline + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buzz) (int id, const char *me, + const char *who, long tm); /* * Name: ext_yahoo_got_im @@ -176,8 +185,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_status_changed)(int id, const char *who, int * 5 * utf8 - whether the message is encoded as utf8 or not */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_got_im)(int id, const char *me, const char *who, const char *msg, long tm, int stat, int utf8); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_im) (int id, const char *me, + const char *who, const char *msg, long tm, int stat, int utf8); /* * Name: ext_yahoo_got_conf_invite @@ -190,8 +199,9 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_im)(int id, const char *me, const char *w * msg - the message * members - the initial members of the conference (null terminated list) */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_got_conf_invite)(int id, const char *me, const char *who, const char *room, const char *msg, YList *members); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_conf_invite) (int id, + const char *me, const char *who, const char *room, + const char *msg, YList *members); /* * Name: ext_yahoo_conf_userdecline @@ -203,8 +213,9 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_conf_invite)(int id, const char *me, cons * room - the room * msg - the declining message */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userdecline)(int id, const char *me, const char *who, const char *room, const char *msg); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userdecline) (int id, + const char *me, const char *who, const char *room, + const char *msg); /* * Name: ext_yahoo_conf_userjoin @@ -215,8 +226,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userdecline)(int id, const char *me, con * who - the user who has joined * room - the room joined */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userjoin)(int id, const char *me, const char *who, const char *room); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userjoin) (int id, + const char *me, const char *who, const char *room); /* * Name: ext_yahoo_conf_userleave @@ -227,8 +238,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userjoin)(int id, const char *me, const * who - the user who has left * room - the room left */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userleave)(int id, const char *me, const char *who, const char *room); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userleave) (int id, + const char *me, const char *who, const char *room); /* * Name: ext_yahoo_chat_cat_xml @@ -237,8 +248,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userleave)(int id, const char *me, const * id - the id that identifies the server connection * xml - ? */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_cat_xml)(int id, const char *xml); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_cat_xml) (int id, + const char *xml); /* * Name: ext_yahoo_chat_join @@ -251,10 +262,10 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_cat_xml)(int id, const char *xml); * topic - the topic of the room, freed by library after call * members - the initial members of the chatroom (null terminated YList * of yahoo_chat_member's) Must be freed by the client - * fd - the socket where the connection is coming from (for tracking) + * fd - the object where the connection is coming from (for tracking) */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_join)(int id, const char *me, const char *room, const char *topic, YList *members, int fd); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_join) (int id, const char *me, + const char *room, const char *topic, YList *members, void *fd); /* * Name: ext_yahoo_chat_userjoin @@ -265,8 +276,9 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_join)(int id, const char *me, const char * room - the room joined * who - the user who has joined, Must be freed by the client */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userjoin)(int id, const char *me, const char *room, struct yahoo_chat_member *who); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userjoin) (int id, + const char *me, const char *room, + struct yahoo_chat_member *who); /* * Name: ext_yahoo_chat_userleave @@ -277,8 +289,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userjoin)(int id, const char *me, const * room - the room left * who - the user who has left (Just the User ID) */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userleave)(int id, const char *me, const char *room, const char *who); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userleave) (int id, + const char *me, const char *room, const char *who); /* * Name: ext_yahoo_chat_message @@ -293,8 +305,9 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userleave)(int id, const char *me, const * 2 = /me type message * utf8 - whether the message is utf8 encoded or not */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_message)(int id, const char *me, const char *who, const char *room, const char *msg, int msgtype, int utf8); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_message) (int id, + const char *me, const char *who, const char *room, + const char *msg, int msgtype, int utf8); /* * @@ -309,8 +322,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_message)(int id, const char *me, const c * Returns: * nothing. */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahoologout)(int id, const char *me); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahoologout) (int id, + const char *me); /* * @@ -326,8 +339,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahoologout)(int id, const char *me); * Returns: * nothing. */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahooerror)(int id, const char *me); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahooerror) (int id, + const char *me); /* * Name: ext_yahoo_conf_message @@ -340,8 +353,9 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahooerror)(int id, const char *me); * msg - the message * utf8 - whether the message is utf8 encoded or not */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_message)(int id, const char *me, const char *who, const char *room, const char *msg, int utf8); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_message) (int id, + const char *me, const char *who, const char *room, + const char *msg, int utf8); /* * Name: ext_yahoo_got_file @@ -350,26 +364,42 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_message)(int id, const char *me, const c * id - the id that identifies the server connection * me - the identity the file was sent to * who - the user who sent the file - * url - the file url - * expires - the expiry date of the file on the server (timestamp) * msg - the message * fname- the file name if direct transfer * fsize- the file size if direct transfer + * trid - transfer id. Unique for this transfer + * + * NOTE: Subsequent callbacks for file transfer do not send all of this + * information again since it is wasteful. Implementations are expected to + * save this information and supply it as callback data when the file or + * confirmation is sent */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_got_file)(int id, const char *me, const char *who, const char *url, long expires, const char *msg, const char *fname, unsigned long fesize); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_file) (int id, const char *me, + const char *who, const char *msg, const char *fname, + unsigned long fesize, char *trid); /* - * Name: ext_yahoo_contact_auth_request - * Called when a contact wants to add you to his/her contact list + * Name: ext_yahoo_got_ft_data + * Called multiple times when parts of the file are received * Params: * id - the id that identifies the server connection - * myid - the identity s/he added - * who - who did it - * msg - any message sent + * in - The data + * len - Length of the data + * data - callback data */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_contact_auth_request)(int id, const char *myid, const char *who, const char *msg); + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ft_data) (int id, + const unsigned char *in, int len, void *data); +/* + * Name: ext_yahoo_file_transfer_done + * File transfer is done + * Params: + * id - the id that identifies the server connection + * result - To notify if it finished successfully or with a failure + * data - callback data + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_file_transfer_done) (int id, + int result, void *data); /* * Name: ext_yahoo_contact_added @@ -380,8 +410,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_contact_auth_request)(int id, const char *myi * who - who was added * msg - any message sent */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_contact_added)(int id, const char *myid, const char *who, const char *msg); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_contact_added) (int id, + const char *myid, const char *who, const char *msg); /* * Name: ext_yahoo_rejected @@ -391,8 +421,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_contact_added)(int id, const char *myid, cons * who - who rejected you * msg - any message sent */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_rejected)(int id, const char *who, const char *msg); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_rejected) (int id, const char *who, + const char *msg); /* * Name: ext_yahoo_typing_notify @@ -403,8 +433,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_rejected)(int id, const char *who, const char * who - the handle of the remote user * stat - 1 if typing, 0 if stopped typing */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_typing_notify)(int id, const char *me, const char *who, int stat); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_typing_notify) (int id, + const char *me, const char *who, int stat); /* * Name: ext_yahoo_game_notify @@ -414,9 +444,10 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_typing_notify)(int id, const char *me, const * me - the handle of the identity the notification is sent to * who - the handle of the remote user * stat - 1 if game, 0 if stopped gaming + * msg - game description and/or other text */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_game_notify)(int id, const char *me, const char *who, int stat); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_game_notify) (int id, const char *me, + const char *who, int stat, const char *msg); /* * Name: ext_yahoo_mail_notify @@ -427,17 +458,20 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_game_notify)(int id, const char *me, const ch * subj - the subject of the mail - NULL if only mail count * cnt - mail count - 0 if new mail notification */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_mail_notify)(int id, const char *from, const char *subj, int cnt); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_mail_notify) (int id, + const char *from, const char *subj, int cnt); /* * Name: ext_yahoo_system_message * System message * Params: * id - the id that identifies the server connection + * me - the handle of the identity the notification is sent to + * who - the source of the system message (there are different types) * msg - the message */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_system_message)(int id, const char *msg); + void YAHOO_CALLBACK_TYPE(ext_yahoo_system_message) (int id, + const char *me, const char *who, const char *msg); /* * Name: ext_yahoo_got_buddyicon @@ -449,7 +483,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_system_message)(int id, const char *msg); * url - the url to use to load the icon * checksum - the checksum of the icon content */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon)(int id, const char *me, const char *who, const char *url, int checksum); + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon) (int id, + const char *me, const char *who, const char *url, int checksum); /* * Name: ext_yahoo_got_buddyicon_checksum @@ -460,7 +495,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon)(int id, const char *me, const * who - the yahoo id of the buddy icon checksum is for * checksum - the checksum of the icon content */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_checksum)(int id, const char *me,const char *who, int checksum); + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_checksum) (int id, + const char *me, const char *who, int checksum); /* * Name: ext_yahoo_got_buddyicon_request @@ -470,7 +506,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_checksum)(int id, const char *m * me - the handle of the identity the notification is sent to * who - the yahoo id of the buddy that requested the buddy icon */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_request)(int id, const char *me, const char *who); + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_request) (int id, + const char *me, const char *who); /* * Name: ext_yahoo_got_buddyicon_request @@ -479,7 +516,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_request)(int id, const char *me * id - the id that identifies the server connection * url - remote url, the uploaded buddy icon can be fetched from */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_buddyicon_uploaded)(int id, const char *url); + void YAHOO_CALLBACK_TYPE(ext_yahoo_buddyicon_uploaded) (int id, + const char *url); /* * Name: ext_yahoo_got_webcam_image @@ -504,11 +542,11 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_buddyicon_uploaded)(int id, const char *url); * to transport then others. When image_size is 0 we can still receive * a timestamp to stay in sync */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_got_webcam_image)(int id, const char * who, - const unsigned char *image, unsigned int image_size, unsigned int real_size, + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_webcam_image) (int id, + const char *who, const unsigned char *image, + unsigned int image_size, unsigned int real_size, unsigned int timestamp); - /* * Name: ext_yahoo_webcam_invite * Called when you get a webcam invitation @@ -517,8 +555,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_webcam_image)(int id, const char * who, * me - identity the invitation is to * from - who the invitation is from */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite)(int id, const char *me, const char *from); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite) (int id, + const char *me, const char *from); /* * Name: ext_yahoo_webcam_invite_reply @@ -529,8 +567,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite)(int id, const char *me, const * from - who the invitation response is from * accept - 0 (decline), 1 (accept) */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite_reply)(int id, const char *me, const char *from, int accept); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite_reply) (int id, + const char *me, const char *from, int accept); /* * Name: ext_yahoo_webcam_closed @@ -544,8 +582,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite_reply)(int id, const char *me, * 3 = user declines permission * 4 = user does not have webcam online */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_closed)(int id, const char *who, int reason); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_closed) (int id, + const char *who, int reason); /* * Name: ext_yahoo_got_search_result @@ -559,8 +597,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_closed)(int id, const char *who, int r * these will be freed after this function returns, so * if you need to use the information, make a copy */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_got_search_result)(int id, int found, int start, int total, YList *contacts); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_search_result) (int id, + int found, int start, int total, YList *contacts); /* * Name: ext_yahoo_error @@ -571,8 +609,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_search_result)(int id, int found, int sta * fatal- whether this error is fatal to the connection or not * num - Which error is this */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_error)(int id, const char *err, int fatal, int num); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_error) (int id, const char *err, + int fatal, int num); /* * Name: ext_yahoo_webcam_viewer @@ -582,8 +620,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_error)(int id, const char *err, int fatal, in * who - the viewer * connect - 0=disconnect 1=connect 2=request */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_viewer)(int id, const char *who, int connect); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_viewer) (int id, + const char *who, int connect); /* * Name: ext_yahoo_webcam_data_request @@ -592,8 +630,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_viewer)(int id, const char *who, int c * id - the id that identifies the server connection * send - whether to send images or not */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_data_request)(int id, int send); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_data_request) (int id, + int send); /* * Name: ext_yahoo_log @@ -603,8 +641,7 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_data_request)(int id, int send); * Returns: * 0 */ -int YAHOO_CALLBACK_TYPE(ext_yahoo_log)(const char *fmt, ...); - + int YAHOO_CALLBACK_TYPE(ext_yahoo_log) (const char *fmt, ...); /* * Name: ext_yahoo_add_handler @@ -613,14 +650,14 @@ int YAHOO_CALLBACK_TYPE(ext_yahoo_log)(const char *fmt, ...); * when a YAHOO_INPUT_WRITE fd is ready. * Params: * id - the id that identifies the server connection - * fd - the fd on which to listen + * fd - the fd object on which to listen * cond - the condition on which to call the callback * data - callback data to pass to yahoo_*_ready * * Returns: a tag to be used when removing the handler */ -int YAHOO_CALLBACK_TYPE(ext_yahoo_add_handler)(int id, int fd, yahoo_input_condition cond, void *data); - + int YAHOO_CALLBACK_TYPE(ext_yahoo_add_handler) (int id, void *fd, + yahoo_input_condition cond, void *data); /* * Name: ext_yahoo_remove_handler @@ -629,8 +666,7 @@ int YAHOO_CALLBACK_TYPE(ext_yahoo_add_handler)(int id, int fd, yahoo_input_condi * id - the id that identifies the connection * tag - the handler tag to remove */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_remove_handler)(int id, int tag); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_remove_handler) (int id, int tag); /* * Name: ext_yahoo_connect @@ -641,12 +677,11 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_remove_handler)(int id, int tag); * Returns: * a unix file descriptor to the socket */ -int YAHOO_CALLBACK_TYPE(ext_yahoo_connect)(const char *host, int port); - + int YAHOO_CALLBACK_TYPE(ext_yahoo_connect) (const char *host, int port); /* * Name: ext_yahoo_connect_async - * Connect to a host:port asynchronously. This function should return + * Connect to a host:port asynchronously. This function should return * immediately returing a tag used to identify the connection handler, * or a pre-connect error (eg: host name lookup failure). * Once the connect completes (successfully or unsuccessfully), callback @@ -659,11 +694,74 @@ int YAHOO_CALLBACK_TYPE(ext_yahoo_connect)(const char *host, int port); * port - the port to connect on * callback - function to call when connect completes * callback_data - data to pass to the callback function + * use_ssl - Whether we need an SSL connection * Returns: - * a unix file descriptor to the socket + * a tag signifying the connection attempt + */ + int YAHOO_CALLBACK_TYPE(ext_yahoo_connect_async) (int id, + const char *host, int port, yahoo_connect_callback callback, + void *callback_data, int use_ssl); + +/* + * Name: ext_yahoo_get_ip_addr + * get IP Address for a domain name + * Params: + * domain - Domain name + * Returns: + * Newly allocated string containing the IP Address in IPv4 notation */ -int YAHOO_CALLBACK_TYPE(ext_yahoo_connect_async)(int id, const char *host, int port, - yahoo_connect_callback callback, void *callback_data); + char *YAHOO_CALLBACK_TYPE(ext_yahoo_get_ip_addr) (const char *domain); + +/* + * Name: ext_yahoo_write + * Write data from the buffer into the socket for the specified connection + * Params: + * fd - the file descriptor object that identifies this connection + * buf - Buffer to write the data from + * len - Length of the data + * Returns: + * Number of bytes written or -1 for error + */ + int YAHOO_CALLBACK_TYPE(ext_yahoo_write) (void *fd, char *buf, int len); + +/* + * Name: ext_yahoo_read + * Read data into a buffer from socket for the specified connection + * Params: + * fd - the file descriptor object that identifies this connection + * buf - Buffer to read the data into + * len - Max length to read + * Returns: + * Number of bytes read or -1 for error + */ + int YAHOO_CALLBACK_TYPE(ext_yahoo_read) (void *fd, char *buf, int len); + +/* + * Name: ext_yahoo_close + * Close the file descriptor object and free its resources. Libyahoo2 will not + * use this object again. + * Params: + * fd - the file descriptor object that identifies this connection + * Returns: + * Nothing + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_close) (void *fd); + +/* + * Name: ext_yahoo_got_buddy_change_group + * Acknowledgement of buddy changing group + * Params: + * id: client id + * me: The user + * who: Buddy name + * old_group: Old group name + * new_group: New group name + * Returns: + * Nothing + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddy_change_group) (int id, + const char *me, const char *who, const char *old_group, + const char *new_group); #ifdef USE_STRUCT_CALLBACKS }; @@ -672,7 +770,7 @@ int YAHOO_CALLBACK_TYPE(ext_yahoo_connect_async)(int id, const char *host, int p * if using a callback structure, call yahoo_register_callbacks * before doing anything else */ -void yahoo_register_callbacks(struct yahoo_callbacks * tyc); +void yahoo_register_callbacks(struct yahoo_callbacks *tyc); #undef YAHOO_CALLBACK_TYPE @@ -683,4 +781,3 @@ void yahoo_register_callbacks(struct yahoo_callbacks * tyc); #endif #endif - diff --git a/protocols/yahoo/yahoo2_types.h b/protocols/yahoo/yahoo2_types.h index f05acb3c..bbade5d8 100644 --- a/protocols/yahoo/yahoo2_types.h +++ b/protocols/yahoo/yahoo2_types.h @@ -28,74 +28,175 @@ extern "C" { #endif -enum yahoo_status { - YAHOO_STATUS_DISCONNECTED = -1, - YAHOO_STATUS_AVAILABLE = 0, - YAHOO_STATUS_BRB, - YAHOO_STATUS_BUSY, - YAHOO_STATUS_NOTATHOME, - YAHOO_STATUS_NOTATDESK, - YAHOO_STATUS_NOTINOFFICE, - YAHOO_STATUS_ONPHONE, - YAHOO_STATUS_ONVACATION, - YAHOO_STATUS_OUTTOLUNCH, - YAHOO_STATUS_STEPPEDOUT, - YAHOO_STATUS_INVISIBLE = 12, - YAHOO_STATUS_CUSTOM = 99, - YAHOO_STATUS_IDLE = 999, - YAHOO_STATUS_WEBLOGIN = 0x5a55aa55, - YAHOO_STATUS_OFFLINE = 0x5a55aa56, /* don't ask */ - YAHOO_STATUS_NOTIFY = 0x16 /* TYPING */ -}; -#define YAHOO_STATUS_GAME 0x2 /* Games don't fit into the regular status model */ - -enum yahoo_login_status { - YAHOO_LOGIN_OK = 0, - YAHOO_LOGIN_LOGOFF = 2, - YAHOO_LOGIN_UNAME = 3, - YAHOO_LOGIN_PASSWD = 13, - YAHOO_LOGIN_LOCK = 14, - YAHOO_LOGIN_DUPL = 99, - YAHOO_LOGIN_SOCK = -1, -}; - -enum ypacket_status { - YPACKET_STATUS_DISCONNECTED = -1, - YPACKET_STATUS_DEFAULT = 0, - YPACKET_STATUS_SERVERACK = 1, - YPACKET_STATUS_GAME = 0x2, - YPACKET_STATUS_AWAY = 0x4, - YPACKET_STATUS_CONTINUED = 0x5, - YPACKET_STATUS_INVISIBLE = 12, - YPACKET_STATUS_NOTIFY = 0x16, /* TYPING */ - YPACKET_STATUS_WEBLOGIN = 0x5a55aa55, - YPACKET_STATUS_OFFLINE = 0x5a55aa56 -}; - -enum yahoo_error { - E_UNKNOWN = -1, - E_CONNECTION = -2, - E_SYSTEM = -3, - E_CUSTOM = 0, - - /* responses from ignore buddy */ - E_IGNOREDUP = 2, - E_IGNORENONE = 3, - E_IGNORECONF = 12, - - /* conference */ - E_CONFNOTAVAIL = 20 -}; - -enum yahoo_log_level { - YAHOO_LOG_NONE = 0, - YAHOO_LOG_FATAL, - YAHOO_LOG_ERR, - YAHOO_LOG_WARNING, - YAHOO_LOG_NOTICE, - YAHOO_LOG_INFO, - YAHOO_LOG_DEBUG -}; + enum yahoo_service { /* these are easier to see in hex */ + YAHOO_SERVICE_LOGON = 1, + YAHOO_SERVICE_LOGOFF, + YAHOO_SERVICE_ISAWAY, + YAHOO_SERVICE_ISBACK, + YAHOO_SERVICE_IDLE, /* 5 (placemarker) */ + YAHOO_SERVICE_MESSAGE, + YAHOO_SERVICE_IDACT, + YAHOO_SERVICE_IDDEACT, + YAHOO_SERVICE_MAILSTAT, + YAHOO_SERVICE_USERSTAT, /* 0xa */ + YAHOO_SERVICE_NEWMAIL, + YAHOO_SERVICE_CHATINVITE, + YAHOO_SERVICE_CALENDAR, + YAHOO_SERVICE_NEWPERSONALMAIL, + YAHOO_SERVICE_NEWCONTACT, + YAHOO_SERVICE_ADDIDENT, /* 0x10 */ + YAHOO_SERVICE_ADDIGNORE, + YAHOO_SERVICE_PING, + YAHOO_SERVICE_GOTGROUPRENAME, /* < 1, 36(old), 37(new) */ + YAHOO_SERVICE_SYSMESSAGE = 0x14, + YAHOO_SERVICE_SKINNAME = 0x15, + YAHOO_SERVICE_PASSTHROUGH2 = 0x16, + YAHOO_SERVICE_CONFINVITE = 0x18, + YAHOO_SERVICE_CONFLOGON, + YAHOO_SERVICE_CONFDECLINE, + YAHOO_SERVICE_CONFLOGOFF, + YAHOO_SERVICE_CONFADDINVITE, + YAHOO_SERVICE_CONFMSG, + YAHOO_SERVICE_CHATLOGON, + YAHOO_SERVICE_CHATLOGOFF, + YAHOO_SERVICE_CHATMSG = 0x20, + YAHOO_SERVICE_GAMELOGON = 0x28, + YAHOO_SERVICE_GAMELOGOFF, + YAHOO_SERVICE_GAMEMSG = 0x2a, + YAHOO_SERVICE_FILETRANSFER = 0x46, + YAHOO_SERVICE_VOICECHAT = 0x4A, + YAHOO_SERVICE_NOTIFY, + YAHOO_SERVICE_VERIFY, + YAHOO_SERVICE_P2PFILEXFER, + YAHOO_SERVICE_PEERTOPEER = 0x4F, /* Checks if P2P possible */ + YAHOO_SERVICE_WEBCAM, + YAHOO_SERVICE_AUTHRESP = 0x54, + YAHOO_SERVICE_LIST, + YAHOO_SERVICE_AUTH = 0x57, + YAHOO_SERVICE_AUTHBUDDY = 0x6d, + YAHOO_SERVICE_ADDBUDDY = 0x83, + YAHOO_SERVICE_REMBUDDY, + YAHOO_SERVICE_IGNORECONTACT, /* > 1, 7, 13 < 1, 66, 13, 0 */ + YAHOO_SERVICE_REJECTCONTACT, + YAHOO_SERVICE_GROUPRENAME = 0x89, /* > 1, 65(new), 66(0), 67(old) */ + YAHOO_SERVICE_Y7_PING = 0x8A, + YAHOO_SERVICE_CHATONLINE = 0x96, /* > 109(id), 1, 6(abcde) < 0,1 */ + YAHOO_SERVICE_CHATGOTO, + YAHOO_SERVICE_CHATJOIN, /* > 1 104-room 129-1600326591 62-2 */ + YAHOO_SERVICE_CHATLEAVE, + YAHOO_SERVICE_CHATEXIT = 0x9b, + YAHOO_SERVICE_CHATADDINVITE = 0x9d, + YAHOO_SERVICE_CHATLOGOUT = 0xa0, + YAHOO_SERVICE_CHATPING, + YAHOO_SERVICE_COMMENT = 0xa8, + YAHOO_SERVICE_GAME_INVITE = 0xb7, + YAHOO_SERVICE_STEALTH_PERM = 0xb9, + YAHOO_SERVICE_STEALTH_SESSION = 0xba, + YAHOO_SERVICE_AVATAR = 0xbc, + YAHOO_SERVICE_PICTURE_CHECKSUM = 0xbd, + YAHOO_SERVICE_PICTURE = 0xbe, + YAHOO_SERVICE_PICTURE_UPDATE = 0xc1, + YAHOO_SERVICE_PICTURE_UPLOAD = 0xc2, + YAHOO_SERVICE_YAB_UPDATE = 0xc4, + YAHOO_SERVICE_Y6_VISIBLE_TOGGLE = 0xc5, /* YMSG13, key 13: 2 = invisible, 1 = visible */ + YAHOO_SERVICE_Y6_STATUS_UPDATE = 0xc6, /* YMSG13 */ + YAHOO_SERVICE_PICTURE_STATUS = 0xc7, /* YMSG13, key 213: 0 = none, 1 = avatar, 2 = picture */ + YAHOO_SERVICE_VERIFY_ID_EXISTS = 0xc8, + YAHOO_SERVICE_AUDIBLE = 0xd0, + YAHOO_SERVICE_Y7_PHOTO_SHARING = 0xd2, + YAHOO_SERVICE_Y7_CONTACT_DETAILS = 0xd3, /* YMSG13 */ + YAHOO_SERVICE_Y7_CHAT_SESSION = 0xd4, + YAHOO_SERVICE_Y7_AUTHORIZATION = 0xd6, /* YMSG13 */ + YAHOO_SERVICE_Y7_FILETRANSFER = 0xdc, /* YMSG13 */ + YAHOO_SERVICE_Y7_FILETRANSFERINFO, /* YMSG13 */ + YAHOO_SERVICE_Y7_FILETRANSFERACCEPT, /* YMSG13 */ + YAHOO_SERVICE_Y7_MINGLE = 0xe1, /* YMSG13 */ + YAHOO_SERVICE_Y7_CHANGE_GROUP = 0xe7, /* YMSG13 */ + YAHOO_SERVICE_MYSTERY = 0xef, /* Don't know what this is for */ + YAHOO_SERVICE_Y8_STATUS = 0xf0, /* YMSG15 */ + YAHOO_SERVICE_Y8_LIST = 0Xf1, /* YMSG15 */ + YAHOO_SERVICE_MESSAGE_CONFIRM = 0xfb, + YAHOO_SERVICE_WEBLOGIN = 0x0226, + YAHOO_SERVICE_SMS_MSG = 0x02ea + }; + + enum yahoo_status { + YAHOO_STATUS_AVAILABLE = 0, + YAHOO_STATUS_BRB, + YAHOO_STATUS_BUSY, + YAHOO_STATUS_NOTATHOME, + YAHOO_STATUS_NOTATDESK, + YAHOO_STATUS_NOTINOFFICE, + YAHOO_STATUS_ONPHONE, + YAHOO_STATUS_ONVACATION, + YAHOO_STATUS_OUTTOLUNCH, + YAHOO_STATUS_STEPPEDOUT, + YAHOO_STATUS_INVISIBLE = 12, + YAHOO_STATUS_CUSTOM = 99, + YAHOO_STATUS_IDLE = 999, + YAHOO_STATUS_OFFLINE = 0x5a55aa56 /* don't ask */ + }; + + enum ypacket_status { + YPACKET_STATUS_DISCONNECTED = -1, + YPACKET_STATUS_DEFAULT = 0, + YPACKET_STATUS_SERVERACK = 1, + YPACKET_STATUS_GAME = 0x2, + YPACKET_STATUS_AWAY = 0x4, + YPACKET_STATUS_CONTINUED = 0x5, + YPACKET_STATUS_INVISIBLE = 12, + YPACKET_STATUS_NOTIFY = 0x16, /* TYPING */ + YPACKET_STATUS_WEBLOGIN = 0x5a55aa55, + YPACKET_STATUS_OFFLINE = 0x5a55aa56 + }; + +#define YAHOO_STATUS_GAME 0x2 /* Games don't fit into the regular status model */ + + enum yahoo_login_status { + YAHOO_LOGIN_OK = 0, + YAHOO_LOGIN_LOGOFF = 1, + YAHOO_LOGIN_UNAME = 3, + YAHOO_LOGIN_PASSWD = 13, + YAHOO_LOGIN_LOCK = 14, + YAHOO_LOGIN_DUPL = 99, + YAHOO_LOGIN_SOCK = -1, + YAHOO_LOGIN_UNKNOWN = 999 + }; + + enum yahoo_error { + E_UNKNOWN = -1, + E_CONNECTION = -2, + E_SYSTEM = -3, + E_CUSTOM = 0, + + /* responses from ignore buddy */ + E_IGNOREDUP = 2, + E_IGNORENONE = 3, + E_IGNORECONF = 12, + + /* conference */ + E_CONFNOTAVAIL = 20 + }; + + enum yahoo_log_level { + YAHOO_LOG_NONE = 0, + YAHOO_LOG_FATAL, + YAHOO_LOG_ERR, + YAHOO_LOG_WARNING, + YAHOO_LOG_NOTICE, + YAHOO_LOG_INFO, + YAHOO_LOG_DEBUG + }; + + enum yahoo_file_transfer { + YAHOO_FILE_TRANSFER_INIT = 1, + YAHOO_FILE_TRANSFER_ACCEPT = 3, + YAHOO_FILE_TRANSFER_REJECT = 4, + YAHOO_FILE_TRANSFER_DONE = 5, + YAHOO_FILE_TRANSFER_RELAY, + YAHOO_FILE_TRANSFER_FAILED, + YAHOO_FILE_TRANSFER_UNKNOWN + }; #define YAHOO_PROTO_VER 0x0010 @@ -120,171 +221,176 @@ enum yahoo_log_level { #define YAHOO_STYLE_URLON "\033[lm" #define YAHOO_STYLE_URLOFF "\033[xlm" -enum yahoo_connection_type { - YAHOO_CONNECTION_PAGER=0, - YAHOO_CONNECTION_FT, - YAHOO_CONNECTION_YAB, - YAHOO_CONNECTION_WEBCAM_MASTER, - YAHOO_CONNECTION_WEBCAM, - YAHOO_CONNECTION_CHATCAT, - YAHOO_CONNECTION_SEARCH, - YAHOO_CONNECTION_AUTH, -}; - -enum yahoo_webcam_direction_type { - YAHOO_WEBCAM_DOWNLOAD=0, - YAHOO_WEBCAM_UPLOAD -}; - -enum yahoo_stealth_visibility_type { - YAHOO_STEALTH_DEFAULT = 0, - YAHOO_STEALTH_ONLINE, - YAHOO_STEALTH_PERM_OFFLINE -}; + enum yahoo_connection_type { + YAHOO_CONNECTION_PAGER = 0, + YAHOO_CONNECTION_FT, + YAHOO_CONNECTION_YAB, + YAHOO_CONNECTION_WEBCAM_MASTER, + YAHOO_CONNECTION_WEBCAM, + YAHOO_CONNECTION_CHATCAT, + YAHOO_CONNECTION_SEARCH, + YAHOO_CONNECTION_AUTH + }; + + enum yahoo_webcam_direction_type { + YAHOO_WEBCAM_DOWNLOAD = 0, + YAHOO_WEBCAM_UPLOAD + }; + + enum yahoo_stealth_visibility_type { + YAHOO_STEALTH_DEFAULT = 0, + YAHOO_STEALTH_ONLINE, + YAHOO_STEALTH_PERM_OFFLINE + }; /* chat member attribs */ #define YAHOO_CHAT_MALE 0x8000 #define YAHOO_CHAT_FEMALE 0x10000 +#define YAHOO_CHAT_FEMALE 0x10000 #define YAHOO_CHAT_DUNNO 0x400 #define YAHOO_CHAT_WEBCAM 0x10 -enum yahoo_webcam_conn_type { Y_WCM_DIALUP, Y_WCM_DSL, Y_WCM_T1 }; - -struct yahoo_webcam { - int direction; /* Uploading or downloading */ - int conn_type; /* 0=Dialup, 1=DSL/Cable, 2=T1/Lan */ - - char *user; /* user we are viewing */ - char *server; /* webcam server to connect to */ - int port; /* webcam port to connect on */ - char *key; /* key to connect to the server with */ - char *description; /* webcam description */ - char *my_ip; /* own ip number */ -}; - -struct yahoo_webcam_data { - unsigned int data_size; - unsigned int to_read; - unsigned int timestamp; - unsigned char packet_type; -}; - -struct yahoo_data { - char *user; - char *password; - - char *cookie_y; - char *cookie_t; - char *cookie_c; - char *login_cookie; - - YList *buddies; - YList *ignore; - YList *identities; - char *login_id; - - int current_status; - int initial_status; - int logged_in; - - int session_id; - - int client_id; - - char *rawbuddylist; - char *ignorelist; - - void *server_settings; - - struct yahoo_process_status_entry *half_user; -}; - -struct yab { - char *id; - char *fname; - char *lname; - char *nname; - char *email; - char *hphone; - char *wphone; - char *mphone; - int dbid; -}; - -struct yahoo_buddy { - char *group; - char *id; - char *real_name; - struct yab *yab_entry; -}; - -enum yahoo_search_type { - YAHOO_SEARCH_KEYWORD = 0, - YAHOO_SEARCH_YID, - YAHOO_SEARCH_NAME -}; - -enum yahoo_search_gender { - YAHOO_GENDER_NONE = 0, - YAHOO_GENDER_MALE, - YAHOO_GENDER_FEMALE -}; - -enum yahoo_search_agerange { - YAHOO_AGERANGE_NONE = 0 -}; - -struct yahoo_found_contact { - char *id; - char *gender; - char *location; - int age; - int online; -}; + enum yahoo_webcam_conn_type { Y_WCM_DIALUP, Y_WCM_DSL, Y_WCM_T1 }; + + struct yahoo_webcam { + int direction; /* Uploading or downloading */ + int conn_type; /* 0=Dialup, 1=DSL/Cable, 2=T1/Lan */ + + char *user; /* user we are viewing */ + char *server; /* webcam server to connect to */ + int port; /* webcam port to connect on */ + char *key; /* key to connect to the server with */ + char *description; /* webcam description */ + char *my_ip; /* own ip number */ + }; + + struct yahoo_webcam_data { + unsigned int data_size; + unsigned int to_read; + unsigned int timestamp; + unsigned char packet_type; + }; + + struct yahoo_data { + char *user; + char *password; + + char *cookie_y; + char *cookie_t; + char *cookie_c; + char *cookie_b; + char *login_cookie; + char *crumb; + char *seed; + + YList *buddies; + YList *ignore; + YList *identities; + char *login_id; + + int current_status; + int initial_status; + int logged_in; + + int session_id; + + int client_id; + + char *rawbuddylist; + char *ignorelist; + + void *server_settings; + + struct yahoo_process_status_entry *half_user; + }; + + struct yab { + int yid; + char *id; + char *fname; + char *lname; + char *nname; + char *email; + char *hphone; + char *wphone; + char *mphone; + int dbid; + }; + + struct yahoo_buddy { + char *group; + char *id; + char *real_name; + struct yab *yab_entry; + }; + + enum yahoo_search_type { + YAHOO_SEARCH_KEYWORD = 0, + YAHOO_SEARCH_YID, + YAHOO_SEARCH_NAME + }; + + enum yahoo_search_gender { + YAHOO_GENDER_NONE = 0, + YAHOO_GENDER_MALE, + YAHOO_GENDER_FEMALE + }; + + enum yahoo_search_agerange { + YAHOO_AGERANGE_NONE = 0 + }; + + struct yahoo_found_contact { + char *id; + char *gender; + char *location; + int age; + int online; + }; /* * Function pointer to be passed to http get/post and send file */ -typedef void (*yahoo_get_fd_callback)(int id, int fd, int error, void *data); + typedef void (*yahoo_get_fd_callback) (int id, void *fd, int error, + void *data); /* * Function pointer to be passed to yahoo_get_url_handle */ -typedef void (*yahoo_get_url_handle_callback)(int id, int fd, int error, - const char *filename, unsigned long size, void *data); - - -struct yahoo_chat_member { - char *id; - int age; - int attribs; - char *alias; - char *location; -}; - -struct yahoo_process_status_entry { - char *name; /* 7 name */ - int state; /* 10 state */ - int flags; /* 13 flags, bit 0 = pager, bit 1 = chat, bit 2 = game */ - int mobile; /* 60 mobile */ - char *msg; /* 19 custom status message */ - int away; /* 47 away (or invisible) */ - int buddy_session; /* 11 state */ - int f17; /* 17 in chat? then what about flags? */ - int idle; /* 137 seconds idle */ - int f138; /* 138 state */ - char *f184; /* 184 state */ - int f192; /* 192 state */ - int f10001; /* 10001 state */ - int f10002; /* 10002 state */ - int f198; /* 198 state */ - char *f197; /* 197 state */ - char *f205; /* 205 state */ - int f213; /* 213 state */ -}; + typedef void (*yahoo_get_url_handle_callback) (int id, void *fd, + int error, const char *filename, unsigned long size, + void *data); + + struct yahoo_chat_member { + char *id; + int age; + int attribs; + char *alias; + char *location; + }; + + struct yahoo_process_status_entry { + char *name; /* 7 name */ + int state; /* 10 state */ + int flags; /* 13 flags, bit 0 = pager, bit 1 = chat, bit 2 = game */ + int mobile; /* 60 mobile */ + char *msg; /* 19 custom status message */ + int away; /* 47 away (or invisible) */ + int buddy_session; /* 11 state */ + int f17; /* 17 in chat? then what about flags? */ + int idle; /* 137 seconds idle */ + int f138; /* 138 state */ + char *f184; /* 184 state */ + int f192; /* 192 state */ + int f10001; /* 10001 state */ + int f10002; /* 10002 state */ + int f198; /* 198 state */ + char *f197; /* 197 state */ + char *f205; /* 205 state */ + int f213; /* 213 state */ + }; #ifdef __cplusplus } #endif - #endif diff --git a/protocols/yahoo/yahoo_fn.h b/protocols/yahoo/yahoo_fn.h index 3f79f524..5400e5d0 100644 --- a/protocols/yahoo/yahoo_fn.h +++ b/protocols/yahoo/yahoo_fn.h @@ -18,16 +18,15 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#define IDENT 1 /* identify function */ -#define XOR 2 /* xor with arg1 */ -#define MULADD 3 /* multipy by arg1 then add arg2 */ -#define LOOKUP 4 /* lookup each byte in the table pointed to by arg1 */ -#define BITFLD 5 /* reorder bits according to table pointed to by arg1 */ +#define IDENT 1 /* identify function */ +#define XOR 2 /* xor with arg1 */ +#define MULADD 3 /* multipy by arg1 then add arg2 */ +#define LOOKUP 4 /* lookup each byte in the table pointed to by arg1 */ +#define BITFLD 5 /* reorder bits according to table pointed to by arg1 */ -struct yahoo_fn -{ - int type; +struct yahoo_fn { + int type; long arg1, arg2; }; -int yahoo_xfrm( int table, int depth, int seed ); +int yahoo_xfrm(int table, int depth, int seed); diff --git a/protocols/yahoo/yahoo_httplib.c b/protocols/yahoo/yahoo_httplib.c index 1b084992..6bb8923d 100644 --- a/protocols/yahoo/yahoo_httplib.c +++ b/protocols/yahoo/yahoo_httplib.c @@ -29,14 +29,13 @@ # define strchr index # define strrchr rindex # endif -char *strchr (), *strrchr (); +char *strchr(), *strrchr(); # if !HAVE_MEMCPY # define memcpy(d, s, n) bcopy ((s), (d), (n)) # define memmove(d, s, n) bcopy ((s), (d), (n)) # endif #endif - #include <errno.h> #ifndef _WIN32 #include <unistd.h> @@ -62,7 +61,7 @@ extern struct yahoo_callbacks *yc; extern enum yahoo_log_level log_level; -int yahoo_tcp_readline(char *ptr, int maxlen, int fd) +int yahoo_tcp_readline(char *ptr, int maxlen, void *fd) { int n, rc; char c; @@ -70,11 +69,11 @@ int yahoo_tcp_readline(char *ptr, int maxlen, int fd) for (n = 1; n < maxlen; n++) { do { - rc = read(fd, &c, 1); - } while(rc == -1 && (errno == EINTR || errno == EAGAIN)); /* this is bad - it should be done asynchronously */ + rc = YAHOO_CALLBACK(ext_yahoo_read) (fd, &c, 1); + } while (rc == -1 && (errno == EINTR || errno == EAGAIN)); /* this is bad - it should be done asynchronously */ if (rc == 1) { - if(c == '\r') /* get rid of \r */ + if (c == '\r') /* get rid of \r */ continue; *ptr = c; if (c == '\n') @@ -82,9 +81,9 @@ int yahoo_tcp_readline(char *ptr, int maxlen, int fd) ptr++; } else if (rc == 0) { if (n == 1) - return (0); /* EOF, no data */ + return (0); /* EOF, no data */ else - break; /* EOF, w/ data */ + break; /* EOF, w/ data */ } else { return -1; } @@ -95,12 +94,12 @@ int yahoo_tcp_readline(char *ptr, int maxlen, int fd) } static int url_to_host_port_path(const char *url, - char *host, int *port, char *path) + char *host, int *port, char *path, int *ssl) { - char *urlcopy=NULL; - char *slash=NULL; - char *colon=NULL; - + char *urlcopy = NULL; + char *slash = NULL; + char *colon = NULL; + /* * http://hostname * http://hostname/ @@ -110,10 +109,14 @@ static int url_to_host_port_path(const char *url, * http://hostname:port/ * http://hostname:port/path * http://hostname:port/path:foo + * and https:// variants of the above */ - if(strstr(url, "http://") == url) { - urlcopy = strdup(url+7); + if (strstr(url, "http://") == url) { + urlcopy = strdup(url + 7); + } else if (strstr(url, "https://") == url) { + urlcopy = strdup(url + 8); + *ssl = 1; } else { WARNING(("Weird url - unknown protocol: %s", url)); return 0; @@ -122,14 +125,17 @@ static int url_to_host_port_path(const char *url, slash = strchr(urlcopy, '/'); colon = strchr(urlcopy, ':'); - if(!colon || (slash && slash < colon)) { - *port = 80; + if (!colon || (slash && slash < colon)) { + if (*ssl) + *port = 443; + else + *port = 80; } else { *colon = 0; - *port = atoi(colon+1); + *port = atoi(colon + 1); } - if(!slash) { + if (!slash) { strcpy(path, "/"); } else { strcpy(path, slash); @@ -137,7 +143,7 @@ static int url_to_host_port_path(const char *url, } strcpy(host, urlcopy); - + FREE(urlcopy); return 1; @@ -145,135 +151,135 @@ static int url_to_host_port_path(const char *url, static int isurlchar(unsigned char c) { - return (isalnum(c) || '-' == c || '_' == c); + return (isalnum(c)); } char *yahoo_urlencode(const char *instr) { - int ipos=0, bpos=0; + int ipos = 0, bpos = 0; char *str = NULL; int len = strlen(instr); - if(!(str = y_new(char, 3*len + 1) )) - return ""; + if (!(str = y_new(char, 3 *len + 1))) + return ""; - while(instr[ipos]) { - while(isurlchar(instr[ipos])) + while (instr[ipos]) { + while (isurlchar(instr[ipos])) str[bpos++] = instr[ipos++]; - if(!instr[ipos]) + if (!instr[ipos]) break; - - snprintf(&str[bpos], 4, "%%%.2x", instr[ipos]); - bpos+=3; + + snprintf(&str[bpos], 4, "%%%02x", instr[ipos] & 0xff); + bpos += 3; ipos++; } - str[bpos]='\0'; + str[bpos] = '\0'; /* free extra alloc'ed mem. */ len = strlen(str); - str = y_renew(char, str, len+1); + str = y_renew(char, str, len + 1); return (str); } char *yahoo_urldecode(const char *instr) { - int ipos=0, bpos=0; + int ipos = 0, bpos = 0; char *str = NULL; - char entity[3]={0,0,0}; + char entity[3] = { 0, 0, 0 }; unsigned dec; int len = strlen(instr); - if(!(str = y_new(char, len+1) )) - return ""; + if (!(str = y_new(char, len + 1))) + return ""; - while(instr[ipos]) { - while(instr[ipos] && instr[ipos]!='%') - if(instr[ipos]=='+') { - str[bpos++]=' '; + while (instr[ipos]) { + while (instr[ipos] && instr[ipos] != '%') + if (instr[ipos] == '+') { + str[bpos++] = ' '; ipos++; } else str[bpos++] = instr[ipos++]; - if(!instr[ipos]) + if (!instr[ipos]) break; - - if(instr[ipos+1] && instr[ipos+2]) { + + if (instr[ipos + 1] && instr[ipos + 2]) { ipos++; - entity[0]=instr[ipos++]; - entity[1]=instr[ipos++]; + entity[0] = instr[ipos++]; + entity[1] = instr[ipos++]; sscanf(entity, "%2x", &dec); str[bpos++] = (char)dec; } else { str[bpos++] = instr[ipos++]; } } - str[bpos]='\0'; + str[bpos] = '\0'; /* free extra alloc'ed mem. */ len = strlen(str); - str = y_renew(char, str, len+1); + str = y_renew(char, str, len + 1); return (str); } char *yahoo_xmldecode(const char *instr) { - int ipos=0, bpos=0, epos=0; + int ipos = 0, bpos = 0, epos = 0; char *str = NULL; - char entity[4]={0,0,0,0}; - char *entitymap[5][2]={ - {"amp;", "&"}, + char entity[4] = { 0, 0, 0, 0 }; + char *entitymap[5][2] = { + {"amp;", "&"}, {"quot;", "\""}, - {"lt;", "<"}, - {"gt;", "<"}, + {"lt;", "<"}, + {"gt;", "<"}, {"nbsp;", " "} }; unsigned dec; int len = strlen(instr); - if(!(str = y_new(char, len+1) )) - return ""; + if (!(str = y_new(char, len + 1))) + return ""; - while(instr[ipos]) { - while(instr[ipos] && instr[ipos]!='&') - if(instr[ipos]=='+') { - str[bpos++]=' '; + while (instr[ipos]) { + while (instr[ipos] && instr[ipos] != '&') + if (instr[ipos] == '+') { + str[bpos++] = ' '; ipos++; } else str[bpos++] = instr[ipos++]; - if(!instr[ipos] || !instr[ipos+1]) + if (!instr[ipos] || !instr[ipos + 1]) break; ipos++; - if(instr[ipos] == '#') { + if (instr[ipos] == '#') { ipos++; - epos=0; - while(instr[ipos] != ';') - entity[epos++]=instr[ipos++]; + epos = 0; + while (instr[ipos] != ';') + entity[epos++] = instr[ipos++]; sscanf(entity, "%u", &dec); str[bpos++] = (char)dec; ipos++; } else { int i; - for (i=0; i<5; i++) - if(!strncmp(instr+ipos, entitymap[i][0], - strlen(entitymap[i][0]))) { - str[bpos++] = entitymap[i][1][0]; + for (i = 0; i < 5; i++) + if (!strncmp(instr + ipos, entitymap[i][0], + strlen(entitymap[i][0]))) { + str[bpos++] = entitymap[i][1][0]; ipos += strlen(entitymap[i][0]); break; } } } - str[bpos]='\0'; + str[bpos] = '\0'; /* free extra alloc'ed mem. */ len = strlen(str); - str = y_renew(char, str, len+1); + str = y_renew(char, str, len + 1); return (str); } -typedef void (*http_connected)(int id, int fd, int error); +typedef void (*http_connected) (int id, void *fd, int error); struct callback_data { int id; @@ -282,150 +288,117 @@ struct callback_data { void *user_data; }; -static void connect_complete(int fd, int error, void *data) +static void connect_complete(void *fd, int error, void *data) { struct callback_data *ccd = data; - if(error == 0 && fd > 0) - write(fd, ccd->request, strlen(ccd->request)); - FREE(ccd->request); + if (error == 0) + YAHOO_CALLBACK(ext_yahoo_write) (fd, ccd->request, + strlen(ccd->request)); + free(ccd->request); ccd->callback(ccd->id, fd, error, ccd->user_data); FREE(ccd); } -static void yahoo_send_http_request(int id, char *host, int port, char *request, - yahoo_get_fd_callback callback, void *data) +static void yahoo_send_http_request(int id, char *host, int port, char *request, + yahoo_get_fd_callback callback, void *data, int use_ssl) { - struct callback_data *ccd=y_new0(struct callback_data, 1); + struct callback_data *ccd = y_new0(struct callback_data, 1); ccd->callback = callback; ccd->id = id; ccd->request = strdup(request); ccd->user_data = data; - - YAHOO_CALLBACK(ext_yahoo_connect_async)(id, host, port, connect_complete, ccd); + + YAHOO_CALLBACK(ext_yahoo_connect_async) (id, host, port, + connect_complete, ccd, use_ssl); } -void yahoo_http_post(int id, const char *url, const char *cookies, long content_length, - yahoo_get_fd_callback callback, void *data) +void yahoo_http_post(int id, const char *url, const char *cookies, + long content_length, yahoo_get_fd_callback callback, void *data) { char host[255]; int port = 80; char path[255]; char buff[1024]; - - if(!url_to_host_port_path(url, host, &port, path)) - return; + int ssl = 0; - snprintf(buff, sizeof(buff), - "POST %s HTTP/1.0\r\n" - "Content-length: %ld\r\n" - "User-Agent: Mozilla/4.5 [en] (" PACKAGE "/" VERSION ")\r\n" - "Host: %s:%d\r\n" - "Cookie: %s\r\n" - "\r\n", - path, content_length, - host, port, - cookies); + if (!url_to_host_port_path(url, host, &port, path, &ssl)) + return; - yahoo_send_http_request(id, host, port, buff, callback, data); + /* thanks to kopete dumpcap */ + snprintf(buff, sizeof(buff), + "POST %s HTTP/1.1\r\n" + "Cookie: %s\r\n" + "User-Agent: Mozilla/5.0\r\n" + "Host: %s\r\n" + "Content-Length: %ld\r\n" + "Cache-Control: no-cache\r\n" + "\r\n", path, cookies, host, content_length); + + yahoo_send_http_request(id, host, port, buff, callback, data, ssl); } -void yahoo_http_get(int id, const char *url, const char *cookies, - yahoo_get_fd_callback callback, void *data) +void yahoo_http_get(int id, const char *url, const char *cookies, int http11, + int keepalive, yahoo_get_fd_callback callback, void *data) { char host[255]; int port = 80; char path[255]; - char buff[1024]; - - if(!url_to_host_port_path(url, host, &port, path)) - return; + char buff[2048]; + char cookiebuff[1024]; + int ssl = 0; - snprintf(buff, sizeof(buff), - "GET %s HTTP/1.0\r\n" - "Host: %s:%d\r\n" - "User-Agent: Mozilla/4.5 [en] (" PACKAGE "/" VERSION ")\r\n" - "Cookie: %s\r\n" - "\r\n", - path, host, port, cookies); + if (!url_to_host_port_path(url, host, &port, path, &ssl)) + return; - yahoo_send_http_request(id, host, port, buff, callback, data); + /* Allow cases when we don't need to send a cookie */ + if (cookies) + snprintf(cookiebuff, sizeof(cookiebuff), "Cookie: %s\r\n", + cookies); + else + cookiebuff[0] = '\0'; + + snprintf(buff, sizeof(buff), + "GET %s HTTP/1.%s\r\n" + "%sHost: %s\r\n" + "User-Agent: Mozilla/4.5 [en] (" PACKAGE "/" VERSION ")\r\n" + "Accept: */*\r\n" + "%s" "\r\n", path, http11?"1":"0", cookiebuff, host, + keepalive? "Connection: Keep-Alive\r\n":"Connection: close\r\n"); + + yahoo_send_http_request(id, host, port, buff, callback, data, ssl); } -struct url_data { - yahoo_get_url_handle_callback callback; - void *user_data; -}; - -static void yahoo_got_url_fd(int id, int fd, int error, void *data) +void yahoo_http_head(int id, const char *url, const char *cookies, int len, + char *payload, yahoo_get_fd_callback callback, void *data) { - char *tmp=NULL; - char buff[1024]; - unsigned long filesize=0; - char *filename=NULL; - int n; - - struct url_data *ud = data; + char host[255]; + int port = 80; + char path[255]; + char buff[2048]; + char cookiebuff[1024]; + int ssl = 0; - if(error || fd < 0) { - ud->callback(id, fd, error, filename, filesize, ud->user_data); - FREE(ud); + if (!url_to_host_port_path(url, host, &port, path, &ssl)) return; - } - - while((n=yahoo_tcp_readline(buff, sizeof(buff), fd)) > 0) { - LOG(("Read:%s:\n", buff)); - if(!strcmp(buff, "")) - break; - - if( !strncasecmp(buff, "Content-length:", - strlen("Content-length:")) ) { - tmp = strrchr(buff, ' '); - if(tmp) - filesize = atol(tmp); - } - - if( !strncasecmp(buff, "Content-disposition:", - strlen("Content-disposition:")) ) { - tmp = strstr(buff, "name="); - if(tmp) { - tmp+=strlen("name="); - if(tmp[0] == '"') { - char *tmp2; - tmp++; - tmp2 = strchr(tmp, '"'); - if(tmp2) - *tmp2 = '\0'; - } else { - char *tmp2; - tmp2 = strchr(tmp, ';'); - if(!tmp2) - tmp2 = strchr(tmp, '\r'); - if(!tmp2) - tmp2 = strchr(tmp, '\n'); - if(tmp2) - *tmp2 = '\0'; - } - filename = strdup(tmp); - } - } - } - - LOG(("n == %d\n", n)); - LOG(("Calling callback, filename:%s, size: %ld\n", filename, filesize)); - ud->callback(id, fd, error, filename, filesize, ud->user_data); - FREE(ud); - FREE(filename); -} - -void yahoo_get_url_fd(int id, const char *url, const struct yahoo_data *yd, - yahoo_get_url_handle_callback callback, void *data) -{ - char buff[1024]; - struct url_data *ud = y_new0(struct url_data, 1); - snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t); - ud->callback = callback; - ud->user_data = data; - yahoo_http_get(id, url, buff, yahoo_got_url_fd, ud); + /* Allow cases when we don't need to send a cookie */ + if (cookies) + snprintf(cookiebuff, sizeof(cookiebuff), "Cookie: %s\r\n", + cookies); + else + cookiebuff[0] = '\0'; + + snprintf(buff, sizeof(buff), + "HEAD %s HTTP/1.0\r\n" + "Accept: */*\r\n" + "Host: %s:%d\r\n" + "User-Agent: Mozilla/4.5 [en] (" PACKAGE "/" VERSION ")\r\n" + "%s" + "Content-Length: %d\r\n" + "Cache-Control: no-cache\r\n" + "\r\n%s", path, host, port, cookiebuff, len, + payload?payload:""); + + yahoo_send_http_request(id, host, port, buff, callback, data, ssl); } diff --git a/protocols/yahoo/yahoo_httplib.h b/protocols/yahoo/yahoo_httplib.h index fd28ad48..ab699b20 100644 --- a/protocols/yahoo/yahoo_httplib.h +++ b/protocols/yahoo/yahoo_httplib.h @@ -28,21 +28,21 @@ extern "C" { #include "yahoo2_types.h" -char *yahoo_urlencode(const char *instr); -char *yahoo_urldecode(const char *instr); -char *yahoo_xmldecode(const char *instr); - -int yahoo_tcp_readline(char *ptr, int maxlen, int fd); -void yahoo_http_post(int id, const char *url, const char *cookies, long size, - yahoo_get_fd_callback callback, void *data); -void yahoo_http_get(int id, const char *url, const char *cookies, - yahoo_get_fd_callback callback, void *data); -void yahoo_get_url_fd(int id, const char *url, const struct yahoo_data *yd, - yahoo_get_url_handle_callback callback, void *data); - + char *yahoo_urlencode(const char *instr); + char *yahoo_urldecode(const char *instr); + char *yahoo_xmldecode(const char *instr); + + int yahoo_tcp_readline(char *ptr, int maxlen, void *fd); + void yahoo_http_post(int id, const char *url, const char *cookies, + long size, yahoo_get_fd_callback callback, void *data); + void yahoo_http_get(int id, const char *url, const char *cookies, + int http11, int keepalive, yahoo_get_fd_callback callback, + void *data); + void yahoo_http_head(int id, const char *url, const char *cookies, + int size, char *payload, yahoo_get_fd_callback callback, + void *data); #ifdef __cplusplus } #endif - #endif diff --git a/protocols/yahoo/yahoo_list.h b/protocols/yahoo/yahoo_list.h index 0d335acd..c2e5ad18 100644 --- a/protocols/yahoo/yahoo_list.h +++ b/protocols/yahoo/yahoo_list.h @@ -23,7 +23,7 @@ #ifndef __YLIST_H__ #define __YLIST_H__ -/* GLib has linked list already, so I don't see why libyahoo2 has to copy this... */ +/* BitlBee already uses GLib so use it. */ typedef GList YList; diff --git a/protocols/yahoo/yahoo_util.c b/protocols/yahoo/yahoo_util.c index 5375205f..33a12674 100644 --- a/protocols/yahoo/yahoo_util.c +++ b/protocols/yahoo/yahoo_util.c @@ -35,12 +35,12 @@ char *strchr (), *strrchr (); #include "yahoo_util.h" -char * y_string_append(char * string, char * append) +char *y_string_append(char *string, char *append) { int size = strlen(string) + strlen(append) + 1; - char * new_string = y_renew(char, string, size); + char *new_string = y_renew(char, string, size); - if(new_string == NULL) { + if (new_string == NULL) { new_string = y_new(char, size); strcpy(new_string, string); FREE(string); diff --git a/protocols/yahoo/yahoo_util.h b/protocols/yahoo/yahoo_util.h index 0046fe16..8cb721c1 100644 --- a/protocols/yahoo/yahoo_util.h +++ b/protocols/yahoo/yahoo_util.h @@ -60,17 +60,19 @@ # define y_new0(type, n) (type *)calloc((n), sizeof(type)) # define y_renew(type, mem, n) (type *)realloc(mem, n) -void * y_memdup(const void * addr, int n); -char ** y_strsplit(char * str, char * sep, int nelem); -void y_strfreev(char ** vector); +void *y_memdup(const void *addr, int n); +char **y_strsplit(char *str, char *sep, int nelem); +void y_strfreev(char **vector); -int strncasecmp(const char * s1, const char * s2, size_t n); -int strcasecmp(const char * s1, const char * s2); +#ifndef _WIN32 +int strncasecmp(const char *s1, const char *s2, size_t n); +int strcasecmp(const char *s1, const char *s2); -char * strdup(const char *s); +char *strdup(const char *s); int snprintf(char *str, size_t size, const char *format, ...); int vsnprintf(char *str, size_t size, const char *format, va_list ap); +#endif #endif @@ -94,9 +96,9 @@ int vsnprintf(char *str, size_t size, const char *format, va_list ap); * The following three functions return newly allocated memory. * You must free it yourself */ -char * y_string_append(char * str, char * append); -char * y_str_to_utf8(const char * in); -char * y_utf8_to_str(const char * in); +char *y_string_append(char *str, char *append); +char *y_str_to_utf8(const char *in); +char *y_utf8_to_str(const char *in); #endif |