view libpurple/protocols/qq/qq_base.c @ 28213:33921125348d

The output buffer passed to qq_encrypt needs to be 17 bytes bigger than the data you're encrypting, not 16 bytes bigger. Fixes #10191. It's hard to say whether this actually causes problems. My guess is that it does not. However, the way the qq protocol plugin constructs the plain text buffer to be passed to qq_encrypt is error prone, and the many calls to g_newa(guint8, MAX_PACKET_SIZE) are really bad because MAX_PACKET_SIZE is 64KB. This is a ridiculous amount of space to request on the stack. All these qq_put8 qq_put16 qq_put32 qq_putdata functions should be changed to insert data into a dynamically allocated GString instead of the stack-allocated buffers that they use now. This eliminates the potential for accidentally overwriting the end of the buffer. And the second g_newa() for the output buffer passed into qq_encrypt() should be changed to allocate space on the heap in most places because, as previously noted, 64KB is a ridiculous amount of memory to request from the stack. Heap allocation may be expensive when compared to stack allocation, but I feel it's usually worth it to eliminate the possibilty of buffer overflow.
author Mark Doliner <mark@kingant.net>
date Fri, 04 Sep 2009 22:50:26 +0000
parents f541583e31bd
children f1437342cc0e
line wrap: on
line source

/**
 * @file qq_base.c
 *
 * 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 "debug.h"
#include "internal.h"
#include "server.h"
#include "cipher.h"
#include "request.h"

#include "buddy_info.h"
#include "buddy_list.h"
#include "char_conv.h"
#include "qq_crypt.h"
#include "group.h"
#include "qq_define.h"
#include "qq_network.h"
#include "qq_base.h"
#include "packet_parse.h"
#include "qq.h"
#include "qq_network.h"
#include "utils.h"

/* generate a md5 key using uid and session_key */
static void get_session_md5(guint8 *session_md5, guint32 uid, guint8 *session_key)
{
	guint8 src[QQ_KEY_LENGTH + QQ_KEY_LENGTH];
	gint bytes = 0;

	bytes += qq_put32(src + bytes, uid);
	bytes += qq_putdata(src + bytes, session_key, QQ_KEY_LENGTH);

	qq_get_md5(session_md5, QQ_KEY_LENGTH, src, bytes);
}

/* process login reply which says OK */
static gint8 process_login_ok(PurpleConnection *gc, guint8 *data, gint len)
{
	qq_data *qd;
	gint bytes;

	guint8 ret;
	guint32 uid;
	struct in_addr ip;
	guint16 port;
	struct tm *tm_local;

	qd = (qq_data *) gc->proto_data;
	/* qq_show_packet("Login reply", data, len); */

	if (len < 148) {
		qq_show_packet("Login reply OK, but length < 139", data, len);
		purple_connection_error_reason(gc,
				PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
				_("Unable to decrypt server reply"));
		return QQ_LOGIN_REPLY_ERR;
	}

	bytes = 0;
	bytes += qq_get8(&ret, data + bytes);
	bytes += qq_getdata(qd->session_key, sizeof(qd->session_key), data + bytes);
	get_session_md5(qd->session_md5, qd->uid, qd->session_key);
	purple_debug_info("QQ", "Got session_key\n");
	bytes += qq_get32(&uid, data + bytes);
	if (uid != qd->uid) {
		purple_debug_warning("QQ", "My uid in login reply is %u, not %u\n", uid, qd->uid);
	}
	bytes += qq_getIP(&qd->my_ip, data + bytes);
	bytes += qq_get16(&qd->my_port, data + bytes);
	purple_debug_info("QQ", "Internet IP: %s, %d\n", inet_ntoa(qd->my_ip), qd->my_port);

	bytes += qq_getIP(&qd->my_local_ip, data + bytes);
	bytes += qq_get16(&qd->my_local_port, data + bytes);
	purple_debug_info("QQ", "Local IP: %s, %d\n", inet_ntoa(qd->my_local_ip), qd->my_local_port);

	bytes += qq_getime(&qd->login_time, data + bytes);
	tm_local = localtime(&qd->login_time);
	purple_debug_info("QQ", "Login time: %d-%d-%d, %d:%d:%d\n",
			(1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday,
			tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec);
	/* skip unknown 2 bytes, 0x(03 0a) */
	bytes += 2;
	/* skip unknown 24 bytes, maybe token to access Qun shared files */
	bytes += 24;
	/* unknow ip and port */
	bytes += qq_getIP(&ip, data + bytes);
	bytes += qq_get16(&port, data + bytes);
	purple_debug_info("QQ", "Unknow IP: %s, %d\n", inet_ntoa(ip), port);
	/* unknow ip and port */
	bytes += qq_getIP(&ip, data + bytes);
	bytes += qq_get16(&port, data + bytes);
	purple_debug_info("QQ", "Unknow IP: %s, %d\n", inet_ntoa(ip), port);
	/* unknown 4 bytes, 0x(00 81 00 00)*/
	bytes += 4;
	/* skip unknown 32 bytes, maybe key to access QQ Home */
	bytes += 32;
	/* skip unknown 16 bytes, 0x(00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 00) */
	bytes += 16;
	/* time */
	bytes += qq_getime(&qd->last_login_time[0], data + bytes);
	tm_local = localtime(&qd->last_login_time[0]);
	purple_debug_info("QQ", "Last login time: %d-%d-%d, %d:%d:%d\n",
			(1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday,
			tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec);
	/* unknow time */
	g_return_val_if_fail(sizeof(qd->last_login_time) / sizeof(time_t) > 1, QQ_LOGIN_REPLY_OK);
	bytes += qq_getime(&qd->last_login_time[1], data + bytes);
	tm_local = localtime(&qd->last_login_time[1]);
	purple_debug_info("QQ", "Time: %d-%d-%d, %d:%d:%d\n",
			(1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday,
			tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec);

	g_return_val_if_fail(sizeof(qd->last_login_time) / sizeof(time_t) > 2, QQ_LOGIN_REPLY_OK);
	bytes += qq_getime(&qd->last_login_time[2], data + bytes);
	tm_local = localtime(&qd->last_login_time[2]);
	purple_debug_info("QQ", "Time: %d-%d-%d, %d:%d:%d\n",
			(1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday,
			tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec);
	/* unknow 9 bytes, 0x(00 0a 00 0a 01 00 00 0e 10) */

	if (len > 148) {
		qq_show_packet("Login reply OK, but length > 139", data, len);
	}
	return QQ_LOGIN_REPLY_OK;
}

/* process login reply packet which includes redirected new server address */
static gint8 process_login_redirect(PurpleConnection *gc, guint8 *data, gint len)
{
	qq_data *qd;
	gint bytes;
	struct {
		guint8 result;
		guint32 uid;
		struct in_addr new_server_ip;
		guint16 new_server_port;
	} packet;


	if (len < 11) {
		purple_connection_error_reason(gc,
				PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
				_("Unable to decrypt server reply"));
		return QQ_LOGIN_REPLY_ERR;
	}

	qd = (qq_data *) gc->proto_data;
	bytes = 0;
	/* 000-000: reply code */
	bytes += qq_get8(&packet.result, data + bytes);
	/* 001-004: login uid */
	bytes += qq_get32(&packet.uid, data + bytes);
	/* 005-008: redirected new server IP */
	bytes += qq_getIP(&packet.new_server_ip, data + bytes);
	/* 009-010: redirected new server port */
	bytes += qq_get16(&packet.new_server_port, data + bytes);

	if (len > 11) {
		purple_debug_error("QQ", "Login redirect more than expected %d bytes, read %d bytes\n", 11, bytes);
	}

	/* redirect to new server, do not disconnect or connect here
	 * those connect should be called at packet_process */
	qd->redirect_ip.s_addr = packet.new_server_ip.s_addr;
	qd->redirect_port = packet.new_server_port;
	return QQ_LOGIN_REPLY_REDIRECT;
}

/* request before login */
void qq_request_token(PurpleConnection *gc)
{
	qq_data *qd;
	guint8 buf[16] = {0};
	gint bytes = 0;

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

	bytes += qq_put8(buf + bytes, 0);

	qd->send_seq++;
	qq_send_cmd_encrypted(gc, QQ_CMD_TOKEN, qd->send_seq, buf, bytes, TRUE);
}

/* send login packet to QQ server */
void qq_request_login(PurpleConnection *gc)
{
	qq_data *qd;
	guint8 *buf, *raw_data;
	gint bytes;
	guint8 *encrypted;
	gint encrypted_len;

	/* for QQ 2005? copy from lumaqq */
	static const guint8 login_23_51[29] = {
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x86, 0xcc, 0x4c, 0x35,
			0x2c, 0xd3, 0x73, 0x6c, 0x14, 0xf6, 0xf6, 0xaf,
			0xc3, 0xfa, 0x33, 0xa4, 0x01
	};

	static const guint8 login_53_68[16] = {
 			0x8D, 0x8B, 0xFA, 0xEC, 0xD5, 0x52, 0x17, 0x4A,
 			0x86, 0xF9, 0xA7, 0x75, 0xE6, 0x32, 0xD1, 0x6D
	};

	static const guint8 login_100_bytes[100] = {
		0x40, 0x0B, 0x04, 0x02, 0x00, 0x01, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x03, 0x09, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x01, 0xE9, 0x03, 0x01,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF3, 0x03,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xED,
		0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
		0xEC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x03, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x01, 0xEE, 0x03, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x01, 0xEF, 0x03, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x01, 0xEB, 0x03, 0x00,
		0x00, 0x00, 0x00, 0x00
	};

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

	g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0);

	raw_data = g_newa(guint8, MAX_PACKET_SIZE - 17);
	memset(raw_data, 0, MAX_PACKET_SIZE - 17);

	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 17 bytes more */

	bytes = 0;
	/* now generate the encrypted data
	 * 000-015 use password_twice_md5 as key to encrypt empty string */
	encrypted_len = qq_encrypt(encrypted, (guint8 *) "", 0, qd->ld.pwd_twice_md5);
	g_return_if_fail(encrypted_len == 16);
	bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len);

	/* 016-016 */
	bytes += qq_put8(raw_data + bytes, 0x00);
	/* 017-020, used to be IP, now zero */
	bytes += qq_put32(raw_data + bytes, 0x00000000);
	/* 021-022, used to be port, now zero */
	bytes += qq_put16(raw_data + bytes, 0x0000);
	/* 023-051, fixed value, unknown */
	bytes += qq_putdata(raw_data + bytes, login_23_51, 29);
	/* 052-052, login mode */
	bytes += qq_put8(raw_data + bytes, qd->login_mode);
	/* 053-068, fixed value, maybe related to per machine */
	bytes += qq_putdata(raw_data + bytes, login_53_68, 16);
	/* 069, login token length */
	bytes += qq_put8(raw_data + bytes, qd->ld.token_len);
	/* 070-093, login token, normally 24 bytes */
	bytes += qq_putdata(raw_data + bytes, qd->ld.token, qd->ld.token_len);
	/* 100 bytes unknown */
	bytes += qq_putdata(raw_data + bytes, login_100_bytes, 100);
	/* all zero left */
	memset(raw_data + bytes, 0, 416 - bytes);
	bytes = 416;

	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key);

	buf = g_newa(guint8, MAX_PACKET_SIZE);
	memset(buf, 0, MAX_PACKET_SIZE);
	bytes = 0;
	bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH);
	bytes += qq_putdata(buf + bytes, encrypted, encrypted_len);

	qd->send_seq++;
	qq_send_cmd_encrypted(gc, QQ_CMD_LOGIN, qd->send_seq, buf, bytes, TRUE);
}

guint8 qq_process_token(PurpleConnection *gc, guint8 *buf, gint buf_len)
{
	qq_data *qd;
	gint bytes;
	guint8 ret;
	guint8 token_len;
	gchar *msg;

	g_return_val_if_fail(buf != NULL && buf_len != 0, QQ_LOGIN_REPLY_ERR);

	g_return_val_if_fail(gc != NULL  && gc->proto_data != NULL, QQ_LOGIN_REPLY_ERR);
	qd = (qq_data *) gc->proto_data;

	bytes = 0;
	bytes += qq_get8(&ret, buf + bytes);
	bytes += qq_get8(&token_len, buf + bytes);

	if (ret != QQ_LOGIN_REPLY_OK) {
		qq_show_packet("Failed requesting token", buf, buf_len);

		msg = g_strdup_printf( _("Failed requesting token, 0x%02X"), ret );
		purple_connection_error_reason(gc,
				PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
				msg);
		g_free(msg);
		return QQ_LOGIN_REPLY_ERR;
	}

	if (bytes + token_len < buf_len) {
		msg = g_strdup_printf( _("Invalid token len, %d"), token_len);
		purple_connection_error_reason(gc,
				PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
				msg);
		g_free(msg);
		return QQ_LOGIN_REPLY_ERR;
	}

	if (bytes + token_len > buf_len) {
		purple_debug_info("QQ", "Extra token data, %d %d\n", token_len, buf_len - bytes);
	}
	/* qq_show_packet("Got token", buf + bytes, buf_len - bytes); */

	if (qd->ld.token != NULL) {
		g_free(qd->ld.token);
		qd->ld.token = NULL;
		qd->ld.token_len = 0;
	}
	qd->ld.token = g_new0(guint8, token_len);
	qd->ld.token_len = token_len;
	g_memmove(qd->ld.token, buf + 2, qd->ld.token_len);
	return ret;
}

/* send logout packets to QQ server */
void qq_request_logout(PurpleConnection *gc)
{
	gint i;
	qq_data *qd;

	qd = (qq_data *) gc->proto_data;
	for (i = 0; i < 4; i++)
		qq_send_cmd(gc, QQ_CMD_LOGOUT, qd->ld.pwd_twice_md5, QQ_KEY_LENGTH);

	qd->is_login = FALSE;	/* update login status AFTER sending logout packets */
}

/* for QQ 2003iii 0117, fixed value */
/* static const guint8 login_23_51[29] = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0xbf, 0x14, 0x11, 0x20,
	0x03, 0x9d, 0xb2, 0xe6, 0xb3, 0x11, 0xb7, 0x13,
	0x95, 0x67, 0xda, 0x2c, 0x01
}; */

/* for QQ 2003iii 0304, fixed value */
/*
static const guint8 login_23_51[29] = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x9a, 0x93, 0xfe, 0x85,
	0xd3, 0xd9, 0x2a, 0x41, 0xc8, 0x0d, 0xff, 0xb6,
	0x40, 0xb8, 0xac, 0x32, 0x01
};
*/

/* fixed value, not affected by version, or mac address */
/*
static const guint8 login_53_68[16] = {
	0x82, 0x2a, 0x91, 0xfd, 0xa5, 0xca, 0x67, 0x4c,
	0xac, 0x81, 0x1f, 0x6f, 0x52, 0x05, 0xa7, 0xbf
};
*/

/* process the login reply packet */
guint8 qq_process_login( PurpleConnection *gc, guint8 *data, gint data_len)
{
	qq_data *qd;
	guint8 ret = data[0];
	gchar *msg, *msg_utf8;
	gchar *error;
	PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;

	g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR);

	qd = (qq_data *) gc->proto_data;

	switch (ret) {
		case QQ_LOGIN_REPLY_OK:
			purple_debug_info("QQ", "Login OK\n");
			return process_login_ok(gc, data, data_len);
		case QQ_LOGIN_REPLY_REDIRECT:
			purple_debug_info("QQ", "Redirect new server\n");
			return process_login_redirect(gc, data, data_len);

		case 0x0A:		/* extend redirect used in QQ2006 */
			error = g_strdup( _("Redirect_EX is not currently supported") );
			reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
			break;
		case 0x05:		/* invalid password */
			if (!purple_account_get_remember_password(gc->account)) {
				purple_account_set_password(gc->account, NULL);
			}
			error = g_strdup( _("Incorrect password"));
			reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
			break;
		case 0x06:		/* need activation */
			error = g_strdup( _("Activation required"));
			reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
			break;

		default:
			qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", data, data_len,
					">>> [default] decrypt and dump");
			error = g_strdup_printf(
						_("Unknown reply code when logging in (0x%02X)"),
						ret );
			reason = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
			break;
	}

	msg = g_strndup((gchar *)data + 1, data_len - 1);
	msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);

	purple_debug_error("QQ", "%s: %s\n", error, msg_utf8);
	purple_connection_error_reason(gc, reason, msg_utf8);

	g_free(error);
	g_free(msg);
	g_free(msg_utf8);
	return QQ_LOGIN_REPLY_ERR;
}

/* send keep-alive packet to QQ server (it is a heart-beat) */
void qq_request_keep_alive(PurpleConnection *gc)
{
	qq_data *qd;
	guint8 raw_data[16] = {0};
	gint bytes= 0;

	qd = (qq_data *) gc->proto_data;

	/* In fact, we can send whatever we like to server
	 * with this command, server return the same result including
	 * the amount of online QQ users, my ip and port */
	bytes += qq_put32(raw_data + bytes, qd->uid);
	qq_send_cmd(gc, QQ_CMD_KEEP_ALIVE, raw_data, bytes);
}

/* parse the return ofqq_process_keep_alive keep-alive packet, it includes some system information */
gboolean qq_process_keep_alive(guint8 *data, gint data_len, PurpleConnection *gc)
{
	qq_data *qd;
	gchar **segments;

	g_return_val_if_fail(data != NULL, FALSE);
	g_return_val_if_fail(data_len != 0, FALSE);

	qd = (qq_data *) gc->proto_data;

	/* qq_show_packet("Keep alive reply packet", data, len); */

	/* the last one is 60, don't know what it is */
	segments = split_data(data, data_len, "\x1f", 6);
	if (segments == NULL)
			return TRUE;

	/* segments[0] and segment[1] are all 0x30 ("0") */
	qd->online_total = strtol(segments[2], NULL, 10);
	if(0 == qd->online_total) {
		purple_connection_error_reason(gc,
				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
				_("Lost connection with server"));
	}
	qd->my_ip.s_addr = inet_addr(segments[3]);
	qd->my_port = strtol(segments[4], NULL, 10);

	purple_debug_info("QQ", "keep alive, %s:%d\n",
		inet_ntoa(qd->my_ip), qd->my_port);

	g_strfreev(segments);
	return TRUE;
}

void qq_request_keep_alive_2007(PurpleConnection *gc)
{
	qq_data *qd;
	guint8 raw_data[32] = {0};
	gint bytes= 0;
	gchar *uid_str;

	qd = (qq_data *) gc->proto_data;

	/* In fact, we can send whatever we like to server
	 * with this command, server return the same result including
	 * the amount of online QQ users, my ip and port */
	uid_str = g_strdup_printf("%u", qd->uid);
	bytes += qq_putdata(raw_data + bytes, (guint8 *)uid_str, strlen(uid_str));
	qq_send_cmd(gc, QQ_CMD_KEEP_ALIVE, raw_data, bytes);

	g_free(uid_str);
}

gboolean qq_process_keep_alive_2007(guint8 *data, gint data_len, PurpleConnection *gc)
{
	qq_data *qd;
	gint bytes= 0;
	guint8 ret;

	g_return_val_if_fail(data != NULL && data_len != 0, FALSE);

	qd = (qq_data *) gc->proto_data;

	/* qq_show_packet("Keep alive reply packet", data, len); */

	bytes = 0;
	bytes += qq_get8(&ret, data + bytes);
	bytes += qq_get32(&qd->online_total, data + bytes);
	if(0 == qd->online_total) {
		purple_connection_error_reason(gc,
				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
				_("Lost connection with server"));
	}

	bytes += qq_getIP(&qd->my_ip, data + bytes);
	bytes += qq_get16(&qd->my_port, data + bytes);
	return TRUE;
}

void qq_request_keep_alive_2008(PurpleConnection *gc)
{
	qq_data *qd;
	guint8 raw_data[16] = {0};
	gint bytes= 0;

	qd = (qq_data *) gc->proto_data;

	/* In fact, we can send whatever we like to server
	 * with this command, server return the same result including
	 * the amount of online QQ users, my ip and port */
	bytes += qq_put32(raw_data + bytes, qd->uid);
	bytes += qq_putime(raw_data + bytes, &qd->login_time);
	qq_send_cmd(gc, QQ_CMD_KEEP_ALIVE, raw_data, bytes);
}

gboolean qq_process_keep_alive_2008(guint8 *data, gint data_len, PurpleConnection *gc)
{
	qq_data *qd;
	gint bytes= 0;
	guint8 ret;
	time_t server_time;
	struct tm *tm_local;

	g_return_val_if_fail(data != NULL && data_len != 0, FALSE);

	qd = (qq_data *) gc->proto_data;

	/* qq_show_packet("Keep alive reply packet", data, len); */

	bytes = 0;
	bytes += qq_get8(&ret, data + bytes);
	bytes += qq_get32(&qd->online_total, data + bytes);
	if(0 == qd->online_total) {
		purple_connection_error_reason(gc,
				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
				_("Lost connection with server"));
	}

	bytes += qq_getIP(&qd->my_ip, data + bytes);
	bytes += qq_get16(&qd->my_port, data + bytes);
	/* skip 2 byytes, 0x(00 3c) */
	bytes += 2;
	bytes += qq_getime(&server_time, data + bytes);
	/* skip 5 bytes, all are 0 */

	purple_debug_info("QQ", "keep alive, %s:%d\n",
		inet_ntoa(qd->my_ip), qd->my_port);

	tm_local = localtime(&server_time);
	purple_debug_info("QQ", "Server time: %d-%d-%d, %d:%d:%d\n",
			(1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday,
			tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec);
	return TRUE;
}

/* For QQ2007/2008 */
void qq_request_get_server(PurpleConnection *gc)
{
	qq_data *qd;
	guint8 *buf, *raw_data;
	gint bytes;
	guint8 *encrypted;
	gint encrypted_len;

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

	raw_data = g_newa(guint8, 128);
	memset(raw_data, 0, 128);

	encrypted = g_newa(guint8, 128 + 17);	/* 17 bytes more */

	bytes = 0;
	if (qd->redirect == NULL) {
		/* first packet to get server */
		qd->redirect_len = 15;
		qd->redirect = g_realloc(qd->redirect, qd->redirect_len);
		memset(qd->redirect, 0, qd->redirect_len);
	}
	bytes += qq_putdata(raw_data + bytes, qd->redirect, qd->redirect_len);

	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key);

	buf = g_newa(guint8, MAX_PACKET_SIZE);
	memset(buf, 0, MAX_PACKET_SIZE);
	bytes = 0;
	bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH);
	bytes += qq_putdata(buf + bytes, encrypted, encrypted_len);

	qd->send_seq++;
	qq_send_cmd_encrypted(gc, QQ_CMD_GET_SERVER, qd->send_seq, buf, bytes, TRUE);
}

guint16 qq_process_get_server(PurpleConnection *gc, guint8 *data, gint data_len)
{
	qq_data *qd;
	gint bytes;
	guint16 ret;

	g_return_val_if_fail (gc != NULL && gc->proto_data != NULL, QQ_LOGIN_REPLY_ERR);
	qd = (qq_data *) gc->proto_data;

	g_return_val_if_fail (data != NULL, QQ_LOGIN_REPLY_ERR);

	/* qq_show_packet("Get Server", data, data_len); */
	bytes = 0;
	bytes += qq_get16(&ret, data + bytes);
	if (ret == 0) {
		/* Notice: do not clear redirect_data here. It will be used in login*/
		qd->redirect_ip.s_addr = 0;
		return QQ_LOGIN_REPLY_OK;
	}

	if (data_len < 15) {
		purple_connection_error_reason(gc,
				PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
				_("Unable to decrypt server reply"));
		return QQ_LOGIN_REPLY_ERR;
	}

	qd->redirect_len = data_len;
	qd->redirect = g_realloc(qd->redirect, qd->redirect_len);
	qq_getdata(qd->redirect, qd->redirect_len, data);
	/* qq_show_packet("Redirect to", qd->redirect, qd->redirect_len); */

	qq_getIP(&qd->redirect_ip, data + 11);
	purple_debug_info("QQ", "Get server %s\n", inet_ntoa(qd->redirect_ip));
	return QQ_LOGIN_REPLY_REDIRECT;
}

void qq_request_token_ex(PurpleConnection *gc)
{
	qq_data *qd;
	guint8 *buf, *raw_data;
	gint bytes;
	guint8 *encrypted;
	gint encrypted_len;

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

	g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0);

	raw_data = g_newa(guint8, MAX_PACKET_SIZE - 17);
	memset(raw_data, 0, MAX_PACKET_SIZE - 17);

	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 17 bytes more */

	bytes = 0;
	bytes += qq_put8(raw_data + bytes, qd->ld.token_len);
	bytes += qq_putdata(raw_data + bytes, qd->ld.token, qd->ld.token_len);
	bytes += qq_put8(raw_data + bytes, 3); 		/* Subcommand */
	bytes += qq_put16(raw_data + bytes, 5);
	bytes += qq_put32(raw_data + bytes, 0);
	bytes += qq_put8(raw_data + bytes, 0); 		/* fragment index */
	bytes += qq_put16(raw_data + bytes, 0); 	/* captcha token */

	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key);

	buf = g_newa(guint8, MAX_PACKET_SIZE);
	memset(buf, 0, MAX_PACKET_SIZE);
	bytes = 0;
	bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH);
	bytes += qq_putdata(buf + bytes, encrypted, encrypted_len);

	qd->send_seq++;
	qq_send_cmd_encrypted(gc, QQ_CMD_TOKEN_EX, qd->send_seq, buf, bytes, TRUE);
}

void qq_request_token_ex_next(PurpleConnection *gc)
{
	qq_data *qd;
	guint8 *buf, *raw_data;
	gint bytes;
	guint8 *encrypted;
	gint encrypted_len;

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

	g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0);

	raw_data = g_newa(guint8, MAX_PACKET_SIZE - 17);
	memset(raw_data, 0, MAX_PACKET_SIZE - 17);

	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 17 bytes more */

	bytes = 0;
	bytes += qq_put8(raw_data + bytes, qd->ld.token_len);
	bytes += qq_putdata(raw_data + bytes, qd->ld.token, qd->ld.token_len);
	bytes += qq_put8(raw_data + bytes, 3); 		/* Subcommand */
	bytes += qq_put16(raw_data + bytes, 5);
	bytes += qq_put32(raw_data + bytes, 0);
	bytes += qq_put8(raw_data + bytes, qd->captcha.next_index); 		/* fragment index */
	bytes += qq_put16(raw_data + bytes, qd->captcha.token_len); 	/* captcha token */
	bytes += qq_putdata(raw_data + bytes, qd->captcha.token, qd->captcha.token_len);

	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key);

	buf = g_newa(guint8, MAX_PACKET_SIZE);
	memset(buf, 0, MAX_PACKET_SIZE);
	bytes = 0;
	bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH);
	bytes += qq_putdata(buf + bytes, encrypted, encrypted_len);

	qd->send_seq++;
	qq_send_cmd_encrypted(gc, QQ_CMD_TOKEN_EX, qd->send_seq, buf, bytes, TRUE);

	purple_connection_update_progress(gc, _("Requesting captcha"), 3, QQ_CONNECT_STEPS);
}

static void request_token_ex_code(PurpleConnection *gc,
		guint8 *token, guint16 token_len, guint8 *code, guint16 code_len)
{
	qq_data *qd;
	guint8 *buf, *raw_data;
	gint bytes;
	guint8 *encrypted;
	gint encrypted_len;

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

	g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0);
	g_return_if_fail(code != NULL && code_len > 0);

	raw_data = g_newa(guint8, MAX_PACKET_SIZE - 17);
	memset(raw_data, 0, MAX_PACKET_SIZE - 17);

	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 17 bytes more */

	bytes = 0;
	bytes += qq_put8(raw_data + bytes, qd->ld.token_len);
	bytes += qq_putdata(raw_data + bytes, qd->ld.token, qd->ld.token_len);
	bytes += qq_put8(raw_data + bytes, 4); 		/* Subcommand */
	bytes += qq_put16(raw_data + bytes, 5);
	bytes += qq_put32(raw_data + bytes, 0);
	bytes += qq_put16(raw_data + bytes, code_len);
	bytes += qq_putdata(raw_data + bytes, code, code_len);
	bytes += qq_put16(raw_data + bytes, qd->ld.token_ex_len); 	/* login token ex */
	bytes += qq_putdata(raw_data + bytes, qd->ld.token_ex, qd->ld.token_ex_len);

	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key);

	buf = g_newa(guint8, MAX_PACKET_SIZE);
	memset(buf, 0, MAX_PACKET_SIZE);
	bytes = 0;
	bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH);
	bytes += qq_putdata(buf + bytes, encrypted, encrypted_len);

	qd->send_seq++;
	qq_send_cmd_encrypted(gc, QQ_CMD_TOKEN_EX, qd->send_seq, buf, bytes, TRUE);

	purple_connection_update_progress(gc, _("Checking captcha"), 3, QQ_CONNECT_STEPS);
}

typedef struct {
	PurpleConnection *gc;
	guint8 *token;
	guint16 token_len;
} qq_captcha_request;

static void captcha_request_destory(qq_captcha_request *captcha_req)
{
	g_return_if_fail(captcha_req != NULL);
	if (captcha_req->token) g_free(captcha_req->token);
	g_free(captcha_req);
}

static void captcha_input_cancel_cb(qq_captcha_request *captcha_req,
		PurpleRequestFields *fields)
{
	captcha_request_destory(captcha_req);

	purple_connection_error_reason(captcha_req->gc,
			PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
			_("Failed captcha verification"));
}

static void captcha_input_ok_cb(qq_captcha_request *captcha_req,
		PurpleRequestFields *fields)
{
	gchar *code;

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

	code = utf8_to_qq(
			purple_request_fields_get_string(fields, "captcha_code"),
			QQ_CHARSET_DEFAULT);

	if (strlen(code) <= 0) {
		captcha_input_cancel_cb(captcha_req, fields);
		return;
	}

	request_token_ex_code(captcha_req->gc,
			captcha_req->token, captcha_req->token_len,
			(guint8 *)code, strlen(code));

	captcha_request_destory(captcha_req);
}

void qq_captcha_input_dialog(PurpleConnection *gc,qq_captcha_data *captcha)
{
	PurpleAccount *account;
	PurpleRequestFields *fields;
	PurpleRequestFieldGroup *group;
	PurpleRequestField *field;
	qq_captcha_request *captcha_req;

	g_return_if_fail(captcha->token != NULL && captcha->token_len > 0);
	g_return_if_fail(captcha->data != NULL && captcha->data_len > 0);

	captcha_req = g_new0(qq_captcha_request, 1);
	captcha_req->gc = gc;
	captcha_req->token = g_new0(guint8, captcha->token_len);
	g_memmove(captcha_req->token, captcha->token, captcha->token_len);
	captcha_req->token_len = captcha->token_len;

	account = purple_connection_get_account(gc);

	fields = purple_request_fields_new();
	group = purple_request_field_group_new(NULL);
	purple_request_fields_add_group(fields, group);

	field = purple_request_field_image_new("captcha_img",
			_("Captcha Image"), (char *)captcha->data, captcha->data_len);
	purple_request_field_group_add_field(group, field);

	field = purple_request_field_string_new("captcha_code",
			_("Enter code"), "", FALSE);
	purple_request_field_string_set_masked(field, FALSE);
	purple_request_field_group_add_field(group, field);

	purple_request_fields(account,
		_("QQ Captcha Verification"),
		_("QQ Captcha Verification"),
		_("Enter the text from the image"),
		fields,
		_("OK"), G_CALLBACK(captcha_input_ok_cb),
		_("Cancel"), G_CALLBACK(captcha_input_cancel_cb),
		purple_connection_get_account(gc), NULL, NULL,
		captcha_req);
}

guint8 qq_process_token_ex(PurpleConnection *gc, guint8 *data, gint data_len)
{
	qq_data *qd;
	int bytes;
	guint8 ret;
	guint8 sub_cmd;
	guint8 reply;
	guint16 captcha_len;
	guint8 curr_index;

	g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR);

	g_return_val_if_fail(gc != NULL  && gc->proto_data != NULL, QQ_LOGIN_REPLY_ERR);
	qd = (qq_data *) gc->proto_data;

	ret = data[0];

	bytes = 0;
	bytes += qq_get8(&sub_cmd, data + bytes); /* 03: ok; 04: need verifying */
	bytes += 2;	/* 0x(00 05) */
	bytes += qq_get8(&reply, data + bytes);

	bytes += qq_get16(&(qd->ld.token_ex_len), data + bytes);
	qd->ld.token_ex = g_realloc(qd->ld.token_ex, qd->ld.token_ex_len);
	bytes += qq_getdata(qd->ld.token_ex, qd->ld.token_ex_len, data + bytes);
	/* qq_show_packet("Get token ex", qd->ld.token_ex, qd->ld.token_ex_len); */

	if(reply != 1)
	{
		purple_debug_info("QQ", "Captcha verified, result %d\n", reply);
		return QQ_LOGIN_REPLY_OK;
	}

	bytes += qq_get16(&captcha_len, data + bytes);

	qd->captcha.data = g_realloc(qd->captcha.data, qd->captcha.data_len + captcha_len);
	bytes += qq_getdata(qd->captcha.data + qd->captcha.data_len, captcha_len, data + bytes);
	qd->captcha.data_len += captcha_len;

	bytes += qq_get8(&curr_index, data + bytes);
	bytes += qq_get8(&qd->captcha.next_index, data + bytes);

	bytes += qq_get16(&qd->captcha.token_len, data + bytes);
	qd->captcha.token = g_realloc(qd->captcha.token, qd->captcha.token_len);
	bytes += qq_getdata(qd->captcha.token, qd->captcha.token_len, data + bytes);
	/* qq_show_packet("Get captcha token", qd->captcha.token, qd->captcha.token_len); */

	purple_debug_info("QQ", "Request next captcha %d, new %d, total %d\n",
			qd->captcha.next_index, captcha_len, qd->captcha.data_len);
	if(qd->captcha.next_index > 0)
	{
		return QQ_LOGIN_REPLY_NEXT_TOKEN_EX;
	}

	return QQ_LOGIN_REPLY_CAPTCHA_DLG;
}

/* source copy from gg's common.c */
static guint32 crc32_table[256];
static int crc32_initialized = 0;

static void crc32_make_table()
{
	guint32 h = 1;
	unsigned int i, j;

	memset(crc32_table, 0, sizeof(crc32_table));

	for (i = 128; i; i >>= 1) {
		h = (h >> 1) ^ ((h & 1) ? 0xedb88320L : 0);

		for (j = 0; j < 256; j += 2 * i)
			crc32_table[i + j] = crc32_table[j] ^ h;
	}

	crc32_initialized = 1;
}

static guint32 crc32(guint32 crc, const guint8 *buf, int len)
{
	if (!crc32_initialized)
		crc32_make_table();

	if (!buf || len < 0)
		return crc;

	crc ^= 0xffffffffL;

	while (len--)
		crc = (crc >> 8) ^ crc32_table[(crc ^ *buf++) & 0xff];

	return crc ^ 0xffffffffL;
}

void qq_request_check_pwd(PurpleConnection *gc)
{
	qq_data *qd;
	guint8 *buf, *raw_data;
	gint bytes;
	guint8 *encrypted;
	gint encrypted_len;
	static guint8 header[] = {
			0x00, 0x5F, 0x00, 0x00, 0x08, 0x04, 0x01, 0xE0
	};
	static guint8 unknown[] = {
			0xDB, 0xB9, 0xF3, 0x0B, 0xF9, 0x13, 0x87, 0xB2,
			0xE6, 0x20, 0x43, 0xBE, 0x53, 0xCA, 0x65, 0x03
	};

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

	g_return_if_fail(qd->ld.token_ex != NULL && qd->ld.token_ex_len > 0);

	raw_data = g_newa(guint8, MAX_PACKET_SIZE - 17);
	memset(raw_data, 0, MAX_PACKET_SIZE - 17);

	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 17 bytes more */

	/* Encrypted password and put in encrypted */
	bytes = 0;
	bytes += qq_putdata(raw_data + bytes, qd->ld.pwd_md5, sizeof(qd->ld.pwd_md5));
	bytes += qq_put16(raw_data + bytes, 0);
	bytes += qq_put16(raw_data + bytes, rand() & 0xffff);

	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.pwd_twice_md5);

	/* create packet */
	bytes = 0;
	bytes += qq_putdata(raw_data + bytes, header, sizeof(header));
	/* token get from qq_request_token_ex */
	bytes += qq_put8(raw_data + bytes, (guint8)(qd->ld.token_ex_len & 0xff));
	bytes += qq_putdata(raw_data + bytes, qd->ld.token_ex, qd->ld.token_ex_len);
	/* password encrypted */
	bytes += qq_put16(raw_data + bytes, encrypted_len);
	bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len);
	/* len of unknown + len of CRC32 */
	bytes += qq_put16(raw_data + bytes, sizeof(unknown) + 4);
	bytes += qq_putdata(raw_data + bytes, unknown, sizeof(unknown));
	bytes += qq_put32(
			raw_data + bytes, crc32(0xFFFFFFFF, unknown, sizeof(unknown)));

	/* put length into first 2 bytes */
	qq_put8(raw_data + 1, bytes - 2);

	/* tail */
	bytes += qq_put16(raw_data + bytes, 0x0003);
	bytes += qq_put8(raw_data + bytes, 0);
	bytes += qq_put8(raw_data + bytes, qd->ld.pwd_md5[1]);
	bytes += qq_put8(raw_data + bytes, qd->ld.pwd_md5[2]);

	/* qq_show_packet("Check password", raw_data, bytes); */
	/* Encrypted by random key*/
	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key);

	buf = g_newa(guint8, MAX_PACKET_SIZE);
	memset(buf, 0, MAX_PACKET_SIZE);
	bytes = 0;
	bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH);
	bytes += qq_putdata(buf + bytes, encrypted, encrypted_len);

	qd->send_seq++;
	qq_send_cmd_encrypted(gc, QQ_CMD_CHECK_PWD, qd->send_seq, buf, bytes, TRUE);
}

guint8 qq_process_check_pwd( PurpleConnection *gc, guint8 *data, gint data_len)
{
	qq_data *qd;
	int bytes;
	guint8 ret;
	gchar *error = NULL;
	guint16 unknow_token_len;
	gchar *msg, *msg_utf8;
	guint16 msg_len;
	PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;

	g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR);

	g_return_val_if_fail(gc != NULL  && gc->proto_data != NULL, QQ_LOGIN_REPLY_ERR);
	qd = (qq_data *) gc->proto_data;

	/* qq_show_packet("Check password reply", data, data_len); */

	bytes = 0;
	bytes += qq_get16(&unknow_token_len, data + bytes);	/* maybe total length */
	bytes += qq_get8(&ret, data + bytes);
	bytes += 4; /* 0x(00 00 6d b9) */
	/* unknow_token_len may 0 when not reply ok*/
	bytes += qq_get16(&unknow_token_len, data + bytes);	/* 0x0020 */
	bytes += unknow_token_len;
	bytes += qq_get16(&unknow_token_len, data + bytes);	/* 0x0020 */
	bytes += unknow_token_len;

	if (ret == 0) {
		/* get login_token */
		bytes += qq_get16(&qd->ld.login_token_len, data + bytes);
		if (qd->ld.login_token != NULL) g_free(qd->ld.login_token);
		qd->ld.login_token = g_new0(guint8, qd->ld.login_token_len);
		bytes += qq_getdata(qd->ld.login_token, qd->ld.login_token_len, data + bytes);
		/* qq_show_packet("Get login token", qd->ld.login_token, qd->ld.login_token_len); */

		/* get login_key */
		bytes += qq_getdata(qd->ld.login_key, sizeof(qd->ld.login_key), data + bytes);
		/* qq_show_packet("Get login key", qd->ld.login_key, sizeof(qd->ld.login_key)); */
		return QQ_LOGIN_REPLY_OK;
	}

	switch (ret)
	{
		case 0x34:		/* invalid password */
			if (!purple_account_get_remember_password(gc->account)) {
				purple_account_set_password(gc->account, NULL);
			}
			error = g_strdup(_("Incorrect password"));
			reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
			break;
		case 0x33:		/* need activation */
		case 0x51:		/* need activation */
			error = g_strdup(_("Activation required"));
			reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
			break;
		case 0xBF:		/* uid is not exist */
			error = g_strdup(_("Username does not exist"));
			reason = PURPLE_CONNECTION_ERROR_INVALID_USERNAME;
			break;
		default:
			qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", data, data_len,
					">>> [default] decrypt and dump");
			error = g_strdup_printf(
						_("Unknown reply when checking password (0x%02X)"),
						ret );
			reason = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
			break;
	}

	bytes += qq_get16(&msg_len, data + bytes);

	msg = g_strndup((gchar *)data + bytes, msg_len);
	msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);

	purple_debug_error("QQ", "%s: %s\n", error, msg_utf8);
	purple_connection_error_reason(gc, reason, msg_utf8);

	g_free(error);
	g_free(msg);
	g_free(msg_utf8);
	return QQ_LOGIN_REPLY_ERR;
}

void qq_request_login_2007(PurpleConnection *gc)
{
	qq_data *qd;
	guint8 *buf, *raw_data;
	gint bytes;
	guint8 *encrypted;
	gint encrypted_len;
	static const guint8 login_1_16[] = {
			0x56, 0x4E, 0xC8, 0xFB, 0x0A, 0x4F, 0xEF, 0xB3,
			0x7A, 0x5D, 0xD8, 0x86, 0x0F, 0xAC, 0xE5, 0x1A
	};
	static const guint8 login_2_16[] = {
			0x5E, 0x22, 0x3A, 0xBE, 0x13, 0xBF, 0xDA, 0x4C,
			0xA9, 0xB7, 0x0B, 0x43, 0x63, 0x51, 0x8E, 0x28
	};
	static const guint8 login_3_83[] = {
			0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x01, 0x40, 0x01, 0x01, 0x58, 0x83,
			0xD0, 0x00, 0x10, 0x9D, 0x14, 0x64, 0x0A, 0x2E,
			0xE2, 0x11, 0xF7, 0x90, 0xF0, 0xB5, 0x5F, 0x16,
			0xFB, 0x41, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x02, 0x76, 0x3C, 0xEE,
			0x4A, 0x00, 0x10, 0x86, 0x81, 0xAD, 0x1F, 0xC8,
			0xC9, 0xCC, 0xCF, 0xCA, 0x9F, 0xFF, 0x88, 0xC0,
			0x5C, 0x88, 0xD5
	};
	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
	qd = (qq_data *) gc->proto_data;

	g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0);

	raw_data = g_newa(guint8, MAX_PACKET_SIZE - 17);
	memset(raw_data, 0, MAX_PACKET_SIZE - 17);

	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 17 bytes more */

	/* Encrypted password and put in encrypted */
	bytes = 0;
	bytes += qq_putdata(raw_data + bytes, qd->ld.pwd_md5, sizeof(qd->ld.pwd_md5));
	bytes += qq_put16(raw_data + bytes, 0);
	bytes += qq_put16(raw_data + bytes, 0xffff);

	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.pwd_twice_md5);

	/* create packet */
	bytes = 0;
	bytes += qq_put16(raw_data + bytes, 0);		/* Unknow */
	/* password encrypted */
	bytes += qq_put16(raw_data + bytes, encrypted_len);
	bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len);
	/* put data which NULL string encrypted by key pwd_twice_md5 */
	encrypted_len = qq_encrypt(encrypted, (guint8 *) "", 0, qd->ld.pwd_twice_md5);
	g_return_if_fail(encrypted_len == 16);
	bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len);
	/* unknow fill 0 */
	memset(raw_data + bytes, 0, 19);
	bytes += 19;
	bytes += qq_putdata(raw_data + bytes, login_1_16, sizeof(login_1_16));

	bytes += qq_put8(raw_data + bytes, rand() & 0xff);
	bytes += qq_put8(raw_data + bytes, qd->login_mode);
	/* unknow 10 bytes zero filled*/
	memset(raw_data + bytes, 0, 10);
	bytes += 10;
	/* redirect data, 15 bytes */
	/* qq_show_packet("Redirect", qd->redirect, qd->redirect_len); */
	bytes += qq_putdata(raw_data + bytes, qd->redirect, qd->redirect_len);
	/* unknow fill */
	bytes += qq_putdata(raw_data + bytes, login_2_16, sizeof(login_2_16));
	/* captcha token get from qq_process_token_ex */
	bytes += qq_put8(raw_data + bytes, (guint8)(qd->ld.token_ex_len & 0xff));
	bytes += qq_putdata(raw_data + bytes, qd->ld.token_ex, qd->ld.token_ex_len);
	/* unknow fill */
	bytes += qq_putdata(raw_data + bytes, login_3_83, sizeof(login_3_83));
	memset(raw_data + bytes, 0, 332 - sizeof(login_3_83));
	bytes += 332 - sizeof(login_3_83);

	/* qq_show_packet("Login", raw_data, bytes); */

	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.login_key);

	buf = g_newa(guint8, MAX_PACKET_SIZE);
	memset(buf, 0, MAX_PACKET_SIZE);
	bytes = 0;
	/* logint token get from qq_process_check_pwd_2007 */
	bytes += qq_put16(buf + bytes, qd->ld.login_token_len);
	bytes += qq_putdata(buf + bytes, qd->ld.login_token, qd->ld.login_token_len);
	bytes += qq_putdata(buf + bytes, encrypted, encrypted_len);

	qd->send_seq++;
	qq_send_cmd_encrypted(gc, QQ_CMD_LOGIN, qd->send_seq, buf, bytes, TRUE);
}

/* process the login reply packet */
guint8 qq_process_login_2007( PurpleConnection *gc, guint8 *data, gint data_len)
{
	qq_data *qd;
	gint bytes;
	guint8 ret;
	guint32 uid;
	gchar *error;
	gchar *msg;
	gchar *msg_utf8;

	g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR);

	qd = (qq_data *) gc->proto_data;

	bytes = 0;
	bytes += qq_get8(&ret, data + bytes);
	if (ret != 0) {
		msg = g_strndup((gchar *)data + bytes, data_len - bytes);
		msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);
		g_free(msg);

		switch (ret) {
			case 0x05:
				purple_debug_error("QQ", "Server busy for %s\n", msg_utf8);
				return QQ_LOGIN_REPLY_REDIRECT;
			case 0x0A:
				/* 0a 2d 9a 4b 9a 01 01 00 00 00 05 00 00 00 00 79 0e 5f fd */
				/* Missing get server before login*/
			default:
				error = g_strdup_printf(
						_("Unknown reply code when logging in (0x%02X):\n%s"),
						ret, msg_utf8);
				break;
		}

		purple_debug_error("QQ", "%s\n", error);
		purple_connection_error_reason(gc,
				PURPLE_CONNECTION_ERROR_OTHER_ERROR,
				error);

		qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", data, data_len, error);

		g_free(error);
		g_free(msg_utf8);
		return QQ_LOGIN_REPLY_ERR;
	}

	bytes += qq_getdata(qd->session_key, sizeof(qd->session_key), data + bytes);
	purple_debug_info("QQ", "Got session_key\n");
	get_session_md5(qd->session_md5, qd->uid, qd->session_key);

	bytes += qq_get32(&uid, data + bytes);
	if (uid != qd->uid) {
		purple_debug_warning("QQ", "My uid in login reply is %u, not %u\n", uid, qd->uid);
	}
	bytes += qq_getIP(&qd->my_ip, data + bytes);
	bytes += qq_get16(&qd->my_port, data + bytes);
	bytes += qq_getIP(&qd->my_local_ip, data + bytes);
	bytes += qq_get16(&qd->my_local_port, data + bytes);
	bytes += qq_getime(&qd->login_time, data + bytes);
	/* skip unknow 50 byte */
	bytes += 50;
	/* skip client key 32 byte */
	bytes += 32;
	/* skip unknow 12 byte */
	bytes += 12;
	/* last login */
	bytes += qq_getIP(&qd->last_login_ip, data + bytes);
	bytes += qq_getime(&qd->last_login_time[0], data + bytes);
	purple_debug_info("QQ", "Last Login: %s, %s\n",
			inet_ntoa(qd->last_login_ip), ctime(&qd->last_login_time[0]));
	return QQ_LOGIN_REPLY_OK;
}

void qq_request_login_2008(PurpleConnection *gc)
{
	qq_data *qd;
	guint8 *buf, *raw_data;
	gint bytes;
	guint8 *encrypted;
	gint encrypted_len;
	guint8 index, count;

	static const guint8 login_1_16[] = {
			0xD2, 0x41, 0x75, 0x12, 0xC2, 0x86, 0x57, 0x10,
			0x78, 0x57, 0xDC, 0x24, 0x8C, 0xAA, 0x8F, 0x4E
	};

	static const guint8 login_2_16[] = {
			0x94, 0x0B, 0x73, 0x7A, 0xA2, 0x51, 0xF0, 0x4B,
			0x95, 0x2F, 0xC6, 0x0A, 0x5B, 0xF6, 0x76, 0x52
	};
	static const guint8 login_3_18[] = {
			0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x01, 0x40, 0x01, 0x1b, 0x02, 0x84,
			0x50, 0x00
	};
	static const guint8 login_4_16[] = {
			0x2D, 0x49, 0x15, 0x55, 0x78, 0xFC, 0xF3, 0xD4,
			0x53, 0x55, 0x60, 0x9C, 0x37, 0x9F, 0xE9, 0x59
	};
	static const guint8 login_5_6[] = {
			0x02, 0x68, 0xe8, 0x07, 0x83, 0x00
	};
	static const guint8 login_6_16[] = {
			0x3B, 0xCE, 0x43, 0xF1, 0x8B, 0xA4, 0x2B, 0xB5,
			0xB3, 0x51, 0x57, 0xF7, 0x06, 0x4B, 0x18, 0xFC
	};
	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
	qd = (qq_data *) gc->proto_data;

	g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0);

	raw_data = g_newa(guint8, MAX_PACKET_SIZE - 17);
	memset(raw_data, 0, MAX_PACKET_SIZE - 17);

	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 17 bytes more */

	/* Encrypted password and put in encrypted */
	bytes = 0;
	bytes += qq_putdata(raw_data + bytes, qd->ld.pwd_md5, sizeof(qd->ld.pwd_md5));
	bytes += qq_put16(raw_data + bytes, 0);
	bytes += qq_put16(raw_data + bytes, 0xffff);

	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.pwd_twice_md5);

	/* create packet */
	bytes = 0;
	bytes += qq_put16(raw_data + bytes, 0);		/* Unknow */
	/* password encrypted */
	bytes += qq_put16(raw_data + bytes, encrypted_len);
	bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len);
	/* put data which NULL string encrypted by key pwd_twice_md5 */
	encrypted_len = qq_encrypt(encrypted, (guint8 *) "", 0, qd->ld.pwd_twice_md5);
	g_return_if_fail(encrypted_len == 16);
	bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len);
	/* unknow 19 bytes zero filled*/
	memset(raw_data + bytes, 0, 19);
	bytes += 19;
	bytes += qq_putdata(raw_data + bytes, login_1_16, sizeof(login_1_16));

	index = rand() % 3;	  /* can be set as 1 */
	for( count = 0; count < encrypted_len;  count++ )
		index ^= encrypted[count];
	for( count = 0; count < sizeof(login_1_16);  count++ )
		index ^= login_1_16[count];
	bytes += qq_put8(raw_data + bytes, index);	/* random in QQ 2007*/

	bytes += qq_put8(raw_data + bytes, qd->login_mode);
	/* unknow 10 bytes zero filled*/
	memset(raw_data + bytes, 0, 10);
	bytes += 10;
	/* redirect data, 15 bytes */
	bytes += qq_putdata(raw_data + bytes, qd->redirect, qd->redirect_len);
	/* unknow fill */
	bytes += qq_putdata(raw_data + bytes, login_2_16, sizeof(login_2_16));
	/* captcha token get from qq_process_token_ex */
	bytes += qq_put8(raw_data + bytes, (guint8)(qd->ld.token_ex_len & 0xff));
	bytes += qq_putdata(raw_data + bytes, qd->ld.token_ex, qd->ld.token_ex_len);
	/* unknow fill */
	bytes += qq_putdata(raw_data + bytes, login_3_18, sizeof(login_3_18));
	bytes += qq_put8(raw_data + bytes , sizeof(login_4_16));
	bytes += qq_putdata(raw_data + bytes, login_4_16, sizeof(login_4_16));
	/* unknow 10 bytes zero filled*/
	memset(raw_data + bytes, 0, 10);
	bytes += 10;
	/* redirect data, 15 bytes */
	bytes += qq_putdata(raw_data + bytes, qd->redirect, qd->redirect_len);
	/* unknow fill */
	bytes += qq_putdata(raw_data + bytes, login_5_6, sizeof(login_5_6));
	bytes += qq_put8(raw_data + bytes , sizeof(login_6_16));
	bytes += qq_putdata(raw_data + bytes, login_6_16, sizeof(login_6_16));
	/* unknow 249 bytes zero filled*/
	memset(raw_data + bytes, 0, 249);
	bytes += 249;

	/* qq_show_packet("Login request", raw_data, bytes); */
	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.login_key);

	buf = g_newa(guint8, MAX_PACKET_SIZE);
	memset(buf, 0, MAX_PACKET_SIZE);
	bytes = 0;
	/* logint token get from qq_process_check_pwd_2007 */
	bytes += qq_put16(buf + bytes, qd->ld.login_token_len);
	bytes += qq_putdata(buf + bytes, qd->ld.login_token, qd->ld.login_token_len);
	bytes += qq_putdata(buf + bytes, encrypted, encrypted_len);

	qd->send_seq++;
	qq_send_cmd_encrypted(gc, QQ_CMD_LOGIN, qd->send_seq, buf, bytes, TRUE);
}

guint8 qq_process_login_2008( PurpleConnection *gc, guint8 *data, gint data_len)
{
	qq_data *qd;
	gint bytes;
	guint8 ret;
	guint32 uid;
	gchar *error;
	gchar *msg;
	gchar *msg_utf8;

	g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR);

	qd = (qq_data *) gc->proto_data;

	bytes = 0;
	bytes += qq_get8(&ret, data + bytes);
	if (ret != 0) {
		msg = g_strndup((gchar *)data + bytes, data_len - bytes);
		msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);
		g_free(msg);

		switch (ret) {
			case 0x05:
				purple_debug_error("QQ", "Server busy for %s\n", msg_utf8);
				return QQ_LOGIN_REPLY_REDIRECT;
				break;
			default:
				error = g_strdup_printf(
						_("Unknown reply code when logging in (0x%02X):\n%s"),
						ret, msg_utf8);
				break;
		}

		purple_debug_error("QQ", "%s\n", error);
		purple_connection_error_reason(gc,
				PURPLE_CONNECTION_ERROR_OTHER_ERROR,
				error);

		qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", data, data_len, error);

		g_free(error);
		g_free(msg_utf8);
		return QQ_LOGIN_REPLY_ERR;
	}

	bytes += qq_getdata(qd->session_key, sizeof(qd->session_key), data + bytes);
	purple_debug_info("QQ", "Got session_key\n");
	get_session_md5(qd->session_md5, qd->uid, qd->session_key);

	bytes += qq_get32(&uid, data + bytes);
	if (uid != qd->uid) {
		purple_debug_warning("QQ", "My uid in login reply is %u, not %u\n", uid, qd->uid);
	}
	bytes += qq_getIP(&qd->my_ip, data + bytes);
	bytes += qq_get16(&qd->my_port, data + bytes);
	bytes += qq_getIP(&qd->my_local_ip, data + bytes);
	bytes += qq_get16(&qd->my_local_port, data + bytes);
	bytes += qq_getime(&qd->login_time, data + bytes);
	/* skip 1 byte, always 0x03 */
	/* skip 1 byte, login mode */
	bytes = 131;
	bytes += qq_getIP(&qd->last_login_ip, data + bytes);
	bytes += qq_getime(&qd->last_login_time[0], data + bytes);
	purple_debug_info("QQ", "Last Login: %s, %s\n",
			inet_ntoa(qd->last_login_ip), ctime(&qd->last_login_time[0]));
	return QQ_LOGIN_REPLY_OK;
}