Mercurial > pidgin
view src/protocols/qq/buddy_list.c @ 13967:99b9b58b19dd
[gaim-migrate @ 16523]
Fix a crazy MSN crash. Basically it's possible to have more than one
slplink associated with a given switchboard, but our code did not
allow for that. I think it happens when you're in a multi-user
chat and you do stuff with multiple users that involves slplinks.
Like maybe file transfer and buddy icon related stuff.
Tracking this down took an ungodly amount of time, but thanks to
Meebo for letting me do it :-)
committer: Tailor Script <tailor@pidgin.im>
author | Mark Doliner <mark@kingant.net> |
---|---|
date | Thu, 20 Jul 2006 07:31:15 +0000 |
parents | 983fd420e86b |
children | 16102b9c5c4a |
line wrap: on
line source
/** * The QQ2003C protocol plugin * * for gaim * * Copyright (C) 2004 Puzzlebird * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // START OF FILE /*****************************************************************************/ #include <string.h> // g_memmove, memmove #include "debug.h" // gaim_debug #include "notify.h" // gaim_notify #include "utils.h" // get_ip_str #include "packet_parse.h" // create_packet, read_packet #include "buddy_list.h" #include "buddy_status.h" // qq_buddy_status #include "buddy_opt.h" // qq_add_buddy_by_recv_packet #include "char_conv.h" // qq_to_utf8 #include "crypt.h" // qq_crypt #include "header_info.h" // cmd alias #include "keep_alive.h" // qq_refresh_all_buddy_status #include "send_core.h" // qq_send_cmd #include "qq.h" // qq_data #include "group.h" // qq_group, by gfhuang #include "group_find.h" // qq_group_find #include "group_hash.h" //qq_group_create_by_id #include "group_info.h" //qq_send_cmd_group_get_group_info #define QQ_GET_ONLINE_BUDDY_02 0x02 #define QQ_GET_ONLINE_BUDDY_03 0x03 // unknown function #define QQ_ONLINE_BUDDY_ENTRY_LEN 38 typedef struct _qq_friends_online_entry { qq_buddy_status *s; guint16 unknown1; guint8 flag1; guint8 comm_flag; guint16 unknown2; guint8 ending; //0x00 } qq_friends_online_entry; //TODO: defined in qq_buddy_status.c, but only used here. Check decomposition. extern void // defined in qq_buddy_status.c _qq_buddy_status_dump_unclear(qq_buddy_status * s); extern gint // defined in qq_buddy_status.c _qq_buddy_status_read(guint8 * data, guint8 ** cursor, gint len, qq_buddy_status * s); /*****************************************************************************/ // get a list of online_buddies void qq_send_packet_get_buddies_online(GaimConnection * gc, guint8 position) { qq_data *qd; guint8 *raw_data, *cursor; g_return_if_fail(gc != NULL && gc->proto_data != NULL); qd = (qq_data *) gc->proto_data; raw_data = g_newa(guint8, 5); cursor = raw_data; // 000-000 get online friends cmd // only 0x02 and 0x03 returns info from server, other valuse all return 0xff // I can also only send the first byte (0x02, or 0x03) // and the result is the same create_packet_b(raw_data, &cursor, QQ_GET_ONLINE_BUDDY_02); // 001-001 seems it supports 255 online buddies at most create_packet_b(raw_data, &cursor, position); // 002-002 create_packet_b(raw_data, &cursor, 0x00); // 003-004 create_packet_w(raw_data, &cursor, 0x0000); qq_send_cmd(gc, QQ_CMD_GET_FRIENDS_ONLINE, TRUE, 0, TRUE, raw_data, 5); qd->last_get_online = time(NULL); } // qq_send_packet_get_buddies_online /*****************************************************************************/ // position starts with 0x0000, // server may return a position tag if list is too long for one packet void qq_send_packet_get_buddies_list(GaimConnection * gc, guint16 position) { guint8 *raw_data, *cursor; gint data_len; g_return_if_fail(gc != NULL); data_len = 3; raw_data = g_newa(guint8, data_len); cursor = raw_data; // 000-001 starting position, can manually specify create_packet_w(raw_data, &cursor, position); // before Mar 18, 2004, any value can work, and we sent 00 // I do not know what data QQ server is expecting, as QQ2003iii 0304 itself // even can sending packets 00 and get no response. // Now I tested that 00,00,00,00,00,01 work perfectly // March 22, fount the 00,00,00 starts to work as well create_packet_b(raw_data, &cursor, 0x00); qq_send_cmd(gc, QQ_CMD_GET_FRIENDS_LIST, TRUE, 0, TRUE, raw_data, data_len); } // qq_send_packet_get_buddies_list // get all list, buddies & Quns with groupsid support, written by gfhuang void qq_send_packet_get_all_list_with_group(GaimConnection *gc, guint32 position) { guint8 *raw_data, *cursor; gint data_len; g_return_if_fail(gc != NULL); data_len = 10; raw_data = g_newa(guint8, data_len); cursor = raw_data; // 0x01 download, 0x02, upload create_packet_b(raw_data, &cursor, 0x01); //unknown 0x02 create_packet_b(raw_data, &cursor, 0x02); //unknown 00 00 00 00 create_packet_dw(raw_data, &cursor, 0x00000000); create_packet_dw(raw_data, &cursor, position); qq_send_cmd(gc, QQ_CMD_GET_ALL_LIST_WITH_GROUP, TRUE, 0, TRUE, raw_data, data_len); } /*****************************************************************************/ static void _qq_buddies_online_reply_dump_unclear(qq_friends_online_entry * fe) { GString *dump; g_return_if_fail(fe != NULL); _qq_buddy_status_dump_unclear(fe->s); dump = g_string_new(""); g_string_append_printf(dump, "unclear fields for [%d]:\n", fe->s->uid); g_string_append_printf(dump, "031-032: %04x (unknown)\n", fe->unknown1); g_string_append_printf(dump, "033: %02x (flag1)\n", fe->flag1); g_string_append_printf(dump, "034: %02x (comm_flag)\n", fe->comm_flag); g_string_append_printf(dump, "035-036: %04x (unknown)\n", fe->unknown2); gaim_debug(GAIM_DEBUG_INFO, "QQ", "Online buddy entry, %s", dump->str); g_string_free(dump, TRUE); } // _qq_buddies_online_reply_dump_unknown /*****************************************************************************/ // process the reply packet for get_buddies_online packet void qq_process_get_buddies_online_reply(guint8 * buf, gint buf_len, GaimConnection * gc) { qq_data *qd; gint len, bytes; guint8 *data, *cursor, position; GaimBuddy *b; qq_buddy *q_bud; qq_friends_online_entry *fe; g_return_if_fail(gc != NULL && gc->proto_data != NULL); g_return_if_fail(buf != NULL && buf_len != 0); qd = (qq_data *) gc->proto_data; len = buf_len; data = g_newa(guint8, len); cursor = data; if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { read_packet_b(data, &cursor, len, &position); fe = g_newa(qq_friends_online_entry, 1); fe->s = g_newa(qq_buddy_status, 1); while (cursor < (data + len)) { // based on one online buddy entry bytes = 0; // 000-030 qq_buddy_status bytes += _qq_buddy_status_read(data, &cursor, len, fe->s); // 031-032: unknown4 bytes += read_packet_w(data, &cursor, len, &fe->unknown1); // 033-033: flag1 bytes += read_packet_b(data, &cursor, len, &fe->flag1); // 034-034: comm_flag bytes += read_packet_b(data, &cursor, len, &fe->comm_flag); // 035-036: bytes += read_packet_w(data, &cursor, len, &fe->unknown2); // 037-037: bytes += read_packet_b(data, &cursor, len, &fe->ending); // 0x00 if (fe->s->uid == 0 || bytes != QQ_ONLINE_BUDDY_ENTRY_LEN) { gaim_debug(GAIM_DEBUG_ERROR, "QQ", "uid=0 or entry complete len(%d) != %d", bytes, QQ_ONLINE_BUDDY_ENTRY_LEN); g_free(fe->s->ip); g_free(fe->s->unknown_key); continue; } // check if it is a valid entry // if (QQ_DEBUG) // _qq_buddies_online_reply_dump_unclear(fe); // update buddy information b = gaim_find_buddy(gaim_connection_get_account(gc), uid_to_gaim_name(fe->s->uid)); q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data; if (q_bud != NULL) { // we find one and update qq_buddy if(0 != fe->s->client_version) q_bud->client_version = fe->s->client_version; //by gfhuang if(0 != *((guint32 *)fe->s->ip)) { // by gfhuang g_memmove(q_bud->ip, fe->s->ip, 4); q_bud->port = fe->s->port; } q_bud->status = fe->s->status; q_bud->flag1 = fe->flag1; q_bud->comm_flag = fe->comm_flag; qq_update_buddy_contact(gc, q_bud); } // if q_bud else { gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Got an online buddy %d, but not in my buddy list", fe->s->uid); } g_free(fe->s->ip); g_free(fe->s->unknown_key); } // while cursor if(cursor > (data + len)) { gaim_debug(GAIM_DEBUG_ERROR, "QQ", "qq_process_get_buddies_online_reply: Dangerous error! maybe protocal changed, notify me!"); } if (position != QQ_FRIENDS_ONLINE_POSITION_END) { gaim_debug(GAIM_DEBUG_INFO, "QQ", "Has more online buddies, position from %d", position); qq_send_packet_get_buddies_online(gc, position); } else qq_refresh_all_buddy_status(gc); } else gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt buddies online"); } // qq_process_get_buddies_online_reply /*****************************************************************************/ // process reply for get_buddies_list void qq_process_get_buddies_list_reply(guint8 * buf, gint buf_len, GaimConnection * gc) { qq_data *qd; qq_buddy *q_bud; gint len, bytes, bytes_expected, i; guint16 position, unknown; guint8 *data, *cursor, bar, pascal_len; gchar *name; GaimBuddy *b; g_return_if_fail(gc != NULL && gc->proto_data != NULL); g_return_if_fail(buf != NULL && buf_len != 0); qd = (qq_data *) gc->proto_data; len = buf_len; data = g_newa(guint8, len); cursor = data; if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { read_packet_w(data, &cursor, len, &position); // the following data is buddy list in this packet i = 0; while (cursor < (data + len)) { q_bud = g_new0(qq_buddy, 1); bytes = 0; // 000-003: uid bytes += read_packet_dw(data, &cursor, len, &q_bud->uid); // 004-004: 0xff if buddy is self, 0x00 otherwise bytes += read_packet_b(data, &cursor, len, &bar); // 005-005: icon index (1-255) bytes += read_packet_b(data, &cursor, len, &q_bud->icon); // 006-006: age bytes += read_packet_b(data, &cursor, len, &q_bud->age); // 007-007: gender bytes += read_packet_b(data, &cursor, len, &q_bud->gender); pascal_len = convert_as_pascal_string(cursor, &q_bud->nickname, QQ_CHARSET_DEFAULT); cursor += pascal_len; bytes += pascal_len; bytes += read_packet_w(data, &cursor, len, &unknown); /* flag1: (0-7) * bit1 => qq show * comm_flag: (0-7) * bit1 => member * bit4 => TCP mode * bit5 => open mobile QQ * bit6 => bind to mobile * bit7 => whether having a video */ bytes += read_packet_b(data, &cursor, len, &q_bud->flag1); bytes += read_packet_b(data, &cursor, len, &q_bud->comm_flag); bytes_expected = 12 + pascal_len; if (q_bud->uid == 0 || bytes != bytes_expected) { gaim_debug(GAIM_DEBUG_INFO, "QQ", "Buddy entry, expect %d bytes, read %d bytes\n", bytes_expected, bytes); g_free(q_bud->nickname); g_free(q_bud); continue; } else i++; if (QQ_DEBUG) gaim_debug(GAIM_DEBUG_INFO, "QQ", "buddy [%09d]: flag1=0x%02x, comm_flag=0x%02x\n", q_bud->uid, q_bud->flag1, q_bud->comm_flag); name = uid_to_gaim_name(q_bud->uid); b = gaim_find_buddy(gc->account, name); g_free(name); if (b == NULL) b = qq_add_buddy_by_recv_packet(gc, q_bud->uid, TRUE, FALSE); b->proto_data = q_bud; qd->buddies = g_list_append(qd->buddies, q_bud); qq_update_buddy_contact(gc, q_bud); } // while cursor if(cursor > (data + len)) { gaim_debug(GAIM_DEBUG_ERROR, "QQ", "qq_process_get_buddies_list_reply: Dangerous error! maybe protocal changed, notify me!"); } if (position == QQ_FRIENDS_LIST_POSITION_END) { gaim_debug(GAIM_DEBUG_INFO, "QQ", "Get friends list done, %d buddies\n", i); qq_send_packet_get_buddies_online(gc, QQ_FRIENDS_ONLINE_POSITION_START); } else qq_send_packet_get_buddies_list(gc, position); } else gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt buddies list"); } // qq_process_get_buddies_list_reply // written by gfhuang void qq_process_get_all_list_with_group_reply(guint8 * buf, gint buf_len, GaimConnection * gc) { qq_data *qd; gint len, i, j; guint8 *data, *cursor; guint8 sub_cmd, reply_code; guint32 unknown, position; guint32 uid; guint8 type, groupid; qq_buddy *q_bud; gchar *name; GaimBuddy *b; qq_group *group; g_return_if_fail(gc != NULL && gc->proto_data != NULL); g_return_if_fail(buf != NULL && buf_len != 0); qd = (qq_data *) gc->proto_data; len = buf_len; data = g_newa(guint8, len); cursor = data; if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { read_packet_b(data, &cursor, len, &sub_cmd); g_return_if_fail(sub_cmd == 0x01); read_packet_b(data, &cursor, len, &reply_code); if(0 != reply_code) { gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Get all list with group reply, reply_code(%d) is not zero", reply_code); } read_packet_dw(data, &cursor, len, &unknown); read_packet_dw(data, &cursor, len, &position); // the following data is all list in this packet i = 0; j = 0; while (cursor < (data + len)) { // 00-03: uid read_packet_dw(data, &cursor, len, &uid); // 04: type 0x1:buddy 0x4:Qun read_packet_b(data, &cursor, len, &type); // 05: groupid*4 read_packet_b(data, &cursor, len, &groupid); groupid >>= 2; // these 2 bits might not be 0, faint! if (uid == 0 || (type != 0x1 && type != 0x4)) { gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Buddy entry, uid=%d, type=%d", uid, type); continue; } if(0x1 == type) { // a buddy name = uid_to_gaim_name(uid); b = gaim_find_buddy(gc->account, name); g_free(name); if (b == NULL) { b = qq_add_buddy_by_recv_packet(gc, uid, TRUE, TRUE); q_bud = b->proto_data; } else { q_bud = NULL; b->proto_data = q_bud; //wrong !!!! } qd->buddies = g_list_append(qd->buddies, q_bud); qq_update_buddy_contact(gc, q_bud); ++i; } else { //a group group = qq_group_find_by_internal_group_id(gc, uid); if(group == NULL) { /* not working, gfhuang group = qq_group_create_by_id(gc, uid, 0); qq_send_cmd_group_get_group_info(gc, group); */ gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Get a Qun with internel group %d\n", uid); gaim_notify_info(gc, _("QQ Qun Operation"), _("Find one Qun in the server list, but i don't know its external id, please re-rejoin it manually"), NULL); } else { group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER; qq_group_refresh(gc, group); qq_send_cmd_group_get_group_info(gc, group); } ++j; } } if(cursor > (data + len)) { gaim_debug(GAIM_DEBUG_ERROR, "QQ", "qq_process_get_all_list_with_group_reply: Dangerous error! maybe protocal changed, notify me!"); } gaim_debug(GAIM_DEBUG_INFO, "QQ", "Get all list done, %d buddies and %d Quns\n", i, j); } else gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt all list with group"); } /*****************************************************************************/ // END OF FILE