Mercurial > pidgin
diff libpurple/protocols/yahoo/yahoo.c @ 15373:5fe8042783c1
Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author | Sean Egan <seanegan@gmail.com> |
---|---|
date | Sat, 20 Jan 2007 02:32:10 +0000 |
parents | |
children | 21bc8d84974f |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/yahoo/yahoo.c Sat Jan 20 02:32:10 2007 +0000 @@ -0,0 +1,3965 @@ +/* + * 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 "yahoochat.h" +#include "yahoo_auth.h" +#include "yahoo_crypt.h" +#include "yahoo_doodle.h" +#include "yahoo_filexfer.h" +#include "yahoo_friend.h" +#include "yahoo_packet.h" +#include "yahoo_picture.h" +#include "ycht.h" + +/* #define YAHOO_DEBUG */ + +static void yahoo_add_buddy(GaimConnection *gc, GaimBuddy *, GaimGroup *); +static void yahoo_login_page_cb(GaimUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message); +static void yahoo_set_status(GaimAccount *account, GaimStatus *status); + +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 */ + gboolean permitted; + + permitted = gaim_privacy_check(gc->account, who); + + /* print some debug info */ + if (!permitted) { + char *deb = NULL; + switch (gc->account->perm_deny) + { + case GAIM_PRIVACY_DENY_ALL: + deb = "GAIM_PRIVACY_DENY_ALL"; + break; + case GAIM_PRIVACY_DENY_USERS: + deb = "GAIM_PRIVACY_DENY_USERS"; + break; + case GAIM_PRIVACY_ALLOW_BUDDYLIST: + deb = "GAIM_PRIVACY_ALLOW_BUDDYLIST"; + break; + } + if(deb) + gaim_debug_info("yahoo", + "%s blocked data received from %s (%s)\n", + gc->account->username,who, deb); + } else if (gc->account->perm_deny == GAIM_PRIVACY_ALLOW_USERS) { + gaim_debug_info("yahoo", + "%s allowed data received from %s (GAIM_PRIVACY_ALLOW_USERS)\n", + gc->account->username,who); + } + + return permitted; +} + +static void yahoo_update_status(GaimConnection *gc, const char *name, YahooFriend *f) +{ + char *status = NULL; + + if (!gc || !name || !f || !gaim_find_buddy(gaim_connection_get_account(gc), name)) + return; + + if (f->status == YAHOO_STATUS_OFFLINE) + { + return; + } + + 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: + case YAHOO_STATUS_IDLE: + if (!f->away) + status = YAHOO_STATUS_TYPE_AVAILABLE; + else + status = YAHOO_STATUS_TYPE_AWAY; + 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; + } + yahoo_set_status(account, gaim_account_get_active_status(account)); + + /* 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; + if (name) + 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 */ + { + guchar *decoded; + char *tmp; + gsize len; + + if (pair->value) { + decoded = gaim_base64_decode(pair->value, &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 (b && 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_buddy_get_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_buddy_get_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_15(GaimConnection *gc, struct yahoo_packet *pkt) +{ + GSList *l = pkt->hash; + + GaimAccount *account = gaim_connection_get_account(gc); + GHashTable *ht; + char *grp = NULL; + char *norm_bud = NULL; + YahooFriend *f = NULL; /* It's your friends. They're going to want you to share your StarBursts. */ + /* But what if you had no friends? */ + GaimBuddy *b; + GaimGroup *g; + + + ht = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_slist_free); + + while (l) { + struct yahoo_pair *pair = l->data; + l = l->next; + + switch (pair->key) { + case 302: + /* This is always 318 before a group, 319 before the first s/n in a group, 320 before any ignored s/n. + * It is not sent for s/n's in a group after the first. + * All ignored s/n's are listed last, so when we see a 320 we clear the group and begin marking the + * s/n's as ignored. It is always followed by an identical 300 key. + */ + if (pair->value && !strcmp(pair->value, "320")) { + /* No longer in any group; this indicates the start of the ignore list. */ + g_free(grp); + grp = NULL; + } + + break; + case 301: /* This is 319 before all s/n's in a group after the first. It is followed by an identical 300. */ + break; + case 300: /* This is 318 before a group, 319 before any s/n in a group, and 320 before any ignored s/n. */ + break; + case 65: /* This is the group */ + g_free(grp); + grp = yahoo_string_decode(gc, pair->value, FALSE); + break; + case 7: /* buddy's s/n */ + g_free(norm_bud); + norm_bud = g_strdup(gaim_normalize(account, pair->value)); + + if (grp) { + /* This buddy is in a group */ + 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); + } + yahoo_do_group_check(account, ht, norm_bud, grp); + + } else { + /* This buddy is on the ignore list (and therefore in no group) */ + gaim_privacy_deny_add(account, norm_bud, 1); + } + break; + case 241: /* another protocol user */ + if (f) { + f->protocol = strtol(pair->value, NULL, 10); + gaim_debug_info("yahoo", "Setting protocol to %d\n", f->protocol); + } + break; + /* case 242: */ /* this seems related to 241 */ + /* break; */ + } + } + + g_hash_table_foreach(ht, yahoo_do_group_cleanup, NULL); + g_hash_table_destroy(ht); + g_free(grp); + g_free(norm_bud); +} + +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; + + 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_PRESENCE_PERM: + if (!yd->tmp_serv_plist) + yd->tmp_serv_plist = g_string_new(pair->value); + else + g_string_append(yd->tmp_serv_plist, 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", "%s privacy defaulting to GAIM_PRIVACY_DENY_USERS.\n", + gc->account->username); + } + + if (yd->tmp_serv_plist) { + buddies = g_strsplit(yd->tmp_serv_plist->str, ",", -1); + for (bud = buddies; bud && *bud; bud++) { + f = yahoo_friend_find(gc, *bud); + if (f) { + gaim_debug_info("yahoo", "%s setting presence for %s to PERM_OFFLINE\n", + gc->account->username, *bud); + f->presence = YAHOO_PRESENCE_PERM_OFFLINE; + } + } + g_strfreev(buddies); + g_string_free(yd->tmp_serv_plist, TRUE); + yd->tmp_serv_plist = NULL; + + } +} + +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) +{ + struct yahoo_data *yd = gc->proto_data; + GSList *l = pkt->hash; + GSList *list = NULL; + struct _yahoo_im *im = NULL; + + const char *imv = NULL; + + if (pkt->status <= 1 || pkt->status == 5) { + while (l != NULL) { + 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; + } + /* IMV key */ + if (pair->key == 63) + { + imv = pair->value; + } + l = l->next; + } + } else if (pkt->status == 2) { + gaim_notify_error(gc, NULL, + _("Your Yahoo! message did not get sent."), NULL); + } + + /** TODO: It seems that this check should be per IM, not global */ + /* Check for the Doodle IMV */ + if (im != NULL && imv!= NULL && im->from != NULL) + { + g_hash_table_replace(yd->imvironments, g_strdup(im->from), g_strdup(imv)); + + if (strcmp(imv, "doodle;11") == 0) + { + GaimWhiteboard *wb; + + if (!yahoo_privacy_check(gc, im->from)) { + gaim_debug_info("yahoo", "Doodle request from %s dropped.\n", im->from); + return; + } + + wb = gaim_whiteboard_get_session(gc->account, im->from); + + /* If a Doodle session doesn't exist between this user */ + if(wb == NULL) + { + wb = gaim_whiteboard_create(gc->account, im->from, DOODLE_STATE_REQUESTED); + + yahoo_doodle_command_send_request(gc, im->from); + yahoo_doodle_command_send_ready(gc, im->from); + } + } + } + + 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); + /* This may actually not be necessary, but it appears + * that at least at one point some clients were sending + * "\r\n" as line delimiters, so we want to avoid double + * lines. */ + m2 = gaim_strreplace(m, "\r\n", "\n"); + g_free(m); + m = m2; + gaim_util_chrreplace(m, '\r', '\n'); + + if (!strcmp(m, "<ding>")) { + GaimConversation *c = gaim_conversation_new(GAIM_CONV_TYPE_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; + + /* TODO: Does this really need to be escaped? It seems like it doesn't. */ + 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); +} + +struct yahoo_add_request { + GaimConnection *gc; + char *id; + char *who; + char *msg; +}; + +static void +yahoo_buddy_add_authorize_cb(struct yahoo_add_request *add_req) { + g_free(add_req->id); + g_free(add_req->who); + g_free(add_req->msg); + g_free(add_req); +} + +static void +yahoo_buddy_add_deny_cb(struct yahoo_add_request *add_req, const char *msg) { + struct yahoo_packet *pkt; + char *encoded_msg = NULL; + struct yahoo_data *yd = add_req->gc->proto_data; + + if (msg) + encoded_msg = yahoo_string_encode(add_req->gc, msg, NULL); + + pkt = yahoo_packet_new(YAHOO_SERVICE_REJECTCONTACT, + YAHOO_STATUS_AVAILABLE, 0); + + yahoo_packet_hash(pkt, "sss", + 1, gaim_normalize(add_req->gc->account, + gaim_account_get_username( + gaim_connection_get_account( + add_req->gc))), + 7, add_req->who, + 14, encoded_msg ? encoded_msg : ""); + + yahoo_packet_send_and_free(pkt, yd); + + g_free(encoded_msg); + + g_free(add_req->id); + g_free(add_req->who); + g_free(add_req->msg); + g_free(add_req); +} + +static void +yahoo_buddy_add_deny_noreason_cb(struct yahoo_add_request *add_req, const char*msg) +{ + yahoo_buddy_add_deny_cb(add_req, NULL); +} + +static void +yahoo_buddy_add_deny_reason_cb(struct yahoo_add_request *add_req) { + gaim_request_input(add_req->gc, NULL, _("Authorization denied message:"), + NULL, _("No reason given."), TRUE, FALSE, NULL, + _("OK"), G_CALLBACK(yahoo_buddy_add_deny_cb), + _("Cancel"), G_CALLBACK(yahoo_buddy_add_deny_noreason_cb), add_req); +} + +static void yahoo_buddy_added_us(GaimConnection *gc, struct yahoo_packet *pkt) { + struct yahoo_add_request *add_req; + char *msg = NULL; + GSList *l = pkt->hash; + + add_req = g_new0(struct yahoo_add_request, 1); + add_req->gc = gc; + + while (l) { + struct yahoo_pair *pair = l->data; + + switch (pair->key) { + case 1: + add_req->id = g_strdup(pair->value); + break; + case 3: + add_req->who = g_strdup(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 (add_req->id) { + if (msg) + add_req->msg = yahoo_string_decode(gc, msg, FALSE); + + /* DONE! this is almost exactly the same as what MSN does, + * this should probably be moved to the core. + */ + gaim_account_request_authorization(gaim_connection_get_account(gc), add_req->who, add_req->id, + NULL, add_req->msg, gaim_find_buddy(gaim_connection_get_account(gc),add_req->who) != NULL, + G_CALLBACK(yahoo_buddy_add_authorize_cb), + G_CALLBACK(yahoo_buddy_add_deny_reason_cb), + add_req); + } else { + g_free(add_req->id); + g_free(add_req->who); + /*g_free(add_req->msg);*/ + g_free(add_req); + } +} + +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 */ + /* TODO: Shouldn't we remove the buddy from our local list? */ + } +} + +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 _ */ +static const 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 ;) */ +static void to_y64(char *out, const unsigned char *in, gsize 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; + guchar 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, (const guchar *)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, (const guchar *)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, (const guchar *)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, (const guchar *)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); + char *enc_pass; + struct yahoo_data *yd = gc->proto_data; + + GaimCipher *md5_cipher; + GaimCipherContext *md5_ctx; + guchar 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; + + unsigned char pass_hash_xor1[64]; + unsigned char pass_hash_xor2[64]; + unsigned char crypt_hash_xor1[64]; + unsigned 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 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; + } + + enc_pass = yahoo_string_encode(gc, pass, NULL); + + /* Get password and crypt hashes as per usual. */ + gaim_cipher_context_reset(md5_ctx, NULL); + gaim_cipher_context_append(md5_ctx, (const guchar *)enc_pass, strlen(enc_pass)); + gaim_cipher_context_digest(md5_ctx, sizeof(md5_digest), + md5_digest, NULL); + to_y64(password_hash, md5_digest, 16); + + crypt_result = yahoo_crypt(enc_pass, "$1$_2S43d5f$"); + + g_free(enc_pass); + enc_pass = NULL; + + gaim_cipher_context_reset(md5_ctx, NULL); + gaim_cipher_context_append(md5_ctx, (const guchar *)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: + case 2: /* This case seems to work, could probably use testing */ + 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; + GaimAccount *account; + gchar *name; + + if (!buddy) + return; + + group = gaim_buddy_get_group(buddy); + name = g_strdup(buddy->name); + account = buddy->account; + + gaim_debug(GAIM_DEBUG_INFO, "blist", + "Removing '%s' from buddy list.\n", buddy->name); + gaim_account_remove_buddy(account, buddy, group); + gaim_blist_remove_buddy(buddy); + + serv_add_deny(account->gc, name); + + 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 screen name.")); + break; + case 13: + if (!yd->wm) { + GaimUtilFetchUrlData *url_data; + yd->wm = TRUE; + if (yd->fd >= 0) + close(yd->fd); + if (gc->inpa) + gaim_input_remove(gc->inpa); + url_data = gaim_util_fetch_url(WEBMESSENGER_URL, TRUE, + "Gaim/" VERSION, FALSE, yahoo_login_page_cb, gc); + if (url_data != NULL) + yd->url_datas = g_slist_prepend(yd->url_datas, url_data); + 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; + guchar *decoded; + gsize 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; + + decoded = gaim_base64_decode(base64, &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((const gchar *)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, *id = 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.locale.bar.baz format + eg: base.tw.smiley.smiley43 */ + id = pair->value; + 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 (!msg) + msg = id; + 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; + } + if (id) { + /* "http://us.dl1.yimg.com/download.yahoo.com/dl/aud/"+locale+"/"+id+".swf" */ + char **audible_locale = g_strsplit(id, ".", 0); + char *buf = g_strdup_printf(_("[ Audible %s/%s/%s.swf ] %s"), YAHOO_AUDIBLE_URL, audible_locale[1], id, msg); + g_strfreev(audible_locale); + + serv_got_im(gc, who, buf, 0, time(NULL)); + g_free(buf); + } else + serv_got_im(gc, who, msg, 0, time(NULL)); +} + +static 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: + case YAHOO_SERVICE_STATUS_15: + 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_LIST_15: + yahoo_process_list_15(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_PRESENCE_PERM: + case YAHOO_SERVICE_PRESENCE_SESSION: + yahoo_process_presence(gc, pkt); + break; + case YAHOO_SERVICE_P2PFILEXFER: + /* This case had no break and continued; thus keeping it this way.*/ + yahoo_process_p2pfilexfer(gc, pkt); + case YAHOO_SERVICE_FILETRANSFER: + yahoo_process_filetransfer(gc, pkt); + break; + case YAHOO_SERVICE_PEERTOPEER: + 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_AVATAR_UPDATE: + yahoo_process_avatar_update(gc, pkt); + break; + case YAHOO_SERVICE_AUDIBLE: + yahoo_process_audible(gc, pkt); + break; + default: + gaim_debug(GAIM_DEBUG_ERROR, "yahoo", + "Unhandled service 0x%02x\n", pkt->service); + break; + } +} + +static 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) { + gchar *tmp; + + if (errno == EAGAIN) + /* No worries */ + return; + + tmp = g_strdup_printf(_("Lost connection with server:\n%s"), + strerror(errno)); + gaim_connection_error(gc, tmp); + g_free(tmp); + return; + } else if (len == 0) { + gaim_connection_error(gc, _("Server closed the connection.")); + 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((char *)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) { + guchar *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, const gchar *error_message) +{ + GaimConnection *gc = data; + struct yahoo_data *yd; + struct yahoo_packet *pkt; + + if (!GAIM_CONNECTION_IS_VALID(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, yd->current_status, 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, const gchar *error_message) +{ + GaimConnection *gc = data; + struct yahoo_data *yd; + struct yahoo_packet *pkt; + + if (!GAIM_CONNECTION_IS_VALID(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 bufread[2048], *i = bufread, *buf = bufread; + int len; + GString *s; + + len = read(source, bufread, sizeof(bufread) - 1); + + if (len < 0) { + gchar *tmp; + + if (errno == EAGAIN) + /* No worries */ + return; + + tmp = g_strdup_printf(_("Lost connection with server:\n%s"), + strerror(errno)); + gaim_connection_error(gc, tmp); + g_free(tmp); + return; + } else if (len == 0) { + gaim_connection_error(gc, _("Server closed the connection.")); + return; + } + + if (yd->rxlen > 0 || !g_strstr_len(buf, len, "\r\n\r\n")) { + yd->rxqueue = g_realloc(yd->rxqueue, yd->rxlen + len + 1); + memcpy(yd->rxqueue + yd->rxlen, buf, len); + yd->rxlen += len; + i = buf = (char *)yd->rxqueue; + len = yd->rxlen; + } + buf[len] = '\0'; + + if ((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, _("Received unexpected HTTP response from server.")); + return; + } + + s = g_string_sized_new(len); + + 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); + g_free(yd->rxqueue); + yd->rxqueue = NULL; + yd->rxlen = 0; + /* Now we have our cookies to login with. I'll go get the milk. */ + if (gaim_proxy_connect(gc, account, "wcs2.msg.dcn.yahoo.com", + gaim_account_get_int(account, "port", YAHOO_PAGER_PORT), + yahoo_got_web_connected, gc) == NULL) { + gaim_connection_error(gc, _("Connection problem")); + return; + } +} + +static void yahoo_got_cookies_send_cb(gpointer data, gint source, GaimInputCondition cond) +{ + GaimConnection *gc; + struct yahoo_data *yd; + int written, remaining; + + gc = data; + yd = gc->proto_data; + + remaining = strlen(yd->auth) - yd->auth_written; + written = write(source, yd->auth + yd->auth_written, remaining); + + if (written < 0 && errno == EAGAIN) + written = 0; + else if (written <= 0) { + g_free(yd->auth); + yd->auth = NULL; + if (gc->inpa) + gaim_input_remove(gc->inpa); + gc->inpa = 0; + gaim_connection_error(gc, _("Unable to connect.")); + return; + } + + if (written < remaining) { + yd->auth_written += written; + return; + } + + g_free(yd->auth); + yd->auth = NULL; + yd->auth_written = 0; + gaim_input_remove(gc->inpa); + gc->inpa = gaim_input_add(source, GAIM_INPUT_READ, yahoo_web_pending, gc); +} + +static void yahoo_got_cookies(gpointer data, gint source, const gchar *error_message) +{ + GaimConnection *gc = data; + + if (source < 0) { + gaim_connection_error(gc, _("Unable to connect.")); + return; + } + + if (gc->inpa == 0) + { + gc->inpa = gaim_input_add(source, GAIM_INPUT_WRITE, + yahoo_got_cookies_send_cb, gc); + yahoo_got_cookies_send_cb(gc, source, GAIM_INPUT_WRITE); + } +} + +static void yahoo_login_page_hash_iter(const char *key, const char *val, GString *url) +{ + if (!strcmp(key, "passwd")) + return; + g_string_append_c(url, '&'); + g_string_append(url, key); + g_string_append_c(url, '='); + if (!strcmp(key, ".save") || !strcmp(key, ".js")) + g_string_append_c(url, '1'); + else if (!strcmp(key, ".challenge")) + g_string_append(url, val); + else + 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; + int input_len = strlen("<input "); + int name_len = strlen("name=\""); + int value_len = strlen("value=\""); + while ((len > ((c - buf) + input_len)) + && (c = strstr(c, "<input "))) { + if (!(c = g_strstr_len(c, len - (c - buf), "name=\""))) + continue; + c += name_len; + count = sizeof(name)-1; + for (d = name; (len > ((c - buf) + 1)) && *c!='"' + && count; c++, d++, count--) + *d = *c; + *d = '\0'; + count = sizeof(value)-1; + if (!(d = g_strstr_len(c, len - (c - buf), "value=\""))) + continue; + d += value_len; + if (strchr(c, '>') < d) + break; + for (c = d, d = value; (len > ((c - buf) + 1)) + && *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(GaimUtilFetchUrlData *url_data, gpointer user_data, + const gchar *url_text, size_t len, const gchar *error_message) +{ + 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(url_text, 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; + guchar digest[16]; + + yd->url_datas = g_slist_remove(yd->url_datas, url_data); + + 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, (const guchar *)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, (const guchar *)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(gc, account, "login.yahoo.com", 80, yahoo_got_cookies, gc) == NULL) { + 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); + char *buddyicon; + + buddyicon = gaim_buddy_icons_get_full_path(gaim_account_get_buddy_icon(account)); + yahoo_set_buddy_icon(gc, buddyicon); + g_free(buddyicon); +} + +static int get_yahoo_status_from_gaim_status(GaimStatus *status) +{ + GaimPresence *presence; + const char *status_id; + const char *msg; + + presence = gaim_status_get_presence(status); + status_id = gaim_status_get_id(status); + msg = gaim_status_get_attr_string(status, "message"); + + if (!strcmp(status_id, YAHOO_STATUS_TYPE_AVAILABLE)) { + if ((msg != NULL) && (*msg != '\0')) + return YAHOO_STATUS_CUSTOM; + else + return YAHOO_STATUS_AVAILABLE; + } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_BRB)) { + return YAHOO_STATUS_BRB; + } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_BUSY)) { + return YAHOO_STATUS_BUSY; + } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_NOTATHOME)) { + return YAHOO_STATUS_NOTATHOME; + } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_NOTATDESK)) { + return YAHOO_STATUS_NOTATDESK; + } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_NOTINOFFICE)) { + return YAHOO_STATUS_NOTINOFFICE; + } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_ONPHONE)) { + return YAHOO_STATUS_ONPHONE; + } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_ONVACATION)) { + return YAHOO_STATUS_ONVACATION; + } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_OUTTOLUNCH)) { + return YAHOO_STATUS_OUTTOLUNCH; + } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_STEPPEDOUT)) { + return YAHOO_STATUS_STEPPEDOUT; + } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_INVISIBLE)) { + return YAHOO_STATUS_INVISIBLE; + } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_AWAY)) { + return YAHOO_STATUS_CUSTOM; + } else if (gaim_presence_is_idle(presence)) { + return YAHOO_STATUS_IDLE; + } else { + gaim_debug_error("yahoo", "Unexpected GaimStatus!\n"); + return YAHOO_STATUS_AVAILABLE; + } +} + +static void yahoo_login(GaimAccount *account) { + GaimConnection *gc = gaim_account_get_connection(account); + struct yahoo_data *yd = gc->proto_data = g_new0(struct yahoo_data, 1); + GaimStatus *status = gaim_account_get_active_status(account); + 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->txhandler = -1; + /* TODO: Is there a good grow size for the buffer? */ + yd->txbuf = gaim_circ_buffer_new(0); + yd->friends = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, yahoo_friend_free); + yd->imvironments = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + yd->confs = NULL; + yd->conf_id = 2; + + yd->current_status = get_yahoo_status_from_gaim_status(status); + + yahoo_server_check(account); + yahoo_picture_check(account); + + if (gaim_account_get_bool(account, "yahoojp", FALSE)) { + yd->jp = TRUE; + if (gaim_proxy_connect(gc, account, + gaim_account_get_string(account, "serverjp", YAHOOJP_PAGER_HOST), + gaim_account_get_int(account, "port", YAHOO_PAGER_PORT), + yahoo_got_connected, gc) == NULL) + { + gaim_connection_error(gc, _("Connection problem")); + return; + } + } else { + yd->jp = FALSE; + if (gaim_proxy_connect(gc, account, + gaim_account_get_string(account, "server", YAHOO_PAGER_HOST), + gaim_account_get_int(account, "port", YAHOO_PAGER_PORT), + yahoo_got_connected, gc) == NULL) + { + 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; + + if (gc->inpa) + gaim_input_remove(gc->inpa); + + while (yd->url_datas) { + gaim_util_fetch_url_cancel(yd->url_datas->data); + yd->url_datas = g_slist_delete_link(yd->url_datas, yd->url_datas); + } + + 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); + + yd->chat_online = 0; + if (yd->in_chat) + yahoo_c_leave(gc, 1); /* 1 = YAHOO_CHAT_ID */ + + g_hash_table_destroy(yd->friends); + g_hash_table_destroy(yd->imvironments); + g_free(yd->chat_name); + + g_free(yd->cookie_y); + g_free(yd->cookie_t); + + if (yd->txhandler) + gaim_input_remove(yd->txhandler); + + gaim_circ_buffer_destroy(yd->txbuf); + + if (yd->fd >= 0) + close(yd->fd); + + g_free(yd->rxqueue); + yd->rxlen = 0; + g_free(yd->picture_url); + + if (yd->buddy_icon_connect_data) + gaim_proxy_connect_cancel(yd->buddy_icon_connect_data); + if (yd->picture_upload_todo) + yahoo_buddy_icon_upload_data_free(yd->picture_upload_todo); + if (yd->ycht) + ycht_connection_close(yd->ycht); + + g_free(yd); + gc->proto_data = NULL; +} + +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; + + 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); + + 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"; + if (f->protocol == 2) + emblems[i] = "msn"; + } + *se = emblems[0]; + *sw = emblems[1]; + *nw = emblems[2]; + *ne = emblems[3]; +} + +static const 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 _("Available"); + } +} + +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_presence_settings(GaimBlistNode *node, gpointer data) { + GaimBuddy *buddy; + GaimConnection *gc; + int presence_val = GPOINTER_TO_INT(data); + + buddy = (GaimBuddy *) node; + gc = gaim_account_get_connection(buddy->account); + + yahoo_friend_update_presence(gc, buddy->name, presence_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; + char *msg2; + + 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; + msg2 = g_markup_escape_text(msg, strlen(msg)); + gaim_util_chrreplace(msg2, '\n', ' '); + return msg2; + + default: + return g_strdup(yahoo_get_status_string(f->status)); + } +} + +void yahoo_tooltip_text(GaimBuddy *b, GaimNotifyUserInfo *user_info, gboolean full) +{ + YahooFriend *f; + char *escaped; + char *status = NULL; + const char *presence = NULL; + + 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_CUSTOM: + if (!yahoo_friend_get_status_message(f)) + return; + 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->presence) { + case YAHOO_PRESENCE_ONLINE: + presence = _("Appear Online"); + break; + case YAHOO_PRESENCE_PERM_OFFLINE: + presence = _("Appear Permanently Offline"); + break; + case YAHOO_PRESENCE_DEFAULT: + break; + default: + gaim_debug_error("yahoo", "Unknown presence in yahoo_tooltip_text\n"); + break; + } + } + + if (status != NULL) { + escaped = g_markup_escape_text(status, strlen(status)); + gaim_notify_user_info_add_pair(user_info, _("Status"), escaped); + g_free(status); + g_free(escaped); + } + + if (presence != NULL) + gaim_notify_user_info_add_pair(user_info, _("Presence"), presence); +} + +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_presence_submenu(YahooFriend *f, GaimConnection *gc) { + GList *m = NULL; + GaimMenuAction *act; + struct yahoo_data *yd = (struct yahoo_data *) gc->proto_data; + + if (yd->current_status == YAHOO_STATUS_INVISIBLE) { + if (f->presence != YAHOO_PRESENCE_ONLINE) { + act = gaim_menu_action_new(_("Appear Online"), + GAIM_CALLBACK(yahoo_presence_settings), + GINT_TO_POINTER(YAHOO_PRESENCE_ONLINE), + NULL); + m = g_list_append(m, act); + } else if (f->presence != YAHOO_PRESENCE_DEFAULT) { + act = gaim_menu_action_new(_("Appear Offline"), + GAIM_CALLBACK(yahoo_presence_settings), + GINT_TO_POINTER(YAHOO_PRESENCE_DEFAULT), + NULL); + m = g_list_append(m, act); + } + } + + if (f->presence == YAHOO_PRESENCE_PERM_OFFLINE) { + act = gaim_menu_action_new(_("Don't Appear Permanently Offline"), + GAIM_CALLBACK(yahoo_presence_settings), + GINT_TO_POINTER(YAHOO_PRESENCE_DEFAULT), + NULL); + m = g_list_append(m, act); + } else { + act = gaim_menu_action_new(_("Appear Permanently Offline"), + GAIM_CALLBACK(yahoo_presence_settings), + GINT_TO_POINTER(YAHOO_PRESENCE_PERM_OFFLINE), + NULL); + m = g_list_append(m, act); + } + + return m; +} + +static void yahoo_doodle_blist_node(GaimBlistNode *node, gpointer data) +{ + GaimBuddy *b = (GaimBuddy *)node; + GaimConnection *gc = b->account->gc; + + yahoo_doodle_initiate(gc, b->name); +} + +static GList *yahoo_buddy_menu(GaimBuddy *buddy) +{ + GList *m = NULL; + GaimMenuAction *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_menu_action_new(_("Add Buddy"), + GAIM_CALLBACK(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_menu_action_new(_("Join in Chat"), + GAIM_CALLBACK(yahoo_chat_goto_menu), + NULL, NULL); + m = g_list_append(m, act); + } + + act = gaim_menu_action_new(_("Initiate Conference"), + GAIM_CALLBACK(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_menu_action_new(buf2, + GAIM_CALLBACK(yahoo_game), + NULL, NULL); + m = g_list_append(m, act); + } + } + } + + if (f) { + act = gaim_menu_action_new(_("Presence Settings"), NULL, NULL, + build_presence_submenu(f, gc)); + m = g_list_append(m, act); + } + + if (f) { + act = gaim_menu_action_new(_("Start Doodling"), + GAIM_CALLBACK(yahoo_doodle_blist_node), + NULL, NULL); + 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; +} + +static int yahoo_send_im(GaimConnection *gc, const char *who, const char *what, GaimMessageFlags 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; + GaimWhiteboard *wb; + int ret = 1; + YahooFriend *f = NULL; + + msg2 = yahoo_string_encode(gc, msg, &utf8); + + yahoo_packet_hash(pkt, "ss", 1, gaim_connection_get_display_name(gc), 5, who); + if ((f = yahoo_friend_find(gc, who)) && f->protocol) + yahoo_packet_hash_int(pkt, 241, f->protocol); + + if (utf8) + yahoo_packet_hash_str(pkt, 97, "1"); + yahoo_packet_hash_str(pkt, 14, msg2); + + /* + * IMVironment. + * + * If this message is to a user who is also Doodling with the local user, + * format the chat packet with the correct IMV information (thanks Yahoo!) + * + * Otherwise attempt to use the same IMVironment as the remote user, + * just so that we don't inadvertantly reset their IMVironment back + * to nothing. + * + * If they have no set an IMVironment, then use the default. + */ + wb = gaim_whiteboard_get_session(gc->account, who); + if (wb) + yahoo_packet_hash_str(pkt, 63, "doodle;11"); + else + { + const char *imv; + imv = g_hash_table_lookup(yd->imvironments, who); + if (imv != NULL) + yahoo_packet_hash_str(pkt, 63, imv); + else + yahoo_packet_hash_str(pkt, 63, ";0"); + } + + 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; +} + +static unsigned int yahoo_send_typing(GaimConnection *gc, const char *who, GaimTypingState state) +{ + 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, state == GAIM_TYPING ? "1" : "0", + 5, who, 1002, "1"); + + yahoo_packet_send_and_free(pkt, yd); + + return 0; +} + +static void yahoo_session_presence_remove(gpointer key, gpointer value, gpointer data) +{ + YahooFriend *f = value; + if (f && f->presence == YAHOO_PRESENCE_ONLINE) + f->presence = YAHOO_PRESENCE_DEFAULT; +} + +static void yahoo_set_status(GaimAccount *account, GaimStatus *status) +{ + GaimConnection *gc; + GaimPresence *presence; + struct yahoo_data *yd; + struct yahoo_packet *pkt; + int old_status; + const char *msg = NULL; + char *tmp = NULL; + char *conv_msg = NULL; + + if (!gaim_status_is_active(status)) + return; + + gc = gaim_account_get_connection(account); + presence = gaim_status_get_presence(status); + yd = (struct yahoo_data *)gc->proto_data; + old_status = yd->current_status; + + yd->current_status = get_yahoo_status_from_gaim_status(status); + + if (yd->current_status == YAHOO_STATUS_CUSTOM) + { + msg = gaim_status_get_attr_string(status, "message"); + + if (gaim_status_is_available(status)) { + tmp = yahoo_string_encode(gc, msg, NULL); + conv_msg = gaim_markup_strip_html(tmp); + g_free(tmp); + } else { + if ((msg == NULL) || (*msg == '\0')) + msg = _("Away"); + tmp = yahoo_string_encode(gc, msg, NULL); + conv_msg = gaim_markup_strip_html(tmp); + g_free(tmp); + } + } + + 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) { + yahoo_packet_hash_str(pkt, 19, conv_msg); + } else { + yahoo_packet_hash_str(pkt, 19, ""); + } + + g_free(conv_msg); + + if (gaim_presence_is_idle(presence)) + yahoo_packet_hash_str(pkt, 47, "2"); + else if (!gaim_status_is_available(status)) + yahoo_packet_hash_str(pkt, 47, "1"); + + yahoo_packet_send_and_free(pkt, yd); + + 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 presence settings are removed */ + g_hash_table_foreach(yd->friends, yahoo_session_presence_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; + GaimStatus *status = NULL; + + if (idle && yd->current_status != YAHOO_STATUS_CUSTOM) + yd->current_status = YAHOO_STATUS_IDLE; + else if (!idle && yd->current_status == YAHOO_STATUS_IDLE) { + status = gaim_presence_get_active_status(gaim_account_get_presence(gaim_connection_get_account(gc))); + yd->current_status = get_yahoo_status_from_gaim_status(status); + } + + 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; + if (status == NULL) + 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_markup_strip_html(msg); + yahoo_packet_hash_str(pkt, 19, msg2); + } else { + /* get_yahoo_status_from_gaim_status() returns YAHOO_STATUS_CUSTOM for + * the generic away state (YAHOO_STATUS_TYPE_AWAY) with no message */ + yahoo_packet_hash_str(pkt, 19, _("Away")); + } + } 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) +{ + GaimStatusType *type; + GList *types = NULL; + + type = gaim_status_type_new_with_attrs(GAIM_STATUS_AVAILABLE, YAHOO_STATUS_TYPE_AVAILABLE, + NULL, TRUE, TRUE, FALSE, + "message", _("Message"), + gaim_value_new(GAIM_TYPE_STRING), NULL); + types = g_list_append(types, type); + + type = gaim_status_type_new_with_attrs(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_AWAY, + NULL, 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_UNAVAILABLE, 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_UNAVAILABLE, YAHOO_STATUS_TYPE_ONPHONE, _("On the Phone"), TRUE); + types = g_list_append(types, type); + + type = gaim_status_type_new(GAIM_STATUS_EXTENDED_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(GAIM_STATUS_INVISIBLE, YAHOO_STATUS_TYPE_INVISIBLE, NULL, TRUE); + types = g_list_append(types, type); + + type = gaim_status_type_new(GAIM_STATUS_OFFLINE, YAHOO_STATUS_TYPE_OFFLINE, NULL, 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_buddy_get_group(buddy); + 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; + struct yahoo_packet *pkt; + GSList *buddies, *l; + GaimGroup *g; + gboolean remove = TRUE; + char *cg; + + if (!(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_buddy_get_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(); + + 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_SEND, 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; +} + +static GaimCmdRet +yahoogaim_cmd_chat_list(GaimConversation *conv, const char *cmd, + char **args, char **error, void *data) +{ + GaimAccount *account = gaim_conversation_get_account(conv); + if (*args && args[0]) + return GAIM_CMD_RET_FAILED; + gaim_roomlist_show_with_account(account); + return GAIM_CMD_RET_OK; +} + +static gboolean yahoo_offline_message(const GaimBuddy *buddy) +{ + return TRUE; +} + +/************************** 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 <room>: Join a chat room on the Yahoo network"), NULL); + gaim_cmd_register("list", "", GAIM_CMD_P_PRPL, + GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | + GAIM_CMD_FLAG_PRPL_ONLY, + "prpl-yahoo", yahoogaim_cmd_chat_list, + _("list: List rooms 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 user to get their attention"), NULL); + gaim_cmd_register("doodle", "", GAIM_CMD_P_PRPL, + GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_PRPL_ONLY, + "prpl-yahoo", yahoo_doodle_gaim_cmd_start, + _("doodle: Request user to start a Doodle session"), NULL); +} + +static GaimWhiteboardPrplOps yahoo_whiteboard_prpl_ops = +{ + yahoo_doodle_start, + yahoo_doodle_end, + yahoo_doodle_get_dimensions, + NULL, + yahoo_doodle_get_brush, + yahoo_doodle_set_brush, + yahoo_doodle_send_draw_list, + yahoo_doodle_clear +}; + +static GaimPluginProtocolInfo prpl_info = +{ + OPT_PROTO_MAIL_CHECK | OPT_PROTO_CHAT_TOPIC, + NULL, /* user_splits */ + NULL, /* protocol_options */ + {"png,gif,jpeg", 96, 96, 96, 96, 0, 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, + 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 */ + gaim_normalize_nocase, /* 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, + yahoo_new_xfer, + yahoo_offline_message, /* offline_message */ + &yahoo_whiteboard_prpl_ops, + NULL, /* send_raw */ + NULL, /* roomlist_room_serialize */ +}; + +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; + + 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 server"), "server", YAHOO_PAGER_HOST); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + option = gaim_account_option_string_new(_("Japan Pager server"), "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 server"), "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 server"), "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); + + option = gaim_account_option_bool_new(_("Ignore conference and chatroom invitations"), "ignore_invites", FALSE); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + option = gaim_account_option_string_new(_("Encoding"), "local_charset", "ISO-8859-1"); + 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(_("Yahoo Chat server"), "ycht-server", YAHOO_YCHT_HOST); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + option = gaim_account_option_int_new(_("Yahoo Chat 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);