Mercurial > pidgin
view libpurple/protocols/yahoo/yahoochat.c @ 32796:5ae7e1f36b43
Fix a possible XMPP remote crash
A series of specially crafted file transfer requests can cause clients
to reference invalid memory. The user must have accepted one of the
file transfer requests.
The fix is to correctly cancel and free a SOCKS5 connection attempt so
that it does not trigger an attempt to access invalid memory later.
This was reported to us by Jos«± Valent«żn Guti«±rrez and this patch is
written by Paul Aurich.
author | Mark Doliner <mark@kingant.net> |
---|---|
date | Mon, 07 May 2012 03:16:31 +0000 |
parents | a8cc50c2279f |
children | 5dafa68c8eb0 3828a61c44da |
line wrap: on
line source
/* * purple * * Purple 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. * * Some code copyright 2003 Tim Ringenbach <omarvo@hotmail.com> * (marv on irc.freenode.net) * Some code borrowed from libyahoo2, copyright (C) 2002, Philip * S Tellis <philip . tellis AT gmx . net> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include "debug.h" #include "privacy.h" #include "prpl.h" #include "conversation.h" #include "notify.h" #include "util.h" #include "libymsg.h" #include "yahoo_packet.h" #include "yahoochat.h" #include "ycht.h" #define YAHOO_CHAT_ID (1) /* prototype(s) */ static void yahoo_chat_leave(PurpleConnection *gc, const char *room, const char *dn, gboolean logout); /* special function to log us on to the yahoo chat service */ static void yahoo_chat_online(PurpleConnection *gc) { YahooData *yd = gc->proto_data; struct yahoo_packet *pkt; const char *rll; if (yd->wm) { ycht_connection_open(gc); return; } rll = purple_account_get_string(purple_connection_get_account(gc), "room_list_locale", YAHOO_ROOMLIST_LOCALE); pkt = yahoo_packet_new(YAHOO_SERVICE_CHATONLINE, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash(pkt, "sssss", 109, purple_connection_get_display_name(gc), 1, purple_connection_get_display_name(gc), 6, "abcde", /* I'm not sure this is the correct way to set this. */ 98, rll, 135, yd->jp ? YAHOO_CLIENT_VERSION : YAHOOJP_CLIENT_VERSION); yahoo_packet_send_and_free(pkt, yd); } /* this is slow, and different from the purple_* version in that it (hopefully) won't add a user twice */ void yahoo_chat_add_users(PurpleConvChat *chat, GList *newusers) { GList *i; for (i = newusers; i; i = i->next) { if (purple_conv_chat_find_user(chat, i->data)) continue; purple_conv_chat_add_user(chat, i->data, NULL, PURPLE_CBFLAGS_NONE, TRUE); } } void yahoo_chat_add_user(PurpleConvChat *chat, const char *user, const char *reason) { if (purple_conv_chat_find_user(chat, user)) return; purple_conv_chat_add_user(chat, user, reason, PURPLE_CBFLAGS_NONE, TRUE); } static PurpleConversation *yahoo_find_conference(PurpleConnection *gc, const char *name) { YahooData *yd; GSList *l; yd = gc->proto_data; for (l = yd->confs; l; l = l->next) { PurpleConversation *c = l->data; if (!purple_utf8_strcasecmp(purple_conversation_get_name(c), name)) return c; } return NULL; } void yahoo_process_conference_invite(PurpleConnection *gc, struct yahoo_packet *pkt) { PurpleAccount *account; GSList *l; char *room = NULL; char *who = NULL; char *msg = NULL; GString *members = NULL; GHashTable *components; if ( (pkt->status == 2) || (pkt->status == 11) ) return; /* Status is 11 when we are being notified about invitation being sent to someone else */ account = purple_connection_get_account(gc); for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 57) { room = yahoo_string_decode(gc, pair->value, FALSE); if (yahoo_find_conference(gc, room) != NULL) { /* Looks like we got invited to an already open conference. */ /* Laters: Should we accept this conference rather than ignoring the invitation ? */ purple_debug_info("yahoo","Ignoring invitation for an already existing chat, room:%s\n",room); g_free(room); return; } } } members = g_string_sized_new(512); for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 1: /* us, but we already know who we are */ break; case 57: g_free(room); room = yahoo_string_decode(gc, pair->value, FALSE); break; case 50: /* inviter */ who = pair->value; g_string_append_printf(members, "%s\n", who); break; case 51: /* This user is being invited to the conference. Comes with status = 11, so we wont reach here */ break; case 52: /* Invited users. Assuming us invited, since we got this packet */ break; /* break needed, or else we add the users to the conference before they accept the invitation */ case 53: /* members who have already joined the conference */ g_string_append_printf(members, "%s\n", pair->value); break; case 58: g_free(msg); msg = yahoo_string_decode(gc, pair->value, FALSE); break; case 13: /* ? */ break; } } if (!room) { g_string_free(members, TRUE); g_free(msg); return; } if (!purple_privacy_check(account, who) || (purple_account_get_bool(account, "ignore_invites", FALSE))) { purple_debug_info("yahoo", "Invite to conference %s from %s has been dropped.\n", room, who); g_free(room); g_free(msg); g_string_free(members, TRUE); return; } components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); g_hash_table_replace(components, g_strdup("room"), room); if (msg) g_hash_table_replace(components, g_strdup("topic"), msg); g_hash_table_replace(components, g_strdup("type"), g_strdup("Conference")); g_hash_table_replace(components, g_strdup("members"), g_string_free(members, FALSE)); serv_got_chat_invite(gc, room, who, msg, components); } void yahoo_process_conference_decline(PurpleConnection *gc, struct yahoo_packet *pkt) { GSList *l; char *room = NULL; char *who = NULL; char *msg = NULL; PurpleConversation *c = NULL; int utf8 = 0; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 57: g_free(room); room = yahoo_string_decode(gc, pair->value, FALSE); break; case 54: who = pair->value; break; case 14: g_free(msg); msg = yahoo_string_decode(gc, pair->value, FALSE); break; case 97: utf8 = strtol(pair->value, NULL, 10); break; } } if (!purple_privacy_check(purple_connection_get_account(gc), who)) { g_free(room); g_free(msg); return; } if (who && room) { /* make sure we're in the room before we process a decline message for it */ if((c = yahoo_find_conference(gc, room))) { char *tmp = NULL, *msg_tmp = NULL; if(msg) { msg_tmp = yahoo_string_decode(gc, msg, utf8); msg = yahoo_codes_to_html(msg_tmp); serv_got_chat_in(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(c)), who, 0, msg, time(NULL)); g_free(msg_tmp); g_free(msg); } tmp = g_strdup_printf(_("%s has declined to join."), who); purple_conversation_write(c, NULL, tmp, PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY, time(NULL)); g_free(tmp); } g_free(room); } } void yahoo_process_conference_logon(PurpleConnection *gc, struct yahoo_packet *pkt) { GSList *l; char *room = NULL; char *who = NULL; PurpleConversation *c; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 57: g_free(room); room = yahoo_string_decode(gc, pair->value, FALSE); break; case 53: who = pair->value; break; } } if (who && room) { c = yahoo_find_conference(gc, room); if (c) { /* Prevent duplicate users in the chat */ if( !purple_conv_chat_find_user(PURPLE_CONV_CHAT(c), who) ) yahoo_chat_add_user(PURPLE_CONV_CHAT(c), who, NULL); } g_free(room); } } void yahoo_process_conference_logoff(PurpleConnection *gc, struct yahoo_packet *pkt) { GSList *l; char *room = NULL; char *who = NULL; PurpleConversation *c; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 57: g_free(room); room = yahoo_string_decode(gc, pair->value, FALSE); break; case 56: who = pair->value; break; } } if (who && room) { c = yahoo_find_conference(gc, room); if (c) purple_conv_chat_remove_user(PURPLE_CONV_CHAT(c), who, NULL); g_free(room); } } void yahoo_process_conference_message(PurpleConnection *gc, struct yahoo_packet *pkt) { GSList *l; char *room = NULL; char *who = NULL; char *msg = NULL; int utf8 = 0; PurpleConversation *c; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 57: g_free(room); room = yahoo_string_decode(gc, pair->value, FALSE); break; case 3: who = pair->value; break; case 14: msg = pair->value; break; case 97: utf8 = strtol(pair->value, NULL, 10); break; } } if (room && who && msg) { char *msg2; c = yahoo_find_conference(gc, room); if (!c) { g_free(room); return; } msg2 = yahoo_string_decode(gc, msg, utf8); msg = yahoo_codes_to_html(msg2); serv_got_chat_in(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(c)), who, 0, msg, time(NULL)); g_free(msg); g_free(msg2); } g_free(room); } static void yahoo_chat_join(PurpleConnection *gc, const char *dn, const char *room, const char *topic, const char *id) { YahooData *yd = gc->proto_data; struct yahoo_packet *pkt; char *room2; gboolean utf8 = TRUE; if (yd->wm) { g_return_if_fail(yd->ycht != NULL); ycht_chat_join(yd->ycht, room); return; } /* apparently room names are always utf8, or else always not utf8, * so we don't have to actually pass the flag in the packet. Or something. */ room2 = yahoo_string_encode(gc, room, &utf8); pkt = yahoo_packet_new(YAHOO_SERVICE_CHATJOIN, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash(pkt, "ssss", 1, purple_connection_get_display_name(gc), 104, room2, 62, "2", 129, id ? id : "0"); yahoo_packet_send_and_free(pkt, yd); g_free(room2); } /* this is a confirmation of yahoo_chat_online(); */ void yahoo_process_chat_online(PurpleConnection *gc, struct yahoo_packet *pkt) { YahooData *yd = (YahooData *) gc->proto_data; if (pkt->status == 1) { yd->chat_online = TRUE; /* We need to goto a user in chat */ if (yd->pending_chat_goto) { struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_CHATGOTO, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash(pkt, "sss", 109, yd->pending_chat_goto, 1, purple_connection_get_display_name(gc), 62, "2"); yahoo_packet_send_and_free(pkt, yd); } else if (yd->pending_chat_room) { yahoo_chat_join(gc, purple_connection_get_display_name(gc), yd->pending_chat_room, yd->pending_chat_topic, yd->pending_chat_id); } g_free(yd->pending_chat_room); yd->pending_chat_room = NULL; g_free(yd->pending_chat_id); yd->pending_chat_id = NULL; g_free(yd->pending_chat_topic); yd->pending_chat_topic = NULL; g_free(yd->pending_chat_goto); yd->pending_chat_goto = NULL; } } /* this is basicly the opposite of chat_online */ void yahoo_process_chat_logout(PurpleConnection *gc, struct yahoo_packet *pkt) { YahooData *yd = (YahooData *) gc->proto_data; GSList *l; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 1) if (g_ascii_strcasecmp(pair->value, purple_connection_get_display_name(gc))) return; } if (pkt->status == 1) { yd->chat_online = FALSE; g_free(yd->pending_chat_room); yd->pending_chat_room = NULL; g_free(yd->pending_chat_id); yd->pending_chat_id = NULL; g_free(yd->pending_chat_topic); yd->pending_chat_topic = NULL; g_free(yd->pending_chat_goto); yd->pending_chat_goto = NULL; if (yd->in_chat) yahoo_c_leave(gc, YAHOO_CHAT_ID); } } void yahoo_process_chat_join(PurpleConnection *gc, struct yahoo_packet *pkt) { PurpleAccount *account = purple_connection_get_account(gc); YahooData *yd = (YahooData *) gc->proto_data; PurpleConversation *c = NULL; GSList *l; GList *members = NULL; GList *roomies = NULL; char *room = NULL; char *topic = NULL; char *someid, *someotherid, *somebase64orhashosomething, *somenegativenumber; if (pkt->status == -1) { /* We can't join */ struct yahoo_pair *pair = pkt->hash->data; gchar const *failed_to_join = _("Failed to join chat"); switch (atoi(pair->value)) { case 0xFFFFFFFA: /* -6 */ purple_notify_error(gc, NULL, failed_to_join, _("Unknown room")); break; case 0xFFFFFFF1: /* -15 */ purple_notify_error(gc, NULL, failed_to_join, _("Maybe the room is full")); break; case 0xFFFFFFDD: /* -35 */ purple_notify_error(gc, NULL, failed_to_join, _("Not available")); break; default: purple_notify_error(gc, NULL, failed_to_join, _("Unknown error. You may need to logout and wait five minutes before being able to rejoin a chatroom")); } return; } for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 104: g_free(room); room = yahoo_string_decode(gc, pair->value, TRUE); break; case 105: g_free(topic); topic = yahoo_string_decode(gc, pair->value, TRUE); break; case 128: someid = pair->value; break; case 108: /* number of joiners */ break; case 129: someotherid = pair->value; break; case 130: somebase64orhashosomething = pair->value; break; case 126: somenegativenumber = pair->value; break; case 13: /* this is 1. maybe its the type of room? (normal, user created, private, etc?) */ break; case 61: /*this looks similar to 130 */ break; /* the previous section was just room info. this next section is info about individual room members, (including us) */ case 109: /* the yahoo id */ members = g_list_append(members, pair->value); break; case 110: /* age */ break; case 141: /* nickname */ break; case 142: /* location */ break; case 113: /* bitmask */ break; } } if (room && yd->chat_name && purple_utf8_strcasecmp(room, yd->chat_name)) yahoo_chat_leave(gc, room, purple_connection_get_display_name(gc), FALSE); c = purple_find_chat(gc, YAHOO_CHAT_ID); if (room && (!c || purple_conv_chat_has_left(PURPLE_CONV_CHAT(c))) && members && (members->next || !g_ascii_strcasecmp(members->data, purple_connection_get_display_name(gc)))) { GList *l; GList *flags = NULL; for (l = members; l; l = l->next) flags = g_list_prepend(flags, GINT_TO_POINTER(PURPLE_CBFLAGS_NONE)); if (c && purple_conv_chat_has_left(PURPLE_CONV_CHAT(c))) { /* this might be a hack, but oh well, it should nicely */ char *tmpmsg; purple_conversation_set_name(c, room); c = serv_got_joined_chat(gc, YAHOO_CHAT_ID, room); if (topic) { purple_conv_chat_set_topic(PURPLE_CONV_CHAT(c), NULL, topic); /* Also print the topic to the backlog so that the captcha link is clickable */ purple_conv_chat_write(PURPLE_CONV_CHAT(c), "", topic, PURPLE_MESSAGE_SYSTEM, time(NULL)); } yd->in_chat = 1; yd->chat_name = g_strdup(room); purple_conv_chat_add_users(PURPLE_CONV_CHAT(c), members, NULL, flags, FALSE); tmpmsg = g_strdup_printf(_("You are now chatting in %s."), room); purple_conv_chat_write(PURPLE_CONV_CHAT(c), "", tmpmsg, PURPLE_MESSAGE_SYSTEM, time(NULL)); g_free(tmpmsg); } else { c = serv_got_joined_chat(gc, YAHOO_CHAT_ID, room); if (topic) { purple_conv_chat_set_topic(PURPLE_CONV_CHAT(c), NULL, topic); /* Also print the topic to the backlog so that the captcha link is clickable */ purple_conv_chat_write(PURPLE_CONV_CHAT(c), "", topic, PURPLE_MESSAGE_SYSTEM, time(NULL)); } yd->in_chat = 1; yd->chat_name = g_strdup(room); purple_conv_chat_add_users(PURPLE_CONV_CHAT(c), members, NULL, flags, FALSE); } g_list_free(flags); } else if (c) { if (topic) { const char *cur_topic = purple_conv_chat_get_topic(PURPLE_CONV_CHAT(c)); if (cur_topic == NULL || strcmp(cur_topic, topic) != 0) purple_conv_chat_set_topic(PURPLE_CONV_CHAT(c), NULL, topic); } yahoo_chat_add_users(PURPLE_CONV_CHAT(c), members); } if (account->deny && c) { PurpleConversationUiOps *ops = purple_conversation_get_ui_ops(c); for (l = account->deny; l != NULL; l = l->next) { for (roomies = members; roomies; roomies = roomies->next) { if (!purple_utf8_strcasecmp((char *)l->data, roomies->data)) { purple_debug_info("yahoo", "Ignoring room member %s in room %s\n" , (char *)roomies->data, room ? room : ""); purple_conv_chat_ignore(PURPLE_CONV_CHAT(c),roomies->data); ops->chat_update_user(c, roomies->data); } } } } g_list_free(roomies); g_list_free(members); g_free(room); g_free(topic); } void yahoo_process_chat_exit(PurpleConnection *gc, struct yahoo_packet *pkt) { char *who = NULL; char *room = NULL; GSList *l; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 104) { g_free(room); room = yahoo_string_decode(gc, pair->value, TRUE); } if (pair->key == 109) who = pair->value; } if (who && room) { PurpleConversation *c = purple_find_chat(gc, YAHOO_CHAT_ID); if (c && !purple_utf8_strcasecmp(purple_conversation_get_name(c), room)) purple_conv_chat_remove_user(PURPLE_CONV_CHAT(c), who, NULL); } g_free(room); } void yahoo_process_chat_message(PurpleConnection *gc, struct yahoo_packet *pkt) { char *room = NULL, *who = NULL, *msg = NULL, *msg2; int msgtype = 1, utf8 = 1; /* default to utf8 */ PurpleConversation *c = NULL; GSList *l; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 97: utf8 = strtol(pair->value, NULL, 10); break; case 104: g_free(room); room = yahoo_string_decode(gc, pair->value, TRUE); break; case 109: who = pair->value; break; case 117: msg = pair->value; break; case 124: msgtype = strtol(pair->value, NULL, 10); break; } } c = purple_find_chat(gc, YAHOO_CHAT_ID); if (!who || !c) { if (room) g_free(room); /* we still get messages after we part, funny that */ return; } if (!msg) { purple_debug_misc("yahoo", "Got a message packet with no message.\nThis probably means something important, but we're ignoring it.\n"); return; } msg2 = yahoo_string_decode(gc, msg, utf8); msg = yahoo_codes_to_html(msg2); g_free(msg2); if (msgtype == 2 || msgtype == 3) { char *tmp; tmp = g_strdup_printf("/me %s", msg); g_free(msg); msg = tmp; } serv_got_chat_in(gc, YAHOO_CHAT_ID, who, 0, msg, time(NULL)); g_free(msg); g_free(room); } void yahoo_process_chat_addinvite(PurpleConnection *gc, struct yahoo_packet *pkt) { PurpleAccount *account; GSList *l; char *room = NULL; char *msg = NULL; char *who = NULL; account = purple_connection_get_account(gc); for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 104: g_free(room); room = yahoo_string_decode(gc, pair->value, TRUE); break; case 129: /* room id? */ break; case 126: /* ??? */ break; case 117: g_free(msg); msg = yahoo_string_decode(gc, pair->value, FALSE); break; case 119: who = pair->value; break; case 118: /* us */ break; } } if (room && who) { GHashTable *components; if (!purple_privacy_check(account, who) || (purple_account_get_bool(account, "ignore_invites", FALSE))) { purple_debug_info("yahoo", "Invite to room %s from %s has been dropped.\n", room, who); g_free(room); g_free(msg); return; } 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(room)); serv_got_chat_invite(gc, room, who, msg, components); } g_free(room); g_free(msg); } void yahoo_process_chat_goto(PurpleConnection *gc, struct yahoo_packet *pkt) { if (pkt->status == -1) purple_notify_error(gc, NULL, _("Failed to join buddy in chat"), _("Maybe they're not in a chat?")); } /* * Functions dealing with conferences * I think conference names are always ascii. */ void yahoo_conf_leave(YahooData *yd, const char *room, const char *dn, GList *who) { struct yahoo_packet *pkt; GList *w; purple_debug_misc("yahoo", "leaving conference %s\n", room); pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGOFF, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash_str(pkt, 1, dn); for (w = who; w; w = w->next) { const char *name = purple_conv_chat_cb_get_name(w->data); yahoo_packet_hash_str(pkt, 3, name); } yahoo_packet_hash_str(pkt, 57, room); yahoo_packet_send_and_free(pkt, yd); } static int yahoo_conf_send(PurpleConnection *gc, const char *dn, const char *room, GList *members, const char *what) { YahooData *yd = gc->proto_data; struct yahoo_packet *pkt; GList *who; char *msg, *msg2; int utf8 = 1; msg = yahoo_html_to_codes(what); msg2 = yahoo_string_encode(gc, msg, &utf8); pkt = yahoo_packet_new(YAHOO_SERVICE_CONFMSG, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash_str(pkt, 1, dn); for (who = members; who; who = who->next) { const char *name = purple_conv_chat_cb_get_name(who->data); yahoo_packet_hash_str(pkt, 53, name); } yahoo_packet_hash(pkt, "ss", 57, room, 14, msg2); if (utf8) yahoo_packet_hash_str(pkt, 97, "1"); /* utf-8 */ yahoo_packet_send_and_free(pkt, yd); g_free(msg); g_free(msg2); return 0; } static void yahoo_conf_join(YahooData *yd, PurpleConversation *c, const char *dn, const char *room, const char *topic, const char *members) { struct yahoo_packet *pkt; char **memarr = NULL; int i; if (members) memarr = g_strsplit(members, "\n", 0); pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGON, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash(pkt, "sss", 1, dn, 3, dn, 57, room); if (memarr) { for(i = 0 ; memarr[i]; i++) { if (!strcmp(memarr[i], "") || !strcmp(memarr[i], dn)) continue; yahoo_packet_hash_str(pkt, 3, memarr[i]); purple_conv_chat_add_user(PURPLE_CONV_CHAT(c), memarr[i], NULL, PURPLE_CBFLAGS_NONE, TRUE); } } yahoo_packet_send_and_free(pkt, yd); if (memarr) g_strfreev(memarr); } static void yahoo_conf_invite(PurpleConnection *gc, PurpleConversation *c, const char *dn, const char *buddy, const char *room, const char *msg) { YahooData *yd = gc->proto_data; struct yahoo_packet *pkt; GList *members; char *msg2 = NULL; if (msg) msg2 = yahoo_string_encode(gc, msg, NULL); members = purple_conv_chat_get_users(PURPLE_CONV_CHAT(c)); pkt = yahoo_packet_new(YAHOO_SERVICE_CONFADDINVITE, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash(pkt, "sssss", 1, dn, 51, buddy, 57, room, 58, msg?msg2:"", 13, "0"); for(; members; members = members->next) { const char *name = purple_conv_chat_cb_get_name(members->data); if (!strcmp(name, dn)) continue; yahoo_packet_hash(pkt, "ss", 52, name, 53, name); } yahoo_packet_send_and_free(pkt, yd); g_free(msg2); } /* * Functions dealing with chats */ static void yahoo_chat_leave(PurpleConnection *gc, const char *room, const char *dn, gboolean logout) { YahooData *yd = gc->proto_data; struct yahoo_packet *pkt; char *eroom; gboolean utf8 = 1; if (yd->wm) { g_return_if_fail(yd->ycht != NULL); ycht_chat_leave(yd->ycht, room, logout); return; } eroom = yahoo_string_encode(gc, room, &utf8); pkt = yahoo_packet_new(YAHOO_SERVICE_CHATEXIT, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash(pkt, "sss", 104, eroom, 109, dn, 108, "1"); yahoo_packet_hash_str(pkt, 112, "0"); /* what does this one mean? */ yahoo_packet_send_and_free(pkt, yd); yd->in_chat = 0; if (yd->chat_name) { g_free(yd->chat_name); yd->chat_name = NULL; } if (purple_find_chat(gc, YAHOO_CHAT_ID) != NULL) serv_got_chat_left(gc, YAHOO_CHAT_ID); if (!logout) return; pkt = yahoo_packet_new(YAHOO_SERVICE_CHATLOGOUT, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash_str(pkt, 1, dn); yahoo_packet_send_and_free(pkt, yd); yd->chat_online = FALSE; g_free(yd->pending_chat_room); yd->pending_chat_room = NULL; g_free(yd->pending_chat_id); yd->pending_chat_id = NULL; g_free(yd->pending_chat_topic); yd->pending_chat_topic = NULL; g_free(yd->pending_chat_goto); yd->pending_chat_goto = NULL; g_free(eroom); } static int yahoo_chat_send(PurpleConnection *gc, const char *dn, const char *room, const char *what, PurpleMessageFlags flags) { YahooData *yd = gc->proto_data; struct yahoo_packet *pkt; int me = 0; char *msg1, *msg2, *room2; gboolean utf8 = TRUE; if (yd->wm) { g_return_val_if_fail(yd->ycht != NULL, 1); return ycht_chat_send(yd->ycht, room, what); } msg1 = g_strdup(what); if (purple_message_meify(msg1, -1)) me = 1; msg2 = yahoo_html_to_codes(msg1); g_free(msg1); msg1 = yahoo_string_encode(gc, msg2, &utf8); g_free(msg2); room2 = yahoo_string_encode(gc, room, NULL); pkt = yahoo_packet_new(YAHOO_SERVICE_COMMENT, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash(pkt, "sss", 1, dn, 104, room2, 117, msg1); if (me) yahoo_packet_hash_str(pkt, 124, "2"); else yahoo_packet_hash_str(pkt, 124, "1"); /* fixme: what about /think? (124=3) */ if (utf8) yahoo_packet_hash_str(pkt, 97, "1"); yahoo_packet_send_and_free(pkt, yd); g_free(msg1); g_free(room2); return 0; } static void yahoo_chat_invite(PurpleConnection *gc, const char *dn, const char *buddy, const char *room, const char *msg) { YahooData *yd = gc->proto_data; struct yahoo_packet *pkt; char *room2, *msg2 = NULL; gboolean utf8 = TRUE; if (yd->wm) { g_return_if_fail(yd->ycht != NULL); ycht_chat_send_invite(yd->ycht, room, buddy, msg); return; } room2 = yahoo_string_encode(gc, room, &utf8); if (msg) msg2 = yahoo_string_encode(gc, msg, NULL); pkt = yahoo_packet_new(YAHOO_SERVICE_CHATADDINVITE, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash(pkt, "sssss", 1, dn, 118, buddy, 104, room2, 117, (msg2?msg2:""), 129, "0"); yahoo_packet_send_and_free(pkt, yd); g_free(room2); g_free(msg2); } void yahoo_chat_goto(PurpleConnection *gc, const char *name) { YahooData *yd; struct yahoo_packet *pkt; yd = gc->proto_data; if (yd->wm) { g_return_if_fail(yd->ycht != NULL); ycht_chat_goto_user(yd->ycht, name); return; } if (!yd->chat_online) { yahoo_chat_online(gc); g_free(yd->pending_chat_room); yd->pending_chat_room = NULL; g_free(yd->pending_chat_id); yd->pending_chat_id = NULL; g_free(yd->pending_chat_topic); yd->pending_chat_topic = NULL; g_free(yd->pending_chat_goto); yd->pending_chat_goto = g_strdup(name); return; } pkt = yahoo_packet_new(YAHOO_SERVICE_CHATGOTO, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash(pkt, "sss", 109, name, 1, purple_connection_get_display_name(gc), 62, "2"); yahoo_packet_send_and_free(pkt, yd); } /* * These are the functions registered with the core * which get called for both chats and conferences. */ void yahoo_c_leave(PurpleConnection *gc, int id) { YahooData *yd = (YahooData *) gc->proto_data; PurpleConversation *c; if (!yd) return; c = purple_find_chat(gc, id); if (!c) return; if (id != YAHOO_CHAT_ID) { yahoo_conf_leave(yd, purple_conversation_get_name(c), purple_connection_get_display_name(gc), purple_conv_chat_get_users(PURPLE_CONV_CHAT(c))); yd->confs = g_slist_remove(yd->confs, c); } else { yahoo_chat_leave(gc, purple_conversation_get_name(c), purple_connection_get_display_name(gc), TRUE); } serv_got_chat_left(gc, id); } int yahoo_c_send(PurpleConnection *gc, int id, const char *what, PurpleMessageFlags flags) { PurpleConversation *c; int ret; YahooData *yd; yd = (YahooData *) gc->proto_data; if (!yd) return -1; c = purple_find_chat(gc, id); if (!c) return -1; if (id != YAHOO_CHAT_ID) { ret = yahoo_conf_send(gc, purple_connection_get_display_name(gc), purple_conversation_get_name(c), purple_conv_chat_get_users(PURPLE_CONV_CHAT(c)), what); } else { ret = yahoo_chat_send(gc, purple_connection_get_display_name(gc), purple_conversation_get_name(c), what, flags); if (!ret) serv_got_chat_in(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(c)), purple_connection_get_display_name(gc), flags, what, time(NULL)); } return ret; } GList *yahoo_c_info(PurpleConnection *gc) { GList *m = NULL; struct proto_chat_entry *pce; pce = g_new0(struct proto_chat_entry, 1); pce->label = _("_Room:"); pce->identifier = "room"; pce->required = TRUE; m = g_list_append(m, pce); return m; } GHashTable *yahoo_c_info_defaults(PurpleConnection *gc, const char *chat_name) { GHashTable *defaults; defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); if (chat_name != NULL) g_hash_table_insert(defaults, "room", g_strdup(chat_name)); return defaults; } char *yahoo_get_chat_name(GHashTable *data) { return g_strdup(g_hash_table_lookup(data, "room")); } void yahoo_c_join(PurpleConnection *gc, GHashTable *data) { YahooData *yd; char *room, *topic, *type; PurpleConversation *c; yd = (YahooData *) gc->proto_data; if (!yd) return; room = g_hash_table_lookup(data, "room"); if (!room) return; topic = g_hash_table_lookup(data, "topic"); if (!topic) topic = ""; if ((type = g_hash_table_lookup(data, "type")) && !strcmp(type, "Conference")) { int id; const char *members = g_hash_table_lookup(data, "members"); id = yd->conf_id++; c = serv_got_joined_chat(gc, id, room); yd->confs = g_slist_prepend(yd->confs, c); purple_conv_chat_set_topic(PURPLE_CONV_CHAT(c), purple_connection_get_display_name(gc), topic); yahoo_conf_join(yd, c, purple_connection_get_display_name(gc), room, topic, members); return; } else { const char *id; /*if (yd->in_chat) yahoo_chat_leave(gc, room, purple_connection_get_display_name(gc), FALSE);*/ id = g_hash_table_lookup(data, "id"); if (!yd->chat_online) { yahoo_chat_online(gc); g_free(yd->pending_chat_room); yd->pending_chat_room = g_strdup(room); g_free(yd->pending_chat_id); yd->pending_chat_id = g_strdup(id); g_free(yd->pending_chat_topic); yd->pending_chat_topic = g_strdup(topic); g_free(yd->pending_chat_goto); yd->pending_chat_goto = NULL; } else { yahoo_chat_join(gc, purple_connection_get_display_name(gc), room, topic, id); } return; } } void yahoo_c_invite(PurpleConnection *gc, int id, const char *msg, const char *name) { PurpleConversation *c; c = purple_find_chat(gc, id); if (!c || !c->name) return; if (id != YAHOO_CHAT_ID) { yahoo_conf_invite(gc, c, purple_connection_get_display_name(gc), name, purple_conversation_get_name(c), msg); } else { yahoo_chat_invite(gc, purple_connection_get_display_name(gc), name, purple_conversation_get_name(c), msg); } } struct yahoo_roomlist { int fd; int inpa; gchar *txbuf; gsize tx_written; guchar *rxqueue; int rxlen; gboolean started; char *path; char *host; PurpleRoomlist *list; PurpleRoomlistRoom *cat; PurpleRoomlistRoom *ucat; GMarkupParseContext *parse; }; static void yahoo_roomlist_destroy(struct yahoo_roomlist *yrl) { if (yrl->inpa) purple_input_remove(yrl->inpa); g_free(yrl->txbuf); g_free(yrl->rxqueue); g_free(yrl->path); g_free(yrl->host); if (yrl->parse) g_markup_parse_context_free(yrl->parse); g_free(yrl); } enum yahoo_room_type { yrt_yahoo, yrt_user }; struct yahoo_chatxml_state { PurpleRoomlist *list; struct yahoo_roomlist *yrl; GQueue *q; struct { enum yahoo_room_type type; char *name; char *topic; char *id; int users, voices, webcams; } room; }; struct yahoo_lobby { int count, users, voices, webcams; }; static struct yahoo_chatxml_state *yahoo_chatxml_state_new(PurpleRoomlist *list, struct yahoo_roomlist *yrl) { struct yahoo_chatxml_state *s; s = g_new0(struct yahoo_chatxml_state, 1); s->list = list; s->yrl = yrl; s->q = g_queue_new(); return s; } static void yahoo_chatxml_state_destroy(struct yahoo_chatxml_state *s) { g_queue_free(s->q); g_free(s->room.name); g_free(s->room.topic); g_free(s->room.id); g_free(s); } static void yahoo_chatlist_start_element(GMarkupParseContext *context, const gchar *ename, const gchar **anames, const gchar **avalues, gpointer user_data, GError **error) { struct yahoo_chatxml_state *s = user_data; PurpleRoomlist *list = s->list; PurpleRoomlistRoom *r; PurpleRoomlistRoom *parent; int i; if (!strcmp(ename, "category")) { const gchar *name = NULL, *id = NULL; for (i = 0; anames[i]; i++) { if (!strcmp(anames[i], "id")) id = avalues[i]; if (!strcmp(anames[i], "name")) name = avalues[i]; } if (!name || !id) return; parent = g_queue_peek_head(s->q); r = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_CATEGORY, name, parent); purple_roomlist_room_add_field(list, r, (gpointer)name); purple_roomlist_room_add_field(list, r, (gpointer)id); purple_roomlist_room_add(list, r); g_queue_push_head(s->q, r); } else if (!strcmp(ename, "room")) { s->room.users = s->room.voices = s->room.webcams = 0; for (i = 0; anames[i]; i++) { if (!strcmp(anames[i], "id")) { g_free(s->room.id); s->room.id = g_strdup(avalues[i]); } else if (!strcmp(anames[i], "name")) { g_free(s->room.name); s->room.name = g_strdup(avalues[i]); } else if (!strcmp(anames[i], "topic")) { g_free(s->room.topic); s->room.topic = g_strdup(avalues[i]); } else if (!strcmp(anames[i], "type")) { if (!strcmp("yahoo", avalues[i])) s->room.type = yrt_yahoo; else s->room.type = yrt_user; } } } else if (!strcmp(ename, "lobby")) { struct yahoo_lobby *lob = g_new0(struct yahoo_lobby, 1); for (i = 0; anames[i]; i++) { if (!strcmp(anames[i], "count")) { lob->count = strtol(avalues[i], NULL, 10); } else if (!strcmp(anames[i], "users")) { s->room.users += lob->users = strtol(avalues[i], NULL, 10); } else if (!strcmp(anames[i], "voices")) { s->room.voices += lob->voices = strtol(avalues[i], NULL, 10); } else if (!strcmp(anames[i], "webcams")) { s->room.webcams += lob->webcams = strtol(avalues[i], NULL, 10); } } g_queue_push_head(s->q, lob); } } static void yahoo_chatlist_end_element(GMarkupParseContext *context, const gchar *ename, gpointer user_data, GError **error) { struct yahoo_chatxml_state *s = user_data; if (!strcmp(ename, "category")) { g_queue_pop_head(s->q); } else if (!strcmp(ename, "room")) { struct yahoo_lobby *lob; PurpleRoomlistRoom *r, *l; if (s->room.type == yrt_yahoo) r = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_CATEGORY|PURPLE_ROOMLIST_ROOMTYPE_ROOM, s->room.name, s->yrl->cat); else r = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_CATEGORY|PURPLE_ROOMLIST_ROOMTYPE_ROOM, s->room.name, s->yrl->ucat); purple_roomlist_room_add_field(s->list, r, s->room.name); purple_roomlist_room_add_field(s->list, r, s->room.id); purple_roomlist_room_add_field(s->list, r, GINT_TO_POINTER(s->room.users)); purple_roomlist_room_add_field(s->list, r, GINT_TO_POINTER(s->room.voices)); purple_roomlist_room_add_field(s->list, r, GINT_TO_POINTER(s->room.webcams)); purple_roomlist_room_add_field(s->list, r, s->room.topic); purple_roomlist_room_add(s->list, r); while ((lob = g_queue_pop_head(s->q))) { char *name = g_strdup_printf("%s:%d", s->room.name, lob->count); l = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, name, r); purple_roomlist_room_add_field(s->list, l, name); purple_roomlist_room_add_field(s->list, l, s->room.id); purple_roomlist_room_add_field(s->list, l, GINT_TO_POINTER(lob->users)); purple_roomlist_room_add_field(s->list, l, GINT_TO_POINTER(lob->voices)); purple_roomlist_room_add_field(s->list, l, GINT_TO_POINTER(lob->webcams)); purple_roomlist_room_add_field(s->list, l, s->room.topic); purple_roomlist_room_add(s->list, l); g_free(name); g_free(lob); } } } static GMarkupParser parser = { yahoo_chatlist_start_element, yahoo_chatlist_end_element, NULL, NULL, NULL }; static void yahoo_roomlist_cleanup(PurpleRoomlist *list, struct yahoo_roomlist *yrl) { purple_roomlist_set_in_progress(list, FALSE); if (yrl) { list->proto_data = g_list_remove(list->proto_data, yrl); yahoo_roomlist_destroy(yrl); } purple_roomlist_unref(list); } static void yahoo_roomlist_pending(gpointer data, gint source, PurpleInputCondition cond) { struct yahoo_roomlist *yrl = data; PurpleRoomlist *list = yrl->list; char buf[1024]; int len; guchar *start; struct yahoo_chatxml_state *s; len = read(yrl->fd, buf, sizeof(buf)); if (len < 0 && errno == EAGAIN) return; if (len <= 0) { if (yrl->parse) g_markup_parse_context_end_parse(yrl->parse, NULL); yahoo_roomlist_cleanup(list, yrl); return; } yrl->rxqueue = g_realloc(yrl->rxqueue, len + yrl->rxlen); memcpy(yrl->rxqueue + yrl->rxlen, buf, len); yrl->rxlen += len; if (!yrl->started) { yrl->started = TRUE; start = (guchar *)g_strstr_len((char *)yrl->rxqueue, yrl->rxlen, "\r\n\r\n"); if (!start || (start - yrl->rxqueue + 4) >= yrl->rxlen) return; start += 4; } else { start = yrl->rxqueue; } if (yrl->parse == NULL) { s = yahoo_chatxml_state_new(list, yrl); yrl->parse = g_markup_parse_context_new(&parser, 0, s, (GDestroyNotify)yahoo_chatxml_state_destroy); } if (!g_markup_parse_context_parse(yrl->parse, (char *)start, (yrl->rxlen - (start - yrl->rxqueue)), NULL)) { yahoo_roomlist_cleanup(list, yrl); return; } yrl->rxlen = 0; } static void yahoo_roomlist_send_cb(gpointer data, gint source, PurpleInputCondition cond) { struct yahoo_roomlist *yrl; PurpleRoomlist *list; int written, remaining; yrl = data; list = yrl->list; remaining = strlen(yrl->txbuf) - yrl->tx_written; written = write(yrl->fd, yrl->txbuf + yrl->tx_written, remaining); if (written < 0 && errno == EAGAIN) written = 0; else if (written <= 0) { purple_input_remove(yrl->inpa); yrl->inpa = 0; g_free(yrl->txbuf); yrl->txbuf = NULL; purple_notify_error(purple_account_get_connection(list->account), NULL, _("Unable to connect"), _("Fetching the room list failed.")); yahoo_roomlist_cleanup(list, yrl); return; } if (written < remaining) { yrl->tx_written += written; return; } g_free(yrl->txbuf); yrl->txbuf = NULL; purple_input_remove(yrl->inpa); yrl->inpa = purple_input_add(yrl->fd, PURPLE_INPUT_READ, yahoo_roomlist_pending, yrl); } static void yahoo_roomlist_got_connected(gpointer data, gint source, const gchar *error_message) { struct yahoo_roomlist *yrl = data; PurpleRoomlist *list = yrl->list; YahooData *yd = purple_account_get_connection(list->account)->proto_data; if (source < 0) { purple_notify_error(purple_account_get_connection(list->account), NULL, _("Unable to connect"), _("Fetching the room list failed.")); yahoo_roomlist_cleanup(list, yrl); return; } yrl->fd = source; yrl->txbuf = g_strdup_printf( "GET http://%s/%s HTTP/1.0\r\n" "Host: %s\r\n" "Cookie: Y=%s; T=%s\r\n\r\n", yrl->host, yrl->path, yrl->host, yd->cookie_y, yd->cookie_t); yrl->inpa = purple_input_add(yrl->fd, PURPLE_INPUT_WRITE, yahoo_roomlist_send_cb, yrl); yahoo_roomlist_send_cb(yrl, yrl->fd, PURPLE_INPUT_WRITE); } PurpleRoomlist *yahoo_roomlist_get_list(PurpleConnection *gc) { PurpleAccount *account; PurpleRoomlist *rl; PurpleRoomlistField *f; GList *fields = NULL; struct yahoo_roomlist *yrl; const char *rll, *rlurl; char *url; account = purple_connection_get_account(gc); /* for Yahoo Japan, it appears there is only one valid URL and locale */ if(purple_account_get_bool(account, "yahoojp", FALSE)) { rll = YAHOOJP_ROOMLIST_LOCALE; rlurl = YAHOOJP_ROOMLIST_URL; } else { /* but for the rest of the world that isn't the case */ rll = purple_account_get_string(account, "room_list_locale", YAHOO_ROOMLIST_LOCALE); rlurl = purple_account_get_string(account, "room_list", YAHOO_ROOMLIST_URL); } url = g_strdup_printf("%s?chatcat=0&intl=%s", rlurl, rll); yrl = g_new0(struct yahoo_roomlist, 1); rl = purple_roomlist_new(account); yrl->list = rl; purple_url_parse(url, &(yrl->host), NULL, &(yrl->path), NULL, NULL); g_free(url); f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", "room", TRUE); fields = g_list_append(fields, f); f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", "id", TRUE); fields = g_list_append(fields, f); f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_INT, _("Users"), "users", FALSE); fields = g_list_append(fields, f); f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_INT, _("Voices"), "voices", FALSE); fields = g_list_append(fields, f); f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_INT, _("Webcams"), "webcams", FALSE); fields = g_list_append(fields, f); f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Topic"), "topic", FALSE); fields = g_list_append(fields, f); purple_roomlist_set_fields(rl, fields); if (purple_proxy_connect(gc, account, yrl->host, 80, yahoo_roomlist_got_connected, yrl) == NULL) { purple_notify_error(gc, NULL, _("Connection problem"), _("Unable to fetch room list.")); yahoo_roomlist_cleanup(rl, yrl); return NULL; } rl->proto_data = g_list_append(rl->proto_data, yrl); purple_roomlist_set_in_progress(rl, TRUE); return rl; } void yahoo_roomlist_cancel(PurpleRoomlist *list) { GList *l, *k; k = l = list->proto_data; list->proto_data = NULL; purple_roomlist_set_in_progress(list, FALSE); for (; l; l = l->next) { yahoo_roomlist_destroy(l->data); purple_roomlist_unref(list); } g_list_free(k); } void yahoo_roomlist_expand_category(PurpleRoomlist *list, PurpleRoomlistRoom *category) { struct yahoo_roomlist *yrl; char *url; char *id; const char *rll; if (category->type != PURPLE_ROOMLIST_ROOMTYPE_CATEGORY) return; if (!(id = g_list_nth_data(category->fields, 1))) { purple_roomlist_set_in_progress(list, FALSE); return; } rll = purple_account_get_string(list->account, "room_list_locale", YAHOO_ROOMLIST_LOCALE); if (rll != NULL && *rll != '\0') { url = g_strdup_printf("%s?chatroom_%s=0&intl=%s", purple_account_get_string(list->account,"room_list", YAHOO_ROOMLIST_URL), id, rll); } else { url = g_strdup_printf("%s?chatroom_%s=0", purple_account_get_string(list->account,"room_list", YAHOO_ROOMLIST_URL), id); } yrl = g_new0(struct yahoo_roomlist, 1); yrl->list = list; yrl->cat = category; list->proto_data = g_list_append(list->proto_data, yrl); purple_url_parse(url, &(yrl->host), NULL, &(yrl->path), NULL, NULL); g_free(url); yrl->ucat = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_CATEGORY, _("User Rooms"), yrl->cat); purple_roomlist_room_add(list, yrl->ucat); if (purple_proxy_connect(purple_account_get_connection(list->account), list->account, yrl->host, 80, yahoo_roomlist_got_connected, yrl) == NULL) { purple_notify_error(purple_account_get_connection(list->account), NULL, _("Connection problem"), _("Unable to fetch room list.")); purple_roomlist_ref(list); yahoo_roomlist_cleanup(list, yrl); return; } purple_roomlist_set_in_progress(list, TRUE); purple_roomlist_ref(list); }