| 1 /** |
|
| 2 * The QQ2003C protocol plugin |
|
| 3 * |
|
| 4 * for gaim |
|
| 5 * |
|
| 6 * Copyright (C) 2004 Puzzlebird |
|
| 7 * |
|
| 8 * This program is free software; you can redistribute it and/or modify |
|
| 9 * it under the terms of the GNU General Public License as published by |
|
| 10 * the Free Software Foundation; either version 2 of the License, or |
|
| 11 * (at your option) any later version. |
|
| 12 * |
|
| 13 * This program is distributed in the hope that it will be useful, |
|
| 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 16 * GNU General Public License for more details. |
|
| 17 * |
|
| 18 * You should have received a copy of the GNU General Public License |
|
| 19 * along with this program; if not, write to the Free Software |
|
| 20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
| 21 */ |
|
| 22 |
|
| 23 #include <string.h> |
|
| 24 #include "debug.h" |
|
| 25 |
|
| 26 #include "notify.h" |
|
| 27 #include "utils.h" |
|
| 28 #include "packet_parse.h" |
|
| 29 #include "buddy_list.h" |
|
| 30 #include "buddy_status.h" |
|
| 31 #include "buddy_opt.h" |
|
| 32 #include "char_conv.h" |
|
| 33 #include "crypt.h" |
|
| 34 #include "header_info.h" |
|
| 35 #include "keep_alive.h" |
|
| 36 #include "send_core.h" |
|
| 37 #include "qq.h" |
|
| 38 #include "group.h" |
|
| 39 #include "group_find.h" |
|
| 40 #include "group_hash.h" |
|
| 41 #include "group_info.h" |
|
| 42 |
|
| 43 #include "qq_proxy.h" |
|
| 44 |
|
| 45 #define QQ_GET_ONLINE_BUDDY_02 0x02 |
|
| 46 #define QQ_GET_ONLINE_BUDDY_03 0x03 /* unknown function */ |
|
| 47 |
|
| 48 #define QQ_ONLINE_BUDDY_ENTRY_LEN 38 |
|
| 49 |
|
| 50 typedef struct _qq_friends_online_entry { |
|
| 51 qq_buddy_status *s; |
|
| 52 guint16 unknown1; |
|
| 53 guint8 flag1; |
|
| 54 guint8 comm_flag; |
|
| 55 guint16 unknown2; |
|
| 56 guint8 ending; /* 0x00 */ |
|
| 57 } qq_friends_online_entry; |
|
| 58 |
|
| 59 /* get a list of online_buddies */ |
|
| 60 void qq_send_packet_get_buddies_online(GaimConnection *gc, guint8 position) |
|
| 61 { |
|
| 62 qq_data *qd; |
|
| 63 guint8 *raw_data, *cursor; |
|
| 64 |
|
| 65 g_return_if_fail(gc != NULL && gc->proto_data != NULL); |
|
| 66 |
|
| 67 qd = (qq_data *) gc->proto_data; |
|
| 68 raw_data = g_newa(guint8, 5); |
|
| 69 cursor = raw_data; |
|
| 70 |
|
| 71 /* 000-000 get online friends cmd |
|
| 72 * only 0x02 and 0x03 returns info from server, other valuse all return 0xff |
|
| 73 * I can also only send the first byte (0x02, or 0x03) |
|
| 74 * and the result is the same */ |
|
| 75 create_packet_b(raw_data, &cursor, QQ_GET_ONLINE_BUDDY_02); |
|
| 76 /* 001-001 seems it supports 255 online buddies at most */ |
|
| 77 create_packet_b(raw_data, &cursor, position); |
|
| 78 /* 002-002 */ |
|
| 79 create_packet_b(raw_data, &cursor, 0x00); |
|
| 80 /* 003-004 */ |
|
| 81 create_packet_w(raw_data, &cursor, 0x0000); |
|
| 82 |
|
| 83 qq_send_cmd(gc, QQ_CMD_GET_FRIENDS_ONLINE, TRUE, 0, TRUE, raw_data, 5); |
|
| 84 qd->last_get_online = time(NULL); |
|
| 85 } |
|
| 86 |
|
| 87 /* position starts with 0x0000, |
|
| 88 * server may return a position tag if list is too long for one packet */ |
|
| 89 void qq_send_packet_get_buddies_list(GaimConnection *gc, guint16 position) |
|
| 90 { |
|
| 91 guint8 *raw_data, *cursor; |
|
| 92 gint data_len; |
|
| 93 |
|
| 94 g_return_if_fail(gc != NULL); |
|
| 95 |
|
| 96 data_len = 3; |
|
| 97 raw_data = g_newa(guint8, data_len); |
|
| 98 cursor = raw_data; |
|
| 99 /* 000-001 starting position, can manually specify */ |
|
| 100 create_packet_w(raw_data, &cursor, position); |
|
| 101 /* before Mar 18, 2004, any value can work, and we sent 00 |
|
| 102 * I do not know what data QQ server is expecting, as QQ2003iii 0304 itself |
|
| 103 * even can sending packets 00 and get no response. |
|
| 104 * Now I tested that 00,00,00,00,00,01 work perfectly |
|
| 105 * March 22, found the 00,00,00 starts to work as well */ |
|
| 106 create_packet_b(raw_data, &cursor, 0x00); |
|
| 107 |
|
| 108 qq_send_cmd(gc, QQ_CMD_GET_FRIENDS_LIST, TRUE, 0, TRUE, raw_data, data_len); |
|
| 109 } |
|
| 110 |
|
| 111 /* get all list, buddies & Quns with groupsid support */ |
|
| 112 void qq_send_packet_get_all_list_with_group(GaimConnection *gc, guint32 position) |
|
| 113 { |
|
| 114 guint8 *raw_data, *cursor; |
|
| 115 gint data_len; |
|
| 116 |
|
| 117 g_return_if_fail(gc != NULL); |
|
| 118 |
|
| 119 data_len = 10; |
|
| 120 raw_data = g_newa(guint8, data_len); |
|
| 121 cursor = raw_data; |
|
| 122 /* 0x01 download, 0x02, upload */ |
|
| 123 create_packet_b(raw_data, &cursor, 0x01); |
|
| 124 /* unknown 0x02 */ |
|
| 125 create_packet_b(raw_data, &cursor, 0x02); |
|
| 126 /* unknown 00 00 00 00 */ |
|
| 127 create_packet_dw(raw_data, &cursor, 0x00000000); |
|
| 128 create_packet_dw(raw_data, &cursor, position); |
|
| 129 |
|
| 130 qq_send_cmd(gc, QQ_CMD_GET_ALL_LIST_WITH_GROUP, TRUE, 0, TRUE, raw_data, data_len); |
|
| 131 } |
|
| 132 |
|
| 133 static void _qq_buddies_online_reply_dump_unclear(qq_friends_online_entry *fe) |
|
| 134 { |
|
| 135 GString *dump; |
|
| 136 |
|
| 137 g_return_if_fail(fe != NULL); |
|
| 138 |
|
| 139 qq_buddy_status_dump_unclear(fe->s); |
|
| 140 |
|
| 141 dump = g_string_new(""); |
|
| 142 g_string_append_printf(dump, "unclear fields for [%d]:\n", fe->s->uid); |
|
| 143 g_string_append_printf(dump, "031-032: %04x (unknown)\n", fe->unknown1); |
|
| 144 g_string_append_printf(dump, "033: %02x (flag1)\n", fe->flag1); |
|
| 145 g_string_append_printf(dump, "034: %02x (comm_flag)\n", fe->comm_flag); |
|
| 146 g_string_append_printf(dump, "035-036: %04x (unknown)\n", fe->unknown2); |
|
| 147 |
|
| 148 gaim_debug(GAIM_DEBUG_INFO, "QQ", "Online buddy entry, %s", dump->str); |
|
| 149 g_string_free(dump, TRUE); |
|
| 150 } |
|
| 151 |
|
| 152 /* process the reply packet for get_buddies_online packet */ |
|
| 153 void qq_process_get_buddies_online_reply(guint8 *buf, gint buf_len, GaimConnection *gc) |
|
| 154 { |
|
| 155 qq_data *qd; |
|
| 156 gint len, bytes; |
|
| 157 guint8 *data, *cursor, position; |
|
| 158 GaimBuddy *b; |
|
| 159 qq_buddy *q_bud; |
|
| 160 qq_friends_online_entry *fe; |
|
| 161 |
|
| 162 g_return_if_fail(gc != NULL && gc->proto_data != NULL); |
|
| 163 g_return_if_fail(buf != NULL && buf_len != 0); |
|
| 164 |
|
| 165 qd = (qq_data *) gc->proto_data; |
|
| 166 len = buf_len; |
|
| 167 data = g_newa(guint8, len); |
|
| 168 cursor = data; |
|
| 169 |
|
| 170 gaim_debug(GAIM_DEBUG_INFO, "QQ", "processing get_buddies_online_reply\n"); |
|
| 171 |
|
| 172 if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { |
|
| 173 |
|
| 174 _qq_show_packet("Get buddies online reply packet", data, len); |
|
| 175 |
|
| 176 read_packet_b(data, &cursor, len, &position); |
|
| 177 |
|
| 178 fe = g_newa(qq_friends_online_entry, 1); |
|
| 179 fe->s = g_newa(qq_buddy_status, 1); |
|
| 180 |
|
| 181 while (cursor < (data + len)) { |
|
| 182 /* based on one online buddy entry */ |
|
| 183 bytes = 0; |
|
| 184 /* 000-030 qq_buddy_status */ |
|
| 185 bytes += qq_buddy_status_read(data, &cursor, len, fe->s); |
|
| 186 /* 031-032: unknown4 */ |
|
| 187 bytes += read_packet_w(data, &cursor, len, &fe->unknown1); |
|
| 188 /* 033-033: flag1 */ |
|
| 189 bytes += read_packet_b(data, &cursor, len, &fe->flag1); |
|
| 190 /* 034-034: comm_flag */ |
|
| 191 bytes += read_packet_b(data, &cursor, len, &fe->comm_flag); |
|
| 192 /* 035-036: */ |
|
| 193 bytes += read_packet_w(data, &cursor, len, &fe->unknown2); |
|
| 194 /* 037-037: */ |
|
| 195 bytes += read_packet_b(data, &cursor, len, &fe->ending); /* 0x00 */ |
|
| 196 |
|
| 197 if (fe->s->uid == 0 || bytes != QQ_ONLINE_BUDDY_ENTRY_LEN) { |
|
| 198 gaim_debug(GAIM_DEBUG_ERROR, "QQ", |
|
| 199 "uid=0 or entry complete len(%d) != %d", |
|
| 200 bytes, QQ_ONLINE_BUDDY_ENTRY_LEN); |
|
| 201 g_free(fe->s->ip); |
|
| 202 g_free(fe->s->unknown_key); |
|
| 203 continue; |
|
| 204 } /* check if it is a valid entry */ |
|
| 205 |
|
| 206 if (QQ_DEBUG) |
|
| 207 _qq_buddies_online_reply_dump_unclear(fe); |
|
| 208 |
|
| 209 /* update buddy information */ |
|
| 210 b = gaim_find_buddy(gaim_connection_get_account(gc), uid_to_gaim_name(fe->s->uid)); |
|
| 211 q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data; |
|
| 212 |
|
| 213 if (q_bud != NULL) { /* we find one and update qq_buddy */ |
|
| 214 if(0 != fe->s->client_version) |
|
| 215 q_bud->client_version = fe->s->client_version; |
|
| 216 g_memmove(q_bud->ip, fe->s->ip, 4); |
|
| 217 q_bud->port = fe->s->port; |
|
| 218 q_bud->status = fe->s->status; |
|
| 219 q_bud->flag1 = fe->flag1; |
|
| 220 q_bud->comm_flag = fe->comm_flag; |
|
| 221 qq_update_buddy_contact(gc, q_bud); |
|
| 222 } |
|
| 223 else { |
|
| 224 gaim_debug(GAIM_DEBUG_ERROR, "QQ", |
|
| 225 "Got an online buddy %d, but not in my buddy list", fe->s->uid); |
|
| 226 } |
|
| 227 |
|
| 228 g_free(fe->s->ip); |
|
| 229 g_free(fe->s->unknown_key); |
|
| 230 } |
|
| 231 |
|
| 232 if(cursor > (data + len)) { |
|
| 233 gaim_debug(GAIM_DEBUG_ERROR, "QQ", |
|
| 234 "qq_process_get_buddies_online_reply: Dangerous error! maybe protocol changed, notify developers!"); |
|
| 235 } |
|
| 236 |
|
| 237 if (position != QQ_FRIENDS_ONLINE_POSITION_END) { |
|
| 238 gaim_debug(GAIM_DEBUG_INFO, "QQ", "Has more online buddies, position from %d", position); |
|
| 239 |
|
| 240 qq_send_packet_get_buddies_online(gc, position); |
|
| 241 } |
|
| 242 else { |
|
| 243 qq_refresh_all_buddy_status(gc); |
|
| 244 } |
|
| 245 |
|
| 246 } else { |
|
| 247 gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt buddies online"); |
|
| 248 } |
|
| 249 } |
|
| 250 |
|
| 251 /* process reply for get_buddies_list */ |
|
| 252 void qq_process_get_buddies_list_reply(guint8 *buf, gint buf_len, GaimConnection *gc) |
|
| 253 { |
|
| 254 qq_data *qd; |
|
| 255 qq_buddy *q_bud; |
|
| 256 gint len, bytes, bytes_expected, i; |
|
| 257 guint16 position, unknown; |
|
| 258 guint8 *data, *cursor, bar, pascal_len; |
|
| 259 gchar *name; |
|
| 260 GaimBuddy *b; |
|
| 261 |
|
| 262 g_return_if_fail(gc != NULL && gc->proto_data != NULL); |
|
| 263 g_return_if_fail(buf != NULL && buf_len != 0); |
|
| 264 |
|
| 265 qd = (qq_data *) gc->proto_data; |
|
| 266 len = buf_len; |
|
| 267 data = g_newa(guint8, len); |
|
| 268 cursor = data; |
|
| 269 |
|
| 270 if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { |
|
| 271 read_packet_w(data, &cursor, len, &position); |
|
| 272 /* the following data is buddy list in this packet */ |
|
| 273 i = 0; |
|
| 274 while (cursor < (data + len)) { |
|
| 275 q_bud = g_new0(qq_buddy, 1); |
|
| 276 bytes = 0; |
|
| 277 /* 000-003: uid */ |
|
| 278 bytes += read_packet_dw(data, &cursor, len, &q_bud->uid); |
|
| 279 /* 004-004: 0xff if buddy is self, 0x00 otherwise */ |
|
| 280 bytes += read_packet_b(data, &cursor, len, &bar); |
|
| 281 /* 005-005: icon index (1-255) */ |
|
| 282 bytes += read_packet_b(data, &cursor, len, &q_bud->icon); |
|
| 283 /* 006-006: age */ |
|
| 284 bytes += read_packet_b(data, &cursor, len, &q_bud->age); |
|
| 285 /* 007-007: gender */ |
|
| 286 bytes += read_packet_b(data, &cursor, len, &q_bud->gender); |
|
| 287 pascal_len = convert_as_pascal_string(cursor, &q_bud->nickname, QQ_CHARSET_DEFAULT); |
|
| 288 cursor += pascal_len; |
|
| 289 bytes += pascal_len; |
|
| 290 bytes += read_packet_w(data, &cursor, len, &unknown); |
|
| 291 /* flag1: (0-7) |
|
| 292 * bit1 => qq show |
|
| 293 * comm_flag: (0-7) |
|
| 294 * bit1 => member |
|
| 295 * bit4 => TCP mode |
|
| 296 * bit5 => open mobile QQ |
|
| 297 * bit6 => bind to mobile |
|
| 298 * bit7 => whether having a video |
|
| 299 */ |
|
| 300 bytes += read_packet_b(data, &cursor, len, &q_bud->flag1); |
|
| 301 bytes += read_packet_b(data, &cursor, len, &q_bud->comm_flag); |
|
| 302 |
|
| 303 bytes_expected = 12 + pascal_len; |
|
| 304 |
|
| 305 if (q_bud->uid == 0 || bytes != bytes_expected) { |
|
| 306 gaim_debug(GAIM_DEBUG_INFO, "QQ", |
|
| 307 "Buddy entry, expect %d bytes, read %d bytes\n", bytes_expected, bytes); |
|
| 308 g_free(q_bud->nickname); |
|
| 309 g_free(q_bud); |
|
| 310 continue; |
|
| 311 } else { |
|
| 312 i++; |
|
| 313 } |
|
| 314 |
|
| 315 if (QQ_DEBUG) { |
|
| 316 gaim_debug(GAIM_DEBUG_INFO, "QQ", |
|
| 317 "buddy [%09d]: flag1=0x%02x, comm_flag=0x%02x\n", |
|
| 318 q_bud->uid, q_bud->flag1, q_bud->comm_flag); |
|
| 319 } |
|
| 320 |
|
| 321 name = uid_to_gaim_name(q_bud->uid); |
|
| 322 b = gaim_find_buddy(gc->account, name); |
|
| 323 g_free(name); |
|
| 324 |
|
| 325 if (b == NULL) |
|
| 326 b = qq_add_buddy_by_recv_packet(gc, q_bud->uid, TRUE, FALSE); |
|
| 327 |
|
| 328 b->proto_data = q_bud; |
|
| 329 qd->buddies = g_list_append(qd->buddies, q_bud); |
|
| 330 qq_update_buddy_contact(gc, q_bud); |
|
| 331 } |
|
| 332 |
|
| 333 if(cursor > (data + len)) { |
|
| 334 gaim_debug(GAIM_DEBUG_ERROR, "QQ", |
|
| 335 "qq_process_get_buddies_list_reply: Dangerous error! maybe protocol changed, notify developers!"); |
|
| 336 } |
|
| 337 if (position == QQ_FRIENDS_LIST_POSITION_END) { |
|
| 338 gaim_debug(GAIM_DEBUG_INFO, "QQ", "Get friends list done, %d buddies\n", i); |
|
| 339 qq_send_packet_get_buddies_online(gc, QQ_FRIENDS_ONLINE_POSITION_START); |
|
| 340 } else { |
|
| 341 qq_send_packet_get_buddies_list(gc, position); |
|
| 342 } |
|
| 343 } else { |
|
| 344 gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt buddies list"); |
|
| 345 } |
|
| 346 } |
|
| 347 |
|
| 348 void qq_process_get_all_list_with_group_reply(guint8 *buf, gint buf_len, GaimConnection *gc) |
|
| 349 { |
|
| 350 qq_data *qd; |
|
| 351 gint len, i, j; |
|
| 352 guint8 *data, *cursor; |
|
| 353 guint8 sub_cmd, reply_code; |
|
| 354 guint32 unknown, position; |
|
| 355 guint32 uid; |
|
| 356 guint8 type, groupid; |
|
| 357 |
|
| 358 qq_buddy *q_bud; |
|
| 359 gchar *name; |
|
| 360 GaimBuddy *b; |
|
| 361 qq_group *group; |
|
| 362 |
|
| 363 g_return_if_fail(gc != NULL && gc->proto_data != NULL); |
|
| 364 g_return_if_fail(buf != NULL && buf_len != 0); |
|
| 365 |
|
| 366 qd = (qq_data *) gc->proto_data; |
|
| 367 len = buf_len; |
|
| 368 data = g_newa(guint8, len); |
|
| 369 cursor = data; |
|
| 370 |
|
| 371 if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { |
|
| 372 read_packet_b(data, &cursor, len, &sub_cmd); |
|
| 373 g_return_if_fail(sub_cmd == 0x01); |
|
| 374 read_packet_b(data, &cursor, len, &reply_code); |
|
| 375 if(0 != reply_code) { |
|
| 376 gaim_debug(GAIM_DEBUG_WARNING, "QQ", |
|
| 377 "Get all list with group reply, reply_code(%d) is not zero", reply_code); |
|
| 378 } |
|
| 379 read_packet_dw(data, &cursor, len, &unknown); |
|
| 380 read_packet_dw(data, &cursor, len, &position); |
|
| 381 /* the following data is all list in this packet */ |
|
| 382 i = 0; |
|
| 383 j = 0; |
|
| 384 while (cursor < (data + len)) { |
|
| 385 /* 00-03: uid */ |
|
| 386 read_packet_dw(data, &cursor, len, &uid); |
|
| 387 /* 04: type 0x1:buddy 0x4:Qun */ |
|
| 388 read_packet_b(data, &cursor, len, &type); |
|
| 389 /* 05: groupid*4 */ |
|
| 390 read_packet_b(data, &cursor, len, &groupid); |
|
| 391 groupid >>= 2; /* these 2 bits might not be 0, faint! */ |
|
| 392 if (uid == 0 || (type != 0x1 && type != 0x4)) { |
|
| 393 gaim_debug(GAIM_DEBUG_WARNING, "QQ", |
|
| 394 "Buddy entry, uid=%d, type=%d", uid, type); |
|
| 395 continue; |
|
| 396 } |
|
| 397 if(0x1 == type) { /* a buddy */ |
|
| 398 name = uid_to_gaim_name(uid); |
|
| 399 b = gaim_find_buddy(gc->account, name); |
|
| 400 g_free(name); |
|
| 401 |
|
| 402 if (b == NULL) { |
|
| 403 b = qq_add_buddy_by_recv_packet(gc, uid, TRUE, TRUE); |
|
| 404 q_bud = b->proto_data; |
|
| 405 } |
|
| 406 else { |
|
| 407 q_bud = NULL; |
|
| 408 b->proto_data = q_bud; /* wrong !!!! */ |
|
| 409 } |
|
| 410 qd->buddies = g_list_append(qd->buddies, q_bud); |
|
| 411 qq_update_buddy_contact(gc, q_bud); |
|
| 412 ++i; |
|
| 413 } else { /* a group */ |
|
| 414 group = qq_group_find_by_internal_group_id(gc, uid); |
|
| 415 if(group == NULL) { |
|
| 416 /*XXX not working |
|
| 417 group = qq_group_create_by_id(gc, uid, 0); |
|
| 418 qq_send_cmd_group_get_group_info(gc, group); |
|
| 419 */ |
|
| 420 gaim_debug(GAIM_DEBUG_ERROR, "QQ", |
|
| 421 "Get a Qun with internal group %d\n", uid); |
|
| 422 gaim_notify_info(gc, _("QQ Qun Operation"), |
|
| 423 _("Find one Qun in the server list, but i don't know its external id, please re-rejoin it manually"), NULL); |
|
| 424 } else { |
|
| 425 group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER; |
|
| 426 qq_group_refresh(gc, group); |
|
| 427 qq_send_cmd_group_get_group_info(gc, group); |
|
| 428 } |
|
| 429 ++j; |
|
| 430 } |
|
| 431 } |
|
| 432 if(cursor > (data + len)) { |
|
| 433 gaim_debug(GAIM_DEBUG_ERROR, "QQ", |
|
| 434 "qq_process_get_all_list_with_group_reply: Dangerous error! maybe protocol changed, notify developers!"); |
|
| 435 } |
|
| 436 gaim_debug(GAIM_DEBUG_INFO, "QQ", "Get all list done, %d buddies and %d Quns\n", i, j); |
|
| 437 } else { |
|
| 438 gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt all list with group"); |
|
| 439 } |
|
| 440 } |
|