gaim/gaim

Initial commit. Development is still in plugin form.
gaim-doodle
2005-07-03, Andrew Diffenbach
ea288fc2284c
Parents cc88ae001c4d
Children 7c2050cd8799
Initial commit. Development is still in plugin form.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/goodle.c Sun Jul 03 04:48:12 2005 -0400
@@ -0,0 +1,698 @@
+/*
+ * Gaim - iChat-like timestamps
+ *
+ * Copyright (C) 2002-2003, Sean Egan
+ * Copyright (C) 2003, Chris J. Friesen <Darth_Sebulba04@yahoo.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+//#define GAIM_PLUGINS
+
+// INCLUDES ============================================================================================
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+
+#include "internal.h"
+
+//#include "account.h"
+
+#include "cmds.h"
+#include "conversation.h"
+#include "debug.h"
+#include "gtkconv.h"
+#include "gtkplugin.h"
+#include "gtkprefs.h"
+#include "gtkutils.h"
+#include "prefs.h"
+#include "util.h"
+#include "version.h"
+
+#include "protocols/yahoo/yahoo.h"
+#include "protocols/yahoo/ycht.h"
+
+// DEFINES =============================================================================================
+
+// Doodle communication commands
+#define DOODLE_CMD_REQUEST 0
+#define DOODLE_CMD_READY 1
+#define DOODLE_CMD_CLEAR 2
+#define DOODLE_CMD_DRAW 3
+#define DOODLE_CMD_UNKNOWN 4
+#define DOODLE_CMD_CONFIRM 5
+
+// Doodle color codes (most likely RGB)
+#define DOODLE_COLOR_RED 13369344
+#define DOODLE_COLOR_ORANGE 16737792
+#define DOODLE_COLOR_YELLOW 15658496
+#define DOODLE_COLOR_GREEN 52224
+#define DOODLE_COLOR_CYAN 52428
+#define DOODLE_COLOR_BLUE 204
+#define DOODLE_COLOR_VIOLET 5381277
+#define DOODLE_COLOR_PURPLE 13369548
+#define DOODLE_COLOR_TAN 12093547
+#define DOODLE_COLOR_BROWN 5256485
+#define DOODLE_COLOR_BLACK 0
+#define DOODLE_COLOR_GREY 11184810
+#define DOODLE_COLOR_WHITE 16777215
+
+// Doodle brush sizes (most likely variable)
+#define DOODLE_BRUSH_SMALL 2
+#define DOODLE_BRUSH_MEDIUM 5
+#define DOODLE_BRUSH_LARGE 10
+
+// PROTOTYPES ==========================================================================================
+static void init_plugin( GaimPlugin *plugin );
+static gboolean goodle_load( GaimPlugin *plugin );
+static gboolean goodle_unload( GaimPlugin *plugin );
+static void goodle_destroy( GaimPlugin *plugin ); // void or gboolean?
+static GtkWidget *get_config_frame( GaimPlugin *plugin );
+
+static void goodle_conv_created( GaimConversation *conv, gpointer data );
+static void goodle_conv_destroyed( GaimConversation *conv, gpointer data );
+
+static void goodle_button_press( GtkButton *button, gpointer data );
+
+static void goodle_set_goodle_pending( gboolean useGoodle );
+static void goodle_pending( gpointer data, gint source, GaimInputCondition cond );
+static void goodle_packet_process( GaimConnection *gc, struct yahoo_packet *pkt );
+
+static void goodle_got_command_request( GaimConnection *gc, char *from );
+static void goodle_got_command_ready( GaimConnection *gc, char *from );
+//static void goodle_got_command_draw( GaimConnection *gc, char *from, char *message );
+//static void goodle_got_command_clear( GaimConnection *gc, char *from );
+static void goodle_got_command_confirm( GaimConnection *gc, char *from );
+//static void goodle_got_command_shutdown(
+
+static void goodle_send_command_confirm( GaimConnection *gc, char *to );
+
+// GLOBALS =============================================================================================
+static gboolean auto_accept = TRUE;
+
+static GList *buttonList = NULL;
+
+//static GList *requestList = NULL;
+//static Glist *goodle_windowList = NULL;
+
+static GaimGtkPluginUiInfo ui_info = { get_config_frame };
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD,
+ GAIM_GTK_PLUGIN_TYPE,
+ 0,
+ NULL,
+ GAIM_PRIORITY_DEFAULT,
+
+ "core-goodle",
+ "Goodle",
+ VERSION,
+
+ "Draw with other Yahoo IM users",
+ "Goodle emulates the Yahoo IM's Doodle IMvironment, in which users can draw together in real-time",
+ "Andrew Dieffenbach (puzzud@gmail.com)",
+ "http://puzzix.homeip.net:31580/goodle",
+
+ goodle_load,
+ goodle_unload,
+ goodle_destroy,
+
+ &ui_info,
+ NULL,
+ NULL,
+ NULL
+};
+
+GAIM_INIT_PLUGIN( goodle, init_plugin, info );
+
+// FUNCTIONS ============================================================================================
+
+// This function is called at the start of Gaim (twice?)
+// Anyhow... this is the biggy... I think :P
+static void init_plugin( GaimPlugin *plugin )
+{
+
+}
+// ------------------------------------------------------------------------------------------------------
+
+// plugin_load is not required. It is called when the plugin is loaded so that you can
+// initialize any variables and so on. Therefore, called when selected in Preferences->Plugins
+static gboolean goodle_load( GaimPlugin *plugin )
+{
+ // Set up signal/functions for when the conversation/windows are created
+ void *conv_handle = gaim_conversations_get_handle();
+
+ // Connect the open and closing conversation signals to our plugin functions
+ gaim_signal_connect( conv_handle, "conversation-created", plugin,
+ GAIM_CALLBACK( goodle_conv_created ), NULL );
+
+ gaim_signal_connect( conv_handle, "deleting-conversation", plugin,
+ GAIM_CALLBACK( goodle_conv_destroyed ), NULL );
+
+ // Make all active Yahoo accounts use the Goodle network packet handler
+ goodle_set_goodle_pending( TRUE );
+
+ return( TRUE );
+}
+// ------------------------------------------------------------------------------------------------------
+
+// plugin_destroy is called when plugin is unloaded (unselected)
+static gboolean goodle_unload( GaimPlugin *plugin )
+{
+ // Destroy any buttons that haven't been destroyed from closing conversation windows
+ g_list_free( buttonList );
+
+ // Return full packet handling to the original Yahoo plugin
+ goodle_set_goodle_pending( FALSE );
+
+ return( TRUE );
+}
+// ------------------------------------------------------------------------------------------------------
+
+// plugin_destroy is called when plugin is destroyed (fails/crashes)
+static void goodle_destroy( GaimPlugin *plugin )
+{
+ goodle_unload( plugin );
+}
+// ------------------------------------------------------------------------------------------------------
+
+static void goodle_conv_created( GaimConversation *conv, gpointer data )
+{
+ GtkWidget *button;
+ GaimGtkConversation *gtkconv;
+ //GaimConversationType type;
+
+ // Check if this is a valid conversation window in Gaim ?
+ if( ( gtkconv = GAIM_GTK_CONVERSATION( conv ) ) == NULL )
+ return;
+
+ //type = gaim_conversation_get_type( conv );
+
+ // Create this button (via Gaim API)
+ button = gaim_gtkconv_button_new( NULL,
+ "Goodle", "Goodle",
+ gtkconv->tooltips,
+ goodle_button_press,
+ ( gpointer )( conv ) );
+
+ // Stamp this button with essentially information attributing it to this particular conversation?
+ g_object_set_data( G_OBJECT( button ), "conv", conv );
+
+ // Add this particular button to the global list of buttons
+ buttonList = g_list_append( buttonList, ( gpointer )( button ) );
+
+ // Place the button at the bottom of the Gaim conversation window
+ gtk_box_pack_end( GTK_BOX( gtkconv->bbox ), button, TRUE, TRUE, 0 );
+ gtk_size_group_add_widget( gtkconv->sg, button );
+ gtk_widget_show( button );
+}
+// ------------------------------------------------------------------------------------------------------
+
+static void goodle_conv_destroyed( GaimConversation *conv, gpointer data )
+{
+ GaimConversation *stored_conv;
+ GtkWidget *button;
+ GList *l, *l_next;
+
+ // Traverse the list of buttons and destroy the one associated with this conversation window
+ for( l = buttonList; l != NULL; l = l_next )
+ {
+ l_next = l->next;
+
+ button = GTK_WIDGET( l->data );
+ stored_conv = ( GaimConversation* )g_object_get_data( G_OBJECT( button ), "conv" );
+
+ // Does this button's 'stamp' match that of this conversation?
+ if( stored_conv == conv )
+ {
+ gtk_widget_destroy( button );
+
+ buttonList = g_list_remove( buttonList, l->data );
+ break;
+ }
+ }
+}
+// ------------------------------------------------------------------------------------------------------
+
+static void goodle_button_press( GtkButton *button, gpointer data )
+{
+ GaimConversation *conv = data;
+ GaimAccount *account = gaim_conversation_get_account( conv );
+ GaimConnection *gc = gaim_account_get_connection( account );
+
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ yd = gc->proto_data;
+
+ // Make and send an acknowledge (ready) Doodle packet
+ pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 );
+ yahoo_packet_hash( pkt, 49, "IMVIRONMENT" );
+ yahoo_packet_hash( pkt, 1, gaim_normalize( gc->account, gaim_account_get_username( account ) ) );
+ yahoo_packet_hash( pkt, 14, "1" );
+ yahoo_packet_hash( pkt, 13, "1" );
+ yahoo_packet_hash( pkt, 5, gaim_conversation_get_name( conv ) );
+ yahoo_packet_hash( pkt, 63, "doodle;11" );
+ yahoo_packet_hash( pkt, 64, "1" );
+ yahoo_packet_hash( pkt, 1002, "1" );
+ yahoo_send_packet( yd, pkt );
+
+ // Make and send a request to start a Doodle session
+ pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 );
+ yahoo_packet_hash( pkt, 49, "IMVIRONMENT" );
+ yahoo_packet_hash( pkt, 1, gaim_normalize( gc->account, gaim_account_get_username( account ) ) );
+ yahoo_packet_hash( pkt, 14, "" );
+ yahoo_packet_hash( pkt, 13, "0" );
+ yahoo_packet_hash( pkt, 5, gaim_conversation_get_name( conv ) );
+ yahoo_packet_hash( pkt, 63, "doodle;11" );
+ yahoo_packet_hash( pkt, 64, "0" );
+ yahoo_packet_hash( pkt, 1002, "1" );
+ yahoo_send_packet( yd, pkt );
+
+ yahoo_packet_free( pkt );
+
+ // TODO Set a variable (in a list?) that shows we are waiting
+ // for the remote client to initialize and accept our request for a Doodle session
+
+ // TODO Write a local message to this conversation showing that
+ // a request for a Doodle session has been made
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+// Configuration dialog for plugin (uses GTK)
+static GtkWidget *get_config_frame( GaimPlugin *plugin )
+{
+ GtkWidget *vbox, *frame;
+
+ vbox = gtk_vbox_new( FALSE, 6 );
+ gtk_container_set_border_width( GTK_CONTAINER( vbox ), 12 );
+
+ frame = gaim_gtk_make_frame( vbox, "Settings" );
+
+ gaim_gtk_prefs_checkbox( "Automatically accept Doodle requests",
+ "/plugins/gtk/goodle/auto_accept",
+ frame );
+
+ gtk_widget_show_all( vbox );
+
+ return( vbox );
+}
+// ------------------------------------------------------------------------------------------------------
+
+static void goodle_set_goodle_pending( gboolean useGoodle )
+{
+ GList *accountList = NULL;
+ GList *l = NULL;
+ GList *l_next = NULL;
+
+ GaimAccount *account;
+ GaimConnection *gc;
+
+ struct yahoo_data *yd;
+
+ g_print( "goodle_insert_pending()\n" );
+
+ // Obtain all the Gaim accounts (no other way to get to the GaimConnections?)
+ accountList = gaim_accounts_get_all();
+
+ // Traverse through the accounts to determine which ones are Yahoo
+ for( l = accountList; l != NULL; l = l_next )
+ {
+ g_print( "An account was found.\n" );
+ l_next = l->next;
+
+ account = ( GaimAccount* )( l->data );
+
+ // Check if this particular account is Yahoo
+ if( !strcmp( gaim_account_get_protocol_id( account ), "prpl-yahoo" ) )
+ {
+ if( gaim_account_is_connected( account ) )
+ {
+ g_print( "%s (yahoo) -> ACTIVE\n", gaim_account_get_username( account ) );
+
+ gc = gaim_account_get_connection( account );
+
+ yd = gc->proto_data;
+
+ if( useGoodle )
+ {
+ // Replace the packet handler function with a modified one
+ // to check for packets concerning 'Doodle'
+ gaim_input_remove( gc->inpa );
+ gc->inpa = gaim_input_add( yd->fd, GAIM_INPUT_READ, goodle_pending, gc );
+ }
+ else
+ {
+ // Replace the modified packet handler function with original
+ gaim_input_remove( gc->inpa );
+ gc->inpa = gaim_input_add( yd->fd, GAIM_INPUT_READ, yahoo_pending, gc );
+ }
+ }
+ }
+ }
+
+ // Destroy the list of accounts obtained
+ //g_list_free( accountList );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+// Handles the interception of Yahoo packets
+// Code copied from Tim Ringenbach's yahoo_pending() in the yahoo.c file of the Yahoo Gaim plugin.
+static void goodle_pending( gpointer data, gint source, GaimInputCondition cond )
+{
+ g_print( "goodle_pending()\n" );
+
+ GaimConnection *gc = data;
+ struct yahoo_data *yd = gc->proto_data;
+ char buf[1024];
+ int len;
+
+ len = read(yd->fd, buf, sizeof(buf));
+
+ if (len <= 0) {
+ gaim_connection_error(gc, _("Unable to read"));
+ return;
+ }
+
+ yd->rxqueue = g_realloc(yd->rxqueue, len + yd->rxlen);
+ memcpy(yd->rxqueue + yd->rxlen, buf, len);
+ yd->rxlen += len;
+
+ while (1) {
+ struct yahoo_packet *pkt;
+ int pos = 0;
+ int pktlen;
+
+ if (yd->rxlen < YAHOO_PACKET_HDRLEN)
+ return;
+
+ if (strncmp(yd->rxqueue, "YMSG", MIN(4, yd->rxlen)) != 0) {
+ // HEY! This isn't even a YMSG packet. What
+ // are you trying to pull?
+ guchar *start;
+
+ gaim_debug_warning("yahoo", "Error in YMSG stream, got something not a YMSG packet!\n");
+
+ start = memchr(yd->rxqueue + 1, 'Y', yd->rxlen - 1);
+ if (start) {
+ g_memmove(yd->rxqueue, start, yd->rxlen - (start - yd->rxqueue));
+ yd->rxlen -= start - yd->rxqueue;
+ continue;
+ } else {
+ g_free(yd->rxqueue);
+ yd->rxqueue = NULL;
+ yd->rxlen = 0;
+ return;
+ }
+ }
+
+ pos += 4; // YMSG
+ pos += 2;
+ pos += 2;
+
+ pktlen = yahoo_get16(yd->rxqueue + pos); pos += 2;
+ gaim_debug(GAIM_DEBUG_MISC, "yahoo",
+ "%d bytes to read, rxlen is %d\n", pktlen, yd->rxlen);
+
+ if (yd->rxlen < (YAHOO_PACKET_HDRLEN + pktlen))
+ return;
+
+ yahoo_packet_dump(yd->rxqueue, YAHOO_PACKET_HDRLEN + pktlen);
+
+ pkt = yahoo_packet_new(0, 0, 0);
+
+ pkt->service = yahoo_get16(yd->rxqueue + pos); pos += 2;
+ pkt->status = yahoo_get32(yd->rxqueue + pos); pos += 4;
+ gaim_debug(GAIM_DEBUG_MISC, "yahoo",
+ "Yahoo Service: 0x%02x Status: %d\n",
+ pkt->service, pkt->status);
+ pkt->id = yahoo_get32(yd->rxqueue + pos); pos += 4;
+
+ yahoo_packet_read(pkt, yd->rxqueue + pos, pktlen);
+
+ yd->rxlen -= YAHOO_PACKET_HDRLEN + pktlen;
+ if (yd->rxlen) {
+ char *tmp = g_memdup(yd->rxqueue + YAHOO_PACKET_HDRLEN + pktlen, yd->rxlen);
+ g_free(yd->rxqueue);
+ yd->rxqueue = tmp;
+ } else {
+ g_free(yd->rxqueue);
+ yd->rxqueue = NULL;
+ }
+
+ // Direct any 'Doodle' (YAHOO_SERVICE_P2PFILEXFER) packets to the Goodle packet processor
+ if( ( pkt->service == YAHOO_SERVICE_P2PFILEXFER ) )
+ {
+ g_print( "YAHOO_SERVICE_P2PFILEXFER\n" );
+ goodle_packet_process( gc, pkt );
+ }
+
+ yahoo_packet_process(gc, pkt);
+
+ yahoo_packet_free(pkt);
+ }
+
+ // Call original function for handling Yahoo communication
+ //g_print( "Reverting packet responsibility!\n" );
+ //yahoo_pending( data, source, cond );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+static void goodle_packet_process( GaimConnection *gc, struct yahoo_packet *pkt )
+{
+ GSList *l = pkt->hash;
+
+ char *me = NULL;
+ char *from = NULL;
+ char *type = NULL;
+ char *message = NULL;
+ char *command = NULL;
+ char *imv = NULL;
+ char *unknown = NULL;
+
+ int cmd;
+
+ // Get all the necessary values from this new packet
+ while( l )
+ {
+ struct yahoo_pair *pair = l->data;
+
+ if ( pair->key == 5 ) // Get who the packet is for
+ me = pair->value;
+
+ if ( pair->key == 4 ) // Get who the packet is from
+ from = pair->value;
+
+ if ( pair->key == 49 ) // Get the type of packet (not service)
+ {
+ type = pair->value;
+
+ // If this packet isn't an IMVIRONMENT, forget about it
+ if( strcmp( type, "IMVIRONMENT" ) )
+ return;
+ else
+ g_print( "%s\n", type );
+ }
+
+ if ( pair->key == 14 ) // Get the 'message' of the packet
+ message = pair->value;
+
+ if ( pair->key == 13 ) // Get the command associated with this packet
+ {
+ command = pair->value;
+
+ g_print( "%s\n", command );
+ }
+
+ if ( pair->key == 63 ) // IMVironment name and version
+ {
+ imv = pair->value;
+
+ // NOTE I may potentially have to handle shutting down Doodle session here
+ // because of Yahoo's 'mono' IMV architecture: shutdown command doesn't specify
+ // a particular IMV, it just specifies IMV in general... eek!
+ /*
+ // If this packet isn't for doodling, forget about it
+ if( strcmp( imv, "doodle;11" ) )
+ return;
+ else
+ g_print( "%s\n", imv );
+ */
+ }
+
+ if ( pair->key == 64 ) // Not sure, but it does vary with initialization of Doodle
+ unknown = pair->value; // So, I'll keep it (for a little while atleast)
+
+ l = l->next;
+ }
+
+ // Now check to see what sort of Doodle message it is
+ cmd = atoi( command );
+
+ switch( cmd )
+ {
+ case DOODLE_CMD_REQUEST:
+ {
+ goodle_got_command_request( gc, from );
+ } break;
+
+ case DOODLE_CMD_READY:
+ {
+ goodle_got_command_ready( gc, from );
+ } break;
+
+ case DOODLE_CMD_CLEAR:
+ {
+ // TODO Call clearing function
+ g_print( "(%s cleared the canvas)\n", from );
+ } break;
+
+ case DOODLE_CMD_DRAW:
+ {
+ // TODO Call drawing function
+ } break;
+
+ case DOODLE_CMD_UNKNOWN:
+ {
+ // Hell if I know (maybe popup a dialog box too for such a rare occasion?!)
+ if( !strcmp( imv, "doodle;11" ) )
+ g_print( "!!! Yahoo.Doodle.Command #4!\n" );
+ } break;
+
+ case DOODLE_CMD_CONFIRM:
+ {
+ goodle_got_command_confirm( gc, from );
+ } break;
+ }
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+static void goodle_got_command_request( GaimConnection *gc, char *from )
+{
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ yd = gc->proto_data;
+
+ if( !auto_accept ) // TODO Ask local user to start Doodle session with remote user
+ {}; // NOTE This if/else statement won't work right--must use dialog results
+
+ // TODO Initialize Goodle window (canvas, palette, etc.)
+
+ // TODO Send ready packet (that local client accepted and is ready to start Doodle session)
+
+ // Make and send an acknowledge (ready) Doodle packet
+ pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 );
+ yahoo_packet_hash( pkt, 49, "IMVIRONMENT" );
+ yahoo_packet_hash( pkt, 1, gaim_account_get_username( gc->account ) );
+ yahoo_packet_hash( pkt, 14, "1" );
+ yahoo_packet_hash( pkt, 13, "1" );
+ yahoo_packet_hash( pkt, 5, from );
+ yahoo_packet_hash( pkt, 63, "doodle;11" );
+ yahoo_packet_hash( pkt, 64, "1" );
+ yahoo_packet_hash( pkt, 1002, "1" );
+ yahoo_send_packet( yd, pkt );
+
+ yahoo_packet_free( pkt );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+static void goodle_got_command_ready( GaimConnection *gc, char *from )
+{
+ // NOTE Only handle this if local client requested Doodle session?
+ // TODO So, check to see if local client requested Doodle session (through global request list)
+
+ goodle_send_command_confirm( gc, from );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+static void goodle_got_command_confirm( GaimConnection *gc, char *from )
+{
+ // TODO Check if local client really requested a Doodle session.
+
+ // TODO Initialize Goodle window (canvas, palette, etc.)
+
+ // TODO Perhaps set some kind of 'Doodle session established' flag as some sort of lock?
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+static void goodle_send_command_confirm( GaimConnection *gc, char *to )
+{
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ yd = gc->proto_data;
+
+ if( !auto_accept ) // TODO Ask local user to start Doodle session with remote user
+ {}; // NOTE This if/else statement won't work right--must use dialog results
+
+ // TODO Initialize Goodle window (canvas, palette, etc.)
+
+ // Send ready packet (that local client accepted and is ready)
+ pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 );
+ yahoo_packet_hash( pkt, 49, "IMVIRONMENT" );
+ yahoo_packet_hash( pkt, 1, gaim_account_get_username( gc->account ) );
+ yahoo_packet_hash( pkt, 14, "1" );
+ yahoo_packet_hash( pkt, 13, "5" );
+ yahoo_packet_hash( pkt, 5, to );
+ yahoo_packet_hash( pkt, 63, "doodle;11" );
+ yahoo_packet_hash( pkt, 64, "1" );
+ yahoo_packet_hash( pkt, 1002, "1" );
+ yahoo_send_packet( yd, pkt );
+
+ yahoo_packet_free( pkt );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+ /*
+ // Make and send a request to clear packet (doesn't work?)
+ pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 );
+ yahoo_packet_hash( pkt, 49, "IMVIRONMENT" );
+ yahoo_packet_hash( pkt, 1, gaim_normalize( gc->account, gaim_account_get_username( account ) ) );
+ yahoo_packet_hash( pkt, 14, " " );
+ yahoo_packet_hash( pkt, 13, "2" );
+ yahoo_packet_hash( pkt, 5, gaim_conversation_get_name( conv ) );
+ yahoo_packet_hash( pkt, 63, "doodle;11" );
+ yahoo_packet_hash( pkt, 64, "1" );
+ yahoo_packet_hash( pkt, 1002, "1" );
+ yahoo_send_packet( yd, pkt );
+ */
+
+ /*
+ // Make and send a drawing packet
+ pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 );
+ yahoo_packet_hash( pkt, 49, "IMVIRONMENT" );
+ yahoo_packet_hash( pkt, 1, gaim_normalize( gc->account, gaim_account_get_username( account ) ) );
+ yahoo_packet_hash( pkt, 14, "\"13369344,5,33,26,0,0,7,0,6,0,7,0,8,6,0,6,0,1,0\"" );
+ yahoo_packet_hash( pkt, 13, "3" );
+ yahoo_packet_hash( pkt, 5, gaim_conversation_get_name( conv ) );
+ yahoo_packet_hash( pkt, 63, "doodle;11" );
+ yahoo_packet_hash( pkt, 64, "1" );
+ yahoo_packet_hash( pkt, 1002, "1" );
+ yahoo_send_packet( yd, pkt );
+ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/yahoo/yahoo.c Sun Jul 03 04:48:12 2005 -0400
@@ -0,0 +1,3604 @@
+/*
+ * gaim
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "internal.h"
+
+#include "account.h"
+#include "accountopt.h"
+#include "blist.h"
+#include "cipher.h"
+#include "cmds.h"
+#include "debug.h"
+#include "notify.h"
+#include "privacy.h"
+#include "prpl.h"
+#include "proxy.h"
+#include "request.h"
+#include "server.h"
+#include "util.h"
+#include "version.h"
+
+#include "yahoo.h"
+#include "yahoo_packet.h"
+#include "yahoo_friend.h"
+#include "yahoochat.h"
+#include "ycht.h"
+#include "yahoo_auth.h"
+#include "yahoo_filexfer.h"
+#include "yahoo_picture.h"
+
+extern char *yahoo_crypt(const char *, const char *);
+
+/* #define YAHOO_DEBUG */
+
+static void yahoo_add_buddy(GaimConnection *gc, GaimBuddy *, GaimGroup *);
+static void yahoo_login_page_cb(void *user_data, const char *buf, size_t len);
+
+static void
+yahoo_add_permit(GaimConnection *gc, const char *who)
+{
+ gaim_debug_info("yahoo",
+ "Permitting ID %s local contact rights for account %s\n", who, gc->account);
+ gaim_privacy_permit_add(gc->account,who,TRUE);
+}
+
+static void
+yahoo_rem_permit(GaimConnection *gc, const char *who)
+{
+ gaim_debug_info("yahoo",
+ "Denying ID %s local contact rights for account %s\n", who, gc->account);
+ gaim_privacy_permit_remove(gc->account,who,TRUE);
+}
+
+gboolean yahoo_privacy_check(GaimConnection *gc, const char *who)
+{
+ /* returns TRUE if allowed through, FALSE otherwise */
+ GSList *list;
+ gboolean permitted=FALSE;
+
+ switch (gc->account->perm_deny) {
+ case GAIM_PRIVACY_ALLOW_ALL:
+ permitted = TRUE;
+ break;
+
+ case GAIM_PRIVACY_DENY_ALL:
+ gaim_debug_info("yahoo",
+ "%s blocked data received from %s (GAIM_PRIVACY_DENY_ALL)\n",
+ gc->account->username,who);
+ break;
+
+ case GAIM_PRIVACY_ALLOW_USERS:
+ for( list=gc->account->permit; list!=NULL; list=list->next ) {
+ if ( !gaim_utf8_strcasecmp(who, gaim_normalize(gc->account,
+ (char *)list->data)) ) {
+ permitted=TRUE;
+ gaim_debug_info("yahoo",
+ "%s allowed data received from %s (GAIM_PRIVACY_ALLOW_USERS)\n",
+ gc->account->username,who);
+ break;
+ }
+ }
+ break;
+
+ case GAIM_PRIVACY_DENY_USERS:
+ /* seeing we're letting everyone through, except the deny list*/
+ permitted=TRUE;
+ for( list=gc->account->deny; list!=NULL; list=list->next ) {
+ if ( !gaim_utf8_strcasecmp(who, gaim_normalize( gc->account,
+ (char *)list->data )) ) {
+ permitted=FALSE;
+ gaim_debug_info("yahoo",
+ "%s blocked data received from %s (GAIM_PRIVACY_DENY_USERS)\n",
+ gc->account->username,who);
+ break;
+ }
+ }
+ break;
+
+ case GAIM_PRIVACY_ALLOW_BUDDYLIST:
+ if ( gaim_find_buddy(gc->account,who) != NULL ) {
+ permitted = TRUE;
+ } else {
+ gaim_debug_info("yahoo",
+ "%s blocked data received from %s (GAIM_PRIVACY_ALLOW_BUDDYLIST)\n",
+ gc->account->username,who);
+ }
+ break;
+
+ default:
+ gaim_debug_warning("yahoo", "Privacy setting was unknown. If you can "
+ "reproduce this, please file a bug report.\n");
+ permitted = FALSE;
+ break;
+ }
+
+ return permitted;
+}
+
+static void yahoo_update_status(GaimConnection *gc, const char *name, YahooFriend *f)
+{
+ gboolean online = TRUE;
+ char *status = NULL;
+
+ if (!gc || !name || !f || !gaim_find_buddy(gaim_connection_get_account(gc), name))
+ return;
+
+ if (f->status == YAHOO_STATUS_OFFLINE)
+ online = FALSE;
+
+ switch (f->status) {
+ case YAHOO_STATUS_AVAILABLE:
+ status = YAHOO_STATUS_TYPE_AVAILABLE;
+ break;
+ case YAHOO_STATUS_BRB:
+ status = YAHOO_STATUS_TYPE_BRB;
+ break;
+ case YAHOO_STATUS_BUSY:
+ status = YAHOO_STATUS_TYPE_BUSY;
+ break;
+ case YAHOO_STATUS_NOTATHOME:
+ status = YAHOO_STATUS_TYPE_NOTATHOME;
+ break;
+ case YAHOO_STATUS_NOTATDESK:
+ status = YAHOO_STATUS_TYPE_NOTATDESK;
+ break;
+ case YAHOO_STATUS_NOTINOFFICE:
+ status = YAHOO_STATUS_TYPE_NOTINOFFICE;
+ break;
+ case YAHOO_STATUS_ONPHONE:
+ status = YAHOO_STATUS_TYPE_ONPHONE;
+ break;
+ case YAHOO_STATUS_ONVACATION:
+ status = YAHOO_STATUS_TYPE_ONVACATION;
+ break;
+ case YAHOO_STATUS_OUTTOLUNCH:
+ status = YAHOO_STATUS_TYPE_OUTTOLUNCH;
+ break;
+ case YAHOO_STATUS_STEPPEDOUT:
+ status = YAHOO_STATUS_TYPE_STEPPEDOUT;
+ break;
+ case YAHOO_STATUS_INVISIBLE: /* this should never happen? */
+ status = YAHOO_STATUS_TYPE_INVISIBLE;
+ break;
+ case YAHOO_STATUS_CUSTOM:
+ if (!f->away)
+ status = YAHOO_STATUS_TYPE_AVAILABLE_WM;
+ else
+ status = YAHOO_STATUS_TYPE_AWAY;
+ break;
+ case YAHOO_STATUS_IDLE:
+ break;
+ default:
+ gaim_debug_warning("yahoo", "Warning, unknown status %d\n", f->status);
+ break;
+ }
+
+ if (status) {
+ if (f->status == YAHOO_STATUS_CUSTOM)
+ gaim_prpl_got_user_status(gaim_connection_get_account(gc), name, status, "message",
+ yahoo_friend_get_status_message(f), NULL);
+ else
+ gaim_prpl_got_user_status(gaim_connection_get_account(gc), name, status, NULL);
+ }
+
+ if (f->idle != 0)
+ gaim_prpl_got_user_idle(gaim_connection_get_account(gc), name, TRUE, f->idle);
+ else
+ gaim_prpl_got_user_idle(gaim_connection_get_account(gc), name, FALSE, 0);
+}
+
+static void yahoo_process_status(GaimConnection *gc, struct yahoo_packet *pkt)
+{
+ GaimAccount *account = gaim_connection_get_account(gc);
+ struct yahoo_data *yd = gc->proto_data;
+ GSList *l = pkt->hash;
+ YahooFriend *f = NULL;
+ char *name = NULL;
+
+ if (pkt->service == YAHOO_SERVICE_LOGOFF && pkt->status == -1) {
+ gc->wants_to_die = TRUE;
+ gaim_connection_error(gc, _("You have signed on from another location."));
+ return;
+ }
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+
+ switch (pair->key) {
+ case 0: /* we won't actually do anything with this */
+ break;
+ case 1: /* we don't get the full buddy list here. */
+ if (!yd->logged_in) {
+ gaim_connection_set_display_name(gc, pair->value);
+ gaim_connection_set_state(gc, GAIM_CONNECTED);
+ yd->logged_in = TRUE;
+ if (yd->picture_upload_todo) {
+ yahoo_buddy_icon_upload(gc, yd->picture_upload_todo);
+ yd->picture_upload_todo = NULL;
+ }
+
+ /* this requests the list. i have a feeling that this is very evil
+ *
+ * scs.yahoo.com sends you the list before this packet without it being
+ * requested
+ *
+ * do_import(gc, NULL);
+ * newpkt = yahoo_packet_new(YAHOO_SERVICE_LIST, YAHOO_STATUS_OFFLINE, 0);
+ * yahoo_packet_send_and_free(newpkt, yd);
+ */
+
+ }
+ break;
+ case 8: /* how many online buddies we have */
+ break;
+ case 7: /* the current buddy */
+ if (name && f) /* update the previous buddy before changing the variables */
+ yahoo_update_status(gc, name, f);
+ name = pair->value;
+ if (name && g_utf8_validate(name, -1, NULL))
+ f = yahoo_friend_find_or_new(gc, name);
+ else {
+ f = NULL;
+ name = NULL;
+ }
+ break;
+ case 10: /* state */
+ if (!f)
+ break;
+
+ f->status = strtol(pair->value, NULL, 10);
+ if ((f->status >= YAHOO_STATUS_BRB) && (f->status <= YAHOO_STATUS_STEPPEDOUT))
+ f->away = 1;
+ else
+ f->away = 0;
+
+ if (f->status == YAHOO_STATUS_IDLE) {
+ /* Idle may have already been set in a more precise way in case 137 */
+ if (f->idle == 0)
+ f->idle = time(NULL);
+ } else
+ f->idle = 0;
+
+ if (f->status != YAHOO_STATUS_CUSTOM)
+ yahoo_friend_set_status_message(f, NULL);
+
+ f->sms = 0;
+ break;
+ case 19: /* custom message */
+ if (f)
+ yahoo_friend_set_status_message(f, yahoo_string_decode(gc, pair->value, FALSE));
+ break;
+ case 11: /* this is the buddy's session id */
+ break;
+ case 17: /* in chat? */
+ break;
+ case 47: /* is custom status away or not? 2=idle*/
+ if (!f)
+ break;
+
+ /* I have no idea what it means when this is
+ * set when someone's available, but it doesn't
+ * mean idle. */
+ if (f->status == YAHOO_STATUS_AVAILABLE)
+ break;
+
+ f->away = strtol(pair->value, NULL, 10);
+ if (f->away == 2) {
+ /* Idle may have already been set in a more precise way in case 137 */
+ if (f->idle == 0)
+ f->idle = time(NULL);
+ }
+
+ break;
+ case 138: /* either we're not idle, or we are but won't say how long */
+ if (!f)
+ break;
+
+ if (f->idle)
+ f->idle = -1;
+ break;
+ case 137: /* usually idle time in seconds, sometimes login time */
+ if (!f)
+ break;
+
+ if (f->status != YAHOO_STATUS_AVAILABLE)
+ f->idle = time(NULL) - strtol(pair->value, NULL, 10);
+ break;
+ case 13: /* bitmask, bit 0 = pager, bit 1 = chat, bit 2 = game */
+ if (strtol(pair->value, NULL, 10) == 0) {
+ if (f)
+ f->status = YAHOO_STATUS_OFFLINE;
+ gaim_prpl_got_user_status(account, name, "offline", NULL);
+ break;
+ }
+ break;
+ case 60: /* SMS */
+ if (f) {
+ f->sms = strtol(pair->value, NULL, 10);
+ yahoo_update_status(gc, name, f);
+ }
+ break;
+ case 197: /* Avatars */
+ {
+ char *decoded, *tmp;
+ guint len;
+
+ if (pair->value) {
+ gaim_base64_decode(pair->value, &decoded, &len);
+ if (len) {
+ tmp = gaim_str_binary_to_ascii(decoded, len);
+ gaim_debug_info("yahoo", "Got key 197, value = %s\n", tmp);
+ g_free(tmp);
+ }
+ g_free(decoded);
+ }
+ break;
+ }
+ case 192: /* Pictures, aka Buddy Icons, checksum */
+ {
+ int cksum = strtol(pair->value, NULL, 10);
+ GaimBuddy *b;
+
+ if (!name)
+ break;
+
+ b = gaim_find_buddy(gc->account, name);
+
+ if (!cksum || (cksum == -1)) {
+ if (f)
+ yahoo_friend_set_buddy_icon_need_request(f, TRUE);
+ gaim_buddy_icons_set_for_user(gc->account, name, NULL, 0);
+ if (b)
+ gaim_blist_node_remove_setting((GaimBlistNode *)b, YAHOO_ICON_CHECKSUM_KEY);
+ break;
+ }
+
+ if (!f)
+ break;
+
+ yahoo_friend_set_buddy_icon_need_request(f, FALSE);
+ if (cksum != gaim_blist_node_get_int((GaimBlistNode*)b, YAHOO_ICON_CHECKSUM_KEY))
+ yahoo_send_picture_request(gc, name);
+
+ break;
+ }
+ case 16: /* Custom error message */
+ {
+ char *tmp = yahoo_string_decode(gc, pair->value, TRUE);
+ gaim_notify_error(gc, NULL, tmp, NULL);
+ g_free(tmp);
+ }
+ break;
+ default:
+ gaim_debug(GAIM_DEBUG_ERROR, "yahoo",
+ "Unknown status key %d\n", pair->key);
+ break;
+ }
+
+ l = l->next;
+ }
+
+ if (name && f) /* update the last buddy */
+ yahoo_update_status(gc, name, f);
+}
+
+static void yahoo_do_group_check(GaimAccount *account, GHashTable *ht, const char *name, const char *group)
+{
+ GaimBuddy *b;
+ GaimGroup *g;
+ GSList *list, *i;
+ gboolean onlist = 0;
+ char *oname = NULL;
+
+ char **oname_p = &oname;
+ GSList **list_p = &list;
+
+ if (!g_hash_table_lookup_extended(ht, gaim_normalize(account, name), (gpointer *) oname_p, (gpointer *) list_p))
+ list = gaim_find_buddies(account, name);
+ else
+ g_hash_table_steal(ht, name);
+
+ for (i = list; i; i = i->next) {
+ b = i->data;
+ g = gaim_find_buddys_group(b);
+ if (!gaim_utf8_strcasecmp(group, g->name)) {
+ gaim_debug(GAIM_DEBUG_MISC, "yahoo",
+ "Oh good, %s is in the right group (%s).\n", name, group);
+ list = g_slist_delete_link(list, i);
+ onlist = 1;
+ break;
+ }
+ }
+
+ if (!onlist) {
+ gaim_debug(GAIM_DEBUG_MISC, "yahoo",
+ "Uhoh, %s isn't on the list (or not in this group), adding him to group %s.\n", name, group);
+ if (!(g = gaim_find_group(group))) {
+ g = gaim_group_new(group);
+ gaim_blist_add_group(g, NULL);
+ }
+ b = gaim_buddy_new(account, name, NULL);
+ gaim_blist_add_buddy(b, NULL, g, NULL);
+ }
+
+ if (list) {
+ if (!oname)
+ oname = g_strdup(gaim_normalize(account, name));
+ g_hash_table_insert(ht, oname, list);
+ } else if (oname)
+ g_free(oname);
+}
+
+static void yahoo_do_group_cleanup(gpointer key, gpointer value, gpointer user_data)
+{
+ char *name = key;
+ GSList *list = value, *i;
+ GaimBuddy *b;
+ GaimGroup *g;
+
+ for (i = list; i; i = i->next) {
+ b = i->data;
+ g = gaim_find_buddys_group(b);
+ gaim_debug(GAIM_DEBUG_MISC, "yahoo", "Deleting Buddy %s from group %s.\n", name, g->name);
+ gaim_blist_remove_buddy(b);
+ }
+}
+
+static char *_getcookie(char *rawcookie)
+{
+ char *cookie = NULL;
+ char *tmpcookie;
+ char *cookieend;
+
+ if (strlen(rawcookie) < 2)
+ return NULL;
+ tmpcookie = g_strdup(rawcookie+2);
+ cookieend = strchr(tmpcookie, ';');
+
+ if (cookieend)
+ *cookieend = '\0';
+
+ cookie = g_strdup(tmpcookie);
+ g_free(tmpcookie);
+
+ return cookie;
+}
+
+static void yahoo_process_cookie(struct yahoo_data *yd, char *c)
+{
+ if (c[0] == 'Y') {
+ if (yd->cookie_y)
+ g_free(yd->cookie_y);
+ yd->cookie_y = _getcookie(c);
+ } else if (c[0] == 'T') {
+ if (yd->cookie_t)
+ g_free(yd->cookie_t);
+ yd->cookie_t = _getcookie(c);
+ }
+}
+
+static void yahoo_process_list(GaimConnection *gc, struct yahoo_packet *pkt)
+{
+ GSList *l = pkt->hash;
+ gboolean export = FALSE;
+ gboolean got_serv_list = FALSE;
+ GaimBuddy *b;
+ GaimGroup *g;
+ YahooFriend *f = NULL;
+ GaimAccount *account = gaim_connection_get_account(gc);
+ struct yahoo_data *yd = gc->proto_data;
+ GHashTable *ht;
+
+ char **lines;
+ char **split;
+ char **buddies;
+ char **tmp, **bud, *norm_bud;
+ char *grp = NULL;
+ char *perm_stealth_buddies = NULL;
+
+ if (pkt->id)
+ yd->session_id = pkt->id;
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+ l = l->next;
+
+ switch (pair->key) {
+ case 87:
+ if (!yd->tmp_serv_blist)
+ yd->tmp_serv_blist = g_string_new(pair->value);
+ else
+ g_string_append(yd->tmp_serv_blist, pair->value);
+ break;
+ case 88:
+ if (!yd->tmp_serv_ilist)
+ yd->tmp_serv_ilist = g_string_new(pair->value);
+ else
+ g_string_append(yd->tmp_serv_ilist, pair->value);
+ break;
+ case 59: /* cookies, yum */
+ yahoo_process_cookie(yd, pair->value);
+ break;
+ case YAHOO_SERVICE_STEALTH_PERM:
+ perm_stealth_buddies = pair->value;
+ break;
+ }
+ }
+
+ if (pkt->status != 0)
+ return;
+
+ if (yd->tmp_serv_blist) {
+ ht = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_slist_free);
+
+ lines = g_strsplit(yd->tmp_serv_blist->str, "\n", -1);
+ for (tmp = lines; *tmp; tmp++) {
+ split = g_strsplit(*tmp, ":", 2);
+ if (!split)
+ continue;
+ if (!split[0] || !split[1]) {
+ g_strfreev(split);
+ continue;
+ }
+ grp = yahoo_string_decode(gc, split[0], FALSE);
+ buddies = g_strsplit(split[1], ",", -1);
+ for (bud = buddies; bud && *bud; bud++) {
+ norm_bud = g_strdup(gaim_normalize(account, *bud));
+ f = yahoo_friend_find_or_new(gc, norm_bud);
+
+ if (!(b = gaim_find_buddy(account, norm_bud))) {
+ if (!(g = gaim_find_group(grp))) {
+ g = gaim_group_new(grp);
+ gaim_blist_add_group(g, NULL);
+ }
+ b = gaim_buddy_new(account, norm_bud, NULL);
+ gaim_blist_add_buddy(b, NULL, g, NULL);
+ export = TRUE;
+ }
+
+ yahoo_do_group_check(account, ht, norm_bud, grp);
+ g_free(norm_bud);
+ }
+ g_strfreev(buddies);
+ g_strfreev(split);
+ g_free(grp);
+ }
+ g_strfreev(lines);
+
+ g_string_free(yd->tmp_serv_blist, TRUE);
+ yd->tmp_serv_blist = NULL;
+ g_hash_table_foreach(ht, yahoo_do_group_cleanup, NULL);
+ g_hash_table_destroy(ht);
+ }
+
+
+ if (yd->tmp_serv_ilist) {
+ buddies = g_strsplit(yd->tmp_serv_ilist->str, ",", -1);
+ for (bud = buddies; bud && *bud; bud++) {
+ /* The server is already ignoring the user */
+ got_serv_list = TRUE;
+ gaim_privacy_deny_add(gc->account, *bud, 1);
+ }
+ g_strfreev(buddies);
+
+ g_string_free(yd->tmp_serv_ilist, TRUE);
+ yd->tmp_serv_ilist = NULL;
+ }
+
+ if (got_serv_list &&
+ ((gc->account->perm_deny != GAIM_PRIVACY_ALLOW_BUDDYLIST) &&
+ (gc->account->perm_deny != GAIM_PRIVACY_DENY_ALL) &&
+ (gc->account->perm_deny != GAIM_PRIVACY_ALLOW_USERS)))
+ {
+ gc->account->perm_deny = GAIM_PRIVACY_DENY_USERS;
+ gaim_debug_info("yahoo", "Privacy defaulting to GAIM_PRIVACY_DENY_USERS.\n",
+ gc->account->username);
+ }
+
+ if (perm_stealth_buddies) {
+ buddies = g_strsplit(perm_stealth_buddies, ",", -1);
+ for (bud = buddies; bud && *bud; bud++) {
+ f = yahoo_friend_find(gc, *bud);
+ if (f)
+ f->stealth = YAHOO_STEALTH_PERM_OFFLINE;
+ }
+ g_strfreev(buddies);
+
+ }
+}
+
+static void yahoo_process_notify(GaimConnection *gc, struct yahoo_packet *pkt)
+{
+ char *msg = NULL;
+ char *from = NULL;
+ char *stat = NULL;
+ char *game = NULL;
+ YahooFriend *f = NULL;
+ GSList *l = pkt->hash;
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 4)
+ from = pair->value;
+ if (pair->key == 49)
+ msg = pair->value;
+ if (pair->key == 13)
+ stat = pair->value;
+ if (pair->key == 14)
+ game = pair->value;
+ l = l->next;
+ }
+
+ if (!from || !msg)
+ return;
+
+ if (!g_ascii_strncasecmp(msg, "TYPING", strlen("TYPING"))
+ && (yahoo_privacy_check(gc, from))) {
+ if (*stat == '1')
+ serv_got_typing(gc, from, 0, GAIM_TYPING);
+ else
+ serv_got_typing_stopped(gc, from);
+ } else if (!g_ascii_strncasecmp(msg, "GAME", strlen("GAME"))) {
+ GaimBuddy *bud = gaim_find_buddy(gc->account, from);
+
+ if (!bud) {
+ gaim_debug(GAIM_DEBUG_WARNING, "yahoo",
+ "%s is playing a game, and doesn't want "
+ "you to know.\n", from);
+ }
+
+ f = yahoo_friend_find(gc, from);
+ if (!f)
+ return; /* if they're not on the list, don't bother */
+
+ yahoo_friend_set_game(f, NULL);
+
+ if (*stat == '1') {
+ yahoo_friend_set_game(f, game);
+ if (bud)
+ yahoo_update_status(gc, from, f);
+ }
+ }
+}
+
+
+struct _yahoo_im {
+ char *from;
+ int time;
+ int utf8;
+ int buddy_icon;
+ char *msg;
+};
+
+static void yahoo_process_message(GaimConnection *gc, struct yahoo_packet *pkt)
+{
+ GSList *l = pkt->hash;
+ GSList *list = NULL;
+ struct _yahoo_im *im = NULL;
+
+ if (pkt->status <= 1 || pkt->status == 5) {
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 4) {
+ im = g_new0(struct _yahoo_im, 1);
+ list = g_slist_append(list, im);
+ im->from = pair->value;
+ im->time = time(NULL);
+ }
+ if (pair->key == 97)
+ if (im)
+ im->utf8 = strtol(pair->value, NULL, 10);
+ if (pair->key == 15)
+ if (im)
+ im->time = strtol(pair->value, NULL, 10);
+ if (pair->key == 206)
+ if (im)
+ im->buddy_icon = strtol(pair->value, NULL, 10);
+ if (pair->key == 14) {
+ if (im)
+ im->msg = pair->value;
+ }
+ l = l->next;
+ }
+ } else if (pkt->status == 2) {
+ gaim_notify_error(gc, NULL,
+ _("Your Yahoo! message did not get sent."), NULL);
+ }
+
+ for (l = list; l; l = l->next) {
+ YahooFriend *f;
+ char *m, *m2;
+ im = l->data;
+
+ if (!im->from || !im->msg) {
+ g_free(im);
+ continue;
+ }
+
+ if (!yahoo_privacy_check(gc, im->from)) {
+ gaim_debug_info("yahoo", "Message from %s dropped.\n", im->from);
+ return;
+ }
+
+ m = yahoo_string_decode(gc, im->msg, im->utf8);
+ gaim_str_strip_cr(m);
+
+ if (!strcmp(m, "<ding>")) {
+ GaimConversation *c = gaim_conversation_new(GAIM_CONV_IM,
+ gaim_connection_get_account(gc), im->from);
+ gaim_conv_im_write(GAIM_CONV_IM(c), "", _("Buzz!!"), GAIM_MESSAGE_NICK|GAIM_MESSAGE_RECV,
+ im->time);
+ g_free(m);
+ g_free(im);
+ continue;
+ }
+
+ m2 = yahoo_codes_to_html(m);
+ g_free(m);
+ serv_got_im(gc, im->from, m2, 0, im->time);
+ g_free(m2);
+
+ if ((f = yahoo_friend_find(gc, im->from)) && im->buddy_icon == 2) {
+ if (yahoo_friend_get_buddy_icon_need_request(f)) {
+ yahoo_send_picture_request(gc, im->from);
+ yahoo_friend_set_buddy_icon_need_request(f, FALSE);
+ }
+ }
+
+ g_free(im);
+ }
+ g_slist_free(list);
+}
+
+static void yahoo_process_sysmessage(GaimConnection *gc, struct yahoo_packet *pkt)
+{
+ GSList *l = pkt->hash;
+ char *prim, *me = NULL, *msg = NULL, *escmsg = NULL;
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+
+ if (pair->key == 5)
+ me = pair->value;
+ if (pair->key == 14)
+ msg = pair->value;
+
+ l = l->next;
+ }
+
+ if (!msg || !g_utf8_validate(msg, -1, NULL))
+ return;
+
+ escmsg = g_markup_escape_text(msg, -1);
+
+ prim = g_strdup_printf(_("Yahoo! system message for %s:"),
+ me?me:gaim_connection_get_display_name(gc));
+ gaim_notify_info(NULL, NULL, prim, escmsg);
+ g_free(prim);
+ g_free(escmsg);
+}
+
+static void yahoo_buddy_added_us(GaimConnection *gc, struct yahoo_packet *pkt) {
+ char *id = NULL;
+ char *who = NULL;
+ char *msg = NULL, *tmpmsg = NULL;
+ GSList *l = pkt->hash;
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+
+ switch (pair->key) {
+ case 1:
+ id = pair->value;
+ break;
+ case 3:
+ who = pair->value;
+ break;
+ case 15: /* time, for when they add us and we're offline */
+ break;
+ case 14:
+ msg = pair->value;
+ break;
+ }
+ l = l->next;
+ }
+
+ if (id) {
+ if (msg)
+ tmpmsg = yahoo_string_decode(gc, msg, FALSE);
+ gaim_account_notify_added(gc->account, id, who, NULL, tmpmsg);
+ if (tmpmsg)
+ g_free(tmpmsg);
+ }
+}
+
+static void yahoo_buddy_denied_our_add(GaimConnection *gc, struct yahoo_packet *pkt)
+{
+ char *who = NULL;
+ char *msg = NULL;
+ GSList *l = pkt->hash;
+ GString *buf = NULL;
+ struct yahoo_data *yd = gc->proto_data;
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+
+ switch (pair->key) {
+ case 3:
+ who = pair->value;
+ break;
+ case 14:
+ msg = pair->value;
+ break;
+ }
+ l = l->next;
+ }
+
+ if (who) {
+ char *msg2;
+ buf = g_string_sized_new(0);
+ if (!msg) {
+ g_string_printf(buf, _("%s has (retroactively) denied your request to add them to your list."), who);
+ } else {
+ msg2 = yahoo_string_decode(gc, msg, FALSE);
+ g_string_printf(buf, _("%s has (retroactively) denied your request to add them to your list for the following reason: %s."), who, msg2);
+ g_free(msg2);
+ }
+ gaim_notify_info(gc, NULL, _("Add buddy rejected"), buf->str);
+ g_string_free(buf, TRUE);
+ g_hash_table_remove(yd->friends, who);
+ gaim_prpl_got_user_status(gaim_connection_get_account(gc), who, "offline", NULL); /* FIXME: make this set not on list status instead */
+ }
+}
+
+static void yahoo_process_contact(GaimConnection *gc, struct yahoo_packet *pkt)
+{
+
+
+ switch (pkt->status) {
+ case 1:
+ yahoo_process_status(gc, pkt);
+ return;
+ case 3:
+ yahoo_buddy_added_us(gc, pkt);
+ break;
+ case 7:
+ yahoo_buddy_denied_our_add(gc, pkt);
+ break;
+ default:
+ break;
+ }
+}
+
+#define OUT_CHARSET "utf-8"
+
+static char *yahoo_decode(const char *text)
+{
+ char *converted = NULL;
+ char *n, *new;
+ const char *end, *p;
+ int i, k;
+
+ n = new = g_malloc(strlen (text) + 1);
+ end = text + strlen(text);
+
+ for (p = text; p < end; p++, n++) {
+ if (*p == '\\') {
+ if (p[1] >= '0' && p[1] <= '7') {
+ p += 1;
+ for (i = 0, k = 0; k < 3; k += 1) {
+ char c = p[k];
+ if (c < '0' || c > '7') break;
+ i *= 8;
+ i += c - '0';
+ }
+ *n = i;
+ p += k - 1;
+ } else { /* bug 959248 */
+ /* If we see a \ not followed by an octal number,
+ * it means that it is actually a \\ with one \
+ * already eaten by some unknown function.
+ * This is arguably broken.
+ *
+ * I think wing is wrong here, there is no function
+ * called that I see that could have done it. I guess
+ * it is just really sending single \'s. That's yahoo
+ * for you.
+ */
+ *n = *p;
+ }
+ }
+ else
+ *n = *p;
+ }
+
+ *n = '\0';
+
+ if (strstr(text, "\033$B"))
+ converted = g_convert(new, n - new, OUT_CHARSET, "iso-2022-jp", NULL, NULL, NULL);
+ if (!converted)
+ converted = g_convert(new, n - new, OUT_CHARSET, "iso-8859-1", NULL, NULL, NULL);
+ g_free(new);
+
+ return converted;
+}
+
+static void yahoo_process_mail(GaimConnection *gc, struct yahoo_packet *pkt)
+{
+ GaimAccount *account = gaim_connection_get_account(gc);
+ struct yahoo_data *yd = gc->proto_data;
+ char *who = NULL;
+ char *email = NULL;
+ char *subj = NULL;
+ char *yahoo_mail_url = (yd->jp? YAHOOJP_MAIL_URL: YAHOO_MAIL_URL);
+ int count = 0;
+ GSList *l = pkt->hash;
+
+ if (!gaim_account_get_check_mail(account))
+ return;
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 9)
+ count = strtol(pair->value, NULL, 10);
+ else if (pair->key == 43)
+ who = pair->value;
+ else if (pair->key == 42)
+ email = pair->value;
+ else if (pair->key == 18)
+ subj = pair->value;
+ l = l->next;
+ }
+
+ if (who && subj && email && *email) {
+ char *dec_who = yahoo_decode(who);
+ char *dec_subj = yahoo_decode(subj);
+ char *from = g_strdup_printf("%s (%s)", dec_who, email);
+
+ gaim_notify_email(gc, dec_subj, from, gaim_account_get_username(account),
+ yahoo_mail_url, NULL, NULL);
+
+ g_free(dec_who);
+ g_free(dec_subj);
+ g_free(from);
+ } else if (count > 0) {
+ const char *to = gaim_account_get_username(account);
+ const char *url = yahoo_mail_url;
+
+ gaim_notify_emails(gc, count, FALSE, NULL, NULL, &to, &url,
+ NULL, NULL);
+ }
+}
+/* This is the y64 alphabet... it's like base64, but has a . and a _ */
+char base64digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._";
+
+/* This is taken from Sylpheed by Hiroyuki Yamamoto. We have our own tobase64 function
+ * in util.c, but it has a bug I don't feel like finding right now ;) */
+void to_y64(unsigned char *out, const unsigned char *in, int inlen)
+ /* raw bytes in quasi-big-endian order to base 64 string (NUL-terminated) */
+{
+ for (; inlen >= 3; inlen -= 3)
+ {
+ *out++ = base64digits[in[0] >> 2];
+ *out++ = base64digits[((in[0] << 4) & 0x30) | (in[1] >> 4)];
+ *out++ = base64digits[((in[1] << 2) & 0x3c) | (in[2] >> 6)];
+ *out++ = base64digits[in[2] & 0x3f];
+ in += 3;
+ }
+ if (inlen > 0)
+ {
+ unsigned char fragment;
+
+ *out++ = base64digits[in[0] >> 2];
+ fragment = (in[0] << 4) & 0x30;
+ if (inlen > 1)
+ fragment |= in[1] >> 4;
+ *out++ = base64digits[fragment];
+ *out++ = (inlen < 2) ? '-' : base64digits[(in[1] << 2) & 0x3c];
+ *out++ = '-';
+ }
+ *out = '\0';
+}
+
+static void yahoo_process_auth_old(GaimConnection *gc, const char *seed)
+{
+ struct yahoo_packet *pack;
+ GaimAccount *account = gaim_connection_get_account(gc);
+ const char *name = gaim_normalize(account, gaim_account_get_username(account));
+ const char *pass = gaim_connection_get_password(gc);
+ struct yahoo_data *yd = gc->proto_data;
+
+ /* 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
+ * bizarre, convoluted, inordinately complicated. It's also no more secure than
+ * crypt() was. The only purpose this scheme could serve is to prevent third
+ * party clients from connecting to their servers.
+ *
+ * Sorry, Yahoo.
+ */
+
+ GaimCipher *cipher;
+ GaimCipherContext *context;
+ guint8 digest[16];
+
+ char *crypt_result;
+ char password_hash[25];
+ char crypt_hash[25];
+ char *hash_string_p = g_malloc(50 + strlen(name));
+ char *hash_string_c = g_malloc(50 + strlen(name));
+
+ char checksum;
+
+ int sv;
+
+ char result6[25];
+ char result96[25];
+
+ sv = seed[15];
+ sv = sv % 8;
+
+ cipher = gaim_ciphers_find_cipher("md5");
+ context = gaim_cipher_context_new(cipher, NULL);
+
+ gaim_cipher_context_append(context, pass, strlen(pass));
+ gaim_cipher_context_digest(context, sizeof(digest), digest, NULL);
+
+ to_y64(password_hash, digest, 16);
+
+ crypt_result = yahoo_crypt(pass, "$1$_2S43d5f$");
+
+ gaim_cipher_context_reset(context, NULL);
+ gaim_cipher_context_append(context, crypt_result, strlen(crypt_result));
+ gaim_cipher_context_digest(context, sizeof(digest), digest, NULL);
+ to_y64(crypt_hash, digest, 16);
+
+ switch (sv) {
+ case 1:
+ case 6:
+ checksum = seed[seed[9] % 16];
+ g_snprintf(hash_string_p, strlen(name) + 50,
+ "%c%s%s%s", checksum, name, seed, password_hash);
+ g_snprintf(hash_string_c, strlen(name) + 50,
+ "%c%s%s%s", checksum, name, seed, crypt_hash);
+ break;
+ case 2:
+ case 7:
+ checksum = seed[seed[15] % 16];
+ g_snprintf(hash_string_p, strlen(name) + 50,
+ "%c%s%s%s", checksum, seed, password_hash, name);
+ g_snprintf(hash_string_c, strlen(name) + 50,
+ "%c%s%s%s", checksum, seed, crypt_hash, name);
+ break;
+ case 3:
+ checksum = seed[seed[1] % 16];
+ g_snprintf(hash_string_p, strlen(name) + 50,
+ "%c%s%s%s", checksum, name, password_hash, seed);
+ g_snprintf(hash_string_c, strlen(name) + 50,
+ "%c%s%s%s", checksum, name, crypt_hash, seed);
+ break;
+ case 4:
+ checksum = seed[seed[3] % 16];
+ g_snprintf(hash_string_p, strlen(name) + 50,
+ "%c%s%s%s", checksum, password_hash, seed, name);
+ g_snprintf(hash_string_c, strlen(name) + 50,
+ "%c%s%s%s", checksum, crypt_hash, seed, name);
+ break;
+ case 0:
+ case 5:
+ checksum = seed[seed[7] % 16];
+ g_snprintf(hash_string_p, strlen(name) + 50,
+ "%c%s%s%s", checksum, password_hash, name, seed);
+ g_snprintf(hash_string_c, strlen(name) + 50,
+ "%c%s%s%s", checksum, crypt_hash, name, seed);
+ break;
+ }
+
+ gaim_cipher_context_reset(context, NULL);
+ gaim_cipher_context_append(context, hash_string_p, strlen(hash_string_p));
+ gaim_cipher_context_digest(context, sizeof(digest), digest, NULL);
+ to_y64(result6, digest, 16);
+
+ gaim_cipher_context_reset(context, NULL);
+ gaim_cipher_context_append(context, hash_string_c, strlen(hash_string_c));
+ gaim_cipher_context_digest(context, sizeof(digest), digest, NULL);
+ gaim_cipher_context_destroy(context);
+ to_y64(result96, digest, 16);
+
+ pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, YAHOO_STATUS_AVAILABLE, 0);
+ yahoo_packet_hash(pack, "ssss", 0, name, 6, result6, 96, result96, 1, name);
+ yahoo_packet_send_and_free(pack, yd);
+
+ g_free(hash_string_p);
+ g_free(hash_string_c);
+}
+
+/* I'm dishing out some uber-mad props to Cerulean Studios for cracking this
+ * and sending the fix! Thanks guys. */
+
+static void yahoo_process_auth_new(GaimConnection *gc, const char *seed)
+{
+ struct yahoo_packet *pack = NULL;
+ GaimAccount *account = gaim_connection_get_account(gc);
+ const char *name = gaim_normalize(account, gaim_account_get_username(account));
+ const char *pass = gaim_connection_get_password(gc);
+ struct yahoo_data *yd = gc->proto_data;
+
+ GaimCipher *md5_cipher;
+ GaimCipherContext *md5_ctx;
+ guint8 md5_digest[16];
+
+ GaimCipher *sha1_cipher;
+ GaimCipherContext *sha1_ctx1;
+ GaimCipherContext *sha1_ctx2;
+
+ char *alphabet1 = "FBZDWAGHrJTLMNOPpRSKUVEXYChImkwQ";
+ char *alphabet2 = "F0E1D2C3B4A59687abcdefghijklmnop";
+
+ char *challenge_lookup = "qzec2tb3um1olpar8whx4dfgijknsvy5";
+ char *operand_lookup = "+|&%/*^-";
+ char *delimit_lookup = ",;";
+
+ char *password_hash = (char *)g_malloc(25);
+ char *crypt_hash = (char *)g_malloc(25);
+ char *crypt_result = NULL;
+
+ char pass_hash_xor1[64];
+ char pass_hash_xor2[64];
+ char crypt_hash_xor1[64];
+ char crypt_hash_xor2[64];
+ char resp_6[100];
+ char resp_96[100];
+
+ unsigned char digest1[20];
+ unsigned char digest2[20];
+ unsigned char comparison_src[20];
+ unsigned char magic_key_char[4];
+ const unsigned char *magic_ptr;
+
+ unsigned int magic[64];
+ unsigned int magic_work = 0;
+ unsigned int magic_4 = 0;
+
+ int x;
+ int y;
+ int cnt = 0;
+ int magic_cnt = 0;
+ int magic_len;
+
+ memset(password_hash, 0, 25);
+ memset(crypt_hash, 0, 25);
+ 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);
+ memset(&comparison_src, 0, 20);
+
+ md5_cipher = gaim_ciphers_find_cipher("md5");
+ md5_ctx = gaim_cipher_context_new(md5_cipher, NULL);
+
+ sha1_cipher = gaim_ciphers_find_cipher("sha1");
+ sha1_ctx1 = gaim_cipher_context_new(sha1_cipher, NULL);
+ sha1_ctx2 = gaim_cipher_context_new(sha1_cipher, NULL);
+
+ /*
+ * 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 = seed;
+
+ while (*magic_ptr != (int)NULL) {
+ 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) {
+ /* SME XXX Error - disconnect here */
+ }
+
+ /* Get offset into lookup table and shl 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) {
+ /* SME XXX Disconnect */
+ }
+
+ 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 + 1 > magic_len) || (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);
+
+ /* First four bytes are magic key. */
+ memcpy(&magic_key_char[0], comparison_src, 4);
+ magic_4 = magic_key_char[0] | (magic_key_char[1]<<8) | (magic_key_char[2]<<16) | (magic_key_char[3]<<24);
+
+ /*
+ * Magic: Phase 4. Determine what function to use later by getting outside/inside
+ * loop values until we match our previous buffer.
+ */
+ for (x = 0; x < 65535; x++) {
+ int leave = 0;
+
+ for (y = 0; y < 5; y++) {
+ unsigned char test[3];
+
+ /* Calculate buffer. */
+ test[0] = x;
+ test[1] = x >> 8;
+ test[2] = y;
+
+ gaim_cipher_context_reset(md5_ctx, NULL);
+ gaim_cipher_context_append(md5_ctx, magic_key_char, 4);
+ gaim_cipher_context_append(md5_ctx, test, 3);
+ gaim_cipher_context_digest(md5_ctx, sizeof(md5_digest),
+ md5_digest, NULL);
+
+ if (!memcmp(md5_digest, comparison_src+4, 16)) {
+ leave = 1;
+ break;
+ }
+ }
+
+ if (leave == 1)
+ break;
+ }
+
+ /* If y != 0, we need some help. */
+ if (y != 0) {
+ unsigned int updated_key;
+
+ /* Update magic stuff.
+ * Call it twice because Yahoo's encryption is super bad ass.
+ */
+ updated_key = yahoo_auth_finalCountdown(magic_4, 0x60, y, x);
+ updated_key = yahoo_auth_finalCountdown(updated_key, 0x60, y, x);
+
+ magic_key_char[0] = updated_key & 0xff;
+ magic_key_char[1] = (updated_key >> 8) & 0xff;
+ magic_key_char[2] = (updated_key >> 16) & 0xff;
+ magic_key_char[3] = (updated_key >> 24) & 0xff;
+ }
+
+ /* Get password and crypt hashes as per usual. */
+ gaim_cipher_context_reset(md5_ctx, NULL);
+ gaim_cipher_context_append(md5_ctx, pass, strlen(pass));
+ gaim_cipher_context_digest(md5_ctx, sizeof(md5_digest),
+ md5_digest, NULL);
+ to_y64(password_hash, md5_digest, 16);
+
+ crypt_result = yahoo_crypt(pass, "$1$_2S43d5f$");
+ gaim_cipher_context_reset(md5_ctx, NULL);
+ gaim_cipher_context_append(md5_ctx, crypt_result, strlen(crypt_result));
+ gaim_cipher_context_digest(md5_ctx, sizeof(md5_digest),
+ md5_digest, NULL);
+ to_y64(crypt_hash, md5_digest, 16);
+
+ /* Our first authentication response is based off of the password hash. */
+ for (x = 0; x < (int)strlen(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(password_hash); x++)
+ pass_hash_xor2[cnt++] = password_hash[x] ^ 0x5c;
+
+ if (cnt < 64)
+ memset(&(pass_hash_xor2[cnt]), 0x5c, 64-cnt);
+
+ /*
+ * The first context gets the password hash XORed with 0x36 plus a magic value
+ * which we previously extrapolated from our challenge.
+ */
+
+ gaim_cipher_context_append(sha1_ctx1, pass_hash_xor1, 64);
+ if (y >= 3)
+ gaim_cipher_context_set_option(sha1_ctx1, "sizeLo", GINT_TO_POINTER(0x1ff));
+ gaim_cipher_context_append(sha1_ctx1, magic_key_char, 4);
+ gaim_cipher_context_digest(sha1_ctx1, sizeof(digest1), digest1, NULL);
+
+ /*
+ * The second context gets the password hash XORed with 0x5c plus the SHA-1 digest
+ * of the first context.
+ */
+
+ gaim_cipher_context_append(sha1_ctx2, pass_hash_xor2, 64);
+ gaim_cipher_context_append(sha1_ctx2, digest1, 20);
+ gaim_cipher_context_digest(sha1_ctx2, sizeof(digest2), digest2, NULL);
+
+ /*
+ * 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];
+
+ lookup = (val >> 0x0b);
+ lookup &= 0x1f;
+ if (lookup >= strlen(alphabet1))
+ break;
+ sprintf(byte, "%c", alphabet1[lookup]);
+ strcat(resp_6, byte);
+ strcat(resp_6, "=");
+
+ lookup = (val >> 0x06);
+ lookup &= 0x1f;
+ if (lookup >= strlen(alphabet2))
+ break;
+ sprintf(byte, "%c", alphabet2[lookup]);
+ strcat(resp_6, byte);
+
+ lookup = (val >> 0x01);
+ lookup &= 0x1f;
+ if (lookup >= strlen(alphabet2))
+ break;
+ sprintf(byte, "%c", alphabet2[lookup]);
+ strcat(resp_6, byte);
+
+ lookup = (val & 0x01);
+ if (lookup >= strlen(delimit_lookup))
+ 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(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(crypt_hash); x++)
+ crypt_hash_xor2[cnt++] = crypt_hash[x] ^ 0x5c;
+
+ if (cnt < 64)
+ memset(&(crypt_hash_xor2[cnt]), 0x5c, 64-cnt);
+
+ gaim_cipher_context_reset(sha1_ctx1, NULL);
+ gaim_cipher_context_reset(sha1_ctx2, NULL);
+
+ /*
+ * The first context gets the password hash XORed with 0x36 plus a magic value
+ * which we previously extrapolated from our challenge.
+ */
+
+ gaim_cipher_context_append(sha1_ctx1, crypt_hash_xor1, 64);
+ if (y >= 3) {
+ gaim_cipher_context_set_option(sha1_ctx1, "sizeLo",
+ GINT_TO_POINTER(0x1ff));
+ }
+ gaim_cipher_context_append(sha1_ctx1, magic_key_char, 4);
+ gaim_cipher_context_digest(sha1_ctx1, sizeof(digest1), digest1, NULL);
+
+ /*
+ * The second context gets the password hash XORed with 0x5c plus the SHA-1 digest
+ * of the first context.
+ */
+
+ gaim_cipher_context_append(sha1_ctx2, crypt_hash_xor2, 64);
+ gaim_cipher_context_append(sha1_ctx2, digest1, 20);
+ gaim_cipher_context_digest(sha1_ctx2, sizeof(digest2), digest2, NULL);
+
+ /*
+ * 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];
+
+ lookup = (val >> 0x0b);
+ lookup &= 0x1f;
+ if (lookup >= strlen(alphabet1))
+ break;
+ sprintf(byte, "%c", alphabet1[lookup]);
+ strcat(resp_96, byte);
+ strcat(resp_96, "=");
+
+ lookup = (val >> 0x06);
+ lookup &= 0x1f;
+ if (lookup >= strlen(alphabet2))
+ break;
+ sprintf(byte, "%c", alphabet2[lookup]);
+ strcat(resp_96, byte);
+
+ lookup = (val >> 0x01);
+ lookup &= 0x1f;
+ if (lookup >= strlen(alphabet2))
+ break;
+ sprintf(byte, "%c", alphabet2[lookup]);
+ strcat(resp_96, byte);
+
+ lookup = (val & 0x01);
+ if (lookup >= strlen(delimit_lookup))
+ break;
+ sprintf(byte, "%c", delimit_lookup[lookup]);
+ strcat(resp_96, byte);
+ }
+ gaim_debug_info("yahoo", "yahoo status: %d\n", yd->current_status);
+ pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, yd->current_status, 0);
+ yahoo_packet_hash(pack, "sssss", 0, name, 6, resp_6, 96, resp_96, 1,
+ name, 135, "6,0,0,1710");
+ if (yd->picture_checksum)
+ yahoo_packet_hash_int(pack, 192, yd->picture_checksum);
+
+ yahoo_packet_send_and_free(pack, yd);
+
+ gaim_cipher_context_destroy(md5_ctx);
+ gaim_cipher_context_destroy(sha1_ctx1);
+ gaim_cipher_context_destroy(sha1_ctx2);
+
+ g_free(password_hash);
+ g_free(crypt_hash);
+}
+
+static void yahoo_process_auth(GaimConnection *gc, struct yahoo_packet *pkt)
+{
+ char *seed = NULL;
+ char *sn = NULL;
+ GSList *l = pkt->hash;
+ int m = 0;
+ gchar *buf;
+
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 94)
+ seed = pair->value;
+ if (pair->key == 1)
+ sn = pair->value;
+ if (pair->key == 13)
+ m = atoi(pair->value);
+ l = l->next;
+ }
+
+ if (seed) {
+ switch (m) {
+ case 0:
+ yahoo_process_auth_old(gc, seed);
+ break;
+ case 1:
+ yahoo_process_auth_new(gc, seed);
+ break;
+ default:
+ buf = g_strdup_printf(_("The Yahoo server has requested the use of an unrecognized "
+ "authentication method. This version of Gaim will likely not be able "
+ "to successfully sign on to Yahoo. Check %s for updates."), GAIM_WEBSITE);
+ gaim_notify_error(gc, "", _("Failed Yahoo! Authentication"),
+ buf);
+ g_free(buf);
+ yahoo_process_auth_new(gc, seed); /* Can't hurt to try it anyway. */
+ }
+ }
+}
+
+static void ignore_buddy(GaimBuddy *buddy) {
+ GaimGroup *group;
+ GaimConversation *conv;
+ GaimAccount *account;
+ gchar *name;
+
+ if (!buddy)
+ return;
+
+ group = gaim_find_buddys_group(buddy);
+ name = g_strdup(buddy->name);
+ account = buddy->account;
+
+ gaim_debug(GAIM_DEBUG_INFO, "blist",
+ "Removing '%s' from buddy list.\n", buddy->name);
+ serv_remove_buddy(account->gc, buddy, group);
+ gaim_blist_remove_buddy(buddy);
+
+ serv_add_deny(account->gc, name);
+
+ /* The follow should really be done by the core... */
+ conv = gaim_find_conversation_with_account(GAIM_CONV_IM, name, account);
+
+ if (conv != NULL)
+ gaim_conversation_update(conv, GAIM_CONV_UPDATE_REMOVE);
+
+ g_free(name);
+}
+
+static void keep_buddy(GaimBuddy *b) {
+ gaim_privacy_deny_remove(b->account, b->name, 1);
+}
+
+static void yahoo_process_ignore(GaimConnection *gc, struct yahoo_packet *pkt) {
+ GaimBuddy *b;
+ GSList *l;
+ gchar *who = NULL;
+ gchar *sn = NULL;
+ gchar buf[BUF_LONG];
+ gint ignore = 0;
+ gint status = 0;
+
+ for (l = pkt->hash; l; l = l->next) {
+ struct yahoo_pair *pair = l->data;
+ switch (pair->key) {
+ case 0:
+ who = pair->value;
+ break;
+ case 1:
+ sn = pair->value;
+ break;
+ case 13:
+ ignore = strtol(pair->value, NULL, 10);
+ break;
+ case 66:
+ status = strtol(pair->value, NULL, 10);
+ break;
+ default:
+ break;
+ }
+ }
+
+ switch (status) {
+ case 12:
+ b = gaim_find_buddy(gc->account, who);
+ g_snprintf(buf, sizeof(buf), _("You have tried to ignore %s, but the "
+ "user is on your buddy list. Clicking \"Yes\" "
+ "will remove and ignore the buddy."), who);
+ gaim_request_yes_no(gc, NULL, _("Ignore buddy?"), buf, 0, b,
+ G_CALLBACK(ignore_buddy),
+ G_CALLBACK(keep_buddy));
+ break;
+ case 2:
+ case 3:
+ case 0:
+ default:
+ break;
+ }
+}
+
+static void yahoo_process_authresp(GaimConnection *gc, struct yahoo_packet *pkt)
+{
+ struct yahoo_data *yd = gc->proto_data;
+ GSList *l = pkt->hash;
+ int err = 0;
+ char *msg;
+ char *url = NULL;
+ char *fullmsg;
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+
+ if (pair->key == 66)
+ err = strtol(pair->value, NULL, 10);
+ if (pair->key == 20)
+ url = pair->value;
+
+ l = l->next;
+ }
+
+ switch (err) {
+ case 3:
+ msg = g_strdup(_("Invalid username."));
+ break;
+ case 13:
+ if (!yd->wm) {
+ yd->wm = TRUE;
+ if (yd->fd >= 0)
+ close(yd->fd);
+ if (gc->inpa)
+ gaim_input_remove(gc->inpa);
+ gaim_url_fetch(WEBMESSENGER_URL, TRUE, "Gaim/" VERSION, FALSE,
+ yahoo_login_page_cb, gc);
+ gaim_notify_warning(gc, NULL, _("Normal authentication failed!"),
+ _("The normal authentication method has failed. "
+ "This means either your password is incorrect, "
+ "or Yahoo!'s authentication scheme has changed. "
+ "Gaim will now attempt to log in using Web "
+ "Messenger authentication, which will result "
+ "in reduced functionality and features."));
+ return;
+ }
+ msg = g_strdup(_("Incorrect password."));
+ break;
+ case 14:
+ msg = g_strdup(_("Your account is locked, please log in to the Yahoo! website."));
+ break;
+ default:
+ msg = g_strdup_printf(_("Unknown error number %d. Logging into the Yahoo! website may fix this."), err);
+ }
+
+ if (url)
+ fullmsg = g_strdup_printf("%s\n%s", msg, url);
+ else
+ fullmsg = g_strdup(msg);
+
+ gc->wants_to_die = TRUE;
+ gaim_connection_error(gc, fullmsg);
+ g_free(msg);
+ g_free(fullmsg);
+}
+
+static void yahoo_process_addbuddy(GaimConnection *gc, struct yahoo_packet *pkt)
+{
+ int err = 0;
+ char *who = NULL;
+ char *group = NULL;
+ char *decoded_group;
+ char *buf;
+ YahooFriend *f;
+ GSList *l = pkt->hash;
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+
+ switch (pair->key) {
+ case 66:
+ err = strtol(pair->value, NULL, 10);
+ break;
+ case 7:
+ who = pair->value;
+ break;
+ case 65:
+ group = pair->value;
+ break;
+ }
+
+ l = l->next;
+ }
+
+ if (!who)
+ return;
+ if (!group)
+ group = "";
+
+ if (!err || (err == 2)) { /* 0 = ok, 2 = already on serv list */
+ f = yahoo_friend_find_or_new(gc, who);
+ yahoo_update_status(gc, who, f);
+ return;
+ }
+
+ decoded_group = yahoo_string_decode(gc, group, FALSE);
+ buf = g_strdup_printf(_("Could not add buddy %s to group %s to the server list on account %s."),
+ who, decoded_group, gaim_connection_get_display_name(gc));
+ if (!gaim_conv_present_error(who, gaim_connection_get_account(gc), buf))
+ gaim_notify_error(gc, NULL, _("Could not add buddy to server list"), buf);
+ g_free(buf);
+ g_free(decoded_group);
+}
+
+static void yahoo_process_p2p(GaimConnection *gc, struct yahoo_packet *pkt)
+{
+ GSList *l = pkt->hash;
+ char *who = NULL;
+ char *base64 = NULL;
+ char *decoded;
+ int len;
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+
+ switch (pair->key) {
+ case 5:
+ /* our identity */
+ break;
+ case 4:
+ who = pair->value;
+ break;
+ case 1:
+ /* who again, the master identity this time? */
+ break;
+ case 12:
+ base64 = pair->value;
+ /* so, this is an ip address. in base64. decoded it's in ascii.
+ after strtol, it's in reversed byte order. Who thought this up?*/
+ break;
+ /*
+ TODO: figure these out
+ yahoo: Key: 61 Value: 0
+ yahoo: Key: 2 Value:
+ yahoo: Key: 13 Value: 0
+ yahoo: Key: 49 Value: PEERTOPEER
+ yahoo: Key: 140 Value: 1
+ yahoo: Key: 11 Value: -1786225828
+ */
+
+ }
+
+ l = l->next;
+ }
+
+ if (base64) {
+ guint32 ip;
+ char *tmp2;
+ YahooFriend *f;
+
+ gaim_base64_decode(base64, &decoded, &len);
+ if (len) {
+ char *tmp = gaim_str_binary_to_ascii(decoded, len);
+ gaim_debug_info("yahoo", "Got P2P service packet (from server): who = %s, ip = %s\n", who, tmp);
+ g_free(tmp);
+ }
+
+ tmp2 = g_strndup(decoded, len); /* so its \0 terminated...*/
+ ip = strtol(tmp2, NULL, 10);
+ g_free(tmp2);
+ g_free(decoded);
+ tmp2 = g_strdup_printf("%u.%u.%u.%u", ip & 0xff, (ip >> 8) & 0xff, (ip >> 16) & 0xff,
+ (ip >> 24) & 0xff);
+ f = yahoo_friend_find(gc, who);
+ if (f)
+ yahoo_friend_set_ip(f, tmp2);
+ g_free(tmp2);
+ }
+}
+
+static void yahoo_process_audible(GaimConnection *gc, struct yahoo_packet *pkt)
+{
+ char *who = NULL, *msg = NULL;
+ GSList *l = pkt->hash;
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+
+ switch (pair->key) {
+ case 4:
+ who = pair->value;
+ break;
+ case 5:
+ /* us */
+ break;
+ case 230:
+ /* the audible, in foo.bar.baz format */
+ break;
+ case 231:
+ /* the text of the audible */
+ msg = pair->value;
+ break;
+ case 232:
+ /* weird number (md5 hash?), like 8ebab9094156135f5dcbaccbeee662a5c5fd1420 */
+ break;
+ }
+
+ l = l->next;
+ }
+
+ if (!who || !msg)
+ return;
+ if (!g_utf8_validate(msg, -1, NULL)) {
+ gaim_debug_misc("yahoo", "Warning, nonutf8 audible, ignoring!\n");
+ return;
+ }
+ if (!yahoo_privacy_check(gc, who)) {
+ gaim_debug_misc("yahoo", "Audible message from %s for %s dropped!\n",
+ gc->account->username, who);
+ return;
+ }
+ serv_got_im(gc, who, msg, 0, time(NULL));
+}
+
+void yahoo_packet_process(GaimConnection *gc, struct yahoo_packet *pkt)
+{
+ switch (pkt->service) {
+ case YAHOO_SERVICE_LOGON:
+ case YAHOO_SERVICE_LOGOFF:
+ case YAHOO_SERVICE_ISAWAY:
+ case YAHOO_SERVICE_ISBACK:
+ case YAHOO_SERVICE_GAMELOGON:
+ case YAHOO_SERVICE_GAMELOGOFF:
+ case YAHOO_SERVICE_CHATLOGON:
+ case YAHOO_SERVICE_CHATLOGOFF:
+ case YAHOO_SERVICE_Y6_STATUS_UPDATE:
+ yahoo_process_status(gc, pkt);
+ break;
+ case YAHOO_SERVICE_NOTIFY:
+ yahoo_process_notify(gc, pkt);
+ break;
+ case YAHOO_SERVICE_MESSAGE:
+ case YAHOO_SERVICE_GAMEMSG:
+ case YAHOO_SERVICE_CHATMSG:
+ yahoo_process_message(gc, pkt);
+ break;
+ case YAHOO_SERVICE_SYSMESSAGE:
+ yahoo_process_sysmessage(gc, pkt);
+ break;
+ case YAHOO_SERVICE_NEWMAIL:
+ yahoo_process_mail(gc, pkt);
+ break;
+ case YAHOO_SERVICE_NEWCONTACT:
+ yahoo_process_contact(gc, pkt);
+ break;
+ case YAHOO_SERVICE_AUTHRESP:
+ yahoo_process_authresp(gc, pkt);
+ break;
+ case YAHOO_SERVICE_LIST:
+ yahoo_process_list(gc, pkt);
+ break;
+ case YAHOO_SERVICE_AUTH:
+ yahoo_process_auth(gc, pkt);
+ break;
+ case YAHOO_SERVICE_ADDBUDDY:
+ yahoo_process_addbuddy(gc, pkt);
+ break;
+ case YAHOO_SERVICE_IGNORECONTACT:
+ yahoo_process_ignore(gc, pkt);
+ break;
+ case YAHOO_SERVICE_CONFINVITE:
+ case YAHOO_SERVICE_CONFADDINVITE:
+ yahoo_process_conference_invite(gc, pkt);
+ break;
+ case YAHOO_SERVICE_CONFDECLINE:
+ yahoo_process_conference_decline(gc, pkt);
+ break;
+ case YAHOO_SERVICE_CONFLOGON:
+ yahoo_process_conference_logon(gc, pkt);
+ break;
+ case YAHOO_SERVICE_CONFLOGOFF:
+ yahoo_process_conference_logoff(gc, pkt);
+ break;
+ case YAHOO_SERVICE_CONFMSG:
+ yahoo_process_conference_message(gc, pkt);
+ break;
+ case YAHOO_SERVICE_CHATONLINE:
+ yahoo_process_chat_online(gc, pkt);
+ break;
+ case YAHOO_SERVICE_CHATLOGOUT:
+ yahoo_process_chat_logout(gc, pkt);
+ break;
+ case YAHOO_SERVICE_CHATGOTO:
+ yahoo_process_chat_goto(gc, pkt);
+ break;
+ case YAHOO_SERVICE_CHATJOIN:
+ yahoo_process_chat_join(gc, pkt);
+ break;
+ case YAHOO_SERVICE_CHATLEAVE: /* XXX is this right? */
+ case YAHOO_SERVICE_CHATEXIT:
+ yahoo_process_chat_exit(gc, pkt);
+ break;
+ case YAHOO_SERVICE_CHATINVITE: /* XXX never seen this one, might not do it right */
+ case YAHOO_SERVICE_CHATADDINVITE:
+ yahoo_process_chat_addinvite(gc, pkt);
+ break;
+ case YAHOO_SERVICE_COMMENT:
+ yahoo_process_chat_message(gc, pkt);
+ break;
+ case YAHOO_SERVICE_STEALTH_PERM:
+ case YAHOO_SERVICE_STEALTH_SESSION:
+ yahoo_process_stealth(gc, pkt);
+ break;
+ case YAHOO_SERVICE_P2PFILEXFER:
+ case YAHOO_SERVICE_FILETRANSFER:
+ yahoo_process_filetransfer(gc, pkt);
+ break;
+ case YAHOO_SERVICE_PEEPTOPEER:
+ yahoo_process_p2p(gc, pkt);
+ break;
+ case YAHOO_SERVICE_PICTURE:
+ yahoo_process_picture(gc, pkt);
+ break;
+ case YAHOO_SERVICE_PICTURE_UPDATE:
+ yahoo_process_picture_update(gc, pkt);
+ break;
+ case YAHOO_SERVICE_PICTURE_CHECKSUM:
+ yahoo_process_picture_checksum(gc, pkt);
+ break;
+ case YAHOO_SERVICE_PICTURE_UPLOAD:
+ yahoo_process_picture_upload(gc, pkt);
+ break;
+ case YAHOO_SERVICE_AUDIBLE:
+ yahoo_process_audible(gc, pkt);
+ default:
+ gaim_debug(GAIM_DEBUG_ERROR, "yahoo",
+ "Unhandled service 0x%02x\n", pkt->service);
+ break;
+ }
+}
+
+void yahoo_pending(gpointer data, gint source, GaimInputCondition cond)
+{
+ GaimConnection *gc = data;
+ struct yahoo_data *yd = gc->proto_data;
+ char buf[1024];
+ int len;
+
+ len = read(yd->fd, buf, sizeof(buf));
+
+ if (len <= 0) {
+ gaim_connection_error(gc, _("Unable to read"));
+ return;
+ }
+
+ yd->rxqueue = g_realloc(yd->rxqueue, len + yd->rxlen);
+ memcpy(yd->rxqueue + yd->rxlen, buf, len);
+ yd->rxlen += len;
+
+ while (1) {
+ struct yahoo_packet *pkt;
+ int pos = 0;
+ int pktlen;
+
+ if (yd->rxlen < YAHOO_PACKET_HDRLEN)
+ return;
+
+ if (strncmp(yd->rxqueue, "YMSG", MIN(4, yd->rxlen)) != 0) {
+ /* HEY! This isn't even a YMSG packet. What
+ * are you trying to pull? */
+ guchar *start;
+
+ gaim_debug_warning("yahoo", "Error in YMSG stream, got something not a YMSG packet!");
+
+ start = memchr(yd->rxqueue + 1, 'Y', yd->rxlen - 1);
+ if (start) {
+ g_memmove(yd->rxqueue, start, yd->rxlen - (start - yd->rxqueue));
+ yd->rxlen -= start - yd->rxqueue;
+ continue;
+ } else {
+ g_free(yd->rxqueue);
+ yd->rxqueue = NULL;
+ yd->rxlen = 0;
+ return;
+ }
+ }
+
+ pos += 4; /* YMSG */
+ pos += 2;
+ pos += 2;
+
+ pktlen = yahoo_get16(yd->rxqueue + pos); pos += 2;
+ gaim_debug(GAIM_DEBUG_MISC, "yahoo",
+ "%d bytes to read, rxlen is %d\n", pktlen, yd->rxlen);
+
+ if (yd->rxlen < (YAHOO_PACKET_HDRLEN + pktlen))
+ return;
+
+ yahoo_packet_dump(yd->rxqueue, YAHOO_PACKET_HDRLEN + pktlen);
+
+ pkt = yahoo_packet_new(0, 0, 0);
+
+ pkt->service = yahoo_get16(yd->rxqueue + pos); pos += 2;
+ pkt->status = yahoo_get32(yd->rxqueue + pos); pos += 4;
+ gaim_debug(GAIM_DEBUG_MISC, "yahoo",
+ "Yahoo Service: 0x%02x Status: %d\n",
+ pkt->service, pkt->status);
+ pkt->id = yahoo_get32(yd->rxqueue + pos); pos += 4;
+
+ yahoo_packet_read(pkt, yd->rxqueue + pos, pktlen);
+
+ yd->rxlen -= YAHOO_PACKET_HDRLEN + pktlen;
+ if (yd->rxlen) {
+ char *tmp = g_memdup(yd->rxqueue + YAHOO_PACKET_HDRLEN + pktlen, yd->rxlen);
+ g_free(yd->rxqueue);
+ yd->rxqueue = tmp;
+ } else {
+ g_free(yd->rxqueue);
+ yd->rxqueue = NULL;
+ }
+
+ yahoo_packet_process(gc, pkt);
+
+ yahoo_packet_free(pkt);
+ }
+}
+
+static void yahoo_got_connected(gpointer data, gint source, GaimInputCondition cond)
+{
+ GaimConnection *gc = data;
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ if (!g_list_find(gaim_connections_get_all(), gc)) {
+ close(source);
+ return;
+ }
+
+ if (source < 0) {
+ gaim_connection_error(gc, _("Unable to connect."));
+ return;
+ }
+
+ yd = gc->proto_data;
+ yd->fd = source;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YAHOO_STATUS_AVAILABLE, 0);
+
+ yahoo_packet_hash_str(pkt, 1, gaim_normalize(gc->account, gaim_account_get_username(gaim_connection_get_account(gc))));
+ yahoo_packet_send_and_free(pkt, yd);
+
+ gc->inpa = gaim_input_add(yd->fd, GAIM_INPUT_READ, yahoo_pending, gc);
+}
+
+static void yahoo_got_web_connected(gpointer data, gint source, GaimInputCondition cond)
+{
+ GaimConnection *gc = data;
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+
+ if (!g_list_find(gaim_connections_get_all(), gc)) {
+ close(source);
+ return;
+ }
+
+ if (source < 0) {
+ gaim_connection_error(gc, _("Unable to connect."));
+ return;
+ }
+
+ yd = gc->proto_data;
+ yd->fd = source;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_WEBLOGIN, YAHOO_STATUS_WEBLOGIN, 0);
+
+ yahoo_packet_hash(pkt, "sss", 0,
+ gaim_normalize(gc->account, gaim_account_get_username(gaim_connection_get_account(gc))),
+ 1, gaim_normalize(gc->account, gaim_account_get_username(gaim_connection_get_account(gc))),
+ 6, yd->auth);
+ yahoo_packet_send_and_free(pkt, yd);
+
+ g_free(yd->auth);
+ gc->inpa = gaim_input_add(yd->fd, GAIM_INPUT_READ, yahoo_pending, gc);
+}
+
+static void yahoo_web_pending(gpointer data, gint source, GaimInputCondition cond)
+{
+ GaimConnection *gc = data;
+ GaimAccount *account = gaim_connection_get_account(gc);
+ struct yahoo_data *yd = gc->proto_data;
+ char buf[2048], *i = buf;
+ int len;
+ GString *s;
+
+ len = read(source, buf, sizeof(buf)-1);
+ if (len <= 0 || (strncmp(buf, "HTTP/1.0 302", strlen("HTTP/1.0 302")) &&
+ strncmp(buf, "HTTP/1.1 302", strlen("HTTP/1.1 302")))) {
+ gaim_connection_error(gc, _("Unable to read"));
+ return;
+ }
+
+ s = g_string_sized_new(len);
+ buf[sizeof(buf)-1] = '\0';
+
+ while ((i = strstr(i, "Set-Cookie: "))) {
+ i += strlen("Set-Cookie: ");
+ for (;*i != ';' && *i != '\0'; i++)
+ g_string_append_c(s, *i);
+
+ g_string_append(s, "; ");
+ }
+
+ yd->auth = g_string_free(s, FALSE);
+ gaim_input_remove(gc->inpa);
+ close(source);
+ /* Now we have our cookies to login with. I'll go get the milk. */
+ if (gaim_proxy_connect(account, "wcs2.msg.dcn.yahoo.com",
+ gaim_account_get_int(account, "port", YAHOO_PAGER_PORT),
+ yahoo_got_web_connected, gc) != 0) {
+ gaim_connection_error(gc, _("Connection problem"));
+ return;
+ }
+}
+
+static void yahoo_got_cookies(gpointer data, gint source, GaimInputCondition cond)
+{
+ GaimConnection *gc = data;
+ struct yahoo_data *yd = gc->proto_data;
+ if (source < 0) {
+ gaim_connection_error(gc, _("Unable to connect."));
+ return;
+ }
+ write(source, yd->auth, strlen(yd->auth));
+ g_free(yd->auth);
+ gc->inpa = gaim_input_add(source, GAIM_INPUT_READ, yahoo_web_pending, gc);
+}
+
+static void yahoo_login_page_hash_iter(const char *key, const char *val, GString *url)
+{
+ if (!strcmp(key, "passwd"))
+ return;
+ url = g_string_append_c(url, '&');
+ url = g_string_append(url, key);
+ url = g_string_append_c(url, '=');
+ if (!strcmp(key, ".save") || !strcmp(key, ".js"))
+ url = g_string_append_c(url, '1');
+ else if (!strcmp(key, ".challenge"))
+ url = g_string_append(url, val);
+ else
+ url = g_string_append(url, gaim_url_encode(val));
+}
+
+static GHashTable *yahoo_login_page_hash(const char *buf, size_t len)
+{
+ GHashTable *hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+ const char *c = buf;
+ char *d;
+ char name[64], value[64];
+ int count = sizeof(name)-1;
+ while ((c < (buf + len)) && (c = strstr(c, "<input "))) {
+ c = strstr(c, "name=\"") + strlen("name=\"");
+ for (d = name; *c!='"' && count; c++, d++, count--)
+ *d = *c;
+ *d = '\0';
+ count = sizeof(value)-1;
+ d = strstr(c, "value=\"") + strlen("value=\"");
+ if (strchr(c, '>') < d)
+ break;
+ for (c = d, d = value; *c!='"' && count; c++, d++, count--)
+ *d = *c;
+ *d = '\0';
+ g_hash_table_insert(hash, g_strdup(name), g_strdup(value));
+ }
+ return hash;
+}
+
+static void yahoo_login_page_cb(void *user_data, const char *buf, size_t len)
+{
+ GaimConnection *gc = (GaimConnection *)user_data;
+ GaimAccount *account = gaim_connection_get_account(gc);
+ struct yahoo_data *yd = gc->proto_data;
+ const char *sn = gaim_account_get_username(account);
+ const char *pass = gaim_connection_get_password(gc);
+ GHashTable *hash = yahoo_login_page_hash(buf, len);
+ GString *url = g_string_new("GET http://login.yahoo.com/config/login?login=");
+ char md5[33], *hashp = md5, *chal;
+ int i;
+ GaimCipher *cipher;
+ GaimCipherContext *context;
+ guint8 digest[16];
+
+ url = g_string_append(url, sn);
+ url = g_string_append(url, "&passwd=");
+
+ cipher = gaim_ciphers_find_cipher("md5");
+ context = gaim_cipher_context_new(cipher, NULL);
+
+ gaim_cipher_context_append(context, pass, strlen(pass));
+ gaim_cipher_context_digest(context, sizeof(digest), digest, NULL);
+ for (i = 0; i < 16; ++i) {
+ g_snprintf(hashp, 3, "%02x", digest[i]);
+ hashp += 2;
+ }
+
+ chal = g_strconcat(md5, g_hash_table_lookup(hash, ".challenge"), NULL);
+ gaim_cipher_context_reset(context, NULL);
+ gaim_cipher_context_append(context, chal, strlen(chal));
+ gaim_cipher_context_digest(context, sizeof(digest), digest, NULL);
+ hashp = md5;
+ for (i = 0; i < 16; ++i) {
+ g_snprintf(hashp, 3, "%02x", digest[i]);
+ hashp += 2;
+ }
+ /*
+ * I dunno why this is here and commented out.. but in case it's needed
+ * I updated it..
+
+ gaim_cipher_context_reset(context, NULL);
+ gaim_cipher_context_append(context, md5, strlen(md5));
+ gaim_cipher_context_digest(context, sizeof(digest), digest, NULL);
+ hashp = md5;
+ for (i = 0; i < 16; ++i) {
+ g_snprintf(hashp, 3, "%02x", digest[i]);
+ hashp += 2;
+ }
+ */
+ g_free(chal);
+
+ url = g_string_append(url, md5);
+ g_hash_table_foreach(hash, (GHFunc)yahoo_login_page_hash_iter, url);
+
+ url = g_string_append(url, "&.hash=1&.md5=1 HTTP/1.1\r\n"
+ "Host: login.yahoo.com\r\n\r\n");
+ g_hash_table_destroy(hash);
+ yd->auth = g_string_free(url, FALSE);
+ if (gaim_proxy_connect(account, "login.yahoo.com", 80, yahoo_got_cookies, gc) != 0) {
+ gaim_connection_error(gc, _("Connection problem"));
+ return;
+ }
+
+ gaim_cipher_context_destroy(context);
+}
+
+static void yahoo_server_check(GaimAccount *account)
+{
+ const char *server;
+
+ server = gaim_account_get_string(account, "server", YAHOO_PAGER_HOST);
+
+ if (strcmp(server, "scs.yahoo.com") == 0)
+ gaim_account_set_string(account, "server", YAHOO_PAGER_HOST);
+}
+
+static void yahoo_picture_check(GaimAccount *account)
+{
+ GaimConnection *gc = gaim_account_get_connection(account);
+ const char *buddyicon;
+
+ buddyicon = gaim_account_get_buddy_icon(account);
+ yahoo_set_buddy_icon(gc, buddyicon);
+}
+
+
+static void yahoo_login(GaimAccount *account, GaimStatus *status) {
+ GaimConnection *gc = gaim_account_get_connection(account);
+ struct yahoo_data *yd = gc->proto_data = g_new0(struct yahoo_data, 1);
+ const char *id = gaim_status_get_id(status);
+ gc->flags |= GAIM_CONNECTION_HTML | GAIM_CONNECTION_NO_BGCOLOR | GAIM_CONNECTION_NO_URLDESC;
+
+ gaim_connection_update_progress(gc, _("Connecting"), 1, 2);
+
+ gaim_connection_set_display_name(gc, gaim_account_get_username(account));
+
+ yd->fd = -1;
+ yd->friends = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, yahoo_friend_free);
+ yd->confs = NULL;
+ yd->conf_id = 2;
+
+ if (!strcmp(id, YAHOO_STATUS_TYPE_AVAILABLE) || !strcmp(id, YAHOO_STATUS_TYPE_ONLINE)) {
+ yd->current_status = YAHOO_STATUS_AVAILABLE;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_BRB)) {
+ yd->current_status = YAHOO_STATUS_BRB;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_BUSY)) {
+ yd->current_status = YAHOO_STATUS_BUSY;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_NOTATHOME)) {
+ yd->current_status = YAHOO_STATUS_NOTATHOME;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_NOTATDESK)) {
+ yd->current_status = YAHOO_STATUS_NOTATDESK;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_NOTINOFFICE)) {
+ yd->current_status = YAHOO_STATUS_NOTINOFFICE;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_ONPHONE)) {
+ yd->current_status = YAHOO_STATUS_ONPHONE;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_ONVACATION)) {
+ yd->current_status = YAHOO_STATUS_ONVACATION;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_OUTTOLUNCH)) {
+ yd->current_status = YAHOO_STATUS_OUTTOLUNCH;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_STEPPEDOUT)) {
+ yd->current_status = YAHOO_STATUS_STEPPEDOUT;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_INVISIBLE)) {
+ yd->current_status = YAHOO_STATUS_INVISIBLE;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_AWAY)) {
+ yd->current_status = YAHOO_STATUS_CUSTOM;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_AVAILABLE_WM)) {
+ yd->current_status = YAHOO_STATUS_CUSTOM;
+ } else if (gc->is_idle) { /* i think this is broken */
+ yd->current_status = YAHOO_STATUS_IDLE;
+ } else {
+ gaim_debug_error("yahoo", "Unexpected GaimStatus passed to yahoo_set_status!\n");
+ yd->current_status = YAHOO_STATUS_AVAILABLE;
+ }
+ yahoo_server_check(account);
+ yahoo_picture_check(account);
+
+ if (gaim_account_get_bool(account, "yahoojp", FALSE)) {
+ yd->jp = TRUE;
+ if (gaim_proxy_connect(account,
+ gaim_account_get_string(account, "serverjp", YAHOOJP_PAGER_HOST),
+ gaim_account_get_int(account, "port", YAHOO_PAGER_PORT),
+ yahoo_got_connected, gc) != 0)
+ {
+ gaim_connection_error(gc, _("Connection problem"));
+ return;
+ }
+ } else {
+ yd->jp = FALSE;
+ if (gaim_proxy_connect(account,
+ gaim_account_get_string(account, "server", YAHOO_PAGER_HOST),
+ gaim_account_get_int(account, "port", YAHOO_PAGER_PORT),
+ yahoo_got_connected, gc) != 0)
+ {
+ gaim_connection_error(gc, _("Connection problem"));
+ return;
+ }
+ }
+}
+
+static void yahoo_close(GaimConnection *gc) {
+ struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data;
+ GSList *l;
+
+ for (l = yd->confs; l; l = l->next) {
+ GaimConversation *conv = l->data;
+
+ yahoo_conf_leave(yd, gaim_conversation_get_name(conv),
+ gaim_connection_get_display_name(gc),
+ gaim_conv_chat_get_users(GAIM_CONV_CHAT(conv)));
+ }
+ g_slist_free(yd->confs);
+
+ g_hash_table_destroy(yd->friends);
+ if (yd->chat_name)
+ g_free(yd->chat_name);
+
+ if (yd->cookie_y)
+ g_free(yd->cookie_y);
+ if (yd->cookie_t)
+ g_free(yd->cookie_t);
+
+ if (yd->fd >= 0)
+ close(yd->fd);
+
+ if (yd->rxqueue)
+ g_free(yd->rxqueue);
+ yd->rxlen = 0;
+ if (yd->picture_url)
+ g_free(yd->picture_url);
+ if (yd->picture_upload_todo)
+ yahoo_buddy_icon_upload_data_free(yd->picture_upload_todo);
+ if (yd->ycht)
+ ycht_connection_close(yd->ycht);
+ if (gc->inpa)
+ gaim_input_remove(gc->inpa);
+ g_free(yd);
+}
+
+static const char *yahoo_list_icon(GaimAccount *a, GaimBuddy *b)
+{
+ return "yahoo";
+}
+
+static void yahoo_list_emblems(GaimBuddy *b, const char **se, const char **sw, const char **nw, const char **ne)
+{
+ int i = 0;
+ char *emblems[4] = {NULL,NULL,NULL,NULL};
+ GaimAccount *account;
+ GaimConnection *gc;
+ struct yahoo_data *yd;
+ YahooFriend *f;
+ GaimPresence *presence;
+ GaimStatus *status;
+ const char *status_id;
+
+ if (!b || !(account = b->account) || !(gc = gaim_account_get_connection(account)) ||
+ !(yd = gc->proto_data))
+ return;
+
+ f = yahoo_friend_find(gc, b->name);
+ if (!f) {
+ *se = "notauthorized";
+ return;
+ }
+
+ presence = gaim_buddy_get_presence(b);
+ status = gaim_presence_get_active_status(presence);
+ status_id = gaim_status_get_id(status);
+
+ if (gaim_presence_is_online(presence) == FALSE) {
+ *se = "offline";
+ return;
+ } else {
+ if (f->away)
+ emblems[i++] = "away";
+ if (f->sms)
+ emblems[i++] = "wireless";
+ if (yahoo_friend_get_game(f))
+ emblems[i++] = "game";
+ }
+ *se = emblems[0];
+ *sw = emblems[1];
+ *nw = emblems[2];
+ *ne = emblems[3];
+}
+
+static char *yahoo_get_status_string(enum yahoo_status a)
+{
+ switch (a) {
+ case YAHOO_STATUS_BRB:
+ return _("Be Right Back");
+ case YAHOO_STATUS_BUSY:
+ return _("Busy");
+ case YAHOO_STATUS_NOTATHOME:
+ return _("Not At Home");
+ case YAHOO_STATUS_NOTATDESK:
+ return _("Not At Desk");
+ case YAHOO_STATUS_NOTINOFFICE:
+ return _("Not In Office");
+ case YAHOO_STATUS_ONPHONE:
+ return _("On The Phone");
+ case YAHOO_STATUS_ONVACATION:
+ return _("On Vacation");
+ case YAHOO_STATUS_OUTTOLUNCH:
+ return _("Out To Lunch");
+ case YAHOO_STATUS_STEPPEDOUT:
+ return _("Stepped Out");
+ case YAHOO_STATUS_INVISIBLE:
+ return _("Invisible");
+ case YAHOO_STATUS_IDLE:
+ return _("Idle");
+ case YAHOO_STATUS_OFFLINE:
+ return _("Offline");
+ default:
+ return _("Online");
+ }
+}
+
+static void yahoo_initiate_conference(GaimBlistNode *node, gpointer data) {
+
+ GaimBuddy *buddy;
+ GaimConnection *gc;
+
+ GHashTable *components;
+ struct yahoo_data *yd;
+ int id;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+ buddy = (GaimBuddy *) node;
+ gc = gaim_account_get_connection(buddy->account);
+ yd = gc->proto_data;
+ id = yd->conf_id;
+
+ components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+ g_hash_table_replace(components, g_strdup("room"),
+ g_strdup_printf("%s-%d", gaim_connection_get_display_name(gc), id));
+ g_hash_table_replace(components, g_strdup("topic"), g_strdup("Join my conference..."));
+ g_hash_table_replace(components, g_strdup("type"), g_strdup("Conference"));
+ yahoo_c_join(gc, components);
+ g_hash_table_destroy(components);
+
+ yahoo_c_invite(gc, id, "Join my conference...", buddy->name);
+}
+
+static void yahoo_stealth_settings(GaimBlistNode *node, gpointer data) {
+ GaimBuddy *buddy;
+ GaimConnection *gc;
+ int stealth_val = GPOINTER_TO_INT(data);
+
+ buddy = (GaimBuddy *) node;
+ gc = gaim_account_get_connection(buddy->account);
+
+ yahoo_friend_update_stealth(gc, buddy->name, stealth_val);
+}
+
+static void yahoo_game(GaimBlistNode *node, gpointer data) {
+
+ GaimBuddy *buddy;
+ GaimConnection *gc;
+
+ struct yahoo_data *yd;
+ const char *game;
+ char *game2;
+ char *t;
+ char url[256];
+ YahooFriend *f;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+ buddy = (GaimBuddy *) node;
+ gc = gaim_account_get_connection(buddy->account);
+ yd = (struct yahoo_data *) gc->proto_data;
+
+ f = yahoo_friend_find(gc, buddy->name);
+ if (!f)
+ return;
+
+ game = yahoo_friend_get_game(f);
+ if (!game)
+ return;
+
+ t = game2 = g_strdup(strstr(game, "ante?room="));
+ while (*t && *t != '\t')
+ t++;
+ *t = 0;
+ g_snprintf(url, sizeof url, "http://games.yahoo.com/games/%s", game2);
+ gaim_notify_uri(gc, url);
+ g_free(game2);
+}
+
+static char *yahoo_status_text(GaimBuddy *b)
+{
+ YahooFriend *f = NULL;
+ const char *msg;
+
+ f = yahoo_friend_find(b->account->gc, b->name);
+ if (!f)
+ return g_strdup(_("Not on server list"));
+
+ switch (f->status) {
+ case YAHOO_STATUS_AVAILABLE:
+ return NULL;
+ case YAHOO_STATUS_IDLE:
+ if (f->idle == -1)
+ return g_strdup(yahoo_get_status_string(f->status));
+ return NULL;
+ case YAHOO_STATUS_CUSTOM:
+ if (!(msg = yahoo_friend_get_status_message(f)))
+ return NULL;
+ return g_markup_escape_text(msg, strlen(msg));
+
+ default:
+ return g_strdup(yahoo_get_status_string(f->status));
+ }
+}
+
+char *yahoo_tooltip_text(GaimBuddy *b)
+{
+ YahooFriend *f;
+ char *escaped, *status = NULL, *stealth = NULL;
+ GString *s = g_string_new("");
+
+ f = yahoo_friend_find(b->account->gc, b->name);
+ if (!f)
+ status = g_strdup_printf("\n%s", _("Not on server list"));
+ else {
+ switch (f->status) {
+ case YAHOO_STATUS_IDLE:
+ if (f->idle == -1) {
+ status = g_strdup(yahoo_get_status_string(f->status));
+ break;
+ }
+ return NULL;
+ case YAHOO_STATUS_CUSTOM:
+ if (!yahoo_friend_get_status_message(f))
+ return NULL;
+ status = g_strdup(yahoo_friend_get_status_message(f));
+ break;
+ case YAHOO_STATUS_OFFLINE:
+ break;
+ default:
+ status = g_strdup(yahoo_get_status_string(f->status));
+ break;
+ }
+
+ switch (f->stealth) {
+ case YAHOO_STEALTH_ONLINE:
+ stealth = _("Appear Online");
+ break;
+ case YAHOO_STEALTH_PERM_OFFLINE:
+ stealth = _("Appear Permanently Offline");
+ break;
+ case YAHOO_STEALTH_DEFAULT:
+ default:
+ stealth = _("None");
+ break;
+ }
+ }
+
+ if (status != NULL) {
+ escaped = g_markup_escape_text(status, strlen(status));
+ g_string_append_printf(s, _("\n<b>%s:</b> %s"), _("Status"), escaped);
+ g_free(status);
+ g_free(escaped);
+ }
+
+ if (stealth != NULL)
+ g_string_append_printf(s, _("\n<b>%s:</b> %s"),
+ _("Stealth"), stealth);
+
+ return g_string_free(s, FALSE);
+}
+
+static void yahoo_addbuddyfrommenu_cb(GaimBlistNode *node, gpointer data)
+{
+ GaimBuddy *buddy;
+ GaimConnection *gc;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+ buddy = (GaimBuddy *) node;
+ gc = gaim_account_get_connection(buddy->account);
+
+ yahoo_add_buddy(gc, buddy, NULL);
+}
+
+
+static void yahoo_chat_goto_menu(GaimBlistNode *node, gpointer data)
+{
+ GaimBuddy *buddy;
+ GaimConnection *gc;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+ buddy = (GaimBuddy *) node;
+ gc = gaim_account_get_connection(buddy->account);
+
+ yahoo_chat_goto(gc, buddy->name);
+}
+
+static GList *build_stealth_submenu(YahooFriend *f, GaimConnection *gc) {
+ GList *m = NULL;
+ GaimBlistNodeAction *act;
+ struct yahoo_data *yd = (struct yahoo_data *) gc->proto_data;
+
+ if (yd->current_status == YAHOO_STATUS_INVISIBLE) {
+ if (f->stealth != YAHOO_STEALTH_ONLINE) {
+ act = gaim_blist_node_action_new(_("Appear Online"),
+ yahoo_stealth_settings,
+ GINT_TO_POINTER(YAHOO_STEALTH_ONLINE),
+ NULL);
+ m = g_list_append(m, act);
+ } else if (f->stealth != YAHOO_STEALTH_DEFAULT) {
+ act = gaim_blist_node_action_new(_("Appear Offline"),
+ yahoo_stealth_settings,
+ GINT_TO_POINTER(YAHOO_STEALTH_DEFAULT),
+ NULL);
+ m = g_list_append(m, act);
+ }
+ }
+
+ if (f->stealth == YAHOO_STEALTH_PERM_OFFLINE) {
+ act = gaim_blist_node_action_new(
+ _("Don't Appear Permanently Offline"),
+ yahoo_stealth_settings,
+ GINT_TO_POINTER(YAHOO_STEALTH_DEFAULT), NULL);
+ m = g_list_append(m, act);
+ } else {
+ act = gaim_blist_node_action_new(
+ _("Appear Permanently Offline"),
+ yahoo_stealth_settings,
+ GINT_TO_POINTER(YAHOO_STEALTH_PERM_OFFLINE),
+ NULL);
+ m = g_list_append(m, act);
+ }
+
+ return m;
+}
+
+static GList *yahoo_buddy_menu(GaimBuddy *buddy)
+{
+ GList *m = NULL;
+ GaimBlistNodeAction *act;
+
+ GaimConnection *gc = gaim_account_get_connection(buddy->account);
+ struct yahoo_data *yd = gc->proto_data;
+ static char buf2[1024];
+ YahooFriend *f;
+
+ f = yahoo_friend_find(gc, buddy->name);
+
+ if (!f && !yd->wm) {
+ act = gaim_blist_node_action_new(_("Add Buddy"),
+ yahoo_addbuddyfrommenu_cb, NULL, NULL);
+ m = g_list_append(m, act);
+
+ return m;
+
+ }
+
+ if (f && f->status != YAHOO_STATUS_OFFLINE) {
+ if (!yd->wm) {
+ act = gaim_blist_node_action_new(_("Join in Chat"),
+ yahoo_chat_goto_menu, NULL, NULL);
+ m = g_list_append(m, act);
+ }
+
+ act = gaim_blist_node_action_new(_("Initiate Conference"),
+ yahoo_initiate_conference, NULL, NULL);
+ m = g_list_append(m, act);
+
+ if (yahoo_friend_get_game(f)) {
+ const char *game = yahoo_friend_get_game(f);
+ char *room;
+ char *t;
+
+ if ((room = strstr(game, "&follow="))) {/* skip ahead to the url */
+ while (*room && *room != '\t') /* skip to the tab */
+ room++;
+ t = room++; /* room as now at the name */
+ while (*t != '\n')
+ t++; /* replace the \n with a space */
+ *t = ' ';
+ g_snprintf(buf2, sizeof buf2, "%s", room);
+
+ act = gaim_blist_node_action_new(buf2, yahoo_game, NULL, NULL);
+ m = g_list_append(m, act);
+ }
+ }
+ }
+
+ if (f) {
+ act = gaim_blist_node_action_new(_("Stealth Settings"),
+ NULL, NULL, build_stealth_submenu(f, gc));
+ m = g_list_append(m, act);
+ }
+
+ return m;
+}
+
+
+static GList *yahoo_blist_node_menu(GaimBlistNode *node)
+{
+ if(GAIM_BLIST_NODE_IS_BUDDY(node)) {
+ return yahoo_buddy_menu((GaimBuddy *) node);
+ } else {
+ return NULL;
+ }
+}
+
+
+static void yahoo_act_id(GaimConnection *gc, const char *entry)
+{
+ struct yahoo_data *yd = gc->proto_data;
+
+ struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_IDACT, YAHOO_STATUS_AVAILABLE, 0);
+ yahoo_packet_hash_str(pkt, 3, entry);
+ yahoo_packet_send_and_free(pkt, yd);
+
+ gaim_connection_set_display_name(gc, entry);
+}
+
+static void yahoo_show_act_id(GaimPluginAction *action)
+{
+ GaimConnection *gc = (GaimConnection *) action->context;
+ gaim_request_input(gc, NULL, _("Active which ID?"), NULL,
+ gaim_connection_get_display_name(gc), FALSE, FALSE, NULL,
+ _("OK"), G_CALLBACK(yahoo_act_id),
+ _("Cancel"), NULL, gc);
+}
+
+static void yahoo_show_chat_goto(GaimPluginAction *action)
+{
+ GaimConnection *gc = (GaimConnection *) action->context;
+ gaim_request_input(gc, NULL, _("Join who in chat?"), NULL,
+ "", FALSE, FALSE, NULL,
+ _("OK"), G_CALLBACK(yahoo_chat_goto),
+ _("Cancel"), NULL, gc);
+}
+
+static GList *yahoo_actions(GaimPlugin *plugin, gpointer context) {
+ GList *m = NULL;
+ GaimPluginAction *act;
+
+ act = gaim_plugin_action_new(_("Activate ID..."),
+ yahoo_show_act_id);
+ m = g_list_append(m, act);
+
+ act = gaim_plugin_action_new(_("Join user in chat..."),
+ yahoo_show_chat_goto);
+ m = g_list_append(m, act);
+
+ return m;
+}
+
+int yahoo_send_im(GaimConnection *gc, const char *who, const char *what, GaimConvImFlags flags)
+{
+ struct yahoo_data *yd = gc->proto_data;
+ struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE, YAHOO_STATUS_OFFLINE, 0);
+ char *msg = yahoo_html_to_codes(what);
+ char *msg2;
+ gboolean utf8 = TRUE;
+ int ret = 1;
+
+ msg2 = yahoo_string_encode(gc, msg, &utf8);
+
+ yahoo_packet_hash(pkt, "ss", 1, gaim_connection_get_display_name(gc), 5, who);
+ if (utf8)
+ yahoo_packet_hash_str(pkt, 97, "1");
+ yahoo_packet_hash_str(pkt, 14, msg2);
+
+ yahoo_packet_hash_str(pkt, 63, ";0"); /* IMvironment */
+ yahoo_packet_hash_str(pkt, 64, "0"); /* no idea */
+ yahoo_packet_hash_str(pkt, 1002, "1"); /* no idea, Yahoo 6 or later only it seems */
+ if (!yd->picture_url)
+ yahoo_packet_hash_str(pkt, 206, "0"); /* 0 = no picture, 2 = picture, maybe 1 = avatar? */
+ else
+ yahoo_packet_hash_str(pkt, 206, "2");
+
+ /* We may need to not send any packets over 2000 bytes, but I'm not sure yet. */
+ if ((YAHOO_PACKET_HDRLEN + yahoo_packet_length(pkt)) <= 2000)
+ yahoo_packet_send(pkt, yd);
+ else
+ ret = -E2BIG;
+
+ yahoo_packet_free(pkt);
+
+ g_free(msg);
+ g_free(msg2);
+
+ return ret;
+}
+
+int yahoo_send_typing(GaimConnection *gc, const char *who, int typ)
+{
+ struct yahoo_data *yd = gc->proto_data;
+ struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YAHOO_STATUS_TYPING, 0);
+ yahoo_packet_hash(pkt, "ssssss", 49, "TYPING", 1, gaim_connection_get_display_name(gc),
+ 14, " ", 13, typ == GAIM_TYPING ? "1" : "0",
+ 5, who, 1002, "1");
+
+ yahoo_packet_send_and_free(pkt, yd);
+
+ return 0;
+}
+
+static void yahoo_session_stealth_remove(gpointer key, gpointer value, gpointer data)
+{
+ YahooFriend *f = value;
+ if (f && f->stealth == YAHOO_STEALTH_ONLINE)
+ f->stealth = YAHOO_STEALTH_DEFAULT;
+}
+
+static void yahoo_set_status(GaimAccount *account, GaimStatus *status)
+{
+ GaimConnection *gc = gaim_account_get_connection(account);
+ gboolean connected = gaim_account_is_connected(account);
+ struct yahoo_data *yd;
+ struct yahoo_packet *pkt;
+ int old_status;
+ const char *id;
+ char *conv_msg = NULL;
+ char *conv_msg2 = NULL;
+
+ id = gaim_status_get_id(status);
+ if (!gaim_status_is_active(status))
+ return;
+ if (strcmp(id, YAHOO_STATUS_TYPE_OFFLINE) && !connected) {
+ gaim_account_connect(account);
+ return;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_OFFLINE) && connected) {
+ gaim_account_disconnect(account);
+ return;
+ }
+
+ if (!connected)
+ return;
+
+ yd = (struct yahoo_data *)gc->proto_data;
+ old_status = yd->current_status;
+
+ if (!strcmp(id, YAHOO_STATUS_TYPE_AVAILABLE)) {
+ yd->current_status = YAHOO_STATUS_AVAILABLE;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_BRB)) {
+ yd->current_status = YAHOO_STATUS_BRB;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_BUSY)) {
+ yd->current_status = YAHOO_STATUS_BUSY;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_NOTATHOME)) {
+ yd->current_status = YAHOO_STATUS_NOTATHOME;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_NOTATDESK)) {
+ yd->current_status = YAHOO_STATUS_NOTATDESK;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_NOTINOFFICE)) {
+ yd->current_status = YAHOO_STATUS_NOTINOFFICE;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_ONPHONE)) {
+ yd->current_status = YAHOO_STATUS_ONPHONE;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_ONVACATION)) {
+ yd->current_status = YAHOO_STATUS_ONVACATION;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_OUTTOLUNCH)) {
+ yd->current_status = YAHOO_STATUS_OUTTOLUNCH;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_STEPPEDOUT)) {
+ yd->current_status = YAHOO_STATUS_STEPPEDOUT;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_INVISIBLE)) {
+ yd->current_status = YAHOO_STATUS_INVISIBLE;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_AWAY)) {
+ yd->current_status = YAHOO_STATUS_CUSTOM;
+ } else if (!strcmp(id, YAHOO_STATUS_TYPE_AVAILABLE_WM)) {
+ yd->current_status = YAHOO_STATUS_CUSTOM;
+ } else if (gc->is_idle) { /* i think this is broken */
+ yd->current_status = YAHOO_STATUS_IDLE;
+ } else {
+ gaim_debug_error("yahoo", "Unexpected GaimStatus passed to yahoo_login!\n");
+ yd->current_status = YAHOO_STATUS_AVAILABLE;
+ }
+
+ if (yd->current_status == YAHOO_STATUS_INVISIBLE) {
+ pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBLE_TOGGLE, YAHOO_STATUS_AVAILABLE, 0);
+ yahoo_packet_hash_str(pkt, 13, "2");
+ yahoo_packet_send_and_free(pkt, yd);
+
+ return;
+ }
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_STATUS_UPDATE, YAHOO_STATUS_AVAILABLE, 0);
+ yahoo_packet_hash_int(pkt, 10, yd->current_status);
+
+ if (yd->current_status == YAHOO_STATUS_CUSTOM) {
+ const char *msg = gaim_status_get_attr_string(status, "message");
+
+ if (msg == NULL) {
+ gaim_debug_info("yahoo", "Attempted to set a NULL status message.\n");
+ msg = "";
+ }
+
+ conv_msg = yahoo_string_encode(gc, msg, NULL);
+ conv_msg2 = gaim_markup_strip_html(conv_msg);
+ yahoo_packet_hash_str(pkt, 19, conv_msg2);
+
+ } else {
+ yahoo_packet_hash_str(pkt, 19, "");
+ }
+
+ if (gc->is_idle)
+ yahoo_packet_hash_str(pkt, 47, "2");
+ else if (!gaim_status_type_is_available(gaim_status_get_type(status)))
+ yahoo_packet_hash_str(pkt, 47, "1");
+
+ yahoo_packet_send_and_free(pkt, yd);
+
+ g_free(conv_msg);
+ g_free(conv_msg2);
+
+ if (old_status == YAHOO_STATUS_INVISIBLE) {
+ pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBLE_TOGGLE, YAHOO_STATUS_AVAILABLE, 0);
+ yahoo_packet_hash_str(pkt, 13, "1");
+ yahoo_packet_send_and_free(pkt, yd);
+
+ /* Any per-session stealth settings are removed */
+ g_hash_table_foreach(yd->friends, yahoo_session_stealth_remove, NULL);
+
+ }
+}
+
+static void yahoo_set_idle(GaimConnection *gc, int idle)
+{
+ struct yahoo_data *yd = gc->proto_data;
+ struct yahoo_packet *pkt = NULL;
+ char *msg = NULL, *msg2 = NULL;
+
+ if (idle && yd->current_status == YAHOO_STATUS_AVAILABLE)
+ yd->current_status = YAHOO_STATUS_IDLE;
+ else if (!idle && yd->current_status == YAHOO_STATUS_IDLE)
+ yd->current_status = YAHOO_STATUS_AVAILABLE;
+
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_STATUS_UPDATE, YAHOO_STATUS_AVAILABLE, 0);
+
+ yahoo_packet_hash_int(pkt, 10, yd->current_status);
+ if (yd->current_status == YAHOO_STATUS_CUSTOM) {
+ const char *tmp;
+ GaimStatus *status = gaim_presence_get_active_status(gaim_account_get_presence(gaim_connection_get_account(gc)));
+ tmp = gaim_status_get_attr_string(status, "message");
+ if (tmp != NULL) {
+ msg = yahoo_string_encode(gc, tmp, NULL);
+ msg2 = gaim_unescape_html(msg);
+ yahoo_packet_hash_str(pkt, 19, msg2);
+ } else {
+ yahoo_packet_hash_str(pkt, 19, "");
+ }
+ } else {
+ yahoo_packet_hash_str(pkt, 19, "");
+ }
+
+ if (idle)
+ yahoo_packet_hash_str(pkt, 47, "2");
+ else if (!gaim_presence_is_available(gaim_account_get_presence(gaim_connection_get_account(gc))))
+ yahoo_packet_hash_str(pkt, 47, "1");
+
+
+ yahoo_packet_send_and_free(pkt, yd);
+
+ g_free(msg);
+ g_free(msg2);
+}
+
+static GList *yahoo_status_types(GaimAccount *account)
+{
+ GaimConnection *gc = gaim_account_get_connection(account);
+ struct yahoo_data *yd = NULL;
+ GaimStatusType *type;
+ GList *types = NULL;
+
+ if (gc)
+ yd = gc->proto_data;
+
+ type = gaim_status_type_new(GAIM_STATUS_OFFLINE, YAHOO_STATUS_TYPE_OFFLINE, _("Offline"), TRUE);
+ types = g_list_append(types, type);
+
+ type = gaim_status_type_new(GAIM_STATUS_ONLINE, YAHOO_STATUS_TYPE_ONLINE, _("Online"), TRUE);
+ types = g_list_append(types, type);
+
+ type = gaim_status_type_new(GAIM_STATUS_AVAILABLE, YAHOO_STATUS_TYPE_AVAILABLE, _("Available"), TRUE);
+ types = g_list_append(types, type);
+
+ if (!yd || !yd->wm) {
+ type = gaim_status_type_new_with_attrs(GAIM_STATUS_AVAILABLE, YAHOO_STATUS_TYPE_AVAILABLE_WM,
+ "Available With Message", TRUE, TRUE, FALSE,
+ "message", _("Message"),
+ gaim_value_new(GAIM_TYPE_STRING), NULL);
+ types = g_list_append(types, type);
+
+
+ type = gaim_status_type_new(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_BRB, _("Be Right Back"), TRUE);
+ types = g_list_append(types, type);
+
+ type = gaim_status_type_new(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_BUSY, _("Busy"), TRUE);
+ types = g_list_append(types, type);
+
+ type = gaim_status_type_new(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_NOTATHOME, _("Not At Home"), TRUE);
+ types = g_list_append(types, type);
+
+ type = gaim_status_type_new(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_NOTATDESK, _("Not At Desk"), TRUE);
+ types = g_list_append(types, type);
+
+ type = gaim_status_type_new(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_NOTINOFFICE, _("Not In Office"), TRUE);
+ types = g_list_append(types, type);
+
+ type = gaim_status_type_new(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_ONPHONE, _("On The Phone"), TRUE);
+ types = g_list_append(types, type);
+
+ type = gaim_status_type_new(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_ONVACATION, _("On Vacation"), TRUE);
+ types = g_list_append(types, type);
+
+ type = gaim_status_type_new(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_OUTTOLUNCH, _("Out To Lunch"), TRUE);
+ types = g_list_append(types, type);
+
+ type = gaim_status_type_new(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_STEPPEDOUT, _("Stepped Out"), TRUE);
+ types = g_list_append(types, type);
+
+ type = gaim_status_type_new_with_attrs(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_AWAY,
+ _("Away"), TRUE, TRUE, FALSE,
+ "message", _("Message"),
+ gaim_value_new(GAIM_TYPE_STRING), NULL);
+ types = g_list_append(types, type);
+ }
+ type = gaim_status_type_new(GAIM_STATUS_HIDDEN, YAHOO_STATUS_TYPE_INVISIBLE, _("Invisible"), TRUE);
+ types = g_list_append(types, type);
+
+
+ return types;
+}
+
+static void yahoo_keepalive(GaimConnection *gc)
+{
+ struct yahoo_data *yd = gc->proto_data;
+ struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_PING, YAHOO_STATUS_AVAILABLE, 0);
+ yahoo_packet_send_and_free(pkt, yd);
+
+ if (!yd->chat_online)
+ return;
+
+ if (yd->wm) {
+ ycht_chat_send_keepalive(yd->ycht);
+ return;
+ }
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_CHATPING, YAHOO_STATUS_AVAILABLE, 0);
+ yahoo_packet_hash_str(pkt, 109, gaim_connection_get_display_name(gc));
+ yahoo_packet_send_and_free(pkt, yd);
+}
+
+/* XXX - What's the deal with GaimGroup *foo? */
+static void yahoo_add_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *foo)
+{
+ struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data;
+ struct yahoo_packet *pkt;
+ GaimGroup *g;
+ char *group = NULL;
+ char *group2 = NULL;
+
+ if (!yd->logged_in)
+ return;
+
+ if (!yahoo_privacy_check(gc, gaim_buddy_get_name(buddy)))
+ return;
+
+ if (foo)
+ group = foo->name;
+ if (!group) {
+ g = gaim_find_buddys_group(gaim_find_buddy(gc->account, buddy->name));
+ if (g)
+ group = g->name;
+ else
+ group = "Buddies";
+ }
+
+ group2 = yahoo_string_encode(gc, group, NULL);
+ pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YAHOO_STATUS_AVAILABLE, 0);
+ yahoo_packet_hash(pkt, "ssss", 1, gaim_connection_get_display_name(gc),
+ 7, buddy->name, 65, group2, 14, "");
+ yahoo_packet_send_and_free(pkt, yd);
+ g_free(group2);
+}
+
+static void yahoo_remove_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group)
+{
+ struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data;
+ YahooFriend *f;
+ struct yahoo_packet *pkt;
+ GSList *buddies, *l;
+ GaimGroup *g;
+ gboolean remove = TRUE;
+ char *cg;
+
+ if (!(f = yahoo_friend_find(gc, buddy->name)))
+ return;
+
+ buddies = gaim_find_buddies(gaim_connection_get_account(gc), buddy->name);
+ for (l = buddies; l; l = l->next) {
+ g = gaim_find_buddys_group(l->data);
+ if (gaim_utf8_strcasecmp(group->name, g->name)) {
+ remove = FALSE;
+ break;
+ }
+ }
+
+ g_slist_free(buddies);
+
+ if (remove)
+ g_hash_table_remove(yd->friends, buddy->name);
+
+ cg = yahoo_string_encode(gc, group->name, NULL);
+ pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YAHOO_STATUS_AVAILABLE, 0);
+ yahoo_packet_hash(pkt, "sss", 1, gaim_connection_get_display_name(gc),
+ 7, buddy->name, 65, cg);
+ yahoo_packet_send_and_free(pkt, yd);
+ g_free(cg);
+}
+
+static void yahoo_add_deny(GaimConnection *gc, const char *who) {
+ struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data;
+ struct yahoo_packet *pkt;
+
+ if (!yd->logged_in)
+ return;
+ /* It seems to work better without this */
+
+ /* if (gc->account->perm_deny != 4)
+ return; */
+
+ if (!who || who[0] == '\0')
+ return;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, YAHOO_STATUS_AVAILABLE, 0);
+ yahoo_packet_hash(pkt, "sss", 1, gaim_connection_get_display_name(gc),
+ 7, who, 13, "1");
+ yahoo_packet_send_and_free(pkt, yd);
+}
+
+static void yahoo_rem_deny(GaimConnection *gc, const char *who) {
+ struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data;
+ struct yahoo_packet *pkt;
+
+ if (!yd->logged_in)
+ return;
+
+ if (!who || who[0] == '\0')
+ return;
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, YAHOO_STATUS_AVAILABLE, 0);
+ yahoo_packet_hash(pkt, "sss", 1, gaim_connection_get_display_name(gc), 7, who, 13, "2");
+ yahoo_packet_send_and_free(pkt, yd);
+}
+
+static void yahoo_set_permit_deny(GaimConnection *gc) {
+ GaimAccount *acct;
+ GSList *deny;
+
+ acct = gc->account;
+
+ switch (acct->perm_deny) {
+ /* privacy 1 */
+ case GAIM_PRIVACY_ALLOW_ALL:
+ for (deny = acct->deny;deny;deny = deny->next)
+ yahoo_rem_deny(gc, deny->data);
+ break;
+ /* privacy 3 */
+ case GAIM_PRIVACY_ALLOW_USERS:
+ for (deny = acct->deny;deny;deny = deny->next)
+ yahoo_rem_deny(gc, deny->data);
+ break;
+ /* privacy 5 */
+ case GAIM_PRIVACY_ALLOW_BUDDYLIST:
+ /* privacy 4 */
+ case GAIM_PRIVACY_DENY_USERS:
+ for (deny = acct->deny;deny;deny = deny->next)
+ yahoo_add_deny(gc, deny->data);
+ break;
+ /* privacy 2 */
+ case GAIM_PRIVACY_DENY_ALL:
+ default:
+ break;
+ }
+}
+
+static gboolean yahoo_unload_plugin(GaimPlugin *plugin)
+{
+ yahoo_dest_colorht();
+
+ gaim_debug_unregister_category("yahoo");
+ gaim_debug_unregister_category("yahoo_filexfer");
+
+ return TRUE;
+}
+
+static void yahoo_change_buddys_group(GaimConnection *gc, const char *who,
+ const char *old_group, const char *new_group)
+{
+ struct yahoo_data *yd = gc->proto_data;
+ struct yahoo_packet *pkt;
+ char *gpn, *gpo;
+
+ /* Step 0: If they aren't on the server list anyway,
+ * don't bother letting the server know.
+ */
+ if (!yahoo_friend_find(gc, who))
+ return;
+
+ /* If old and new are the same, we would probably
+ * end up deleting the buddy, which would be bad.
+ * This might happen because of the charset conversation.
+ */
+ gpn = yahoo_string_encode(gc, new_group, NULL);
+ gpo = yahoo_string_encode(gc, old_group, NULL);
+ if (!strcmp(gpn, gpo)) {
+ g_free(gpn);
+ g_free(gpo);
+ return;
+ }
+
+ /* Step 1: Add buddy to new group. */
+ pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YAHOO_STATUS_AVAILABLE, 0);
+ yahoo_packet_hash(pkt, "ssss", 1, gaim_connection_get_display_name(gc),
+ 7, who, 65, gpn, 14, "");
+ yahoo_packet_send_and_free(pkt, yd);
+
+ /* Step 2: Remove buddy from old group */
+ pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YAHOO_STATUS_AVAILABLE, 0);
+ yahoo_packet_hash(pkt, "sss", 1, gaim_connection_get_display_name(gc), 7, who, 65, gpo);
+ yahoo_packet_send_and_free(pkt, yd);
+ g_free(gpn);
+ g_free(gpo);
+}
+
+static void yahoo_rename_group(GaimConnection *gc, const char *old_name,
+ GaimGroup *group, GList *moved_buddies)
+{
+ struct yahoo_data *yd = gc->proto_data;
+ struct yahoo_packet *pkt;
+ char *gpn, *gpo;
+
+ gpn = yahoo_string_encode(gc, group->name, NULL);
+ gpo = yahoo_string_encode(gc, old_name, NULL);
+ if (!strcmp(gpn, gpo)) {
+ g_free(gpn);
+ g_free(gpo);
+ return;
+ }
+
+ pkt = yahoo_packet_new(YAHOO_SERVICE_GROUPRENAME, YAHOO_STATUS_AVAILABLE, 0);
+ yahoo_packet_hash(pkt, "sss", 1, gaim_connection_get_display_name(gc),
+ 65, gpo, 67, gpn);
+ yahoo_packet_send_and_free(pkt, yd);
+ g_free(gpn);
+ g_free(gpo);
+}
+
+/********************************* Commands **********************************/
+
+static GaimCmdRet
+yahoogaim_cmd_buzz(GaimConversation *c, const gchar *cmd, gchar **args, gchar **error, void *data) {
+
+ GaimAccount *account = gaim_conversation_get_account(c);
+ const char *username = gaim_account_get_username(account);
+
+ if (*args && args[0])
+ return GAIM_CMD_RET_FAILED;
+
+ gaim_debug(GAIM_DEBUG_INFO, "yahoo",
+ "Sending <ding> on account %s to buddy %s.\n", username, c->name);
+ gaim_conv_im_send(GAIM_CONV_IM(c), "<ding>");
+ gaim_conv_im_write(GAIM_CONV_IM(c), "", _("Buzz!!"), GAIM_MESSAGE_NICK|GAIM_MESSAGE_RECV, time(NULL));
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimPlugin *my_protocol = NULL;
+
+
+static GaimCmdRet
+yahoogaim_cmd_chat_join(GaimConversation *conv, const char *cmd,
+ char **args, char **error, void *data)
+{
+ GHashTable *comp;
+ GaimConnection *gc;
+ struct yahoo_data *yd;
+ int id;
+
+ if (!args || !args[0])
+ return GAIM_CMD_RET_FAILED;
+
+ gc = gaim_conversation_get_gc(conv);
+ yd = gc->proto_data;
+ id = yd->conf_id;
+ gaim_debug(GAIM_DEBUG_INFO, "yahoo",
+ "Trying to join %s \n", args[0]);
+
+ comp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+ g_hash_table_replace(comp, g_strdup("room"),
+ g_strdup_printf("%s", g_ascii_strdown(args[0], strlen(args[0]))));
+ g_hash_table_replace(comp, g_strdup("type"), g_strdup("Chat"));
+
+ yahoo_c_join(gc, comp);
+
+ g_hash_table_destroy(comp);
+ return GAIM_CMD_RET_OK;
+}
+/************************** Plugin Initialization ****************************/
+static void
+yahoogaim_register_commands(void)
+{
+ gaim_cmd_register("join", "s", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT |
+ GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-yahoo", yahoogaim_cmd_chat_join,
+ _("join &lt;room&gt;: Join a chat room on the Yahoo network"), NULL);
+ gaim_cmd_register("buzz", "", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-yahoo", yahoogaim_cmd_buzz,
+ _("buzz: Buzz a contact to get their attention"), NULL);
+}
+
+static GaimPluginProtocolInfo prpl_info =
+{
+ OPT_PROTO_MAIL_CHECK | OPT_PROTO_CHAT_TOPIC,
+ NULL, /* user_splits */
+ NULL, /* protocol_options */
+ {"png", 96, 96, 96, 96, GAIM_ICON_SCALE_SEND},
+ yahoo_list_icon,
+ yahoo_list_emblems,
+ yahoo_status_text,
+ yahoo_tooltip_text,
+ yahoo_status_types,
+ yahoo_blist_node_menu,
+ yahoo_c_info,
+ yahoo_c_info_defaults,
+ yahoo_login,
+ yahoo_close,
+ yahoo_send_im,
+ NULL, /* set info */
+ yahoo_send_typing,
+ yahoo_get_info,
+ yahoo_set_status,
+ yahoo_set_idle,
+ NULL, /* change_passwd*/
+ yahoo_add_buddy,
+ NULL, /* add_buddies */
+ yahoo_remove_buddy,
+ NULL, /*remove_buddies */
+ yahoo_add_permit,
+ yahoo_add_deny,
+ yahoo_rem_permit,
+ yahoo_rem_deny,
+ yahoo_set_permit_deny,
+ NULL, /* warn */
+ yahoo_c_join,
+ NULL, /* reject chat invite */
+ yahoo_get_chat_name,
+ yahoo_c_invite,
+ yahoo_c_leave,
+ NULL, /* chat whisper */
+ yahoo_c_send,
+ yahoo_keepalive,
+ NULL, /* register_user */
+ NULL, /* get_cb_info */
+ NULL, /* get_cb_away */
+ NULL, /* alias_buddy */
+ yahoo_change_buddys_group,
+ yahoo_rename_group,
+ NULL, /* buddy_free */
+ NULL, /* convo_closed */
+ NULL, /* normalize */
+ yahoo_set_buddy_icon,
+ NULL, /* void (*remove_group)(GaimConnection *gc, const char *group);*/
+ NULL, /* char *(*get_cb_real_name)(GaimConnection *gc, int id, const char *who); */
+ NULL, /* set_chat_topic */
+ NULL, /* find_blist_chat */
+ yahoo_roomlist_get_list,
+ yahoo_roomlist_cancel,
+ yahoo_roomlist_expand_category,
+ NULL, /* can_receive_file */
+ yahoo_send_file
+};
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_PROTOCOL, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ "prpl-yahoo", /**< id */
+ "Yahoo", /**< name */
+ VERSION, /**< version */
+ /** summary */
+ N_("Yahoo Protocol Plugin"),
+ /** description */
+ N_("Yahoo Protocol Plugin"),
+ NULL, /**< author */
+ GAIM_WEBSITE, /**< homepage */
+
+ NULL, /**< load */
+ yahoo_unload_plugin, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ &prpl_info, /**< extra_info */
+ NULL,
+ yahoo_actions
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+ GaimAccountOption *option;
+
+ gaim_debug_register_category("yahoo");
+ gaim_debug_register_category("yahoo_filexfer");
+
+ option = gaim_account_option_bool_new(_("Yahoo Japan"), "yahoojp", FALSE);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+ option = gaim_account_option_string_new(_("Pager host"), "server", YAHOO_PAGER_HOST);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+ option = gaim_account_option_string_new(_("Japan Pager host"), "serverjp", YAHOOJP_PAGER_HOST);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+ option = gaim_account_option_int_new(_("Pager port"), "port", YAHOO_PAGER_PORT);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+ option = gaim_account_option_string_new(_("File transfer host"), "xfer_host", YAHOO_XFER_HOST);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+ option = gaim_account_option_string_new(_("Japan File transfer host"), "xferjp_host", YAHOOJP_XFER_HOST);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+ option = gaim_account_option_int_new(_("File transfer port"), "xfer_port", YAHOO_XFER_PORT);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+ option = gaim_account_option_string_new(_("Chat Room Locale"), "room_list_locale", YAHOO_ROOMLIST_LOCALE);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+#if 0
+ option = gaim_account_option_string_new(_("Chat Room List Url"), "room_list", YAHOO_ROOMLIST_URL);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+ option = gaim_account_option_string_new(_("YCHT Host"), "ycht-server", YAHOO_YCHT_HOST);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+ option = gaim_account_option_int_new(_("YCHT Port"), "ycht-port", YAHOO_YCHT_PORT);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+#endif
+
+ my_protocol = plugin;
+ yahoogaim_register_commands();
+ yahoo_init_colorht();
+}
+
+GAIM_INIT_PLUGIN(yahoo, init_plugin, info);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/yahoo/yahoo.h Sun Jul 03 04:48:12 2005 -0400
@@ -0,0 +1,207 @@
+/**
+ * @file yahoo.h The Yahoo! protocol plugin
+ *
+ * gaim
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _YAHOO_H_
+#define _YAHOO_H_
+
+#include "prpl.h"
+
+#define YAHOO_PAGER_HOST "scs.msg.yahoo.com"
+#define YAHOO_PAGER_PORT 5050
+#define YAHOO_PROFILE_URL "http://profiles.yahoo.com/"
+#define YAHOO_MAIL_URL "https://mail.yahoo.com/"
+#define YAHOO_XFER_HOST "filetransfer.msg.yahoo.com"
+#define YAHOO_XFER_PORT 80
+#define YAHOO_ROOMLIST_URL "http://insider.msg.yahoo.com/ycontent/"
+#define YAHOO_ROOMLIST_LOCALE ""
+/* really we should get the list of servers from
+ http://update.messenger.yahoo.co.jp/servers.html */
+#define YAHOOJP_PAGER_HOST "cs.yahoo.co.jp"
+#define YAHOOJP_PROFILE_URL "http://profiles.yahoo.co.jp/"
+#define YAHOOJP_MAIL_URL "http://mail.yahoo.co.jp/"
+#define YAHOOJP_XFER_HOST "filetransfer.msg.yahoo.co.jp"
+#define YAHOOJP_WEBCAM_HOST "wc.yahoo.co.jp"
+
+#define WEBMESSENGER_URL "http://login.yahoo.com/config/login?.src=pg"
+
+#define YAHOO_ICON_CHECKSUM_KEY "icon_checksum"
+#define YAHOO_PICURL_SETTING "picture_url"
+#define YAHOO_PICCKSUM_SETTING "picture_checksum"
+#define YAHOO_PICEXPIRE_SETTING "picture_expire"
+
+
+
+#define YAHOO_STATUS_TYPE_OFFLINE "offline"
+#define YAHOO_STATUS_TYPE_ONLINE "online"
+#define YAHOO_STATUS_TYPE_AVAILABLE "available"
+#define YAHOO_STATUS_TYPE_AVAILABLE_WM "available-wm"
+#define YAHOO_STATUS_TYPE_BRB "brb"
+#define YAHOO_STATUS_TYPE_BUSY "busy"
+#define YAHOO_STATUS_TYPE_NOTATHOME "notathome"
+#define YAHOO_STATUS_TYPE_NOTATDESK "notatdesk"
+#define YAHOO_STATUS_TYPE_NOTINOFFICE "notinoffice"
+#define YAHOO_STATUS_TYPE_ONPHONE "onphone"
+#define YAHOO_STATUS_TYPE_ONVACATION "onvacation"
+#define YAHOO_STATUS_TYPE_OUTTOLUNCH "outtolunch"
+#define YAHOO_STATUS_TYPE_STEPPEDOUT "steppedout"
+#define YAHOO_STATUS_TYPE_AWAY "away"
+#define YAHOO_STATUS_TYPE_INVISIBLE "invisible"
+
+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_WEBLOGIN = 0x5a55aa55,
+ YAHOO_STATUS_OFFLINE = 0x5a55aa56, /* don't ask */
+ YAHOO_STATUS_TYPING = 0x16
+};
+
+struct yahoo_buddy_icon_upload_data {
+ GaimConnection *gc;
+ GString *str;
+ char *filename;
+ int pos;
+ int fd;
+ guint watcher;
+};
+
+struct _YchtConn;
+
+struct yahoo_data {
+ int fd;
+ guchar *rxqueue;
+ int rxlen;
+ GHashTable *friends;
+ int current_status;
+ gboolean logged_in;
+ GString *tmp_serv_blist, *tmp_serv_ilist;
+ GSList *confs;
+ unsigned int conf_id; /* just a counter */
+ gboolean chat_online;
+ gboolean in_chat;
+ char *chat_name;
+ char *auth;
+ char *cookie_y;
+ char *cookie_t;
+ int session_id;
+ gboolean jp;
+ gboolean wm; /* connected w/ web messenger method */
+ /* picture aka buddy icon stuff */
+ char *picture_url;
+ int picture_checksum;
+
+ /* ew. we have to check the icon before we connect,
+ * but can't upload it til we're connected. */
+ struct yahoo_buddy_icon_upload_data *picture_upload_todo;
+
+ struct _YchtConn *ycht;
+};
+
+
+#define YAHOO_MAX_STATUS_MESSAGE_LENGTH (255)
+
+
+/* sometimes i wish prpls could #include things from other prpls. then i could just
+ * use the routines from libfaim and not have to admit to knowing how they work. */
+#define yahoo_put16(buf, data) ( \
+ (*(buf) = (unsigned char)((data)>>8)&0xff), \
+ (*((buf)+1) = (unsigned char)(data)&0xff), \
+ 2)
+#define yahoo_get16(buf) ((((*(buf))<<8)&0xff00) + ((*((buf)+1)) & 0xff))
+#define yahoo_put32(buf, data) ( \
+ (*((buf)) = (unsigned char)((data)>>24)&0xff), \
+ (*((buf)+1) = (unsigned char)((data)>>16)&0xff), \
+ (*((buf)+2) = (unsigned char)((data)>>8)&0xff), \
+ (*((buf)+3) = (unsigned char)(data)&0xff), \
+ 4)
+#define yahoo_get32(buf) ((((*(buf))<<24)&0xff000000) + \
+ (((*((buf)+1))<<16)&0x00ff0000) + \
+ (((*((buf)+2))<< 8)&0x0000ff00) + \
+ (((*((buf)+3) )&0x000000ff)))
+
+
+
+/* util.c */
+void yahoo_init_colorht();
+void yahoo_dest_colorht();
+char *yahoo_codes_to_html(const char *x);
+char *yahoo_html_to_codes(const char *src);
+
+/**
+ * Encode some text to send to the yahoo server.
+ *
+ * @param gc The connection handle.
+ * @param str The null terminated utf8 string to encode.
+ * @param utf8 If not @c NULL, whether utf8 is okay or not.
+ * Even if it is okay, we may not use it. If we
+ * used it, we set this to @c TRUE, else to
+ * @c FALSE. If @c NULL, false is assumed, and
+ * it is not dereferenced.
+ * @return The g_malloced string in the appropriate encoding.
+ */
+char *yahoo_string_encode(GaimConnection *gc, const char *str, gboolean *utf8);
+
+/**
+ * Decode some text received from the server.
+ *
+ * @param gc The gc handle.
+ * @param str The null terminated string to decode.
+ * @param utf8 Did the server tell us it was supposed to be utf8?
+ * @return The decoded, utf-8 string, which must be g_free()'d.
+ */
+char *yahoo_string_decode(GaimConnection *gc, const char *str, gboolean utf8);
+
+/* previously-static functions, now needed for yahoo_profile.c */
+char *yahoo_tooltip_text(GaimBuddy *b);
+
+/* yahoo_profile.c */
+void yahoo_get_info(GaimConnection *gc, const char *name);
+
+/**
+ * Check to see whether the sender is permitted to send
+ *
+ * @param gc The gc handle.
+ * @param who The sender of the packet to check
+*/
+gboolean yahoo_privacy_check
+ (GaimConnection *gc, const char *who);
+
+#endif /* _YAHOO_H_ */
+void yahoo_packet_read(struct yahoo_packet *pkt, guchar *data, int len);
+void yahoo_packet_process(GaimConnection *gc, struct yahoo_packet *pkt);
+void yahoo_pending(gpointer data, gint source, GaimInputCondition cond);
+void yahoo_packet_dump(guchar *data, int len);
+
+int yahoo_send_im(GaimConnection *gc, const char *who, const char *what, GaimConvImFlags flags);
+