Mercurial > pidgin.yaz
view libpurple/protocols/yahoo/libymsg.c @ 32827:4a34689eeb33 default tip
merged from im.pidgin.pidgin
author | Yoshiki Yazawa <yaz@honeyplanet.jp> |
---|---|
date | Sat, 19 Nov 2011 14:42:54 +0900 |
parents | 0f94ec89f0bc 3e7a7e14af62 |
children |
line wrap: on
line source
/* * purple * * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h" #include "account.h" #include "accountopt.h" #include "blist.h" #include "cipher.h" #include "cmds.h" #include "core.h" #include "debug.h" #include "network.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 "xmlnode.h" #include "libymsg.h" #include "yahoochat.h" #include "yahoo_aliases.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 */ /* #define TRY_WEBMESSENGER_LOGIN 0 */ /* One hour */ #define PING_TIMEOUT 3600 /* One minute */ #define KEEPALIVE_TIMEOUT 60 #ifdef TRY_WEBMESSENGER_LOGIN static void yahoo_login_page_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message); #endif /* TRY_WEBMESSENGER_LOGIN */ static gboolean yahoo_is_japan(PurpleAccount *account) { return purple_strequal(purple_account_get_protocol_id(account), "prpl-yahoojp"); } static void yahoo_update_status(PurpleConnection *gc, const char *name, YahooFriend *f) { char *status = NULL; if (!gc || !name || !f || !purple_find_buddy(purple_connection_get_account(gc), name)) return; switch (f->status) { case YAHOO_STATUS_OFFLINE: status = YAHOO_STATUS_TYPE_OFFLINE; break; 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: purple_debug_warning("yahoo", "Warning, unknown status %d\n", f->status); break; } if (status) { if (f->status == YAHOO_STATUS_CUSTOM) purple_prpl_got_user_status(purple_connection_get_account(gc), name, status, "message", yahoo_friend_get_status_message(f), NULL); else purple_prpl_got_user_status(purple_connection_get_account(gc), name, status, NULL); } if (f->idle != 0) purple_prpl_got_user_idle(purple_connection_get_account(gc), name, TRUE, f->idle); else purple_prpl_got_user_idle(purple_connection_get_account(gc), name, FALSE, 0); if (f->sms) purple_prpl_got_user_status(purple_connection_get_account(gc), name, YAHOO_STATUS_TYPE_MOBILE, NULL); else purple_prpl_got_user_status_deactive(purple_connection_get_account(gc), name, YAHOO_STATUS_TYPE_MOBILE); } static void yahoo_process_status(PurpleConnection *gc, struct yahoo_packet *pkt) { PurpleAccount *account = purple_connection_get_account(gc); GSList *l = pkt->hash; YahooFriend *f = NULL; char *name = NULL; gboolean unicode = FALSE; char *message = NULL; YahooFederation fed = YAHOO_FEDERATION_NONE; char *fedname = NULL; if (pkt->service == YAHOO_SERVICE_LOGOFF && pkt->status == -1) { if (!purple_account_get_remember_password(account)) purple_account_set_password(account, NULL); purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NAME_IN_USE, _("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 */ case 1: /* we won't actually do anything with this */ break; case 8: /* how many online buddies we have */ break; case 7: /* the current buddy */ /* update the previous buddy before changing the variables */ if (f) { if (message) yahoo_friend_set_status_message(f, yahoo_string_decode(gc, message, unicode)); if (name) yahoo_update_status(gc, name, f); } name = message = NULL; f = NULL; if (pair->value && g_utf8_validate(pair->value, -1, NULL)) { GSList *tmplist; name = pair->value; /* Look ahead to see if we have the federation info about the buddy */ for (tmplist = l->next; tmplist; tmplist = tmplist->next) { struct yahoo_pair *p = tmplist->data; if (p->key == 7) break; if (p->key == 241) { fed = strtol(p->value, NULL, 10); g_free(fedname); switch (fed) { case YAHOO_FEDERATION_MSN: name = fedname = g_strconcat("msn/", name, NULL); break; case YAHOO_FEDERATION_OCS: name = fedname = g_strconcat("ocs/", name, NULL); break; case YAHOO_FEDERATION_IBM: name = fedname = g_strconcat("ibm/", name, NULL); break; case YAHOO_FEDERATION_NONE: default: fedname = NULL; break; } break; } } f = yahoo_friend_find_or_new(gc, name); f->fed = fed; } 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) { if(pkt->service == YAHOO_SERVICE_STATUS_15) f->idle = -1; else 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) message = pair->value; break; case 11: /* this is the buddy's session id */ if (f) f->session_id = strtol(pair->value, NULL, 10); 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) { if(pkt->service == YAHOO_SERVICE_STATUS_15) f->idle = -1; else f->idle = time(NULL); } } break; case 138: /* when value is 1, either we're not idle, or we are but won't say how long */ if (!f) break; if( (strtol(pair->value, NULL, 10) == 1) && (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) { purple_prpl_got_user_status(account, name, "offline", NULL); purple_prpl_got_user_status_deactive(account, name, YAHOO_STATUS_TYPE_MOBILE); } 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 = purple_base64_decode(pair->value, &len); if (decoded && len > 0) { tmp = purple_str_binary_to_ascii(decoded, len); purple_debug_info("yahoo", "Got key 197, value = %s\n", tmp); g_free(tmp); } g_free(decoded); } break; } case 192: /* Pictures, aka Buddy Icons, checksum */ { /* FIXME: Please, if you know this protocol, * FIXME: fix up the strtol() stuff if possible. */ int cksum = strtol(pair->value, NULL, 10); const char *locksum = NULL; PurpleBuddy *b; if (!name) break; b = purple_find_buddy(purple_connection_get_account(gc), name); if (!cksum || (cksum == -1)) { if (f) yahoo_friend_set_buddy_icon_need_request(f, TRUE); purple_buddy_icons_set_for_user(purple_connection_get_account(gc), name, NULL, 0, NULL); break; } if (!f) break; yahoo_friend_set_buddy_icon_need_request(f, FALSE); if (b) { locksum = purple_buddy_icons_get_checksum_for_user(b); if (!locksum || (cksum != strtol(locksum, NULL, 10))) yahoo_send_picture_request(gc, name); } break; } case 16: /* Custom error message */ { char *tmp = yahoo_string_decode(gc, pair->value, TRUE); purple_notify_error(gc, NULL, tmp, NULL); g_free(tmp); } break; case 97: /* Unicode status message */ unicode = !strcmp(pair->value, "1"); break; case 244: /* client version number. Yahoo Client Detection */ if(f && strtol(pair->value, NULL, 10)) f->version_id = strtol(pair->value, NULL, 10); break; case 241: /* Federated network buddy belongs to */ break; /* We process this when get '7' */ default: purple_debug_warning("yahoo", "Unknown status key %d\n", pair->key); break; } l = l->next; } if (f) { if (pkt->service == YAHOO_SERVICE_LOGOFF) f->status = YAHOO_STATUS_OFFLINE; if (message) yahoo_friend_set_status_message(f, yahoo_string_decode(gc, message, unicode)); if (name) /* update the last buddy */ yahoo_update_status(gc, name, f); } g_free(fedname); } static void yahoo_do_group_check(PurpleAccount *account, GHashTable *ht, const char *name, const char *group) { PurpleBuddy *b; PurpleGroup *g; GSList *list, *i; gboolean onlist = FALSE; char *oname = NULL; if (g_hash_table_lookup_extended(ht, name, (gpointer *)&oname, (gpointer *)&list)) g_hash_table_steal(ht, oname); else list = purple_find_buddies(account, name); for (i = list; i; i = i->next) { b = i->data; g = purple_buddy_get_group(b); if (!purple_utf8_strcasecmp(group, purple_group_get_name(g))) { purple_debug_misc("yahoo", "Oh good, %s is in the right group (%s).\n", name, group); list = g_slist_delete_link(list, i); onlist = TRUE; break; } } if (!onlist) { purple_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 = purple_find_group(group))) { g = purple_group_new(group); purple_blist_add_group(g, NULL); } b = purple_buddy_new(account, name, NULL); purple_blist_add_buddy(b, NULL, g, NULL); } if (list) { if (!oname) oname = g_strdup(name); g_hash_table_insert(ht, oname, list); } else g_free(oname); } static void yahoo_do_group_cleanup(gpointer key, gpointer value, gpointer user_data) { char *name = key; GSList *list = value, *i; PurpleBuddy *b; PurpleGroup *g; for (i = list; i; i = i->next) { b = i->data; g = purple_buddy_get_group(b); purple_debug_misc("yahoo", "Deleting Buddy %s from group %s.\n", name, purple_group_get_name(g)); purple_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(YahooData *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); } else purple_debug_info("yahoo", "Unrecognized cookie '%c'\n", c[0]); yd->cookies = g_slist_prepend(yd->cookies, g_strdup(c)); } static void yahoo_process_list_15(PurpleConnection *gc, struct yahoo_packet *pkt) { GSList *l = pkt->hash; PurpleAccount *account = purple_connection_get_account(gc); YahooData *yd = purple_connection_get_protocol_data(gc); GHashTable *ht; char *norm_bud = NULL; char *temp = 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? */ YahooFederation fed = YAHOO_FEDERATION_NONE; int stealth = 0; 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(yd->current_list15_grp); yd->current_list15_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. */ if(temp != NULL) { switch (fed) { case YAHOO_FEDERATION_MSN: norm_bud = g_strconcat("msn/", temp, NULL); break; case YAHOO_FEDERATION_OCS: norm_bud = g_strconcat("ocs/", temp, NULL); break; case YAHOO_FEDERATION_IBM: norm_bud = g_strconcat("ibm/", temp, NULL); break; case YAHOO_FEDERATION_PBX: norm_bud = g_strconcat("pbx/", temp, NULL); break; case YAHOO_FEDERATION_NONE: norm_bud = g_strdup(temp); break; } if (yd->current_list15_grp) { /* This buddy is in a group */ f = yahoo_friend_find_or_new(gc, norm_bud); if (!purple_find_buddy(account, norm_bud)) { PurpleBuddy *b; PurpleGroup *g; if (!(g = purple_find_group(yd->current_list15_grp))) { g = purple_group_new(yd->current_list15_grp); purple_blist_add_group(g, NULL); } b = purple_buddy_new(account, norm_bud, NULL); purple_blist_add_buddy(b, NULL, g, NULL); } yahoo_do_group_check(account, ht, norm_bud, yd->current_list15_grp); if(fed) { f->fed = fed; purple_debug_info("yahoo", "Setting federation to %d\n", f->fed); } if(stealth == 2) f->presence = YAHOO_PRESENCE_PERM_OFFLINE; /* set p2p status not connected and no p2p packet sent */ if(fed == YAHOO_FEDERATION_NONE) { yahoo_friend_set_p2p_status(f, YAHOO_P2PSTATUS_NOT_CONNECTED); f->p2p_packet_sent = 0; } else yahoo_friend_set_p2p_status(f, YAHOO_P2PSTATUS_DO_NOT_CONNECT); } else { /* This buddy is on the ignore list (and therefore in no group) */ purple_debug_info("yahoo", "%s adding %s to the deny list because of the ignore list / no group was found\n", purple_account_get_username(account), norm_bud); purple_privacy_deny_add(account, norm_bud, 1); } g_free(norm_bud); norm_bud=NULL; fed = YAHOO_FEDERATION_NONE; stealth = 0; g_free(temp); temp = NULL; } 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(yd->current_list15_grp); yd->current_list15_grp = yahoo_string_decode(gc, pair->value, FALSE); break; case 7: /* buddy's s/n */ g_free(temp); temp = g_strdup(purple_normalize(account, pair->value)); break; case 241: /* user on federated network */ fed = strtol(pair->value, NULL, 10); break; case 59: /* somebody told cookies come here too, but im not sure */ yahoo_process_cookie(yd, pair->value); break; case 317: /* Stealth Setting */ stealth = strtol(pair->value, NULL, 10); break; /* case 242: */ /* this seems related to 241 */ /* break; */ } } g_hash_table_foreach(ht, yahoo_do_group_cleanup, NULL); /* The reporter of ticket #9745 determined that we weren't retrieving the * aliases during buddy list retrieval, so we never updated aliases that * changed while we were signed off. */ yahoo_fetch_aliases(gc); /* Now that we have processed the buddy list, we can say yahoo has connected */ purple_connection_set_display_name(gc, purple_normalize(account, purple_account_get_username(account))); yd->logged_in = TRUE; purple_debug_info("yahoo","Authentication: Connection established\n"); purple_connection_set_state(gc, PURPLE_CONNECTED); if (yd->picture_upload_todo) { yahoo_buddy_icon_upload(gc, yd->picture_upload_todo); yd->picture_upload_todo = NULL; } yahoo_set_status(account, purple_account_get_active_status(account)); g_hash_table_destroy(ht); g_free(temp); } static void yahoo_process_list(PurpleConnection *gc, struct yahoo_packet *pkt) { GSList *l = pkt->hash; gboolean export = FALSE; gboolean got_serv_list = FALSE; YahooFriend *f = NULL; PurpleAccount *account = purple_connection_get_account(gc); YahooData *yd = purple_connection_get_protocol_data(gc); 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 89: yd->profiles = g_strsplit(pair->value, ",", -1); 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(purple_normalize(account, *bud)); f = yahoo_friend_find_or_new(gc, norm_bud); if (!purple_find_buddy(account, norm_bud)) { PurpleBuddy *b; PurpleGroup *g; if (!(g = purple_find_group(grp))) { g = purple_group_new(grp); purple_blist_add_group(g, NULL); } b = purple_buddy_new(account, norm_bud, NULL); purple_blist_add_buddy(b, NULL, g, NULL); export = TRUE; } yahoo_do_group_check(account, ht, norm_bud, grp); /* set p2p status not connected and no p2p packet sent */ yahoo_friend_set_p2p_status(f, YAHOO_P2PSTATUS_NOT_CONNECTED); f->p2p_packet_sent = 0; 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; purple_privacy_deny_add(account, *bud, 1); } g_strfreev(buddies); g_string_free(yd->tmp_serv_ilist, TRUE); yd->tmp_serv_ilist = NULL; } if (got_serv_list && ((purple_account_get_privacy_type(account) != PURPLE_PRIVACY_ALLOW_BUDDYLIST) && (purple_account_get_privacy_type(account) != PURPLE_PRIVACY_DENY_ALL) && (purple_account_get_privacy_type(account) != PURPLE_PRIVACY_ALLOW_USERS))) { purple_account_set_privacy_type(account, PURPLE_PRIVACY_DENY_USERS); purple_debug_info("yahoo", "%s privacy defaulting to PURPLE_PRIVACY_DENY_USERS.\n", purple_account_get_username(account)); } 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) { purple_debug_info("yahoo", "%s setting presence for %s to PERM_OFFLINE\n", purple_account_get_username(account), *bud); f->presence = YAHOO_PRESENCE_PERM_OFFLINE; } } g_strfreev(buddies); g_string_free(yd->tmp_serv_plist, TRUE); yd->tmp_serv_plist = NULL; } /* Now that we've got the list, request aliases */ yahoo_fetch_aliases(gc); } /* pkt_type is YAHOO_PKT_TYPE_SERVER if pkt arrives from yahoo server, YAHOO_PKT_TYPE_P2P if pkt arrives through p2p */ static void yahoo_process_notify(PurpleConnection *gc, struct yahoo_packet *pkt, yahoo_pkt_type pkt_type) { PurpleAccount *account; char *msg = NULL; char *from = NULL; char *stat = NULL; char *game = NULL; YahooFriend *f = NULL; GSList *l = pkt->hash; gint val_11 = 0; YahooData *yd = purple_connection_get_protocol_data(gc); YahooFederation fed = YAHOO_FEDERATION_NONE; account = purple_connection_get_account(gc); while (l) { struct yahoo_pair *pair = l->data; if (pair->key == 4 || pair->key == 1) from = pair->value; if (pair->key == 49) msg = pair->value; if (pair->key == 13) stat = pair->value; if (pair->key == 14) game = pair->value; if (pair->key == 11) val_11 = strtol(pair->value, NULL, 10); if (pair->key == 241) fed = strtol(pair->value, NULL, 10); l = l->next; } if (!from || !msg) return; /* disconnect the peer if connected through p2p and sends wrong value for session id */ if( (pkt_type == YAHOO_PKT_TYPE_P2P) && (val_11 != yd->session_id) ) { purple_debug_warning("yahoo","p2p: %s sent us notify with wrong session id. Disconnecting p2p connection to peer\n", from); /* remove from p2p connection lists, also calls yahoo_p2p_disconnect_destroy_data */ g_hash_table_remove(yd->peers, from); return; } if (!g_ascii_strncasecmp(msg, "TYPING", strlen("TYPING")) && (purple_privacy_check(account, from))) { char *fed_from = from; switch (fed) { case YAHOO_FEDERATION_MSN: fed_from = g_strconcat("msn/", from, NULL); break; case YAHOO_FEDERATION_OCS: fed_from = g_strconcat("ocs/", from, NULL); break; case YAHOO_FEDERATION_IBM: fed_from = g_strconcat("ibm/", from, NULL); break; case YAHOO_FEDERATION_PBX: fed_from = g_strconcat("pbx/", from, NULL); break; case YAHOO_FEDERATION_NONE: default: break; } if (stat && *stat == '1') serv_got_typing(gc, fed_from, 0, PURPLE_TYPING); else serv_got_typing_stopped(gc, fed_from); if (fed_from != from) g_free(fed_from); } else if (!g_ascii_strncasecmp(msg, "GAME", strlen("GAME"))) { PurpleBuddy *bud = purple_find_buddy(account, from); if (!bud) { purple_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 && *stat == '1') { yahoo_friend_set_game(f, game); if (bud) yahoo_update_status(gc, from, f); } } else if (!g_ascii_strncasecmp(msg, "WEBCAMINVITE", strlen("WEBCAMINVITE"))) { PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, from, account); char *buf = g_strdup_printf(_("%s has sent you a webcam invite, which is not yet supported."), from); purple_conversation_write(conv, NULL, buf, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, time(NULL)); g_free(buf); } } struct _yahoo_im { char *from; char *active_id; int time; int utf8; int buddy_icon; char *id; char *msg; YahooFederation fed; char *fed_from; }; static void yahoo_process_sms_message(PurpleConnection *gc, struct yahoo_packet *pkt) { PurpleAccount *account; GSList *l = pkt->hash; struct _yahoo_im *sms = NULL; YahooData *yd; char *server_msg = NULL; char *m; yd = purple_connection_get_protocol_data(gc); account = purple_connection_get_account(gc); while (l != NULL) { struct yahoo_pair *pair = l->data; if (pair->key == 4) { sms = g_new0(struct _yahoo_im, 1); sms->from = g_strdup_printf("+%s", pair->value); sms->time = time(NULL); sms->utf8 = TRUE; } if (pair->key == 14) { if (sms) sms->msg = pair->value; } if (pair->key == 68) if(sms) g_hash_table_insert(yd->sms_carrier, g_strdup(sms->from), g_strdup(pair->value)); if (pair->key == 16) server_msg = pair->value; l = l->next; } if(!sms) { purple_debug_info("yahoo", "Received a malformed SMS packet!\n"); return; } if( (pkt->status == -1) || (pkt->status == YAHOO_STATUS_DISCONNECTED) ) { if (server_msg) { PurpleConversation *c; c = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, sms->from, account); if (c == NULL) c = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, sms->from); purple_conversation_write(c, NULL, server_msg, PURPLE_MESSAGE_SYSTEM, time(NULL)); } else purple_notify_error(gc, NULL, _("Your SMS was not delivered"), NULL); g_free(sms->from); g_free(sms); return ; } if (!sms->from || !sms->msg) { g_free(sms); return; } m = yahoo_string_decode(gc, sms->msg, sms->utf8); serv_got_im(gc, sms->from, m, 0, sms->time); g_free(m); g_free(sms->from); g_free(sms); } /* pkt_type is YAHOO_PKT_TYPE_SERVER if pkt arrives from yahoo server, YAHOO_PKT_TYPE_P2P if pkt arrives through p2p */ static void yahoo_process_message(PurpleConnection *gc, struct yahoo_packet *pkt, yahoo_pkt_type pkt_type) { PurpleAccount *account; YahooData *yd = purple_connection_get_protocol_data(gc); GSList *l = pkt->hash; GSList *list = NULL; struct _yahoo_im *im = NULL; account = purple_connection_get_account(gc); if (pkt->status <= 1 || pkt->status == 5 || pkt->status == YAHOO_STATUS_OFFLINE) { /* messages are received with status YAHOO_STATUS_OFFLINE in case of p2p */ while (l != NULL) { struct yahoo_pair *pair = l->data; if (pair->key == 4 || pair->key == 1) { im = g_new0(struct _yahoo_im, 1); list = g_slist_append(list, im); im->from = pair->value; im->time = time(NULL); im->utf8 = TRUE; im->fed = YAHOO_FEDERATION_NONE; im->fed_from = g_strdup(im->from); } if (im && pair->key == 5) im->active_id = pair->value; 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; } if (im && pair->key == 241) { im->fed = strtol(pair->value, NULL, 10); g_free(im->fed_from); switch (im->fed) { case YAHOO_FEDERATION_MSN: im->fed_from = g_strconcat("msn/",im->from, NULL); break; case YAHOO_FEDERATION_OCS: im->fed_from = g_strconcat("ocs/",im->from, NULL); break; case YAHOO_FEDERATION_IBM: im->fed_from = g_strconcat("ibm/",im->from, NULL); break; case YAHOO_FEDERATION_PBX: im->fed_from = g_strconcat("pbx/",im->from, NULL); break; case YAHOO_FEDERATION_NONE: default: im->fed_from = g_strdup(im->from); break; } purple_debug_info("yahoo", "Message from federated (%d) buddy %s.\n", im->fed, im->fed_from); } /* peer session id */ if (im && (pair->key == 11)) { /* disconnect the peer if connected through p2p and sends wrong value for session id */ if( (im->fed == YAHOO_FEDERATION_NONE) && (pkt_type == YAHOO_PKT_TYPE_P2P) && (yd->session_id != strtol(pair->value, NULL, 10)) ) { purple_debug_warning("yahoo","p2p: %s sent us message with wrong session id. Disconnecting p2p connection to peer\n", im->fed_from); /* remove from p2p connection lists, also calls yahoo_p2p_disconnect_destroy_data */ g_hash_table_remove(yd->peers, im->fed_from); g_free(im->fed_from); g_free(im); return; /* Not sure whether we should process remaining IMs in this packet */ } } /* IMV key */ if (im && pair->key == 63) { /* Check for the Doodle IMV, no IMvironment for federated buddies */ if (im->from != NULL && im->fed == YAHOO_FEDERATION_NONE) { g_hash_table_replace(yd->imvironments, g_strdup(im->from), g_strdup(pair->value)); if (strstr(pair->value, "doodle;") != NULL) { PurpleWhiteboard *wb; if (!purple_privacy_check(account, im->from)) { purple_debug_info("yahoo", "Doodle request from %s dropped.\n", im->from); g_free(im->fed_from); g_free(im); return; } /* I'm not sure the following ever happens -DAA */ wb = purple_whiteboard_get_session(account, im->from); /* If a Doodle session doesn't exist between this user */ if(wb == NULL) { doodle_session *ds; wb = purple_whiteboard_create(account, im->from, DOODLE_STATE_REQUESTED); ds = purple_whiteboard_get_protocol_data(wb); ds->imv_key = g_strdup(pair->value); yahoo_doodle_command_send_request(gc, im->from, pair->value); yahoo_doodle_command_send_ready(gc, im->from, pair->value); } } } } if (pair->key == 429) if (im) im->id = pair->value; l = l->next; } } else if (pkt->status == 2) { purple_notify_error(gc, NULL, _("Your Yahoo! message did not get sent."), NULL); } for (l = list; l; l = l->next) { YahooFriend *f; char *m, *m2; im = l->data; if (!im->fed_from || !im->msg) { g_free(im->fed_from); g_free(im); continue; } if (!purple_privacy_check(account, im->fed_from)) { purple_debug_info("yahoo", "Message from %s dropped.\n", im->fed_from); return; } /* * TODO: Is there anything else we should check when determining whether * we should send an acknowledgement? */ if (im->id != NULL) { /* Send acknowledgement. If we don't do this then the official * Yahoo Messenger client for Windows will send us the same * message 7 seconds later as an offline message. This is true * for at least version 9.0.0.2162 on Windows XP. */ struct yahoo_packet *pkt2; pkt2 = yahoo_packet_new(YAHOO_SERVICE_MESSAGE_ACK, YAHOO_STATUS_AVAILABLE, pkt->id); yahoo_packet_hash(pkt2, "ssisii", 1, im->active_id, /* May not always be the connection's display name */ 5, im->from, 302, 430, 430, im->id, 303, 430, 450, 0); yahoo_packet_send_and_free(pkt2, yd); } 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 = purple_strreplace(m, "\r\n", "\n"); g_free(m); m = m2; purple_util_chrreplace(m, '\r', '\n'); if (!strcmp(m, "<ding>")) { PurpleConversation *conv = NULL; char *username; username = g_markup_escape_text(im->fed_from, -1); conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, username, account); purple_prpl_got_attention(gc, username, YAHOO_BUZZ); g_free(username); g_free(m); g_free(im->fed_from); g_free(im); continue; } m2 = yahoo_codes_to_html(m); g_free(m); serv_got_im(gc, im->fed_from, m2, 0, im->time); g_free(m2); /* Official clients don't share buddy images with federated buddies */ if (im->fed == YAHOO_FEDERATION_NONE) { 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->fed_from); g_free(im); } g_slist_free(list); } static void yahoo_process_sysmessage(PurpleConnection *gc, struct yahoo_packet *pkt) { GSList *l = pkt->hash; char *prim, *me = NULL, *msg = 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; prim = g_strdup_printf(_("Yahoo! system message for %s:"), me?me:purple_connection_get_display_name(gc)); purple_notify_info(NULL, NULL, prim, msg); g_free(prim); } struct yahoo_add_request { PurpleConnection *gc; char *id; char *who; YahooFederation fed; }; static void yahoo_buddy_add_authorize_cb(gpointer data) { struct yahoo_add_request *add_req = data; struct yahoo_packet *pkt; YahooData *yd = purple_connection_get_protocol_data(add_req->gc); const char *who = add_req->who; pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH_REQ_15, YAHOO_STATUS_AVAILABLE, yd->session_id); if (add_req->fed) { who += 4; yahoo_packet_hash(pkt, "ssiii", 1, add_req->id, 5, who, 241, add_req->fed, 13, 1, 334, 0); } else { yahoo_packet_hash(pkt, "ssii", 1, add_req->id, 5, who, 13, 1, 334, 0); } yahoo_packet_send_and_free(pkt, yd); g_free(add_req->id); g_free(add_req->who); g_free(add_req); } static void yahoo_buddy_add_deny_cb(struct yahoo_add_request *add_req, const char *msg) { YahooData *yd = purple_connection_get_protocol_data(add_req->gc); struct yahoo_packet *pkt; char *encoded_msg = NULL; const char *who = add_req->who; if (msg && *msg) encoded_msg = yahoo_string_encode(add_req->gc, msg, NULL); pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH_REQ_15, YAHOO_STATUS_AVAILABLE, yd->session_id); if (add_req->fed) { who += 4; /* Skip fed identifier (msn|ocs|ibm)/' */ yahoo_packet_hash(pkt, "ssiiiis", 1, add_req->id, 5, who, 241, add_req->fed, 13, 2, 334, 0, 97, 1, 14, encoded_msg ? encoded_msg : ""); } else { yahoo_packet_hash(pkt, "ssiiis", 1, add_req->id, 5, who, 13, 2, 334, 0, 97, 1, 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); } 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(gpointer data) { struct yahoo_add_request *add_req = data; purple_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), purple_connection_get_account(add_req->gc), add_req->who, NULL, add_req); } static void yahoo_buddy_denied_our_add(PurpleConnection *gc, const char *who, const char *reason) { char *notify_msg; YahooData *yd = purple_connection_get_protocol_data(gc); if (who == NULL) return; if (reason != NULL) { char *msg2 = yahoo_string_decode(gc, reason, FALSE); notify_msg = g_strdup_printf(_("%s has (retroactively) denied your request to add them to your list for the following reason: %s."), who, msg2); g_free(msg2); } else notify_msg = g_strdup_printf(_("%s has (retroactively) denied your request to add them to your list."), who); purple_notify_info(gc, NULL, _("Add buddy rejected"), notify_msg); g_free(notify_msg); g_hash_table_remove(yd->friends, who); purple_prpl_got_user_status(purple_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_buddy_auth_req_15(PurpleConnection *gc, struct yahoo_packet *pkt) { PurpleAccount *account; GSList *l = pkt->hash; const char *msg = NULL; account = purple_connection_get_account(gc); /* Buddy authorized/declined our addition */ if (pkt->status == 1) { char *temp = NULL; char *who = NULL; int response = 0; YahooFederation fed = YAHOO_FEDERATION_NONE; while (l) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 4: temp = pair->value; break; case 13: response = strtol(pair->value, NULL, 10); break; case 14: msg = pair->value; break; case 241: fed = strtol(pair->value, NULL, 10); break; } l = l->next; } switch (fed) { case YAHOO_FEDERATION_MSN: who = g_strconcat("msn/", temp, NULL); break; case YAHOO_FEDERATION_OCS: who = g_strconcat("ocs/", temp, NULL); break; case YAHOO_FEDERATION_IBM: who = g_strconcat("ibm/", temp, NULL); break; case YAHOO_FEDERATION_NONE: default: who = g_strdup(temp); break; } if (response == 1) /* Authorized */ purple_debug_info("yahoo", "Received authorization from buddy '%s'.\n", who ? who : "(Unknown Buddy)"); else if (response == 2) { /* Declined */ purple_debug_info("yahoo", "Received authorization decline from buddy '%s'.\n", who ? who : "(Unknown Buddy)"); yahoo_buddy_denied_our_add(gc, who, msg); } else purple_debug_error("yahoo", "Received unknown authorization response of %d from buddy '%s'.\n", response, who ? who : "(Unknown Buddy)"); g_free(who); } /* Buddy requested authorization to add us. */ else if (pkt->status == 3) { struct yahoo_add_request *add_req; const char *firstname = NULL, *lastname = NULL; char *temp = NULL; add_req = g_new0(struct yahoo_add_request, 1); add_req->gc = gc; add_req->fed = YAHOO_FEDERATION_NONE; while (l) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 4: temp = pair->value; break; case 5: add_req->id = g_strdup(pair->value); break; case 14: msg = pair->value; break; case 216: firstname = pair->value; break; case 241: add_req->fed = strtol(pair->value, NULL, 10); break; case 254: lastname = pair->value; break; } l = l->next; } switch (add_req->fed) { case YAHOO_FEDERATION_MSN: add_req->who = g_strconcat("msn/", temp, NULL); break; case YAHOO_FEDERATION_OCS: add_req->who = g_strconcat("ocs/", temp, NULL); break; case YAHOO_FEDERATION_IBM: add_req->who = g_strconcat("ibm/", temp, NULL); break; case YAHOO_FEDERATION_NONE: default: add_req->who = g_strdup(temp); break; } if (add_req->id && add_req->who) { char *alias = NULL, *dec_msg = NULL; if (!purple_privacy_check(account, add_req->who)) { purple_debug_misc("yahoo", "Auth. request from %s dropped and automatically denied due to privacy settings!\n", add_req->who); yahoo_buddy_add_deny_cb(add_req, NULL); return; } if (msg) dec_msg = yahoo_string_decode(gc, msg, FALSE); if (firstname && lastname) alias = g_strdup_printf("%s %s", firstname, lastname); else if (firstname) alias = g_strdup(firstname); else if (lastname) alias = g_strdup(lastname); /* DONE! this is almost exactly the same as what MSN does, * this should probably be moved to the core. */ purple_account_request_authorization(account, add_req->who, add_req->id, alias, dec_msg, purple_find_buddy(account, add_req->who) != NULL, yahoo_buddy_add_authorize_cb, yahoo_buddy_add_deny_reason_cb, add_req); g_free(alias); g_free(dec_msg); } else { g_free(add_req->id); g_free(add_req->who); g_free(add_req); } } else { purple_debug_error("yahoo", "Received authorization of unknown status (%d).\n", pkt->status); } } /* I don't think this happens anymore in Version 15 */ static void yahoo_buddy_added_us(PurpleConnection *gc, struct yahoo_packet *pkt) { PurpleAccount *account; struct yahoo_add_request *add_req; char *msg = NULL; GSList *l = pkt->hash; account = purple_connection_get_account(gc); 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 && add_req->who) { char *dec_msg = NULL; if (!purple_privacy_check(account, add_req->who)) { purple_debug_misc("yahoo", "Auth. request from %s dropped and automatically denied due to privacy settings!\n", add_req->who); yahoo_buddy_add_deny_cb(add_req, NULL); return; } if (msg) dec_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. */ purple_account_request_authorization(account, add_req->who, add_req->id, NULL, dec_msg, purple_find_buddy(account,add_req->who) != NULL, yahoo_buddy_add_authorize_cb, yahoo_buddy_add_deny_reason_cb, add_req); g_free(dec_msg); } else { g_free(add_req->id); g_free(add_req->who); g_free(add_req); } } /* I have no idea if this every gets called in version 15 */ static void yahoo_buddy_denied_our_add_old(PurpleConnection *gc, struct yahoo_packet *pkt) { char *who = NULL; char *msg = NULL; GSList *l = pkt->hash; 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; } yahoo_buddy_denied_our_add(gc, who, msg); } static void yahoo_process_contact(PurpleConnection *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_old(gc, pkt); break; default: break; } } #define OUT_CHARSET "utf-8" static char *yahoo_decode(PurpleConnection *gc, const char *text) { YahooData *yd = gc->proto_data; 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 (yd->jp) { converted = g_convert(new, n - new, OUT_CHARSET, "UTF-8", NULL, NULL, NULL); } if (!yd->jp || !converted) { 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(PurpleConnection *gc, struct yahoo_packet *pkt) { PurpleAccount *account = purple_connection_get_account(gc); YahooData *yd = purple_connection_get_protocol_data(gc); const char *who = NULL; const char *email = NULL; const char *subj = NULL; const char *yahoo_mail_url = (yd->jp? YAHOOJP_MAIL_URL: YAHOO_MAIL_URL); int count = 0; GSList *l = pkt->hash; if (!purple_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(gc, who); char *dec_subj = yahoo_decode(gc, subj); char *from = g_strdup_printf("%s (%s)", dec_who, email); purple_notify_email(gc, dec_subj, from, purple_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 *tos[2] = { purple_account_get_username(account) }; const char *urls[2] = { yahoo_mail_url }; purple_notify_emails(gc, count, FALSE, NULL, NULL, tos, urls, NULL, NULL); } } /* We use this structure once while we authenticate */ struct yahoo_auth_data { PurpleConnection *gc; char *seed; }; /* 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 is different from the one yahoo uses */ 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_auth16_stage3(PurpleConnection *gc, const char *crypt) { YahooData *yd = purple_connection_get_protocol_data(gc); PurpleAccount *account = purple_connection_get_account(gc); const char *name = purple_normalize(account, purple_account_get_username(account)); PurpleCipher *md5_cipher; PurpleCipherContext *md5_ctx; guchar md5_digest[16]; gchar base64_string[25]; struct yahoo_packet *pkt; purple_debug_info("yahoo","Authentication: In yahoo_auth16_stage3\n"); md5_cipher = purple_ciphers_find_cipher("md5"); md5_ctx = purple_cipher_context_new(md5_cipher, NULL); purple_cipher_context_append(md5_ctx, (guchar *)crypt, strlen(crypt)); purple_cipher_context_digest(md5_ctx, sizeof(md5_digest), md5_digest, NULL); to_y64(base64_string, md5_digest, 16); purple_debug_info("yahoo", "yahoo status: %d\n", yd->current_status); pkt = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, yd->current_status, yd->session_id); if(yd->cookie_b) { /* send B cookie if we have it */ yahoo_packet_hash(pkt, "ssssssssss", 1, name, 0, name, 277, yd->cookie_y, 278, yd->cookie_t, 307, base64_string, 244, yd->jp ? YAHOOJP_CLIENT_VERSION_ID : YAHOO_CLIENT_VERSION_ID, 2, name, 2, "1", 59, yd->cookie_b, 98, yd->jp ? "jp" : purple_account_get_string(account, "room_list_locale", "us"), 135, yd->jp ? YAHOOJP_CLIENT_VERSION : YAHOO_CLIENT_VERSION); } else { /* don't try to send an empty B cookie - the server will be mad */ yahoo_packet_hash(pkt, "sssssssss", 1, name, 0, name, 277, yd->cookie_y, 278, yd->cookie_t, 307, base64_string, 244, yd->jp ? YAHOOJP_CLIENT_VERSION_ID : YAHOO_CLIENT_VERSION_ID, 2, name, 2, "1", 98, yd->jp ? "jp" : purple_account_get_string(account, "room_list_locale", "us"), 135, yd->jp ? YAHOOJP_CLIENT_VERSION : YAHOO_CLIENT_VERSION); } if (yd->picture_checksum) yahoo_packet_hash_int(pkt, 192, yd->picture_checksum); yahoo_packet_send_and_free(pkt, yd); purple_cipher_context_destroy(md5_ctx); } static gchar *yahoo_auth16_get_cookie_b(gchar *headers) { gchar **splits = g_strsplit(headers, "\r\n", -1); gchar *tmp = NULL, *tmp2 = NULL, *sem = NULL; int elements = g_strv_length(splits), i; if(elements > 1) { for(i = 0; i < elements; i++) { if(g_ascii_strncasecmp(splits[i], "Set-Cookie: B=", 14) == 0) { tmp = &splits[i][14]; sem = strchr(tmp, ';'); if (sem != NULL) { tmp2 = g_strndup(tmp, sem - tmp); purple_debug_info("yahoo", "Got needed part of B cookie: %s\n", tmp2 ? tmp2 : "(null)"); break; } } } } g_strfreev(splits); return tmp2; } static void yahoo_auth16_stage2(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *ret_data, size_t len, const gchar *error_message) { struct yahoo_auth_data *auth_data = user_data; PurpleConnection *gc = auth_data->gc; YahooData *yd = purple_connection_get_protocol_data(gc); gboolean try_login_on_error = FALSE; purple_debug_info("yahoo","Authentication: In yahoo_auth16_stage2\n"); yd->url_datas = g_slist_remove(yd->url_datas, url_data); if (error_message != NULL) { purple_debug_error("yahoo", "Login Failed, unable to retrieve stage 2 url: %s\n", error_message); purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_message); g_free(auth_data->seed); g_free(auth_data); return; } else if (len > 0 && ret_data && *ret_data) { gchar **splits = g_strsplit(ret_data, "\r\n\r\n", -1), **split_data = NULL; int totalelements = 0; int response_no = -1; char *crumb = NULL; char *crypt = NULL; if(g_strv_length(splits) > 1) { yd->cookie_b = yahoo_auth16_get_cookie_b(splits[0]); split_data = g_strsplit(splits[1], "\r\n", -1); totalelements = g_strv_length(split_data); } if (totalelements >= 4) { int i; for(i = 0; i < totalelements; i++) { /* I'm not exactly a fan of the magic numbers, but it's obvious, * so no sense in wasting a bajillion vars or calls to strlen */ if(g_ascii_isdigit(split_data[i][0])) { /* if the current line and the next line both start with numbers, * the current line is the length of the body, so skip. If not, * then the current line is the response code from the login process. */ if(!g_ascii_isdigit(split_data[i + 1][0])) { response_no = strtol(split_data[i], NULL, 10); purple_debug_info("yahoo", "Got auth16 stage 2 response code: %d\n", response_no); } } else if(strncmp(split_data[i], "crumb=", 6) == 0) { crumb = g_strdup(&split_data[i][6]); if(purple_debug_is_unsafe()) purple_debug_info("yahoo", "Got crumb: %s\n", crumb); } else if(strncmp(split_data[i], "Y=", 2) == 0) { yd->cookie_y = g_strdup(&split_data[i][2]); if(purple_debug_is_unsafe()) purple_debug_info("yahoo", "Got Y cookie: %s\n", yd->cookie_y); } else if(strncmp(split_data[i], "T=", 2) == 0) { yd->cookie_t = g_strdup(&split_data[i][2]); if(purple_debug_is_unsafe()) purple_debug_info("yahoo", "Got T cookie: %s\n", yd->cookie_t); } } } g_strfreev(splits); g_strfreev(split_data); if(response_no != 0) { /* Some error in the login process */ PurpleConnectionError error; char *error_reason = NULL; switch(response_no) { case -1: /* Some error in the received stream */ error_reason = g_strdup(_("Received invalid data")); error = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; break; case 100: /* Unknown error */ error_reason = g_strdup(_("Unknown error")); error = PURPLE_CONNECTION_ERROR_OTHER_ERROR; break; default: /* if we have everything we need, why not try to login irrespective of response */ if((crumb != NULL) && (yd->cookie_y != NULL) && (yd->cookie_t != NULL)) { try_login_on_error = TRUE; break; } error_reason = g_strdup(_("Unknown error")); error = PURPLE_CONNECTION_ERROR_OTHER_ERROR; break; } if(error_reason) { purple_debug_error("yahoo", "Authentication error: %s. " "Code %d\n", error_reason, response_no); purple_connection_error(gc, error, error_reason); g_free(error_reason); g_free(auth_data->seed); g_free(auth_data); return; } } crypt = g_strconcat(crumb, auth_data->seed, NULL); yahoo_auth16_stage3(gc, crypt); g_free(crypt); g_free(crumb); } g_free(auth_data->seed); g_free(auth_data); } static void yahoo_auth16_stage1_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *ret_data, size_t len, const gchar *error_message) { struct yahoo_auth_data *auth_data = user_data; PurpleConnection *gc = auth_data->gc; YahooData *yd = purple_connection_get_protocol_data(gc); purple_debug_info("yahoo","Authentication: In yahoo_auth16_stage1_cb\n"); yd->url_datas = g_slist_remove(yd->url_datas, url_data); if (error_message != NULL) { purple_debug_error("yahoo", "Login Failed, unable to retrieve login url: %s\n", error_message); purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_message); g_free(auth_data->seed); g_free(auth_data); return; } else if (len > 0 && ret_data && *ret_data) { PurpleAccount *account = purple_connection_get_account(gc); gchar **split_data = g_strsplit(ret_data, "\r\n", -1); int totalelements = 0; int response_no = -1; char *token = NULL; totalelements = g_strv_length(split_data); if(totalelements == 1) { /* Received an error code */ response_no = strtol(split_data[0], NULL, 10); } else if(totalelements == 2 || totalelements == 3 ) { /* received valid data */ response_no = strtol(split_data[0], NULL, 10); token = g_strdup(split_data[1] + strlen("ymsgr=")); } else { /* It looks like a transparent proxy has returned a document we don't want */ response_no = -1; } g_strfreev(split_data); if(response_no != 0) { /* Some error in the login process */ PurpleConnectionError error; char *error_reason; switch(response_no) { case -1: /* Some error in the received stream */ error_reason = g_strdup(_("Received invalid data")); error = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; break; case 1212: /* Password incorrect */ /* Set password to NULL. Avoids account locking. Brings dialog to enter password if clicked on Re-enable account */ if (!purple_account_get_remember_password(account)) purple_account_set_password(account, NULL); error_reason = g_strdup(_("Incorrect password")); error = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; break; case 1213: /* security lock from too many failed login attempts */ error_reason = g_strdup(_("Account locked: Too many failed login " "attempts. Logging into the Yahoo! website may fix this.")); error = PURPLE_CONNECTION_ERROR_OTHER_ERROR; break; case 1235: /* the username does not exist */ error_reason = g_strdup(_("Username does not exist")); error = PURPLE_CONNECTION_ERROR_INVALID_USERNAME; break; case 1214: /* indicates a lock of some description */ error_reason = g_strdup(_("Account locked: Unknown reason. Logging " "into the Yahoo! website may fix this.")); error = PURPLE_CONNECTION_ERROR_OTHER_ERROR; break; case 1236: /* indicates a lock due to logging in too frequently */ error_reason = g_strdup(_("Account locked: You have been logging in too " "frequently. Wait a few minutes before trying to connect " "again. Logging into the Yahoo! website may help.")); error = PURPLE_CONNECTION_ERROR_OTHER_ERROR; break; case 100: /* username or password missing */ error_reason = g_strdup(_("Username or password missing")); error = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; break; default: /* Unknown error! */ error_reason = g_strdup_printf(_("Unknown error (%d)"), response_no); error = PURPLE_CONNECTION_ERROR_OTHER_ERROR; break; } purple_debug_error("yahoo", "Authentication error: %s. Code %d\n", error_reason, response_no); purple_connection_error(gc, error, error_reason); g_free(error_reason); g_free(auth_data->seed); g_free(auth_data); g_free(token); } else { /* OK to login, correct information provided */ PurpleUtilFetchUrlData *url_data = NULL; char *url = NULL; gboolean yahoojp = yahoo_is_japan(account); gboolean proxy_ssl = purple_account_get_bool(account, "proxy_ssl", FALSE); url = g_strdup_printf(yahoojp ? YAHOOJP_LOGIN_URL : YAHOO_LOGIN_URL, token); url_data = purple_util_fetch_url_request( proxy_ssl ? account : NULL, url, TRUE, YAHOO_CLIENT_USERAGENT, TRUE, NULL, TRUE, -1, yahoo_auth16_stage2, auth_data); if (url_data) yd->url_datas = g_slist_prepend(yd->url_datas, url_data); g_free(url); g_free(token); } } } static void yahoo_auth16_stage1(PurpleConnection *gc, const char *seed) { YahooData *yd = purple_connection_get_protocol_data(gc); PurpleAccount *account = purple_connection_get_account(gc); PurpleUtilFetchUrlData *url_data = NULL; struct yahoo_auth_data *auth_data = NULL; char *url = NULL; char *encoded_username; char *encoded_password; gboolean yahoojp = yahoo_is_japan(account); gboolean proxy_ssl = purple_account_get_bool(account, "proxy_ssl", FALSE); purple_debug_info("yahoo", "Authentication: In yahoo_auth16_stage1\n"); if(!purple_ssl_is_supported()) { purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("SSL support unavailable")); return; } auth_data = g_new0(struct yahoo_auth_data, 1); auth_data->gc = gc; auth_data->seed = g_strdup(seed); encoded_username = g_strdup(purple_url_encode(purple_account_get_username(purple_connection_get_account(gc)))); encoded_password = g_strdup(purple_url_encode(purple_connection_get_password(gc))); url = g_strdup_printf(yahoojp ? YAHOOJP_TOKEN_URL : YAHOO_TOKEN_URL, encoded_username, encoded_password, purple_url_encode(seed)); g_free(encoded_password); g_free(encoded_username); url_data = purple_util_fetch_url_request( proxy_ssl ? account : NULL, url, TRUE, YAHOO_CLIENT_USERAGENT, TRUE, NULL, FALSE, -1, yahoo_auth16_stage1_cb, auth_data); if (url_data) yd->url_datas = g_slist_prepend(yd->url_datas, url_data); g_free(url); } static void yahoo_process_auth(PurpleConnection *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: /* used to be for really old auth routine, dont support now */ case 1: case 2: /* Yahoo ver 16 authentication */ yahoo_auth16_stage1(gc, seed); break; default: { GHashTable *ui_info = purple_core_get_ui_info(); buf = g_strdup_printf(_("The Yahoo server has requested the use of an unrecognized " "authentication method. You will probably not be able " "to successfully sign on to Yahoo. Check %s for updates."), ((ui_info && g_hash_table_lookup(ui_info, "website")) ? (char *)g_hash_table_lookup(ui_info, "website") : PURPLE_WEBSITE)); purple_notify_error(gc, "", _("Failed Yahoo! Authentication"), buf); g_free(buf); yahoo_auth16_stage1(gc, seed); /* Can't hurt to try it anyway. */ break; } } } } static void ignore_buddy(PurpleBuddy *buddy) { PurpleGroup *group; PurpleAccount *account; gchar *name; if (!buddy) return; group = purple_buddy_get_group(buddy); name = g_strdup(purple_buddy_get_name(buddy)); account = purple_buddy_get_account(buddy); purple_debug_info("yahoo", "blist: Removing '%s' from buddy list.\n", name); purple_account_remove_buddy(account, buddy, group); purple_blist_remove_buddy(buddy); serv_add_deny(purple_account_get_connection(account), name); g_free(name); } static void keep_buddy(PurpleBuddy *b) { purple_privacy_deny_remove(purple_buddy_get_account(b), purple_buddy_get_name(b), 1); } static void yahoo_process_ignore(PurpleConnection *gc, struct yahoo_packet *pkt) { PurpleBuddy *b; GSList *l; gchar *who = NULL; gchar *me = NULL; gchar buf[BUF_LONG]; gboolean ignore = TRUE; 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: me = pair->value; break; case 13: /* 1 == ignore, 2 == unignore */ ignore = (strtol(pair->value, NULL, 10) == 1); break; case 66: status = strtol(pair->value, NULL, 10); break; default: break; } } /* * status * 0 - ok * 2 - already in ignore list, could not add * 3 - not in ignore list, could not delete * 12 - is a buddy, could not add (and possibly also a not-in-ignore list condition?) */ switch (status) { case 12: purple_debug_info("yahoo", "Server reported \"is a buddy\" for %s while %s", who, (ignore ? "ignoring" : "unignoring")); if (ignore) { b = purple_find_buddy(purple_connection_get_account(gc), 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); purple_request_yes_no(gc, NULL, _("Ignore buddy?"), buf, 0, purple_connection_get_account(gc), who, NULL, b, G_CALLBACK(ignore_buddy), G_CALLBACK(keep_buddy)); break; } case 2: purple_debug_info("yahoo", "Server reported that %s is already in the ignore list.\n", who); break; case 3: purple_debug_info("yahoo", "Server reported that %s is not in the ignore list; could not delete\n", who); case 0: default: break; } } static void yahoo_process_authresp(PurpleConnection *gc, struct yahoo_packet *pkt) { #ifdef TRY_WEBMESSENGER_LOGIN YahooData *yd = purple_connection_get_protocol_data(gc); #endif /* TRY_WEBMESSENGER_LOGIN */ GSList *l = pkt->hash; int err = 0; char *msg; char *url = NULL; char *fullmsg; PurpleAccount *account = purple_connection_get_account(gc); PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_OTHER_ERROR; while (l) { struct yahoo_pair *pair = l->data; if (pair->key == 66) err = strtol(pair->value, NULL, 10); else if (pair->key == 20) url = pair->value; l = l->next; } switch (err) { case 0: msg = g_strdup(_("Unknown error")); reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; break; case 3: msg = g_strdup(_("Username does not exist")); reason = PURPLE_CONNECTION_ERROR_INVALID_USERNAME; break; case 13: #ifdef TRY_WEBMESSENGER_LOGIN if (!yd->wm) { PurpleUtilFetchUrlData *url_data; yd->wm = TRUE; if (yd->fd >= 0) close(yd->fd); if (yd->inpa) { purple_input_remove(yd->inpa); yd->inpa = 0; } url_data = purple_util_fetch_url(WEBMESSENGER_URL, TRUE, "Purple/" VERSION, FALSE, -1, yahoo_login_page_cb, gc); if (url_data != NULL) yd->url_datas = g_slist_prepend(yd->url_datas, url_data); return; } #endif /* TRY_WEBMESSENGER_LOGIN */ if (!purple_account_get_remember_password(account)) purple_account_set_password(account, NULL); msg = g_strdup(_("Invalid username or password")); reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; break; case 14: msg = g_strdup(_("Your account has been locked due to too many failed login attempts." " Please try logging into the Yahoo! website.")); reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; break; case 52: /* See #9660. As much as we know, reconnecting shouldn't hurt */ purple_debug_info("yahoo", "Got error 52, Set to autoreconnect\n"); msg = g_strdup_printf(_("Unknown error 52. Reconnecting should fix this.")); reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; break; case 1013: msg = g_strdup(_("Error 1013: The username you have entered is invalid." " The most common cause of this error is entering your email" " address instead of your Yahoo! ID.")); reason = PURPLE_CONNECTION_ERROR_INVALID_USERNAME; 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); purple_connection_error(gc, reason, fullmsg); g_free(msg); g_free(fullmsg); } static void yahoo_process_addbuddy(PurpleConnection *gc, struct yahoo_packet *pkt) { int err = 0; char *who = NULL; char *temp = NULL; char *group = NULL; char *decoded_group; char *buf; YahooFriend *f; GSList *l = pkt->hash; YahooData *yd = purple_connection_get_protocol_data(gc); YahooFederation fed = YAHOO_FEDERATION_NONE; while (l) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 66: err = strtol(pair->value, NULL, 10); break; case 7: temp = pair->value; break; case 65: group = pair->value; break; case 241: fed = strtol(pair->value, NULL, 10); break; } l = l->next; } if (!temp) return; if (!group) group = ""; switch (fed) { case YAHOO_FEDERATION_MSN: who = g_strconcat("msn/", temp, NULL); break; case YAHOO_FEDERATION_OCS: who = g_strconcat("ocs/", temp, NULL); break; case YAHOO_FEDERATION_IBM: who = g_strconcat("ibm/", temp, NULL); break; case YAHOO_FEDERATION_NONE: default: who = g_strdup(temp); break; } 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); f->fed = fed; if( !g_hash_table_lookup(yd->peers, who) ) { /* we are not connected as client, so set friend to not connected */ if(fed) yahoo_friend_set_p2p_status(f, YAHOO_P2PSTATUS_DO_NOT_CONNECT); else { yahoo_friend_set_p2p_status(f, YAHOO_P2PSTATUS_NOT_CONNECTED); f->p2p_packet_sent = 0; } } else /* we are already connected. set friend to YAHOO_P2PSTATUS_WE_ARE_CLIENT */ yahoo_friend_set_p2p_status(f, YAHOO_P2PSTATUS_WE_ARE_CLIENT); g_free(who); return; } decoded_group = yahoo_string_decode(gc, group, FALSE); buf = g_strdup_printf(_("Unable to add buddy %s to group %s to the server list on account %s."), who, decoded_group, purple_connection_get_display_name(gc)); if (!purple_conv_present_error(who, purple_connection_get_account(gc), buf)) purple_notify_error(gc, NULL, _("Unable to add buddy to server list"), buf); g_free(buf); g_free(decoded_group); g_free(who); } /* write pkt to the source */ static void yahoo_p2p_write_pkt(gint source, struct yahoo_packet *pkt) { size_t pkt_len; guchar *raw_packet; /*build the raw packet and send it to the host*/ pkt_len = yahoo_packet_build(pkt, 0, 0, 0, &raw_packet); if(write(source, raw_packet, pkt_len) != pkt_len) purple_debug_warning("yahoo","p2p: couldn't write to the source\n"); g_free(raw_packet); } static void yahoo_p2p_keepalive_cb(gpointer key, gpointer value, gpointer user_data) { struct yahoo_p2p_data *p2p_data = value; PurpleConnection *gc = user_data; struct yahoo_packet *pkt_to_send; PurpleAccount *account; YahooData *yd = purple_connection_get_protocol_data(gc); account = purple_connection_get_account(gc); pkt_to_send = yahoo_packet_new(YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash(pkt_to_send, "ssisi", 4, purple_normalize(account, purple_account_get_username(account)), 5, p2p_data->host_username, 241, 0, /* Protocol identifier */ 49, "PEERTOPEER", 13, 7); yahoo_p2p_write_pkt(p2p_data->source, pkt_to_send); yahoo_packet_free(pkt_to_send); } static gboolean yahoo_p2p_keepalive(gpointer data) { PurpleConnection *gc = data; YahooData *yd = purple_connection_get_protocol_data(gc); g_hash_table_foreach(yd->peers, yahoo_p2p_keepalive_cb, gc); return TRUE; } /* destroy p2p_data associated with a peer and close p2p connection. * g_hash_table_remove() calls this function to destroy p2p_data associated with the peer, * call g_hash_table_remove() instead of this fucntion if peer has an entry in the table */ static void yahoo_p2p_disconnect_destroy_data(gpointer data) { struct yahoo_p2p_data *p2p_data; YahooFriend *f; if(!(p2p_data = data)) return ; /* If friend, set him not connected */ f = yahoo_friend_find(p2p_data->gc, p2p_data->host_username); if (f) yahoo_friend_set_p2p_status(f, YAHOO_P2PSTATUS_NOT_CONNECTED); if(p2p_data->source >= 0) close(p2p_data->source); if (p2p_data->input_event > 0) purple_input_remove(p2p_data->input_event); g_free(p2p_data->host_ip); g_free(p2p_data->host_username); g_free(p2p_data); } /* exchange of initial p2pfilexfer packets, service type YAHOO_SERVICE_P2PFILEXFER */ static void yahoo_p2p_process_p2pfilexfer(gpointer data, gint source, struct yahoo_packet *pkt) { struct yahoo_p2p_data *p2p_data; char *who = NULL; GSList *l = pkt->hash; struct yahoo_packet *pkt_to_send; PurpleAccount *account; int val_13_to_send = 0; YahooData *yd; YahooFriend *f; if(!(p2p_data = data)) return ; yd = purple_connection_get_protocol_data(p2p_data->gc); /* lets see whats in the packet */ while (l) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 4: who = pair->value; if(strncmp(who, p2p_data->host_username, strlen(p2p_data->host_username)) != 0) { /* from whom are we receiving the packets ?? */ purple_debug_warning("yahoo","p2p: received data from wrong user\n"); return; } break; case 13: p2p_data->val_13 = strtol(pair->value, NULL, 10); /* Value should be 5-7 */ break; /* case 5, 49 look laters, no use right now */ } l = l->next; } account = purple_connection_get_account(p2p_data->gc); /* key_13: sort of a counter. * WHEN WE ARE CLIENT: yahoo server sends val_13 = 0, we send to peer val_13 = 1, receive back val_13 = 5, * we send val_13=6, receive val_13=7, we send val_13=7, HALT. Keep sending val_13 = 7 as keep alive. * WHEN WE ARE SERVER: we send val_13 = 0 to yahoo server, peer sends us val_13 = 1, we send val_13 = 5, * receive val_13 = 6, send val_13 = 7, receive val_13 = 7. HALT. Keep sending val_13 = 7 as keep alive. */ switch(p2p_data->val_13) { case 1 : val_13_to_send = 5; break; case 5 : val_13_to_send = 6; break; case 6 : val_13_to_send = 7; break; case 7 : if( g_hash_table_lookup(yd->peers, p2p_data->host_username) ) return; val_13_to_send = 7; break; default: purple_debug_warning("yahoo","p2p:Unknown value for key 13\n"); return; } /* Build the yahoo packet */ pkt_to_send = yahoo_packet_new(YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash(pkt_to_send, "ssisi", 4, purple_normalize(account, purple_account_get_username(account)), 5, p2p_data->host_username, 241, 0, /* Protocol identifier */ 49, "PEERTOPEER", 13, val_13_to_send); /* build the raw packet and send it to the host */ yahoo_p2p_write_pkt(source, pkt_to_send); yahoo_packet_free(pkt_to_send); if( val_13_to_send == 7 ) if( !g_hash_table_lookup(yd->peers, p2p_data->host_username) ) { g_hash_table_insert(yd->peers, g_strdup(p2p_data->host_username), p2p_data); /* If the peer is a friend, set him connected */ f = yahoo_friend_find(p2p_data->gc, p2p_data->host_username); if (f) { if(p2p_data->connection_type == YAHOO_P2P_WE_ARE_SERVER) { p2p_data->session_id = f->session_id; yahoo_friend_set_p2p_status(f, YAHOO_P2PSTATUS_WE_ARE_SERVER); } else yahoo_friend_set_p2p_status(f, YAHOO_P2PSTATUS_WE_ARE_CLIENT); } } } /* callback function associated with receiving of data, not considering receipt of multiple YMSG packets in a single TCP packet */ static void yahoo_p2p_read_pkt_cb(gpointer data, gint source, PurpleInputCondition cond) { guchar buf[1024]; /* is it safe to assume a fixed array length of 1024 ?? */ int len; int pos = 0; int pktlen; struct yahoo_packet *pkt; guchar *start = NULL; struct yahoo_p2p_data *p2p_data; YahooData *yd; if(!(p2p_data = data)) return ; yd = purple_connection_get_protocol_data(p2p_data->gc); len = read(source, buf, sizeof(buf)); if ((len < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) return ; /* No Worries*/ else if (len <= 0) { purple_debug_warning("yahoo","p2p: Error in connection, or host disconnected\n"); /* remove from p2p connection lists, also calls yahoo_p2p_disconnect_destroy_data */ if( g_hash_table_lookup(yd->peers, p2p_data->host_username) ) g_hash_table_remove(yd->peers,p2p_data->host_username); else yahoo_p2p_disconnect_destroy_data(data); return; } if(len < YAHOO_PACKET_HDRLEN) return; if(strncmp((char *)buf, "YMSG", MIN(4, len)) != 0) { /* Not a YMSG packet */ purple_debug_warning("yahoo","p2p: Got something other than YMSG packet\n"); start = memchr(buf + 1, 'Y', len - 1); if (start == NULL) return; g_memmove(buf, start, len - (start - buf)); len -= start - buf; } pos += 4; /* YMSG */ pos += 2; pos += 2; pktlen = yahoo_get16(buf + pos); pos += 2; purple_debug_misc("yahoo", "p2p: %d bytes to read\n", len); pkt = yahoo_packet_new(0, 0, 0); pkt->service = yahoo_get16(buf + pos); pos += 2; pkt->status = yahoo_get32(buf + pos); pos += 4; pkt->id = yahoo_get32(buf + pos); pos += 4; purple_debug_misc("yahoo", "p2p: Yahoo Service: 0x%02x Status: %d\n",pkt->service, pkt->status); yahoo_packet_read(pkt, buf + pos, pktlen); /* packet processing */ switch(pkt->service) { case YAHOO_SERVICE_P2PFILEXFER: yahoo_p2p_process_p2pfilexfer(data, source, pkt); break; case YAHOO_SERVICE_MESSAGE: yahoo_process_message(p2p_data->gc, pkt, YAHOO_PKT_TYPE_P2P); break; case YAHOO_SERVICE_NOTIFY: yahoo_process_notify(p2p_data->gc, pkt, YAHOO_PKT_TYPE_P2P); break; default: purple_debug_warning("yahoo","p2p: p2p service %d Unhandled\n",pkt->service); } yahoo_packet_free(pkt); } static void yahoo_p2p_server_send_connected_cb(gpointer data, gint source, PurpleInputCondition cond) { int acceptfd; struct yahoo_p2p_data *p2p_data; YahooData *yd; if(!(p2p_data = data)) return ; yd = purple_connection_get_protocol_data(p2p_data->gc); acceptfd = accept(source, NULL, 0); if(acceptfd == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) return; else if(acceptfd == -1) { purple_debug_warning("yahoo","yahoo_p2p_server_send_connected_cb: accept: %s\n", g_strerror(errno)); yahoo_p2p_disconnect_destroy_data(data); return; } /* remove timeout */ if (yd->yahoo_p2p_server_timeout_handle) { purple_timeout_remove(yd->yahoo_p2p_server_timeout_handle); yd->yahoo_p2p_server_timeout_handle = 0; } /* remove watcher and close p2p server */ if (yd->yahoo_p2p_server_watcher) { purple_input_remove(yd->yahoo_p2p_server_watcher); yd->yahoo_p2p_server_watcher = 0; } if (yd->yahoo_local_p2p_server_fd >= 0) { close(yd->yahoo_local_p2p_server_fd); yd->yahoo_local_p2p_server_fd = -1; } /* Add an Input Read event to the file descriptor */ p2p_data->input_event = purple_input_add(acceptfd, PURPLE_INPUT_READ, yahoo_p2p_read_pkt_cb, data); p2p_data->source = acceptfd; } static gboolean yahoo_cancel_p2p_server_listen_cb(gpointer data) { struct yahoo_p2p_data *p2p_data; YahooData *yd; if(!(p2p_data = data)) return FALSE; yd = purple_connection_get_protocol_data(p2p_data->gc); purple_debug_warning("yahoo","yahoo p2p server timeout, peer failed to connect\n"); yahoo_p2p_disconnect_destroy_data(data); purple_input_remove(yd->yahoo_p2p_server_watcher); yd->yahoo_p2p_server_watcher = 0; close(yd->yahoo_local_p2p_server_fd); yd->yahoo_local_p2p_server_fd = -1; yd->yahoo_p2p_server_timeout_handle = 0; return FALSE; } static void yahoo_p2p_server_listen_cb(int listenfd, gpointer data) { struct yahoo_p2p_data *p2p_data; YahooData *yd; if(!(p2p_data = data)) return ; yd = purple_connection_get_protocol_data(p2p_data->gc); yd->listen_data = NULL; if(listenfd == -1) { purple_debug_warning("yahoo","p2p: error starting p2p server\n"); yahoo_p2p_disconnect_destroy_data(data); return; } /* Add an Input Read event to the file descriptor */ yd->yahoo_local_p2p_server_fd = listenfd; yd->yahoo_p2p_server_watcher = purple_input_add(listenfd, PURPLE_INPUT_READ, yahoo_p2p_server_send_connected_cb,data); /* add timeout */ yd->yahoo_p2p_server_timeout_handle = purple_timeout_add_seconds(YAHOO_P2P_SERVER_TIMEOUT, yahoo_cancel_p2p_server_listen_cb, data); } /* send p2p pkt containing our encoded ip, asking peer to connect to us */ void yahoo_send_p2p_pkt(PurpleConnection *gc, const char *who, int val_13) { const char *public_ip; guint32 temp[4]; guint32 ip; char temp_str[100]; gchar *base64_ip = NULL; YahooFriend *f; struct yahoo_packet *pkt; PurpleAccount *account; YahooData *yd = purple_connection_get_protocol_data(gc); struct yahoo_p2p_data *p2p_data; const char *norm_username; f = yahoo_friend_find(gc, who); account = purple_connection_get_account(gc); /* Do not send invitation if already listening for other connection */ if(yd->yahoo_local_p2p_server_fd >= 0) return; /* One shouldn't try to connect to self */ if( strcmp(purple_normalize(account, purple_account_get_username(account)), who) == 0) return; /* send packet to only those friends who arent p2p connected and to whom we havent already sent. Do not send if this condition doesn't hold good */ if( !( f && (yahoo_friend_get_p2p_status(f) == YAHOO_P2PSTATUS_NOT_CONNECTED) && (f->p2p_packet_sent == 0)) ) return; /* Dont send p2p packet to buddies of other protocols */ if(f->fed) return; /* Finally, don't try to connect to buddies not online or on sms */ if( (f->status == YAHOO_STATUS_OFFLINE) || f->sms ) return; public_ip = purple_network_get_public_ip(); if( (sscanf(public_ip, "%u.%u.%u.%u", &temp[0], &temp[1], &temp[2], &temp[3])) !=4 ) return ; ip = (temp[3] << 24) | (temp[2] <<16) | (temp[1] << 8) | temp[0]; sprintf(temp_str, "%d", ip); base64_ip = purple_base64_encode( (guchar *)temp_str, strlen(temp_str) ); norm_username = purple_normalize(account, purple_account_get_username(account)); pkt = yahoo_packet_new(YAHOO_SERVICE_PEERTOPEER, YAHOO_STATUS_AVAILABLE, 0); yahoo_packet_hash(pkt, "sssissis", 1, norm_username, 4, norm_username, 12, base64_ip, /* base64 encode ip */ 61, 0, /* To-do : figure out what is 61 for?? */ 2, "", 5, who, 13, val_13, 49, "PEERTOPEER"); yahoo_packet_send_and_free(pkt, yd); f->p2p_packet_sent = 1; /* set p2p_packet_sent to sent */ p2p_data = g_new0(struct yahoo_p2p_data, 1); p2p_data->gc = gc; p2p_data->host_ip = NULL; p2p_data->host_username = g_strdup(who); p2p_data->val_13 = val_13; p2p_data->connection_type = YAHOO_P2P_WE_ARE_SERVER; p2p_data->source = -1; /* FIXME: If the port is already used, purple_network_listener returns NULL and old listener won't be canceled * in yahoo_close function. */ if (yd->listen_data) purple_debug_warning("yahoo","p2p: Failed to create p2p server - server already exists\n"); else { yd->listen_data = purple_network_listen(YAHOO_PAGER_PORT_P2P, AF_UNSPEC, SOCK_STREAM, TRUE, yahoo_p2p_server_listen_cb, p2p_data); if (yd->listen_data == NULL) purple_debug_warning("yahoo","p2p: Failed to created p2p server\n"); } g_free(base64_ip); } /* function called when connection to p2p host is setup */ static void yahoo_p2p_init_cb(gpointer data, gint source, const gchar *error_message) { struct yahoo_p2p_data *p2p_data; struct yahoo_packet *pkt_to_send; PurpleAccount *account; YahooData *yd; p2p_data = data; yd = purple_connection_get_protocol_data(p2p_data->gc); if(error_message != NULL) { purple_debug_warning("yahoo","p2p: %s\n",error_message); yahoo_send_p2p_pkt(p2p_data->gc, p2p_data->host_username, 2);/* send p2p init packet with val_13=2 */ yahoo_p2p_disconnect_destroy_data(p2p_data); return; } /* Add an Input Read event to the file descriptor */ p2p_data->input_event = purple_input_add(source, PURPLE_INPUT_READ, yahoo_p2p_read_pkt_cb, data); p2p_data->source = source; account = purple_connection_get_account(p2p_data->gc); /* Build the yahoo packet */ pkt_to_send = yahoo_packet_new(YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash(pkt_to_send, "ssisi", 4, purple_normalize(account, purple_account_get_username(account)), 5, p2p_data->host_username, 241, 0, /* Protocol identifier */ 49, "PEERTOPEER", 13, 1); /* we receive key13= 0 or 2, we send key13=1 */ yahoo_p2p_write_pkt(source, pkt_to_send); /* build raw packet and send */ yahoo_packet_free(pkt_to_send); } static void yahoo_process_p2p(PurpleConnection *gc, struct yahoo_packet *pkt) { GSList *l = pkt->hash; char *who = NULL; char *base64 = NULL; guchar *decoded; gsize len; gint val_13 = 0; gint val_11 = 0; PurpleAccount *account; YahooFriend *f; /* if status is not YAHOO_STATUS_BRB or YAHOO_STATUS_P2P, the packet bounced back, * so it contains our own ip */ if(pkt->status != YAHOO_STATUS_BRB && pkt->status != YAHOO_STATUS_P2P) return ; 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; case 13: val_13 = strtol(pair->value, NULL, 10); break; case 11: val_11 = strtol(pair->value, NULL, 10); /* session id of peer */ if( (f = yahoo_friend_find(gc, who)) ) f->session_id = val_11; break; /* TODO: figure these out yahoo: Key: 61 Value: 0 yahoo: Key: 2 Value: yahoo: Key: 13 Value: 0 packet count ?? yahoo: Key: 49 Value: PEERTOPEER yahoo: Key: 140 Value: 1 */ } l = l->next; } if (base64) { guint32 ip; YahooFriend *f; char *host_ip, *tmp; struct yahoo_p2p_data *p2p_data; decoded = purple_base64_decode(base64, &len); if (decoded == NULL) { purple_debug_info("yahoo","p2p: Unable to decode base64 IP (%s) \n", base64); return; } tmp = purple_str_binary_to_ascii(decoded, len); purple_debug_info("yahoo", "Got P2P service packet (from server): who = %s, ip = %s\n", who, tmp); g_free(tmp); ip = strtol((gchar *)decoded, NULL, 10); g_free(decoded); host_ip = 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, host_ip); purple_debug_info("yahoo", "IP : %s\n", host_ip); account = purple_connection_get_account(gc); if(val_11==0) { if(!f) return; else val_11 = f->session_id; } p2p_data = g_new0(struct yahoo_p2p_data, 1); p2p_data->host_username = g_strdup(who); p2p_data->val_13 = val_13; p2p_data->session_id = val_11; p2p_data->host_ip = host_ip; p2p_data->gc = gc; p2p_data->connection_type = YAHOO_P2P_WE_ARE_CLIENT; p2p_data->source = -1; /* connect to host */ if((purple_proxy_connect(gc, account, host_ip, YAHOO_PAGER_PORT_P2P, yahoo_p2p_init_cb, p2p_data))==NULL) { purple_debug_info("yahoo","p2p: Connection to %s failed\n", host_ip); g_free(p2p_data->host_ip); g_free(p2p_data->host_username); g_free(p2p_data); } } } static void yahoo_process_audible(PurpleConnection *gc, struct yahoo_packet *pkt) { PurpleAccount *account; char *who = NULL, *msg = NULL, *id = NULL; GSList *l = pkt->hash; account = purple_connection_get_account(gc); 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)) { purple_debug_misc("yahoo", "Warning, nonutf8 audible, ignoring!\n"); return; } if (!purple_privacy_check(account, who)) { purple_debug_misc("yahoo", "Audible message from %s for %s dropped!\n", purple_account_get_username(account), 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(PurpleConnection *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, YAHOO_PKT_TYPE_SERVER); break; case YAHOO_SERVICE_MESSAGE: case YAHOO_SERVICE_GAMEMSG: case YAHOO_SERVICE_CHATMSG: yahoo_process_message(gc, pkt, YAHOO_PKT_TYPE_SERVER); 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_AUTH_REQ_15: yahoo_buddy_auth_req_15(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_p2p(gc, pkt); /* P2PFILEXFER handled the same way as process_p2p */ yahoo_process_p2pfilexfer(gc, pkt); /* redundant ??, need to have a break now */ 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_CHECKSUM: yahoo_process_picture_checksum(gc, pkt); break; case YAHOO_SERVICE_PICTURE_UPLOAD: yahoo_process_picture_upload(gc, pkt); break; case YAHOO_SERVICE_PICTURE_UPDATE: case YAHOO_SERVICE_AVATAR_UPDATE: yahoo_process_avatar_update(gc, pkt); break; case YAHOO_SERVICE_AUDIBLE: yahoo_process_audible(gc, pkt); break; case YAHOO_SERVICE_CONTACT_DETAILS: yahoo_process_contact_details(gc, pkt); break; case YAHOO_SERVICE_FILETRANS_15: yahoo_process_filetrans_15(gc, pkt); break; case YAHOO_SERVICE_FILETRANS_INFO_15: yahoo_process_filetrans_info_15(gc, pkt); break; case YAHOO_SERVICE_FILETRANS_ACC_15: yahoo_process_filetrans_acc_15(gc, pkt); break; case YAHOO_SERVICE_SMS_MSG: yahoo_process_sms_message(gc, pkt); break; default: purple_debug_error("yahoo", "Unhandled service 0x%02x\n", pkt->service); break; } } static void yahoo_pending(gpointer data, gint source, PurpleInputCondition cond) { PurpleConnection *gc = data; YahooData *yd = purple_connection_get_protocol_data(gc); 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: %s"), g_strerror(errno)); purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); return; } else if (len == 0) { purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Server closed the connection")); return; } purple_connection_update_last_received(gc); 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; purple_debug_warning("yahoo", "Error in YMSG stream, got something not a YMSG packet!\n"); start = memchr(yd->rxqueue + 1, 'Y', yd->rxlen - 1); if (start) { g_memmove(yd->rxqueue, start, yd->rxlen - (start - yd->rxqueue)); yd->rxlen -= start - yd->rxqueue; continue; } else { g_free(yd->rxqueue); yd->rxqueue = NULL; yd->rxlen = 0; return; } } pos += 4; /* YMSG */ pos += 2; pos += 2; pktlen = yahoo_get16(yd->rxqueue + pos); pos += 2; purple_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; purple_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) { PurpleConnection *gc = data; YahooData *yd; struct yahoo_packet *pkt; if (source < 0) { gchar *tmp; tmp = g_strdup_printf(_("Unable to connect: %s"), error_message); purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); return; } yd = purple_connection_get_protocol_data(gc); yd->fd = source; pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, yd->current_status, yd->session_id); yahoo_packet_hash_str(pkt, 1, purple_normalize(purple_connection_get_account(gc), purple_account_get_username(purple_connection_get_account(gc)))); yahoo_packet_send_and_free(pkt, yd); yd->inpa = purple_input_add(yd->fd, PURPLE_INPUT_READ, yahoo_pending, gc); } #ifdef TRY_WEBMESSENGER_LOGIN static void yahoo_got_web_connected(gpointer data, gint source, const gchar *error_message) { PurpleConnection *gc = data; YahooData *yd; struct yahoo_packet *pkt; if (source < 0) { gchar *tmp; tmp = g_strdup_printf(_("Unable to connect: %s"), error_message); purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); return; } yd = purple_connection_get_protocol_data(gc); yd->fd = source; pkt = yahoo_packet_new(YAHOO_SERVICE_WEBLOGIN, YAHOO_STATUS_WEBLOGIN, yd->session_id); yahoo_packet_hash(pkt, "sss", 0, purple_normalize(purple_connection_get_account(gc), purple_account_get_username(purple_connection_get_account(gc))), 1, purple_normalize(purple_connection_get_account(gc), purple_account_get_username(purple_connection_get_account(gc))), 6, yd->auth); yahoo_packet_send_and_free(pkt, yd); g_free(yd->auth); yd->inpa = purple_input_add(yd->fd, PURPLE_INPUT_READ, yahoo_pending, gc); } static void yahoo_web_pending(gpointer data, gint source, PurpleInputCondition cond) { PurpleConnection *gc = data; PurpleAccount *account = purple_connection_get_account(gc); YahooData *yd = purple_connection_get_protocol_data(gc); 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: %s"), g_strerror(errno)); purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); return; } else if (len == 0) { purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("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")))) { purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Received unexpected HTTP response from server")); purple_debug_misc("yahoo", "Unexpected HTTP response: %s\n", buf); 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, "; "); /* Should these cookies be included too when trying for xfer? * It seems to work without these */ } yd->auth = g_string_free(s, FALSE); purple_input_remove(yd->inpa); yd->inpa = 0; 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 (purple_proxy_connect(gc, account, "wcs2.msg.dcn.yahoo.com", purple_account_get_int(account, "port", YAHOO_PAGER_PORT), yahoo_got_web_connected, gc) == NULL) { purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect")); return; } } static void yahoo_got_cookies_send_cb(gpointer data, gint source, PurpleInputCondition cond) { PurpleConnection *gc = data; YahooData *yd = purple_connection_get_protocol_data(gc); int written, remaining; 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) { gchar *tmp; g_free(yd->auth); yd->auth = NULL; if (yd->inpa) { purple_input_remove(yd->inpa); yd->inpa = 0; } tmp = g_strdup_printf(_("Lost connection with %s: %s"), "login.yahoo.com:80", g_strerror(errno)); purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); return; } if (written < remaining) { yd->auth_written += written; return; } g_free(yd->auth); yd->auth = NULL; yd->auth_written = 0; purple_input_remove(yd->inpa); yd->inpa = purple_input_add(source, PURPLE_INPUT_READ, yahoo_web_pending, gc); } static void yahoo_got_cookies(gpointer data, gint source, const gchar *error_message) { PurpleConnection *gc = data; YahooData *yd = purple_connection_get_protocol_data(gc); if (source < 0) { gchar *tmp; tmp = g_strdup_printf(_("Unable to establish a connection with %s: %s"), "login.yahoo.com:80", error_message); purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); return; } if (yd->inpa == 0) { yd->inpa = purple_input_add(source, PURPLE_INPUT_WRITE, yahoo_got_cookies_send_cb, gc); yahoo_got_cookies_send_cb(gc, source, PURPLE_INPUT_WRITE); } } static void yahoo_login_page_hash_iter(const char *key, const char *val, GString *url) { if (!strcmp(key, "passwd") || !strcmp(key, "login")) 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, purple_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(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message) { PurpleConnection *gc = (PurpleConnection *)user_data; PurpleAccount *account = purple_connection_get_account(gc); YahooData *yd = purple_connection_get_protocol_data(gc); const char *sn = purple_account_get_username(account); const char *pass = purple_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; PurpleCipher *cipher; PurpleCipherContext *context; guchar digest[16]; yd->url_datas = g_slist_remove(yd->url_datas, url_data); if (error_message != NULL) { purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_message); return; } url = g_string_append(url, sn); url = g_string_append(url, "&passwd="); cipher = purple_ciphers_find_cipher("md5"); context = purple_cipher_context_new(cipher, NULL); purple_cipher_context_append(context, (const guchar *)pass, strlen(pass)); purple_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); purple_cipher_context_reset(context, NULL); purple_cipher_context_append(context, (const guchar *)chal, strlen(chal)); purple_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.. purple_cipher_context_reset(context, NULL); purple_cipher_context_append(context, md5, strlen(md5)); purple_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 (purple_proxy_connect(gc, account, "login.yahoo.com", 80, yahoo_got_cookies, gc) == NULL) { purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect")); return; } purple_cipher_context_destroy(context); } #endif /* TRY_WEBMESSENGER_LOGIN */ static void yahoo_picture_check(PurpleAccount *account) { PurpleConnection *gc = purple_account_get_connection(account); PurpleStoredImage *img = purple_buddy_icons_find_account_icon(account); yahoo_set_buddy_icon(gc, img); purple_imgstore_unref(img); } static int get_yahoo_status_from_purple_status(PurpleStatus *status) { PurplePresence *presence; const char *status_id; const char *msg; presence = purple_status_get_presence(status); status_id = purple_status_get_id(status); msg = purple_status_get_attr_string(status, "message"); if ((msg != NULL) && (*msg != '\0')) { return YAHOO_STATUS_CUSTOM; } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_AVAILABLE)) { 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 (purple_presence_is_idle(presence)) { return YAHOO_STATUS_IDLE; } else { purple_debug_error("yahoo", "Unexpected PurpleStatus!\n"); return YAHOO_STATUS_AVAILABLE; } } static void yahoo_got_pager_server(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) { YahooData *yd = user_data; PurpleConnection *gc = yd->gc; PurpleAccount *a = purple_connection_get_account(gc); gchar **strings = NULL, *cs_server = NULL; int port = purple_account_get_int(a, "port", YAHOO_PAGER_PORT); int stringslen = 0; yd->url_datas = g_slist_remove(yd->url_datas, url_data); if(error_message != NULL || len == 0) { purple_debug_error("yahoo", "Unable to retrieve server info. %" G_GSIZE_FORMAT " bytes retrieved with error message: %s\n", len, error_message ? error_message : "(null)"); if(yahoo_is_japan(a)) { /* We don't know fallback hosts for Yahoo Japan :( */ purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect: The server returned an empty response.")); } else { if(purple_proxy_connect(gc, a, YAHOO_PAGER_HOST_FALLBACK, port, yahoo_got_connected, gc) == NULL) { purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect")); } } } else { strings = g_strsplit(url_text, "\r\n", -1); if((stringslen = g_strv_length(strings)) > 1) { int i; for(i = 0; i < stringslen; i++) { if(g_ascii_strncasecmp(strings[i], "COLO_CAPACITY=", 14) == 0) { purple_debug_info("yahoo", "Got COLO Capacity: %s\n", &(strings[i][14])); } else if(g_ascii_strncasecmp(strings[i], "CS_IP_ADDRESS=", 14) == 0) { cs_server = g_strdup(&strings[i][14]); purple_debug_info("yahoo", "Got CS IP address: %s\n", cs_server); } } } if(cs_server) { /* got an address; get on with connecting */ if(purple_proxy_connect(gc, a, cs_server, port, yahoo_got_connected, gc) == NULL) purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect")); } else { purple_debug_error("yahoo", "No CS address retrieved! Server " "response:\n%s\n", url_text ? url_text : "(null)"); if(yahoo_is_japan(a)) { /* We don't know fallback hosts for Yahoo Japan :( */ purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect: The server's response did not contain " "the necessary information")); } else if(purple_proxy_connect(gc, a, YAHOO_PAGER_HOST_FALLBACK, port, yahoo_got_connected, gc) == NULL) { purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect")); } } } g_strfreev(strings); g_free(cs_server); } void yahoo_login(PurpleAccount *account) { PurpleConnection *gc = purple_account_get_connection(account); YahooData *yd = g_new0(YahooData, 1); PurpleStatus *status = purple_account_get_active_status(account); gboolean use_whole_url = yahoo_account_use_http_proxy(gc); gboolean proxy_ssl = purple_account_get_bool(account, "proxy_ssl", FALSE); PurpleUtilFetchUrlData *url_data; purple_connection_set_protocol_data(gc, yd); purple_connection_set_flags(gc, PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_NO_BGCOLOR | PURPLE_CONNECTION_NO_URLDESC); purple_connection_update_progress(gc, _("Connecting"), 1, 2); purple_connection_set_display_name(gc, purple_account_get_username(account)); yd->gc = gc; yd->jp = yahoo_is_japan(account); yd->yahoo_local_p2p_server_fd = -1; yd->fd = -1; yd->txhandler = 0; /* TODO: Is there a good grow size for the buffer? */ yd->txbuf = purple_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->xfer_peer_idstring_map = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); yd->peers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, yahoo_p2p_disconnect_destroy_data); yd->sms_carrier = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); yd->yahoo_p2p_timer = purple_timeout_add_seconds(YAHOO_P2P_KEEPALIVE_SECS, yahoo_p2p_keepalive, gc); yd->confs = NULL; yd->conf_id = 2; yd->last_keepalive = yd->last_ping = time(NULL); yd->current_status = get_yahoo_status_from_purple_status(status); yahoo_picture_check(account); /* Get the pager server. Actually start connecting in the callback since we * must have the contents of the HTTP response to proceed. */ url_data = purple_util_fetch_url_request( proxy_ssl ? purple_connection_get_account(gc) : NULL, yd->jp ? YAHOOJP_PAGER_HOST_REQ_URL : YAHOO_PAGER_HOST_REQ_URL, use_whole_url ? TRUE : FALSE, YAHOO_CLIENT_USERAGENT, FALSE, NULL, FALSE, -1, yahoo_got_pager_server, yd); if (url_data) yd->url_datas = g_slist_prepend(yd->url_datas, url_data); return; } void yahoo_close(PurpleConnection *gc) { YahooData *yd = purple_connection_get_protocol_data(gc); GSList *l; if (yd->inpa) { purple_input_remove(yd->inpa); yd->inpa = 0; } while (yd->url_datas) { purple_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) { PurpleConversation *conv = l->data; yahoo_conf_leave(yd, purple_conversation_get_name(conv), purple_connection_get_display_name(gc), purple_conv_chat_get_users(PURPLE_CONV_CHAT(conv))); } g_slist_free(yd->confs); for (l = yd->cookies; l; l = l->next) { g_free(l->data); l->data=NULL; } g_slist_free(yd->cookies); yd->chat_online = FALSE; if (yd->in_chat) yahoo_c_leave(gc, 1); /* 1 = YAHOO_CHAT_ID */ purple_timeout_remove(yd->yahoo_p2p_timer); if(yd->yahoo_p2p_server_timeout_handle != 0) { purple_timeout_remove(yd->yahoo_p2p_server_timeout_handle); yd->yahoo_p2p_server_timeout_handle = 0; } /* close p2p server if it is waiting for a peer to connect */ if (yd->yahoo_p2p_server_watcher) { purple_input_remove(yd->yahoo_p2p_server_watcher); yd->yahoo_p2p_server_watcher = 0; } if (yd->yahoo_local_p2p_server_fd >= 0) { close(yd->yahoo_local_p2p_server_fd); yd->yahoo_local_p2p_server_fd = -1; } g_hash_table_destroy(yd->sms_carrier); g_hash_table_destroy(yd->peers); g_hash_table_destroy(yd->friends); g_hash_table_destroy(yd->imvironments); g_hash_table_destroy(yd->xfer_peer_idstring_map); g_free(yd->chat_name); g_free(yd->cookie_y); g_free(yd->cookie_t); g_free(yd->cookie_b); if (yd->txhandler) purple_input_remove(yd->txhandler); purple_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) purple_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); if (yd->listen_data != NULL) purple_network_listen_cancel(yd->listen_data); g_free(yd->pending_chat_room); g_free(yd->pending_chat_id); g_free(yd->pending_chat_topic); g_free(yd->pending_chat_goto); g_strfreev(yd->profiles); yahoo_personal_details_reset(&yd->ypd, TRUE); g_free(yd->current_list15_grp); g_free(yd); purple_connection_set_protocol_data(gc, NULL); } const char *yahoo_list_icon(PurpleAccount *a, PurpleBuddy *b) { return "yahoo"; } const char *yahoo_list_emblem(PurpleBuddy *b) { PurpleAccount *account; PurpleConnection *gc; YahooFriend *f; PurplePresence *presence; if (!b || !(account = purple_buddy_get_account(b)) || !(gc = purple_account_get_connection(account)) || !purple_connection_get_protocol_data(gc)) return NULL; f = yahoo_friend_find(gc, purple_buddy_get_name(b)); if (!f) { return "not-authorized"; } presence = purple_buddy_get_presence(b); if (purple_presence_is_online(presence)) { if (yahoo_friend_get_game(f)) return "game"; if (f->fed) return "external"; } return NULL; } 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(PurpleBlistNode *node, gpointer data) { PurpleBuddy *buddy; PurpleConnection *gc; GHashTable *components; YahooData *yd; int id; g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); buddy = (PurpleBuddy *) node; gc = purple_account_get_connection(purple_buddy_get_account(buddy)); yd = purple_connection_get_protocol_data(gc); 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", purple_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...", purple_buddy_get_name(buddy)); } static void yahoo_presence_settings(PurpleBlistNode *node, gpointer data) { PurpleBuddy *buddy; PurpleConnection *gc; int presence_val = GPOINTER_TO_INT(data); buddy = (PurpleBuddy *) node; gc = purple_account_get_connection(purple_buddy_get_account(buddy)); yahoo_friend_update_presence(gc, purple_buddy_get_name(buddy), presence_val); } static void yahoo_game(PurpleBlistNode *node, gpointer data) { PurpleBuddy *buddy; PurpleConnection *gc; const char *game; char *game2; char *t; char url[256]; YahooFriend *f; g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); buddy = (PurpleBuddy *) node; gc = purple_account_get_connection(purple_buddy_get_account(buddy)); f = yahoo_friend_find(gc, purple_buddy_get_name(buddy)); 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); purple_notify_uri(gc, url); g_free(game2); } char *yahoo_status_text(PurpleBuddy *b) { YahooFriend *f = NULL; const char *msg; char *msg2; PurpleAccount *account; PurpleConnection *gc; account = purple_buddy_get_account(b); gc = purple_account_get_connection(account); if (!gc || !purple_connection_get_protocol_data(gc)) return NULL; f = yahoo_friend_find(gc, purple_buddy_get_name(b)); 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)); purple_util_chrreplace(msg2, '\n', ' '); return msg2; default: return g_strdup(yahoo_get_status_string(f->status)); } } void yahoo_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full) { YahooFriend *f; char *status = NULL; const char *presence = NULL; PurpleAccount *account; account = purple_buddy_get_account(b); f = yahoo_friend_find(purple_account_get_connection(account), purple_buddy_get_name(b)); 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: purple_debug_error("yahoo", "Unknown presence in yahoo_tooltip_text\n"); break; } } if (status != NULL) { purple_notify_user_info_add_pair_plaintext(user_info, _("Status"), status); g_free(status); } if (presence != NULL) purple_notify_user_info_add_pair_plaintext(user_info, _("Presence"), presence); if (f && full) { YahooPersonalDetails *ypd = &f->ypd; if (ypd->phone.home && *ypd->phone.home) purple_notify_user_info_add_pair_plaintext(user_info, _("Home Phone Number"), ypd->phone.home); if (ypd->phone.work && *ypd->phone.work) purple_notify_user_info_add_pair_plaintext(user_info, _("Work Phone Number"), ypd->phone.work); if (ypd->phone.mobile && *ypd->phone.mobile) purple_notify_user_info_add_pair_plaintext(user_info, _("Mobile Phone Number"), ypd->phone.mobile); } } static void yahoo_addbuddyfrommenu_cb(PurpleBlistNode *node, gpointer data) { PurpleBuddy *buddy; PurpleConnection *gc; g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); buddy = (PurpleBuddy *) node; gc = purple_account_get_connection(purple_buddy_get_account(buddy)); yahoo_add_buddy(gc, buddy, NULL, NULL); } static void yahoo_chat_goto_menu(PurpleBlistNode *node, gpointer data) { PurpleBuddy *buddy; PurpleConnection *gc; g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); buddy = (PurpleBuddy *) node; gc = purple_account_get_connection(purple_buddy_get_account(buddy)); yahoo_chat_goto(gc, purple_buddy_get_name(buddy)); } static GList *build_presence_submenu(YahooFriend *f, PurpleConnection *gc) { GList *m = NULL; PurpleMenuAction *act; YahooData *yd = purple_connection_get_protocol_data(gc); if (yd->current_status == YAHOO_STATUS_INVISIBLE) { if (f->presence != YAHOO_PRESENCE_ONLINE) { act = purple_menu_action_new(_("Appear Online"), PURPLE_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 = purple_menu_action_new(_("Appear Offline"), PURPLE_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 = purple_menu_action_new(_("Don't Appear Permanently Offline"), PURPLE_CALLBACK(yahoo_presence_settings), GINT_TO_POINTER(YAHOO_PRESENCE_DEFAULT), NULL); m = g_list_append(m, act); } else { act = purple_menu_action_new(_("Appear Permanently Offline"), PURPLE_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(PurpleBlistNode *node, gpointer data) { PurpleBuddy *b = (PurpleBuddy *)node; PurpleAccount *account = purple_buddy_get_account(b); PurpleConnection *gc = purple_account_get_connection(account); yahoo_doodle_initiate(gc, purple_buddy_get_name(b)); } static void yahoo_userinfo_blist_node(PurpleBlistNode *node, gpointer data) { PurpleBuddy *b = (PurpleBuddy *)node; PurpleAccount *account = purple_buddy_get_account(b); PurpleConnection *gc = purple_account_get_connection(account); yahoo_set_userinfo_for_buddy(gc, b); } static GList *yahoo_buddy_menu(PurpleBuddy *buddy) { GList *m = NULL; PurpleMenuAction *act; PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy)); YahooData *yd = purple_connection_get_protocol_data(gc); static char buf2[1024]; YahooFriend *f; f = yahoo_friend_find(gc, purple_buddy_get_name(buddy)); if (!f && !yd->wm) { act = purple_menu_action_new(_("Add Buddy"), PURPLE_CALLBACK(yahoo_addbuddyfrommenu_cb), NULL, NULL); m = g_list_append(m, act); return m; } if (f && f->status != YAHOO_STATUS_OFFLINE && f->fed == YAHOO_FEDERATION_NONE) { if (!yd->wm) { act = purple_menu_action_new(_("Join in Chat"), PURPLE_CALLBACK(yahoo_chat_goto_menu), NULL, NULL); m = g_list_append(m, act); } act = purple_menu_action_new(_("Initiate Conference"), PURPLE_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 = purple_menu_action_new(buf2, PURPLE_CALLBACK(yahoo_game), NULL, NULL); m = g_list_append(m, act); } } } if (f) { act = purple_menu_action_new(_("Presence Settings"), NULL, NULL, build_presence_submenu(f, gc)); m = g_list_append(m, act); if (f->fed == YAHOO_FEDERATION_NONE) { act = purple_menu_action_new(_("Start Doodling"), PURPLE_CALLBACK(yahoo_doodle_blist_node), NULL, NULL); m = g_list_append(m, act); } act = purple_menu_action_new(_("Set User Info..."), PURPLE_CALLBACK(yahoo_userinfo_blist_node), NULL, NULL); m = g_list_append(m, act); } return m; } GList *yahoo_blist_node_menu(PurpleBlistNode *node) { if(PURPLE_BLIST_NODE_IS_BUDDY(node)) { return yahoo_buddy_menu((PurpleBuddy *) node); } else { return NULL; } } static void yahoo_act_id(PurpleConnection *gc, PurpleRequestFields *fields) { YahooData *yd = purple_connection_get_protocol_data(gc); const char *name = yd->profiles[purple_request_fields_get_choice(fields, "id")]; struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_IDACT, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash_str(pkt, 3, name); yahoo_packet_send_and_free(pkt, yd); purple_connection_set_display_name(gc, name); } static void yahoo_get_inbox_token_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *token, size_t len, const gchar *error_message) { PurpleConnection *gc = user_data; gboolean set_cookie = FALSE; gchar *url; YahooData *yd = purple_connection_get_protocol_data(gc); g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc)); yd->url_datas = g_slist_remove(yd->url_datas, url_data); if (error_message != NULL) purple_debug_error("yahoo", "Requesting mail login token failed: %s\n", error_message); else if (len > 0 && token && *token) { /* Should we not be hardcoding the rd url? */ url = g_strdup_printf( "http://login.yahoo.com/config/reset_cookies_token?" ".token=%s" "&.done=http://us.rd.yahoo.com/messenger/client/%%3fhttp://mail.yahoo.com/", token); set_cookie = TRUE; } if (!set_cookie) { purple_debug_error("yahoo", "No mail login token; forwarding to login screen.\n"); url = g_strdup(yd->jp ? YAHOOJP_MAIL_URL : YAHOO_MAIL_URL); } /* Open the mailbox with the parsed url data */ purple_notify_uri(gc, url); g_free(url); } static void yahoo_show_inbox(PurplePluginAction *action) { /* Setup a cookie that can be used by the browser */ /* XXX I have no idea how this will work with Yahoo! Japan. */ PurpleConnection *gc = action->context; YahooData *yd = purple_connection_get_protocol_data(gc); PurpleUtilFetchUrlData *url_data; const char* base_url = "http://login.yahoo.com"; /* use whole URL if using HTTP Proxy */ gboolean use_whole_url = yahoo_account_use_http_proxy(gc); gchar *request = g_strdup_printf( "POST %s/config/cookie_token HTTP/1.0\r\n" "Cookie: T=%s; path=/; domain=.yahoo.com; Y=%s;\r\n" "User-Agent: " YAHOO_CLIENT_USERAGENT "\r\n" "Host: login.yahoo.com\r\n" "Content-Length: 0\r\n\r\n", use_whole_url ? base_url : "", yd->cookie_t, yd->cookie_y); url_data = purple_util_fetch_url_request( purple_connection_get_account(gc), base_url, use_whole_url, YAHOO_CLIENT_USERAGENT, TRUE, request, FALSE, -1, yahoo_get_inbox_token_cb, gc); g_free(request); if (url_data != NULL) yd->url_datas = g_slist_prepend(yd->url_datas, url_data); else { const char *yahoo_mail_url = (yd->jp ? YAHOOJP_MAIL_URL : YAHOO_MAIL_URL); purple_debug_error("yahoo", "Unable to request mail login token; forwarding to login screen."); purple_notify_uri(gc, yahoo_mail_url); } } static void yahoo_set_userinfo_fn(PurplePluginAction *action) { yahoo_set_userinfo(action->context); } static void yahoo_show_act_id(PurplePluginAction *action) { PurpleRequestFields *fields; PurpleRequestFieldGroup *group; PurpleRequestField *field; PurpleConnection *gc = (PurpleConnection *) action->context; YahooData *yd = purple_connection_get_protocol_data(gc); const char *name = purple_connection_get_display_name(gc); int iter; fields = purple_request_fields_new(); group = purple_request_field_group_new(NULL); purple_request_fields_add_group(fields, group); field = purple_request_field_choice_new("id", "Activate which ID?", 0); purple_request_field_group_add_field(group, field); for (iter = 0; yd->profiles[iter]; iter++) { purple_request_field_choice_add(field, yd->profiles[iter]); if (purple_strequal(yd->profiles[iter], name)) purple_request_field_choice_set_default_value(field, iter); } purple_request_fields(gc, NULL, _("Select the ID you want to activate"), NULL, fields, _("OK"), G_CALLBACK(yahoo_act_id), _("Cancel"), NULL, purple_connection_get_account(gc), NULL, NULL, gc); } static void yahoo_show_chat_goto(PurplePluginAction *action) { PurpleConnection *gc = (PurpleConnection *) action->context; purple_request_input(gc, NULL, _("Join whom in chat?"), NULL, "", FALSE, FALSE, NULL, _("OK"), G_CALLBACK(yahoo_chat_goto), _("Cancel"), NULL, purple_connection_get_account(gc), NULL, NULL, gc); } GList *yahoo_actions(PurplePlugin *plugin, gpointer context) { GList *m = NULL; PurplePluginAction *act; act = purple_plugin_action_new(_("Set User Info..."), yahoo_set_userinfo_fn); m = g_list_append(m, act); act = purple_plugin_action_new(_("Activate ID..."), yahoo_show_act_id); m = g_list_append(m, act); act = purple_plugin_action_new(_("Join User in Chat..."), yahoo_show_chat_goto); m = g_list_append(m, act); m = g_list_append(m, NULL); act = purple_plugin_action_new(_("Open Inbox"), yahoo_show_inbox); m = g_list_append(m, act); return m; } struct yahoo_sms_carrier_cb_data { PurpleConnection *gc; char *who; char *what; }; static void yahoo_get_sms_carrier_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *webdata, size_t len, const gchar *error_message) { struct yahoo_sms_carrier_cb_data *sms_cb_data = user_data; PurpleConnection *gc = sms_cb_data->gc; YahooData *yd = purple_connection_get_protocol_data(gc); char *status = NULL; char *carrier = NULL; PurpleAccount *account = purple_connection_get_account(gc); PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, sms_cb_data->who, account); yd->url_datas = g_slist_remove(yd->url_datas, url_data); if (error_message != NULL) { purple_conversation_write(conv, NULL, _("Can't send SMS. Unable to obtain mobile carrier."), PURPLE_MESSAGE_SYSTEM, time(NULL)); g_free(sms_cb_data->who); g_free(sms_cb_data->what); g_free(sms_cb_data); return ; } else if (len > 0 && webdata && *webdata) { xmlnode *validate_data_root = xmlnode_from_str(webdata, -1); xmlnode *validate_data_child = xmlnode_get_child(validate_data_root, "mobile_no"); const char *mobile_no = xmlnode_get_attrib(validate_data_child, "msisdn"); validate_data_root = xmlnode_copy(validate_data_child); validate_data_child = xmlnode_get_child(validate_data_root, "status"); status = xmlnode_get_data(validate_data_child); validate_data_child = xmlnode_get_child(validate_data_root, "carrier"); carrier = xmlnode_get_data(validate_data_child); purple_debug_info("yahoo", "SMS validate data: %s\n", webdata); if (status && g_str_equal(status, "Valid")) { g_hash_table_insert(yd->sms_carrier, g_strdup_printf("+%s", mobile_no), g_strdup(carrier)); yahoo_send_im(sms_cb_data->gc, sms_cb_data->who, sms_cb_data->what, PURPLE_MESSAGE_SEND); } else { g_hash_table_insert(yd->sms_carrier, g_strdup_printf("+%s", mobile_no), g_strdup("Unknown")); purple_conversation_write(conv, NULL, _("Can't send SMS. Unknown mobile carrier."), PURPLE_MESSAGE_SYSTEM, time(NULL)); } xmlnode_free(validate_data_child); xmlnode_free(validate_data_root); g_free(sms_cb_data->who); g_free(sms_cb_data->what); g_free(sms_cb_data); g_free(status); g_free(carrier); } } static void yahoo_get_sms_carrier(PurpleConnection *gc, gpointer data) { YahooData *yd = purple_connection_get_protocol_data(gc); PurpleUtilFetchUrlData *url_data; struct yahoo_sms_carrier_cb_data *sms_cb_data; char *validate_request_str = NULL; char *request = NULL; gboolean use_whole_url = FALSE; xmlnode *validate_request_root = NULL; xmlnode *validate_request_child = NULL; if(!(sms_cb_data = data)) return; validate_request_root = xmlnode_new("validate"); xmlnode_set_attrib(validate_request_root, "intl", "us"); xmlnode_set_attrib(validate_request_root, "version", YAHOO_CLIENT_VERSION); xmlnode_set_attrib(validate_request_root, "qos", "0"); validate_request_child = xmlnode_new_child(validate_request_root, "mobile_no"); xmlnode_set_attrib(validate_request_child, "msisdn", sms_cb_data->who + 1); validate_request_str = xmlnode_to_str(validate_request_root, NULL); xmlnode_free(validate_request_child); xmlnode_free(validate_request_root); request = g_strdup_printf( "POST /mobileno?intl=us&version=%s HTTP/1.1\r\n" "Cookie: T=%s; path=/; domain=.yahoo.com; Y=%s; path=/; domain=.yahoo.com;\r\n" "User-Agent: " YAHOO_CLIENT_USERAGENT "\r\n" "Host: validate.msg.yahoo.com\r\n" "Content-Length: %" G_GSIZE_FORMAT "\r\n" "Cache-Control: no-cache\r\n\r\n%s", YAHOO_CLIENT_VERSION, yd->cookie_t, yd->cookie_y, strlen(validate_request_str), validate_request_str); /* use whole URL if using HTTP Proxy */ if ((purple_account_get_proxy_info(purple_connection_get_account(gc))) && (purple_proxy_info_get_type(purple_account_get_proxy_info(purple_connection_get_account(gc))) == PURPLE_PROXY_HTTP)) use_whole_url = TRUE; url_data = purple_util_fetch_url_request( purple_connection_get_account(gc), YAHOO_SMS_CARRIER_URL, use_whole_url, YAHOO_CLIENT_USERAGENT, TRUE, request, FALSE, -1, yahoo_get_sms_carrier_cb, data); g_free(request); g_free(validate_request_str); if (url_data) yd->url_datas = g_slist_prepend(yd->url_datas, url_data); else { PurpleAccount *account = purple_connection_get_account(gc); PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, sms_cb_data->who, account); purple_conversation_write(conv, NULL, _("Can't send SMS. Unable to obtain mobile carrier."), PURPLE_MESSAGE_SYSTEM, time(NULL)); g_free(sms_cb_data->who); g_free(sms_cb_data->what); g_free(sms_cb_data); } } int yahoo_send_im(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags) { YahooData *yd = purple_connection_get_protocol_data(gc); struct yahoo_packet *pkt = NULL; char *msg = yahoo_html_to_codes(what); char *msg2; gboolean utf8 = TRUE; PurpleWhiteboard *wb; int ret = 1; const char *fed_who; gsize lenb = 0; glong lenc = 0; struct yahoo_p2p_data *p2p_data; YahooFederation fed = YAHOO_FEDERATION_NONE; msg2 = yahoo_string_encode(gc, msg, &utf8); if(msg2) { lenb = strlen(msg2); lenc = g_utf8_strlen(msg2, -1); if(lenb > YAHOO_MAX_MESSAGE_LENGTH_BYTES || lenc > YAHOO_MAX_MESSAGE_LENGTH_CHARS) { purple_debug_info("yahoo", "Message too big. Length is %" G_GSIZE_FORMAT " bytes, %ld characters. Max is %d bytes, %d chars." " Message is '%s'.\n", lenb, lenc, YAHOO_MAX_MESSAGE_LENGTH_BYTES, YAHOO_MAX_MESSAGE_LENGTH_CHARS, msg2); g_free(msg); g_free(msg2); return -E2BIG; } } fed = yahoo_get_federation_from_name(who); if (who[0] == '+') { /* we have an sms to be sent */ gchar *carrier = NULL; const char *alias = NULL; PurpleAccount *account = purple_connection_get_account(gc); PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, account); carrier = g_hash_table_lookup(yd->sms_carrier, who); if (!carrier) { struct yahoo_sms_carrier_cb_data *sms_cb_data; sms_cb_data = g_malloc(sizeof(struct yahoo_sms_carrier_cb_data)); sms_cb_data->gc = gc; sms_cb_data->who = g_strdup(who); sms_cb_data->what = g_strdup(what); purple_conversation_write(conv, NULL, _("Getting mobile carrier to send the SMS."), PURPLE_MESSAGE_SYSTEM, time(NULL)); yahoo_get_sms_carrier(gc, sms_cb_data); g_free(msg); g_free(msg2); return ret; } else if( strcmp(carrier,"Unknown") == 0 ) { purple_conversation_write(conv, NULL, _("Can't send SMS. Unknown mobile carrier."), PURPLE_MESSAGE_SYSTEM, time(NULL)); g_free(msg); g_free(msg2); return -1; } alias = purple_account_get_alias(account); pkt = yahoo_packet_new(YAHOO_SERVICE_SMS_MSG, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash(pkt, "sssss", 1, purple_connection_get_display_name(gc), 69, alias, 5, who + 1, 68, carrier, 14, msg2); yahoo_packet_send_and_free(pkt, yd); g_free(msg); g_free(msg2); return ret; } pkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE, YAHOO_STATUS_OFFLINE, yd->session_id); fed_who = who; switch (fed) { case YAHOO_FEDERATION_MSN: case YAHOO_FEDERATION_OCS: case YAHOO_FEDERATION_IBM: case YAHOO_FEDERATION_PBX: fed_who += 4; break; case YAHOO_FEDERATION_NONE: default: break; } yahoo_packet_hash(pkt, "ss", 1, purple_connection_get_display_name(gc), 5, fed_who); if (fed) yahoo_packet_hash_int(pkt, 241, fed); 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 not set an IMVironment, then use the default. */ wb = purple_whiteboard_get_session(purple_connection_get_account(gc), who); if (wb) yahoo_packet_hash_str(pkt, 63, DOODLE_IMV_KEY); 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) { /* if p2p link exists, send through it. To-do: key 15, time value to be sent in case of p2p */ if( (p2p_data = g_hash_table_lookup(yd->peers, who)) && !fed) { yahoo_packet_hash_int(pkt, 11, p2p_data->session_id); yahoo_p2p_write_pkt(p2p_data->source, pkt); } else { yahoo_packet_send(pkt, yd); if(!fed) yahoo_send_p2p_pkt(gc, who, 0); /* send p2p packet, with val_13=0 */ } } else ret = -E2BIG; yahoo_packet_free(pkt); g_free(msg); g_free(msg2); return ret; } unsigned int yahoo_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state) { YahooData *yd = purple_connection_get_protocol_data(gc); struct yahoo_p2p_data *p2p_data; YahooFederation fed = YAHOO_FEDERATION_NONE; struct yahoo_packet *pkt = NULL; fed = yahoo_get_federation_from_name(who); /* Don't do anything if sms is being typed */ if( strncmp(who, "+", 1) == 0 ) return 0; pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YAHOO_STATUS_TYPING, yd->session_id); /* check to see if p2p link exists, send through it */ if( (p2p_data = g_hash_table_lookup(yd->peers, who)) && !fed) { yahoo_packet_hash(pkt, "sssssis", 49, "TYPING", 1, purple_connection_get_display_name(gc), 14, " ", 13, state == PURPLE_TYPING ? "1" : "0", 5, who, 11, p2p_data->session_id, 1002, "1"); /* To-do: key 15 to be sent in case of p2p */ yahoo_p2p_write_pkt(p2p_data->source, pkt); yahoo_packet_free(pkt); } else { /* send through yahoo server */ const char *fed_who = who; switch (fed) { case YAHOO_FEDERATION_MSN: case YAHOO_FEDERATION_OCS: case YAHOO_FEDERATION_IBM: case YAHOO_FEDERATION_PBX: fed_who += 4; break; case YAHOO_FEDERATION_NONE: default: break; } yahoo_packet_hash(pkt, "ssssss", 49, "TYPING", 1, purple_connection_get_display_name(gc), 14, " ", 13, state == PURPLE_TYPING ? "1" : "0", 5, fed_who, 1002, "1"); if (fed) yahoo_packet_hash_int(pkt, 241, fed); 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; } void yahoo_set_status(PurpleAccount *account, PurpleStatus *status) { PurpleConnection *gc; PurplePresence *presence; YahooData *yd; struct yahoo_packet *pkt; int old_status; const char *msg = NULL; char *tmp = NULL; char *conv_msg = NULL; gboolean utf8 = TRUE; if (!purple_status_is_active(status)) return; gc = purple_account_get_connection(account); presence = purple_status_get_presence(status); yd = purple_connection_get_protocol_data(gc); old_status = yd->current_status; yd->current_status = get_yahoo_status_from_purple_status(status); if (yd->current_status == YAHOO_STATUS_CUSTOM) { msg = purple_status_get_attr_string(status, "message"); if (purple_status_is_available(status)) { tmp = yahoo_string_encode(gc, msg, &utf8); conv_msg = purple_markup_strip_html(tmp); g_free(tmp); } else { if ((msg == NULL) || (*msg == '\0')) msg = _("Away"); tmp = yahoo_string_encode(gc, msg, &utf8); conv_msg = purple_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, yd->session_id); 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, yd->session_id); yahoo_packet_hash_int(pkt, 10, yd->current_status); if (yd->current_status == YAHOO_STATUS_CUSTOM) { yahoo_packet_hash_str(pkt, 97, utf8 ? "1" : 0); yahoo_packet_hash_str(pkt, 19, conv_msg); } else { yahoo_packet_hash_str(pkt, 19, ""); } g_free(conv_msg); if (purple_presence_is_idle(presence)) yahoo_packet_hash_str(pkt, 47, "2"); else { if (!purple_status_is_available(status)) yahoo_packet_hash_str(pkt, 47, "1"); else yahoo_packet_hash_str(pkt, 47, "0"); } 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, yd->session_id); 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); } } void yahoo_set_idle(PurpleConnection *gc, int idle) { YahooData *yd = purple_connection_get_protocol_data(gc); struct yahoo_packet *pkt = NULL; char *msg = NULL, *msg2 = NULL; PurpleStatus *status = NULL; gboolean invisible = FALSE; gboolean utf8 = TRUE; if (idle && yd->current_status != YAHOO_STATUS_CUSTOM) yd->current_status = YAHOO_STATUS_IDLE; else if (!idle && yd->current_status == YAHOO_STATUS_IDLE) { status = purple_presence_get_active_status(purple_account_get_presence(purple_connection_get_account(gc))); yd->current_status = get_yahoo_status_from_purple_status(status); } invisible = (yd->current_status == YAHOO_STATUS_INVISIBLE); pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_STATUS_UPDATE, YAHOO_STATUS_AVAILABLE, yd->session_id); if (!idle && invisible) yahoo_packet_hash_int(pkt, 10, YAHOO_STATUS_AVAILABLE); else yahoo_packet_hash_int(pkt, 10, yd->current_status); if (yd->current_status == YAHOO_STATUS_CUSTOM) { const char *tmp; if (status == NULL) status = purple_presence_get_active_status(purple_account_get_presence(purple_connection_get_account(gc))); tmp = purple_status_get_attr_string(status, "message"); if (tmp != NULL) { msg = yahoo_string_encode(gc, tmp, &utf8); msg2 = purple_markup_strip_html(msg); yahoo_packet_hash_str(pkt, 97, utf8 ? "1" : 0); yahoo_packet_hash_str(pkt, 19, msg2); } else { /* get_yahoo_status_from_purple_status() returns YAHOO_STATUS_CUSTOM for * the generic away state (YAHOO_STATUS_TYPE_AWAY) with no message */ yahoo_packet_hash_str(pkt, 97, utf8 ? "1" : 0); 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 (yd->current_status == YAHOO_STATUS_CUSTOM && !purple_status_is_available(status)) /* We are still unavailable in this case. * Make sure Yahoo knows that */ yahoo_packet_hash_str(pkt, 47, "1"); yahoo_packet_send_and_free(pkt, yd); g_free(msg); g_free(msg2); } GList *yahoo_status_types(PurpleAccount *account) { PurpleStatusType *type; GList *types = NULL; type = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE, YAHOO_STATUS_TYPE_AVAILABLE, NULL, TRUE, TRUE, FALSE, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), NULL); types = g_list_append(types, type); type = purple_status_type_new_with_attrs(PURPLE_STATUS_AWAY, YAHOO_STATUS_TYPE_AWAY, NULL, TRUE, TRUE, FALSE, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), NULL); types = g_list_append(types, type); type = purple_status_type_new(PURPLE_STATUS_AWAY, YAHOO_STATUS_TYPE_BRB, _("Be Right Back"), TRUE); types = g_list_append(types, type); type = purple_status_type_new_with_attrs(PURPLE_STATUS_UNAVAILABLE, YAHOO_STATUS_TYPE_BUSY, _("Busy"), TRUE, TRUE, FALSE, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), NULL); types = g_list_append(types, type); type = purple_status_type_new(PURPLE_STATUS_AWAY, YAHOO_STATUS_TYPE_NOTATHOME, _("Not at Home"), TRUE); types = g_list_append(types, type); type = purple_status_type_new(PURPLE_STATUS_AWAY, YAHOO_STATUS_TYPE_NOTATDESK, _("Not at Desk"), TRUE); types = g_list_append(types, type); type = purple_status_type_new(PURPLE_STATUS_AWAY, YAHOO_STATUS_TYPE_NOTINOFFICE, _("Not in Office"), TRUE); types = g_list_append(types, type); type = purple_status_type_new(PURPLE_STATUS_UNAVAILABLE, YAHOO_STATUS_TYPE_ONPHONE, _("On the Phone"), TRUE); types = g_list_append(types, type); type = purple_status_type_new(PURPLE_STATUS_EXTENDED_AWAY, YAHOO_STATUS_TYPE_ONVACATION, _("On Vacation"), TRUE); types = g_list_append(types, type); type = purple_status_type_new(PURPLE_STATUS_AWAY, YAHOO_STATUS_TYPE_OUTTOLUNCH, _("Out to Lunch"), TRUE); types = g_list_append(types, type); type = purple_status_type_new(PURPLE_STATUS_AWAY, YAHOO_STATUS_TYPE_STEPPEDOUT, _("Stepped Out"), TRUE); types = g_list_append(types, type); type = purple_status_type_new(PURPLE_STATUS_INVISIBLE, YAHOO_STATUS_TYPE_INVISIBLE, NULL, TRUE); types = g_list_append(types, type); type = purple_status_type_new(PURPLE_STATUS_OFFLINE, YAHOO_STATUS_TYPE_OFFLINE, NULL, TRUE); types = g_list_append(types, type); type = purple_status_type_new_full(PURPLE_STATUS_MOBILE, YAHOO_STATUS_TYPE_MOBILE, NULL, FALSE, FALSE, TRUE); types = g_list_append(types, type); return types; } void yahoo_keepalive(PurpleConnection *gc) { struct yahoo_packet *pkt; YahooData *yd = purple_connection_get_protocol_data(gc); time_t now = time(NULL); /* We're only allowed to send a ping once an hour or the servers will boot us */ if ((now - yd->last_ping) >= PING_TIMEOUT) { yd->last_ping = now; /* The native client will only send PING or CHATPING */ if (yd->chat_online) { if (yd->wm) { ycht_chat_send_keepalive(yd->ycht); } else { pkt = yahoo_packet_new(YAHOO_SERVICE_CHATPING, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash_str(pkt, 109, purple_connection_get_display_name(gc)); yahoo_packet_send_and_free(pkt, yd); } } else { pkt = yahoo_packet_new(YAHOO_SERVICE_PING, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_send_and_free(pkt, yd); } } if ((now - yd->last_keepalive) >= KEEPALIVE_TIMEOUT) { yd->last_keepalive = now; pkt = yahoo_packet_new(YAHOO_SERVICE_KEEPALIVE, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash_str(pkt, 0, purple_connection_get_display_name(gc)); yahoo_packet_send_and_free(pkt, yd); } } void yahoo_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *g, const char *message) { YahooData *yd = purple_connection_get_protocol_data(gc); struct yahoo_packet *pkt; const char *group = NULL; char *group2; const char *bname; const char *fed_bname; YahooFederation fed = YAHOO_FEDERATION_NONE; if (!yd->logged_in) return; fed_bname = bname = purple_buddy_get_name(buddy); if (!purple_privacy_check(purple_connection_get_account(gc), bname)) return; fed = yahoo_get_federation_from_name(bname); if (fed != YAHOO_FEDERATION_NONE) fed_bname += 4; g = purple_buddy_get_group(buddy); if (g) group = purple_group_get_name(g); else group = "Buddies"; group2 = yahoo_string_encode(gc, group, NULL); pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YAHOO_STATUS_AVAILABLE, yd->session_id); if (fed) { yahoo_packet_hash(pkt, "sssssssisss", 14, "", 65, group2, 97, "1", 1, purple_connection_get_display_name(gc), 302, "319", 300, "319", 7, fed_bname, 241, fed, 334, "0", 301, "319", 303, "319" ); } else { yahoo_packet_hash(pkt, "ssssssssss", 14, "", 65, group2, 97, "1", 1, purple_connection_get_display_name(gc), 302, "319", 300, "319", 7, fed_bname, 334, "0", 301, "319", 303, "319" ); } yahoo_packet_send_and_free(pkt, yd); g_free(group2); } void yahoo_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) { YahooData *yd = purple_connection_get_protocol_data(gc); struct yahoo_packet *pkt; GSList *buddies, *l; PurpleGroup *g; gboolean remove = TRUE; char *cg; const char *bname, *gname; YahooFriend *f = NULL; YahooFederation fed = YAHOO_FEDERATION_NONE; bname = purple_buddy_get_name(buddy); f = yahoo_friend_find(gc, bname); if (!f) return; fed = f->fed; gname = purple_group_get_name(group); buddies = purple_find_buddies(purple_connection_get_account(gc), bname); for (l = buddies; l; l = l->next) { g = purple_buddy_get_group(l->data); if (purple_utf8_strcasecmp(gname, purple_group_get_name(g))) { remove = FALSE; break; } } g_slist_free(buddies); if (remove) { g_hash_table_remove(yd->friends, bname); f = NULL; /* f no longer valid - Just making it clear */ } cg = yahoo_string_encode(gc, gname, NULL); pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YAHOO_STATUS_AVAILABLE, yd->session_id); switch (fed) { case YAHOO_FEDERATION_MSN: case YAHOO_FEDERATION_OCS: case YAHOO_FEDERATION_IBM: bname += 4; break; case YAHOO_FEDERATION_NONE: default: break; } yahoo_packet_hash(pkt, "sss", 1, purple_connection_get_display_name(gc), 7, bname, 65, cg); if (fed) yahoo_packet_hash_int(pkt, 241, fed); yahoo_packet_send_and_free(pkt, yd); g_free(cg); } void yahoo_add_deny(PurpleConnection *gc, const char *who) { YahooData *yd = purple_connection_get_protocol_data(gc); struct yahoo_packet *pkt; YahooFederation fed = YAHOO_FEDERATION_NONE; if (!yd->logged_in) return; if (!who || who[0] == '\0') return; fed = yahoo_get_federation_from_name(who); pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, YAHOO_STATUS_AVAILABLE, yd->session_id); if(fed) yahoo_packet_hash(pkt, "ssis", 1, purple_connection_get_display_name(gc), 7, who+4, 241, fed, 13, "1"); else yahoo_packet_hash(pkt, "sss", 1, purple_connection_get_display_name(gc), 7, who, 13, "1"); yahoo_packet_send_and_free(pkt, yd); } void yahoo_rem_deny(PurpleConnection *gc, const char *who) { YahooData *yd = purple_connection_get_protocol_data(gc); struct yahoo_packet *pkt; YahooFederation fed = YAHOO_FEDERATION_NONE; if (!yd->logged_in) return; if (!who || who[0] == '\0') return; fed = yahoo_get_federation_from_name(who); pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, YAHOO_STATUS_AVAILABLE, yd->session_id); if(fed) yahoo_packet_hash(pkt, "ssis", 1, purple_connection_get_display_name(gc), 7, who+4, 241, fed, 13, "2"); else yahoo_packet_hash(pkt, "sss", 1, purple_connection_get_display_name(gc), 7, who, 13, "2"); yahoo_packet_send_and_free(pkt, yd); } void yahoo_set_permit_deny(PurpleConnection *gc) { PurpleAccount *account; GSList *deny; account = purple_connection_get_account(gc); switch (purple_account_get_privacy_type(account)) { case PURPLE_PRIVACY_ALLOW_ALL: for (deny = account->deny; deny; deny = deny->next) yahoo_rem_deny(gc, deny->data); break; case PURPLE_PRIVACY_ALLOW_BUDDYLIST: case PURPLE_PRIVACY_ALLOW_USERS: case PURPLE_PRIVACY_DENY_USERS: case PURPLE_PRIVACY_DENY_ALL: for (deny = account->deny; deny; deny = deny->next) yahoo_add_deny(gc, deny->data); break; } } void yahoo_change_buddys_group(PurpleConnection *gc, const char *who, const char *old_group, const char *new_group) { YahooData *yd = purple_connection_get_protocol_data(gc); struct yahoo_packet *pkt; char *gpn, *gpo; YahooFriend *f = yahoo_friend_find(gc, who); const char *temp = NULL; /* Step 0: If they aren't on the server list anyway, * don't bother letting the server know. */ if (!f) return; if(f->fed) { temp = who+4; } else temp = who; /* 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; } pkt = yahoo_packet_new(YAHOO_SERVICE_CHGRP_15, YAHOO_STATUS_AVAILABLE, yd->session_id); if(f->fed) yahoo_packet_hash(pkt, "ssssissss", 1, purple_connection_get_display_name(gc), 302, "240", 300, "240", 7, temp, 241, f->fed, 224, gpo, 264, gpn, 301, "240", 303, "240"); else yahoo_packet_hash(pkt, "ssssssss", 1, purple_connection_get_display_name(gc), 302, "240", 300, "240", 7, temp, 224, gpo, 264, gpn, 301, "240", 303, "240"); yahoo_packet_send_and_free(pkt, yd); g_free(gpn); g_free(gpo); } void yahoo_rename_group(PurpleConnection *gc, const char *old_name, PurpleGroup *group, GList *moved_buddies) { YahooData *yd = purple_connection_get_protocol_data(gc); struct yahoo_packet *pkt; char *gpn, *gpo; gpn = yahoo_string_encode(gc, purple_group_get_name(group), 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, yd->session_id); yahoo_packet_hash(pkt, "sss", 1, purple_connection_get_display_name(gc), 65, gpo, 67, gpn); yahoo_packet_send_and_free(pkt, yd); g_free(gpn); g_free(gpo); } /********************************* Commands **********************************/ PurpleCmdRet yahoopurple_cmd_buzz(PurpleConversation *c, const gchar *cmd, gchar **args, gchar **error, void *data) { PurpleAccount *account = purple_conversation_get_account(c); if (*args && args[0]) return PURPLE_CMD_RET_FAILED; purple_prpl_send_attention(purple_account_get_connection(account), purple_conversation_get_name(c), YAHOO_BUZZ); return PURPLE_CMD_RET_OK; } PurpleCmdRet yahoopurple_cmd_chat_join(PurpleConversation *conv, const char *cmd, char **args, char **error, void *data) { GHashTable *comp; PurpleConnection *gc; if (!args || !args[0]) return PURPLE_CMD_RET_FAILED; gc = purple_conversation_get_connection(conv); purple_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_ascii_strdown(args[0], -1)); g_hash_table_replace(comp, g_strdup("type"), g_strdup("Chat")); yahoo_c_join(gc, comp); g_hash_table_destroy(comp); return PURPLE_CMD_RET_OK; } PurpleCmdRet yahoopurple_cmd_chat_list(PurpleConversation *conv, const char *cmd, char **args, char **error, void *data) { PurpleAccount *account = purple_conversation_get_account(conv); if (*args && args[0]) return PURPLE_CMD_RET_FAILED; purple_roomlist_show_with_account(account); return PURPLE_CMD_RET_OK; } gboolean yahoo_offline_message(const PurpleBuddy *buddy) { return TRUE; } gboolean yahoo_send_attention(PurpleConnection *gc, const char *username, guint type) { PurpleConversation *c; c = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, username, purple_connection_get_account(gc)); g_return_val_if_fail(c != NULL, FALSE); purple_debug_info("yahoo", "Sending <ding> on account %s to buddy %s.\n", username, purple_conversation_get_name(c)); purple_conv_im_send_with_flags(PURPLE_CONV_IM(c), "<ding>", PURPLE_MESSAGE_INVISIBLE); return TRUE; } GList *yahoo_attention_types(PurpleAccount *account) { static GList *list = NULL; if (!list) { /* Yahoo only supports one attention command: the 'buzz'. */ /* This is index number YAHOO_BUZZ. */ list = g_list_append(list, purple_attention_type_new("Buzz", _("Buzz"), _("%s has buzzed you!"), _("Buzzing %s..."))); } return list; }