view src/protocols/qq/buddy_status.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 2be9dfa9569b
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
#include "debug.h"		// gaim_debug
#include "prefs.h"		// gaim_prefs_get_bool

#include "utils.h"		// get_ip_str
#include "packet_parse.h"	// create_packet,
#include "buddy_status.h"
#include "crypt.h"		// qq_crypt.h
#include "header_info.h"	// cmd alias
#include "keep_alive.h"		// qq_update_buddy_contact
#include "send_core.h"		// qq_send_cmd

#define QQ_MISC_STATUS_HAVING_VIIDEO      0x00000001

#define QQ_ICON_SUFFIX_DEFAULT            QQ_ICON_SUFFIX_OFFLINE
#define QQ_CHANGE_ONLINE_STATUS_REPLY_OK 	0x30	// ASCii value of "0"

enum {
	QQ_ICON_SUFFIX_NORMAL = 1,
	QQ_ICON_SUFFIX_OFFLINE = 2,
	QQ_ICON_SUFFIX_AWAY = 3,
};

/*****************************************************************************/
static void _qq_buddy_status_dump_unclear(qq_buddy_status * s)
{
	GString *dump;

	g_return_if_fail(s != NULL);

	dump = g_string_new("");
	g_string_append_printf(dump, "unclear fields for [%d]:\n", s->uid);
	g_string_append_printf(dump, "004:     %02x   (unknown)\n", s->unknown1);
	g_string_append_printf(dump, "011:     %02x   (unknown)\n", s->unknown2);
	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Buddy status entry, %s", dump->str);
	g_string_free(dump, TRUE);
}				// _qq_buddy_status_dump_unclear

/*****************************************************************************/
// parse the data into qq_buddy_status
gint _qq_buddy_status_read(guint8 * data, guint8 ** cursor, gint len, qq_buddy_status * s) {
	gint bytes;

	g_return_val_if_fail(data != NULL && *cursor != NULL && s != NULL, -1);

	bytes = 0;

	// 000-003: uid
	bytes += read_packet_dw(data, cursor, len, &s->uid);
	// 004-004: 0x01
	bytes += read_packet_b(data, cursor, len, &s->unknown1);
	// 005-008: ip
	s->ip = g_new0(guint8, 4);
	bytes += read_packet_data(data, cursor, len, s->ip, 4);
	// 009-010: port
	bytes += read_packet_w(data, cursor, len, &s->port);
	// 011-011: 0x00
	bytes += read_packet_b(data, cursor, len, &s->unknown2);
	// 012-012: status
	bytes += read_packet_b(data, cursor, len, &s->status);
	// 013-014: client_version 
	bytes += read_packet_w(data, cursor, len, &s->client_version);
	// 015-030: unknown key
	s->unknown_key = g_new0(guint8, QQ_KEY_LENGTH);
	bytes += read_packet_data(data, cursor, len, s->unknown_key, QQ_KEY_LENGTH);

	if (s->uid == 0 || bytes != 31)
		return -1;

	return bytes;

}				// _qq_buddy_status_read

/*****************************************************************************/
// check if status means online or offline
gboolean is_online(guint8 status)
{
//	return (status == QQ_BUDDY_ONLINE_NORMAL ? TRUE : (status == QQ_BUDDY_ONLINE_AWAY ? TRUE : FALSE));
	//rewritten by gfhuang
	switch(status) {
	case QQ_BUDDY_ONLINE_NORMAL:
	case QQ_BUDDY_ONLINE_AWAY:
	case QQ_BUDDY_ONLINE_INVISIBLE:
		return TRUE;
	case QQ_BUDDY_ONLINE_OFFLINE:
		return FALSE;
	}
	return FALSE;
}				// is_online

/*****************************************************************************/
// the icon suffix is detemined by status
// although QQ server may return the right icon, I set it here myself
gchar get_suffix_from_status(guint8 status)
{
	switch (status) {
	case QQ_BUDDY_ONLINE_NORMAL:
		return QQ_ICON_SUFFIX_NORMAL;
	case QQ_BUDDY_ONLINE_AWAY:
		return QQ_ICON_SUFFIX_AWAY;
	case QQ_BUDDY_ONLINE_INVISIBLE:
	case QQ_BUDDY_ONLINE_OFFLINE:
		return QQ_ICON_SUFFIX_OFFLINE;
	default:
		return QQ_ICON_SUFFIX_DEFAULT;
	}			// switch
}				// get_suffix_from_status

/*****************************************************************************/
// send a packet to change my online status
void qq_send_packet_change_status(GaimConnection * gc)
{
	qq_data *qd;
	guint8 *raw_data, *cursor, away_cmd;
	guint32 misc_status;
	gboolean fake_video;

	g_return_if_fail(gc != NULL && gc->proto_data != NULL);

	qd = (qq_data *) gc->proto_data;
	if (!qd->logged_in)
		return;

	switch (qd->status) {
	case QQ_SELF_STATUS_AVAILABLE:
		away_cmd = QQ_BUDDY_ONLINE_NORMAL;
		break;
	case QQ_SELF_STATUS_INVISIBLE:
		away_cmd = QQ_BUDDY_ONLINE_INVISIBLE;
		break;
	case QQ_SELF_STATUS_AWAY:
	case QQ_SELF_STATUS_IDLE:
	case QQ_SELF_STATUS_CUSTOM:
		away_cmd = QQ_BUDDY_ONLINE_AWAY;
		break;
	default:
		away_cmd = QQ_BUDDY_ONLINE_NORMAL;
	}			// switch

	raw_data = g_new0(guint8, 5);
	cursor = raw_data;
	misc_status = 0x00000000;

	fake_video = gaim_prefs_get_bool("/plugins/prpl/qq/show_fake_video");
	if (fake_video)
		misc_status |= QQ_MISC_STATUS_HAVING_VIIDEO;

	create_packet_b(raw_data, &cursor, away_cmd);
	create_packet_dw(raw_data, &cursor, misc_status);

	qq_send_cmd(gc, QQ_CMD_CHANGE_ONLINE_STATUS, TRUE, 0, TRUE, raw_data, 5);

	g_free(raw_data);
}				// qq_send_packet_change_status

/*****************************************************************************/
// parse the reply packet for change_status
void qq_process_change_status_reply(guint8 * buf, gint buf_len, GaimConnection * gc) {
	qq_data *qd;
	gint len;
	guint8 *data, *cursor, reply;

	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);

	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
		cursor = data;
		read_packet_b(data, &cursor, len, &reply);
		if (reply != QQ_CHANGE_ONLINE_STATUS_REPLY_OK) {
			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Change status fail\n");
		} else
			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Change status OK\n");
	} else
		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt chg status reply\n");

}				// qq_process_change_status_reply

/*****************************************************************************/
// it is a server message 
// indicating that one of my buddies changes its status
void qq_process_friend_change_status(guint8 * buf, gint buf_len, GaimConnection * gc) {
	qq_data *qd;
	gint len, bytes;
	guint32 my_uid;
	guint8 *data, *cursor;
	GaimBuddy *b;
	qq_buddy *q_bud;
	qq_buddy_status *s;
	gchar *name;

	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)) {
		s = g_new0(qq_buddy_status, 1);
		bytes = 0;
		// 000-030: qq_buddy_status;
		bytes += _qq_buddy_status_read(data, &cursor, len, s);
		// 031-034: my uid
		bytes += read_packet_dw(data, &cursor, len, &my_uid);

		if (my_uid == 0 || bytes != 35) {
			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "my_uid == 0 || bytes(%d) != 35\n", bytes);
			g_free(s->ip);
			g_free(s->unknown_key);
			g_free(s);
			return;
		}
//		if (QQ_DEBUG)						gfhuang
//			_qq_buddy_status_dump_unclear(s);

		name = uid_to_gaim_name(s->uid);	//by gfhuang
		b = gaim_find_buddy(gc->account, name);
		g_free(name);
		q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
		if (q_bud) {
			gaim_debug(GAIM_DEBUG_INFO, "QQ", "s->uid = %d, q_bud->uid = %d\n", s->uid , q_bud->uid);
			if(0 != *((guint32 *)s->ip)) { //by gfhuang
				g_memmove(q_bud->ip, s->ip, 4);
				q_bud->port = s->port;
			}
			q_bud->status = s->status;
			if(0 != s->client_version) 
				q_bud->client_version = s->client_version;  //gfhuang
			qq_update_buddy_contact(gc, q_bud);
		}
		else 
			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "got information of unknown buddy by gfhuang %d\n", s->uid);

		g_free(s->ip);
		g_free(s->unknown_key);
		g_free(s);
	} else
		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt buddy status change packet\n");

}				// qq_process_friend_change_status

/*****************************************************************************/
// END OF FILE