view libpurple/protocols/qq/qq_base.c @ 26198:e1b91b7b5f69

merge of '7829ec76bdb008583f8da54e238c2265a1140db2' and 'e10c42213b8452c2fd4906e4ca501ef3eb83bd69'
author Paul Aurich <paul@darkrain42.org>
date Fri, 20 Mar 2009 05:46:04 +0000
parents 8f757b2139d2
children f541583e31bd
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,
				_("Cannot 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,
				_("Cannot 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 - 16);
	memset(raw_data, 0, MAX_PACKET_SIZE - 16);

	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 16 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 + 16);	/* 16 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,
				_("Could not 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 - 16);
	memset(raw_data, 0, MAX_PACKET_SIZE - 16);

	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 16 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 - 16);
	memset(raw_data, 0, MAX_PACKET_SIZE - 16);

	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 16 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 - 16);
	memset(raw_data, 0, MAX_PACKET_SIZE - 16);

	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 16 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 - 16);
	memset(raw_data, 0, MAX_PACKET_SIZE - 16);

	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 16 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(_("Invalid username."));
			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 - 16);
	memset(raw_data, 0, MAX_PACKET_SIZE - 16);

	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 16 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 - 16);
	memset(raw_data, 0, MAX_PACKET_SIZE - 16);

	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 16 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;
}