pidgin.openq: bb5558d3: patch 20080922 from ccpaging <ccpaging(a...

csyfek at gmail.com csyfek at gmail.com
Wed Oct 22 12:12:21 EDT 2008


-----------------------------------------------------------------
Revision: bb5558d316fe6765728168f9e19639095ba6478d
Ancestor: 960054dafdd2879a6835f8e04214129667b11423
Author: csyfek at gmail.com
Date: 2008-10-22T14:33:20
Branch: im.pidgin.pidgin.openq
URL: http://d.pidgin.im/viewmtn/revision/info/bb5558d316fe6765728168f9e19639095ba6478d

Modified files:
        libpurple/protocols/qq/Makefile.am
        libpurple/protocols/qq/Makefile.mingw
        libpurple/protocols/qq/buddy_info.c
        libpurple/protocols/qq/buddy_list.c
        libpurple/protocols/qq/buddy_opt.c
        libpurple/protocols/qq/file_trans.c
        libpurple/protocols/qq/group.c
        libpurple/protocols/qq/group_conv.c
        libpurple/protocols/qq/group_im.c
        libpurple/protocols/qq/group_info.c
        libpurple/protocols/qq/group_join.c
        libpurple/protocols/qq/group_opt.c
        libpurple/protocols/qq/group_search.c
        libpurple/protocols/qq/im.c libpurple/protocols/qq/qq.c
        libpurple/protocols/qq/qq.h libpurple/protocols/qq/qq_base.c
        libpurple/protocols/qq/qq_base.h
        libpurple/protocols/qq/qq_define.c
        libpurple/protocols/qq/qq_define.h
        libpurple/protocols/qq/qq_network.c
        libpurple/protocols/qq/qq_process.c
        libpurple/protocols/qq/qq_process.h
        libpurple/protocols/qq/qq_trans.c
        libpurple/protocols/qq/send_file.c

ChangeLog: 

patch 20080922 from ccpaging <ccpaging(at)gmail.com>

-------------- next part --------------
============================================================
--- libpurple/protocols/qq/Makefile.am	2b1030056344af9a423a29ad3a8ca21831baf583
+++ libpurple/protocols/qq/Makefile.am	207abbad5767fe734607409821587869d715218b
@@ -36,8 +36,8 @@ QQSOURCES = \
 	group_opt.h \
 	group_search.c \
 	group_search.h \
-	header_info.c \
-	header_info.h \
+	qq_define.c \
+	qq_define.h \
 	im.c \
 	im.h \
 	qq_process.c \
============================================================
--- libpurple/protocols/qq/Makefile.mingw	bfdbb323f7edc788f1e3ce2ed7f3731791b4de5a
+++ libpurple/protocols/qq/Makefile.mingw	eeef4e620e2b8d4d6d3336b10a7bae215657c8b8
@@ -55,7 +55,7 @@ C_SRC = \
 	group_join.c \
 	group_opt.c \
 	group_search.c \
-	header_info.c \
+	qq_define.c \
 	im.c \
 	packet_parse.c \
 	qq.c \
============================================================
--- libpurple/protocols/qq/buddy_info.c	20ae76b726968ba91406b83eabe311b503650f6b
+++ libpurple/protocols/qq/buddy_info.c	b82b64007711ec63c899a89220ba30ed3241b2c8
@@ -32,7 +32,7 @@
 #include "buddy_list.h"
 #include "buddy_info.h"
 #include "char_conv.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "qq_base.h"
 #include "qq_network.h"
 
@@ -426,7 +426,11 @@ static void info_modify_dialogue(PurpleC
 	info_request->iclass = iclass;
 	info_request->segments = segments;
 
-	purple_request_fields(gc, utf8_title, utf8_prim, NULL,	fields,
+	purple_request_fields(gc,
+			utf8_title,
+			utf8_prim,
+			NULL,
+			fields,
 			_("Update"), G_CALLBACK(info_modify_ok_cb),
 			_("Cancel"), G_CALLBACK(info_modify_cancel_cb),
 			purple_connection_get_account(gc), NULL, NULL,
============================================================
--- libpurple/protocols/qq/buddy_list.c	9b1ed5dfeeed12c2f6879cba8e4e6aecb8398414
+++ libpurple/protocols/qq/buddy_list.c	a2fd962185dde0cffff2556d476f1659d00d65d5
@@ -34,7 +34,7 @@
 #include "buddy_list.h"
 #include "buddy_opt.h"
 #include "char_conv.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "qq_base.h"
 #include "group.h"
 #include "group_find.h"
@@ -78,7 +78,6 @@ void qq_request_get_buddies_online(Purpl
 	bytes += qq_put16(raw_data + bytes, 0x0000);
 
 	qq_send_cmd_mess(gc, QQ_CMD_GET_BUDDIES_ONLINE, raw_data, 5, update_class, 0);
-	qd->last_get_online = time(NULL);
 }
 
 /* position starts with 0x0000,
============================================================
--- libpurple/protocols/qq/buddy_opt.c	d3534cf0719079b8d8ef27b6e5e1e0f1fd03ea43
+++ libpurple/protocols/qq/buddy_opt.c	494a448c64ee5e7ea82091e45f136183beb2a954
@@ -32,7 +32,7 @@
 #include "buddy_list.h"
 #include "buddy_opt.h"
 #include "char_conv.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "im.h"
 #include "qq_base.h"
 #include "packet_parse.h"
============================================================
--- libpurple/protocols/qq/file_trans.c	2775840f1f4b59ea87cddfc4d8a71b825f1ecd3a
+++ libpurple/protocols/qq/file_trans.c	2c8f48f87dabe9f111faa4bb4fba3e6134d342d9
@@ -30,7 +30,7 @@
 
 #include "qq_crypt.h"
 #include "file_trans.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "im.h"
 #include "packet_parse.h"
 #include "proxy.h"
@@ -81,7 +81,7 @@ static void _fill_file_md5(const gchar *
 	const gint QQ_MAX_FILE_MD5_LENGTH = 10002432;
 
 	g_return_if_fail(filename != NULL && md5 != NULL);
-	if (filelen > QQ_MAX_FILE_MD5_LENGTH) 
+	if (filelen > QQ_MAX_FILE_MD5_LENGTH)
 		filelen = QQ_MAX_FILE_MD5_LENGTH;
 
 	fp = fopen(filename, "rb");
@@ -161,7 +161,7 @@ static int _qq_xfer_open_file(const gcha
 		fd = open(purple_xfer_get_local_filename(xfer), O_RDONLY);
 		info->buffer = mmap(0, purple_xfer_get_size(xfer), PROT_READ, MAP_PRIVATE, fd, 0);
 	}
-	else 
+	else
 	{
 		fd = open(purple_xfer_get_local_filename(xfer), O_RDWR|O_CREAT, 0644);
 		info->buffer = mmap(0, purple_xfer_get_size(xfer), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FILE, fd, 0);
@@ -248,7 +248,7 @@ static gint _qq_send_file(PurpleConnecti
 	file_key = _gen_file_key();
 
 	bytes += qq_put8(raw_data + bytes, packet_type);
-	bytes += qq_put16(raw_data + bytes, QQ_CLIENT);
+	bytes += qq_put16(raw_data + bytes, qd->client_version);
 	bytes += qq_put8(raw_data + bytes, file_key & 0xff);
 	bytes += qq_put32(raw_data + bytes, _encrypt_qq_uid(qd->uid, file_key));
 	bytes += qq_put32(raw_data + bytes, _encrypt_qq_uid(to_uid, file_key));
@@ -364,7 +364,7 @@ void qq_send_file_ctl_packet(PurpleConne
 }
 
 /* send a file to udp channel with QQ_FILE_DATA_PACKET_TAG */
-static void _qq_send_file_data_packet(PurpleConnection *gc, guint16 packet_type, guint8 sub_type, 
+static void _qq_send_file_data_packet(PurpleConnection *gc, guint16 packet_type, guint8 sub_type,
 		guint32 fragment_index, guint16 seq, guint8 *data, gint len)
 {
 	guint8 *raw_data, filename_md5[QQ_KEY_LENGTH], file_md5[QQ_KEY_LENGTH];
@@ -402,11 +402,11 @@ static void _qq_send_file_data_packet(Pu
 					_fill_file_md5(purple_xfer_get_local_filename(qd->xfer),
 							purple_xfer_get_size(qd->xfer),
 							file_md5);
-					
+
 					info->fragment_num = (filesize - 1) / QQ_FILE_FRAGMENT_MAXLEN + 1;
 					info->fragment_len = QQ_FILE_FRAGMENT_MAXLEN;
 
-					purple_debug_info("QQ", 
+					purple_debug_info("QQ",
 							"start transfering data, %d fragments with %d length each\n",
 							info->fragment_num, info->fragment_len);
 					/* Unknown */
@@ -431,7 +431,7 @@ static void _qq_send_file_data_packet(Pu
 							filename_len);
 					break;
 				case QQ_FILE_DATA_INFO:
-					purple_debug_info("QQ", 
+					purple_debug_info("QQ",
 							"sending %dth fragment with length %d, offset %d\n",
 							fragment_index, len, (fragment_index-1)*fragment_size);
 					/* bytes += qq_put16(raw_data + bytes, ++(qd->send_seq)); */
@@ -532,7 +532,7 @@ static void _qq_process_recv_file_ctl_pa
 			decryped_bytes = 0;
 			qq_get_conn_info(info, decrypted_data + decryped_bytes);
 			/* qq_send_file_ctl_packet(gc, QQ_FILE_CMD_PING, fh->sender_uid, 0); */
-			qq_send_file_ctl_packet(gc, QQ_FILE_CMD_SENDER_SAY_HELLO, fh.sender_uid, 0);	
+			qq_send_file_ctl_packet(gc, QQ_FILE_CMD_SENDER_SAY_HELLO, fh.sender_uid, 0);
 			break;
 		case QQ_FILE_CMD_SENDER_SAY_HELLO:
 			/* I'm receiver, if we receive SAY_HELLO from sender, we send back the ACK */
@@ -573,8 +573,8 @@ static void _qq_recv_file_progess(Purple
 	ft_info *info = (ft_info *) xfer->data;
 	guint32 mask;
 
-	purple_debug_info("QQ", 
-			"receiving %dth fragment with length %d, slide window status %o, max_fragment_index %d\n", 
+	purple_debug_info("QQ",
+			"receiving %dth fragment with length %d, slide window status %o, max_fragment_index %d\n",
 			index, len, info->window, info->max_fragment_index);
 	if (info->window == 0 && info->max_fragment_index == 0) {
 		if (_qq_xfer_open_file(purple_xfer_get_local_filename(xfer), "wb", xfer) == -1) {
@@ -605,7 +605,7 @@ static void _qq_recv_file_progess(Purple
 		if (mask & 0x8000) mask = 0x0001;
 		else mask = mask << 1;
 	}
-	purple_debug_info("QQ", "procceed %dth fragment, slide window status %o, max_fragment_index %d\n", 
+	purple_debug_info("QQ", "procceed %dth fragment, slide window status %o, max_fragment_index %d\n",
 			index, info->window, info->max_fragment_index);
 }
 
@@ -650,10 +650,10 @@ static void _qq_update_send_progess(Purp
 	PurpleXfer *xfer = qd->xfer;
 	ft_info *info = (ft_info *) xfer->data;
 
-	purple_debug_info("QQ", 
-			"receiving %dth fragment ack, slide window status %o, max_fragment_index %d\n", 
+	purple_debug_info("QQ",
+			"receiving %dth fragment ack, slide window status %o, max_fragment_index %d\n",
 			fragment_index, info->window, info->max_fragment_index);
-	if (fragment_index < info->max_fragment_index || 
+	if (fragment_index < info->max_fragment_index ||
 			fragment_index >= info->max_fragment_index + sizeof(info->window)) {
 		purple_debug_info("QQ", "duplicate %dth fragment, drop it!\n", fragment_index+1);
 		return;
@@ -681,7 +681,7 @@ static void _qq_update_send_progess(Purp
 			info->window &= ~mask;
 
 			buffer = g_newa(guint8, info->fragment_len);
-			readbytes = _qq_xfer_read_file(buffer, info->max_fragment_index + sizeof(info->window), 
+			readbytes = _qq_xfer_read_file(buffer, info->max_fragment_index + sizeof(info->window),
 					info->fragment_len, xfer);
 			if (readbytes > 0)
 				_qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP, QQ_FILE_DATA_INFO,
@@ -692,8 +692,8 @@ static void _qq_update_send_progess(Purp
 			else mask = mask << 1;
 		}
 	}
-	purple_debug_info("QQ", 
-			"procceed %dth fragment ack, slide window status %o, max_fragment_index %d\n", 
+	purple_debug_info("QQ",
+			"procceed %dth fragment ack, slide window status %o, max_fragment_index %d\n",
 			fragment_index, info->window, info->max_fragment_index);
 }
 
@@ -727,13 +727,13 @@ static void _qq_process_recv_file_data(P
 					bytes += qq_get32(&info->fragment_num, data + bytes);
 					bytes += qq_get32(&info->fragment_len, data + bytes);
 
-					/* FIXME: We must check the md5 here, 
-					 * if md5 doesn't match we will ignore 
+					/* FIXME: We must check the md5 here,
+					 * if md5 doesn't match we will ignore
 					 * the packet or send sth as error number */
 
 					info->max_fragment_index = 0;
 					info->window = 0;
-					purple_debug_info("QQ", 
+					purple_debug_info("QQ",
 							"start receiving data, %d fragments with %d length each\n",
 							info->fragment_num, info->fragment_len);
 					_qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP_ACK, sub_type,
@@ -743,7 +743,7 @@ static void _qq_process_recv_file_data(P
 					bytes += qq_get32(&fragment_index, data + bytes);
 					bytes += qq_get32(&fragment_offset, data + bytes);
 					bytes += qq_get16(&fragment_len, data + bytes);
-					purple_debug_info("QQ", 
+					purple_debug_info("QQ",
 							"received %dth fragment with length %d, offset %d\n",
 							fragment_index, fragment_len, fragment_offset);
 
============================================================
--- libpurple/protocols/qq/group.c	0dc229d6263edb2c6a6fa1ee7f661d125b99cee7
+++ libpurple/protocols/qq/group.c	7fa0414e92ad66f7941cc795a9377cd720a7a37f
@@ -33,7 +33,7 @@
 #include "group_search.h"
 #include "utils.h"
 #include "qq_network.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "group_free.h"
 
 static void _qq_group_search_callback(PurpleConnection *gc, const gchar *input)
============================================================
--- libpurple/protocols/qq/group_conv.c	074bbf6f60a497fd05ecc24032b818243819bbb2
+++ libpurple/protocols/qq/group_conv.c	5b30fdabc44113fce4d6e14886de4b9581276962
@@ -27,7 +27,7 @@
 
 #include "group_conv.h"
 #include "buddy_list.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "qq_network.h"
 #include "qq_process.h"
 #include "utils.h"
============================================================
--- libpurple/protocols/qq/group_im.c	94009eace5bf8cd9a136720df3449493ea5380dc
+++ libpurple/protocols/qq/group_im.c	2c084034186d20995d280b3c9b29688ae3efda63
@@ -39,7 +39,7 @@
 #include "group_opt.h"
 #include "group_conv.h"
 #include "im.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "packet_parse.h"
 #include "qq_network.h"
 #include "qq_process.h"
============================================================
--- libpurple/protocols/qq/group_info.c	5e52c58147c9ab1c318c91212128f8ea6a7d32f1
+++ libpurple/protocols/qq/group_info.c	0089d00bee0b490ac0b3f79637718984fa83f7e1
@@ -32,7 +32,7 @@
 #include "group_internal.h"
 #include "group_info.h"
 #include "buddy_list.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "packet_parse.h"
 #include "qq_network.h"
 #include "utils.h"
============================================================
--- libpurple/protocols/qq/group_join.c	1dc85c7d1bc5f5fb56c0a1c6aece13022460e4a8
+++ libpurple/protocols/qq/group_join.c	752655badb239f6df8e7e9913f64947c318ecf7c
@@ -38,7 +38,7 @@
 #include "group_opt.h"
 #include "group_conv.h"
 #include "group_search.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "packet_parse.h"
 #include "qq_network.h"
 #include "qq_process.h"
============================================================
--- libpurple/protocols/qq/group_opt.c	87b480c3666e20d961653cf0f0e16dbabcb50cb1
+++ libpurple/protocols/qq/group_opt.c	9512e5600ad6681ad60d46e8aeccdc65ef8ebd78
@@ -35,7 +35,7 @@
 #include "group_info.h"
 #include "group_join.h"
 #include "group_opt.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "packet_parse.h"
 #include "qq_network.h"
 #include "qq_process.h"
============================================================
--- libpurple/protocols/qq/group_search.c	3ab74d8fa384063407170325c69f1f61e2021511
+++ libpurple/protocols/qq/group_search.c	9109c264ca8a47e9e284ba5388fd2fdb1a16e6b1
@@ -33,7 +33,7 @@
 #include "group_join.h"
 #include "group_search.h"
 #include "utils.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "packet_parse.h"
 #include "qq_network.h"
 
============================================================
--- libpurple/protocols/qq/im.c	702c7ac1372e88b719dd20b88f106b41e75c155a
+++ libpurple/protocols/qq/im.c	12ab7161d337f989800516b234b5b1124cccc672
@@ -36,7 +36,7 @@
 #include "buddy_opt.h"
 #include "char_conv.h"
 #include "group_im.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "im.h"
 #include "packet_parse.h"
 #include "qq_network.h"
@@ -502,7 +502,7 @@ void qq_send_packet_im(PurpleConnection 
 {
 	qq_data *qd;
 	guint8 *raw_data, *send_im_tail;
-	guint16 client_tag, normal_im_type;
+	guint16 normal_im_type;
 	gint msg_len, raw_len, font_name_len, tail_len, bytes;
 	time_t now;
 	gchar *msg_filtered;
@@ -512,7 +512,6 @@ void qq_send_packet_im(PurpleConnection 
 	const gchar *start, *end, *last;
 
 	qd = (qq_data *) gc->proto_data;
-	client_tag = QQ_CLIENT;
 	normal_im_type = QQ_NORMAL_IM_TEXT;
 
 	last = msg;
@@ -572,7 +571,7 @@ void qq_send_packet_im(PurpleConnection 
 	/* 004-007: sender uid */
 	bytes += qq_put32(raw_data + bytes, to_uid);
 	/* 008-009: sender client version */
-	bytes += qq_put16(raw_data + bytes, client_tag);
+	bytes += qq_put16(raw_data + bytes, qd->client_version);
 	/* 010-013: receiver uid */
 	bytes += qq_put32(raw_data + bytes, qd->uid);
 	/* 014-017: sender uid */
============================================================
--- libpurple/protocols/qq/qq.c	a07a58489c8d3ba77a04a9a8b5f5877df11221ba
+++ libpurple/protocols/qq/qq.c	dc390467f68a19e02ea4906d2029990188041368
@@ -44,7 +44,7 @@
 #include "group_info.h"
 #include "group_join.h"
 #include "group_opt.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "im.h"
 #include "qq_process.h"
 #include "qq_base.h"
@@ -128,6 +128,7 @@ static void qq_login(PurpleAccount *acco
 	PurpleConnection *gc;
 	qq_data *qd;
 	PurplePresence *presence;
+	const gchar *version_str;
 
 	g_return_if_fail(account != NULL);
 
@@ -154,6 +155,16 @@ static void qq_login(PurpleAccount *acco
 	server_list_create(account);
 	purple_debug_info("QQ", "Server list has %d\n", g_list_length(qd->servers));
 
+	version_str = purple_account_get_string(account, "client_version", NULL);
+	qd->client_version = QQ_CLIENT_0D55;	/* set default as QQ2005 */
+	qd->is_above_2007 = FALSE;
+	if (version_str != NULL && strlen(version_str) != 0) {
+		if (strcmp(version_str, "qq2007") == 0) {
+			qd->client_version = QQ_CLIENT_111D;
+			qd->is_above_2007 = TRUE;
+		}
+	}
+
 	qd->is_show_notice = purple_account_get_bool(account, "show_notice", TRUE);
 	qd->is_show_news = purple_account_get_bool(account, "show_news", TRUE);
 
@@ -203,6 +214,11 @@ static void qq_close(PurpleConnection *g
 	}
 
 	qq_disconnect(gc);
+
+	if (qd->ld.token) g_free(qd->ld.token);
+	if (qd->captcha.token) g_free(qd->captcha.token);
+	if (qd->captcha.data) g_free(qd->captcha.data);
+
 	server_list_remove_all(qd);
 
 	g_free(qd);
@@ -536,18 +552,20 @@ static void action_show_account_info(Pur
 	GString *info;
 
 	qd = (qq_data *) gc->proto_data;
-	info = g_string_new("<html><body>\n");
+	info = g_string_new("<html><body>");
 
-	g_string_append_printf(info, _("<b>Current Online</b>: %d<br>\n"), qd->total_online);
-	g_string_append_printf(info, _("<b>Last Refresh</b>: %s<br>\n"), ctime(&qd->last_get_online));
+	g_string_append_printf(info, _("<b>This Login</b>: %s<br>\n"), ctime(&qd->login_time));
+	g_string_append_printf(info, _("<b>Online Buddies</b>: %d<br>\n"), qd->online_total);
+	g_string_append_printf(info, _("<b>Last Refresh</b>: %s<br>\n"), ctime(&qd->online_last_update));
 
-	g_string_append(info, "<hr>\n");
+	g_string_append(info, "<hr>");
 
 	g_string_append_printf(info, _("<b>Server</b>: %s<br>\n"), qd->curr_server);
+	g_string_append_printf(info, _("<b>Client Version</b>: %s<br>\n"), qq_get_ver_desc(qd->client_version));
 	g_string_append_printf(info, _("<b>Connection Mode</b>: %s<br>\n"), qd->use_tcp ? "TCP" : "UDP");
-	g_string_append_printf(info, _("<b>My Internet Address</b>: %s<br>\n"), inet_ntoa(qd->my_ip));
+	g_string_append_printf(info, _("<b>My Internet IP</b>: %s<br>\n"), inet_ntoa(qd->my_ip));
 
-	g_string_append(info, "<hr>\n");
+	g_string_append(info, "<hr>");
 	g_string_append(info, "<i>Network Status</i><br>\n");
 	g_string_append_printf(info, _("<b>Sent</b>: %lu<br>\n"), qd->net_stat.sent);
 	g_string_append_printf(info, _("<b>Resend</b>: %lu<br>\n"), qd->net_stat.resend);
@@ -555,12 +573,11 @@ static void action_show_account_info(Pur
 	g_string_append_printf(info, _("<b>Received</b>: %lu<br>\n"), qd->net_stat.rcved);
 	g_string_append_printf(info, _("<b>Received Duplicate</b>: %lu<br>\n"), qd->net_stat.rcved_dup);
 
-	g_string_append(info, "<hr>\n");
+	g_string_append(info, "<hr>");
 	g_string_append(info, "<i>Information below may not be accurate</i><br>\n");
 
-	g_string_append_printf(info, _("<b>Login Time</b>: %s<br>\n"), ctime(&qd->login_time));
+	g_string_append_printf(info, _("<b>Last Login</b>: %s\n"), ctime(&qd->last_login_time));
 	g_string_append_printf(info, _("<b>Last Login IP</b>: %s<br>\n"), qd->last_login_ip);
-	g_string_append_printf(info, _("<b>Last Login Time</b>: %s\n"), ctime(&qd->last_login_time));
 
 	g_string_append(info, "</body></html>");
 
@@ -848,35 +865,53 @@ static void init_plugin(PurplePlugin *pl
 {
 	PurpleAccountOption *option;
 	PurpleKeyValuePair *kvp;
-	GList *list = NULL;
-	GList *kvlist = NULL;
-	GList *entry;
+	GList *server_list = NULL;
+	GList *server_kv_list = NULL;
+	GList *it;
+	GList *version_kv_list = NULL;
 
-	list = server_list_build('A');
+	server_list = server_list_build('A');
 
-	purple_prefs_add_string_list("/plugins/prpl/qq/serverlist", list);
-	list = purple_prefs_get_string_list("/plugins/prpl/qq/serverlist");
+	purple_prefs_add_string_list("/plugins/prpl/qq/serverlist", server_list);
+	server_list = purple_prefs_get_string_list("/plugins/prpl/qq/serverlist");
 
-	kvlist = NULL;
+	server_kv_list = NULL;
 	kvp = g_new0(PurpleKeyValuePair, 1);
 	kvp->key = g_strdup(_("Auto"));
 	kvp->value = g_strdup("auto");
-	kvlist = g_list_append(kvlist, kvp);
+	server_kv_list = g_list_append(server_kv_list, kvp);
 
-	entry = list;
-	while(entry) {
-		if (entry->data != NULL && strlen(entry->data) > 0) {
+	it = server_list;
+	while(it) {
+		if (it->data != NULL && strlen(it->data) > 0) {
 			kvp = g_new0(PurpleKeyValuePair, 1);
-			kvp->key = g_strdup(entry->data);
-			kvp->value = g_strdup(entry->data);
-			kvlist = g_list_append(kvlist, kvp);
+			kvp->key = g_strdup(it->data);
+			kvp->value = g_strdup(it->data);
+			server_kv_list = g_list_append(server_kv_list, kvp);
 		}
-		entry = entry->next;
+		it = it->next;
 	}
 
-	option = purple_account_option_list_new(_("Server"), "server", kvlist);
+	g_list_free(server_list);
+
+	option = purple_account_option_list_new(_("Select Server"), "server", server_kv_list);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
+#ifdef DEBUG
+	kvp = g_new0(PurpleKeyValuePair, 1);
+	kvp->key = g_strdup(_("QQ2005"));
+	kvp->value = g_strdup("qq2005");
+	version_kv_list = g_list_append(version_kv_list, kvp);
+
+	kvp = g_new0(PurpleKeyValuePair, 1);
+	kvp->key = g_strdup(_("QQ2007"));
+	kvp->value = g_strdup("qq2007");
+	version_kv_list = g_list_append(version_kv_list, kvp);
+
+	option = purple_account_option_list_new(_("Client Version"), "client_version", version_kv_list);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+#endif
+
 	option = purple_account_option_bool_new(_("Connect by TCP"), "use_tcp", TRUE);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
============================================================
--- libpurple/protocols/qq/qq.h	7d26c6dc840ce378d9b814a90c231c4af6e41c7f
+++ libpurple/protocols/qq/qq.h	3c31740ed75db80caffc96a2decb799775baeec6
@@ -41,7 +41,36 @@ typedef struct _qq_add_request qq_add_re
 typedef struct _qq_interval qq_interval;
 typedef struct _qq_net_stat qq_net_stat;
 typedef struct _qq_add_request qq_add_request;
+typedef struct _qq_redirect_data qq_redirect_data;
+typedef struct _qq_login_data qq_login_data;
+typedef struct _qq_captcha_data qq_captcha_data;
 
+struct _qq_captcha_data {
+	guint8 *token;
+	guint16 token_len;
+	guint8 next_index;
+	guint8 *data;
+	guint16 data_len;
+};
+
+struct _qq_login_data {
+	guint8 init_key[QQ_KEY_LENGTH];			/* first encrypt key generated by client */
+	guint8 *token;		/* get from server*/
+	guint8 token_len;
+	guint8 *token_ex;		/* get from server*/
+	guint16 token_ex_len;
+	guint8 captcha_key[QQ_KEY_LENGTH];	/* encrypt key used in captcha generated by client */
+	guint8 pwd_twice_md5[QQ_KEY_LENGTH];			/* password in md5 (or md5' md5) */
+};
+
+struct _qq_redirect_data {
+	guint16 ret;
+	guint8 b1;
+	guint32 w1;
+	guint32 w2;
+	guint32 ip;
+};
+
 struct _qq_add_request {
 	guint32 uid;
 	PurpleConnection *gc;
@@ -111,8 +140,13 @@ struct _qq_data {
 	GList *servers;
 	gchar *curr_server;		/* point to servers->data, do not free*/
 
+	guint16 client_version;
+	gboolean is_above_2007;
+
 	struct in_addr redirect_ip;
 	guint16 redirect_port;
+	qq_redirect_data redirect_data;
+
 	guint check_watcher;
 	guint connect_watcher;
 	gint connect_retry;
@@ -125,10 +159,10 @@ struct _qq_data {
 	GList *transactions;	/* check ack packet and resend */
 
 	guint32 uid;			/* QQ number */
-	guint8 *token;		/* get from server*/
-	int token_len;
-	guint8 inikey[QQ_KEY_LENGTH];			/* initial key to encrypt login packet */
-	guint8 password_twice_md5[QQ_KEY_LENGTH];			/* password in md5 (or md5' md5) */
+	
+	qq_login_data ld;
+	qq_captcha_data captcha;
+	
 	guint8 session_key[QQ_KEY_LENGTH];		/* later use this as key in this session */
 	guint8 session_md5[QQ_KEY_LENGTH];		/* concatenate my uid with session_key and md5 it */
 
@@ -147,8 +181,8 @@ struct _qq_data {
 	guint16 my_port;		/* my port detected by server */
 	guint16 my_icon;		/* my icon index */
 	guint16 my_level;		/* my level */
-	guint32 total_online;		/* the number of online QQ users */
-	time_t last_get_online;		/* last time send get_friends_online packet */
+	guint32 online_total;		/* the number of online QQ users */
+	time_t online_last_update;		/* last time send get_friends_online packet */
 
 	PurpleRoomlist *roomlist;
 	gint channel;			/* the id for opened chat conversation */
============================================================
--- libpurple/protocols/qq/qq_base.c	6ca17e28fe0a03df4a08018804b3c0c2a51f0b11
+++ libpurple/protocols/qq/qq_base.c	767955d22a2632010a419ef0aee3a009a6f9d029
@@ -26,13 +26,14 @@
 #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 "header_info.h"
+#include "qq_define.h"
 #include "qq_base.h"
 #include "packet_parse.h"
 #include "qq.h"
@@ -101,39 +102,6 @@ static const guint8 login_53_68[16] = {
 */
 
 
-typedef struct _qq_login_reply_ok qq_login_reply_ok_packet;
-typedef struct _qq_login_reply_redirect qq_login_reply_redirect_packet;
-
-struct _qq_login_reply_ok {
-	guint8 result;
-	guint8 session_key[QQ_KEY_LENGTH];
-	guint32 uid;
-	struct in_addr client_ip;	/* those detected by server */
-	guint16 client_port;
-	struct in_addr server_ip;
-	guint16 server_port;
-	time_t login_time;
-	guint8 unknown1[26];
-	struct in_addr unknown_server1_ip;
-	guint16 unknown_server1_port;
-	struct in_addr unknown_server2_ip;
-	guint16 unknown_server2_port;
-	guint16 unknown2;	/* 0x0001 */
-	guint16 unknown3;	/* 0x0000 */
-	guint8 unknown4[32];
-	guint8 unknown5[12];
-	struct in_addr last_client_ip;
-	time_t last_login_time;
-	guint8 unknown6[8];
-};
-
-struct _qq_login_reply_redirect {
-	guint8 result;
-	guint32 uid;
-	struct in_addr new_server_ip;
-	guint16 new_server_port;
-};
-
 /* generate a md5 key using uid and session_key */
 static void get_session_md5(guint8 *session_md5, guint32 uid, guint8 *session_key)
 {
@@ -151,53 +119,75 @@ static gint8 process_login_ok(PurpleConn
 {
 	gint bytes;
 	qq_data *qd;
-	qq_login_reply_ok_packet lrop;
 
+	struct {
+		guint8 result;
+		guint8 session_key[QQ_KEY_LENGTH];
+		guint32 uid;
+		struct in_addr client_ip;	/* those detected by server */
+		guint16 client_port;
+		struct in_addr server_ip;
+		guint16 server_port;
+		time_t login_time;
+		guint8 unknown1[26];
+		struct in_addr unknown_server1_ip;
+		guint16 unknown_server1_port;
+		struct in_addr unknown_server2_ip;
+		guint16 unknown_server2_port;
+		guint16 unknown2;	/* 0x0001 */
+		guint16 unknown3;	/* 0x0000 */
+		guint8 unknown4[32];
+		guint8 unknown5[12];
+		struct in_addr last_client_ip;
+		time_t last_login_time;
+		guint8 unknown6[8];
+	} packet;
+
 	qd = (qq_data *) gc->proto_data;
 	/* FIXME, check QQ_LOGIN_REPLY_OK_PACKET_LEN here */
 	bytes = 0;
 
 	/* 000-000: reply code */
-	bytes += qq_get8(&lrop.result, data + bytes);
+	bytes += qq_get8(&packet.result, data + bytes);
 	/* 001-016: session key */
-	bytes += qq_getdata(lrop.session_key, sizeof(lrop.session_key), data + bytes);
+	bytes += qq_getdata(packet.session_key, sizeof(packet.session_key), data + bytes);
 	purple_debug_info("QQ", "Got session_key\n");
 	/* 017-020: login uid */
-	bytes += qq_get32(&lrop.uid, data + bytes);
+	bytes += qq_get32(&packet.uid, data + bytes);
 	/* 021-024: server detected user public IP */
-	bytes += qq_getIP(&lrop.client_ip, data + bytes);
+	bytes += qq_getIP(&packet.client_ip, data + bytes);
 	/* 025-026: server detected user port */
-	bytes += qq_get16(&lrop.client_port, data + bytes);
+	bytes += qq_get16(&packet.client_port, data + bytes);
 	/* 027-030: server detected itself ip 127.0.0.1 ? */
-	bytes += qq_getIP(&lrop.server_ip, data + bytes);
+	bytes += qq_getIP(&packet.server_ip, data + bytes);
 	/* 031-032: server listening port */
-	bytes += qq_get16(&lrop.server_port, data + bytes);
+	bytes += qq_get16(&packet.server_port, data + bytes);
 	/* 033-036: login time for current session */
-	bytes += qq_getime(&lrop.login_time, data + bytes);
+	bytes += qq_getime(&packet.login_time, data + bytes);
 	/* 037-062: 26 bytes, unknown */
-	bytes += qq_getdata((guint8 *) &lrop.unknown1, 26, data + bytes);
+	bytes += qq_getdata((guint8 *) &packet.unknown1, 26, data + bytes);
 	/* 063-066: unknown server1 ip address */
-	bytes += qq_getIP(&lrop.unknown_server1_ip, data + bytes);
+	bytes += qq_getIP(&packet.unknown_server1_ip, data + bytes);
 	/* 067-068: unknown server1 port */
-	bytes += qq_get16(&lrop.unknown_server1_port, data + bytes);
+	bytes += qq_get16(&packet.unknown_server1_port, data + bytes);
 	/* 069-072: unknown server2 ip address */
-	bytes += qq_getIP(&lrop.unknown_server2_ip, data + bytes);
+	bytes += qq_getIP(&packet.unknown_server2_ip, data + bytes);
 	/* 073-074: unknown server2 port */
-	bytes += qq_get16(&lrop.unknown_server2_port, data + bytes);
+	bytes += qq_get16(&packet.unknown_server2_port, data + bytes);
 	/* 075-076: 2 bytes unknown */
-	bytes += qq_get16(&lrop.unknown2, data + bytes);
+	bytes += qq_get16(&packet.unknown2, data + bytes);
 	/* 077-078: 2 bytes unknown */
-	bytes += qq_get16(&lrop.unknown3, data + bytes);
+	bytes += qq_get16(&packet.unknown3, data + bytes);
 	/* 079-110: 32 bytes unknown */
-	bytes += qq_getdata((guint8 *) &lrop.unknown4, 32, data + bytes);
+	bytes += qq_getdata((guint8 *) &packet.unknown4, 32, data + bytes);
 	/* 111-122: 12 bytes unknown */
-	bytes += qq_getdata((guint8 *) &lrop.unknown5, 12, data + bytes);
+	bytes += qq_getdata((guint8 *) &packet.unknown5, 12, data + bytes);
 	/* 123-126: login IP of last session */
-	bytes += qq_getIP(&lrop.last_client_ip, data + bytes);
+	bytes += qq_getIP(&packet.last_client_ip, data + bytes);
 	/* 127-130: login time of last session */
-	bytes += qq_getime(&lrop.last_login_time, data + bytes);
+	bytes += qq_getime(&packet.last_login_time, data + bytes);
 	/* 131-138: 8 bytes unknown */
-	bytes += qq_getdata((guint8 *) &lrop.unknown6, 8, data + bytes);
+	bytes += qq_getdata((guint8 *) &packet.unknown6, 8, data + bytes);
 
 	if (bytes != QQ_LOGIN_REPLY_OK_PACKET_LEN) {	/* fail parsing login info */
 		purple_debug_warning("QQ",
@@ -205,15 +195,15 @@ static gint8 process_login_ok(PurpleConn
 			   QQ_LOGIN_REPLY_OK_PACKET_LEN, bytes);
 	}			/* but we still go on as login OK */
 
-	memcpy(qd->session_key, lrop.session_key, sizeof(qd->session_key));
+	memcpy(qd->session_key, packet.session_key, sizeof(qd->session_key));
 	get_session_md5(qd->session_md5, qd->uid, qd->session_key);
 
-	qd->my_ip.s_addr = lrop.client_ip.s_addr;
+	qd->my_ip.s_addr = packet.client_ip.s_addr;
 
-	qd->my_port = lrop.client_port;
-	qd->login_time = lrop.login_time;
-	qd->last_login_time = lrop.last_login_time;
-	qd->last_login_ip = g_strdup( inet_ntoa(lrop.last_client_ip) );
+	qd->my_port = packet.client_port;
+	qd->login_time = packet.login_time;
+	qd->last_login_time = packet.last_login_time;
+	qd->last_login_ip = g_strdup( inet_ntoa(packet.last_client_ip) );
 
 	return QQ_LOGIN_REPLY_OK;
 }
@@ -223,35 +213,41 @@ static gint8 process_login_redirect(Purp
 {
 	qq_data *qd;
 	gint bytes;
-	qq_login_reply_redirect_packet lrrp;
+	struct {
+		guint8 result;
+		guint32 uid;
+		struct in_addr new_server_ip;
+		guint16 new_server_port;
+	} packet;
 
+
 	qd = (qq_data *) gc->proto_data;
 	bytes = 0;
 	/* 000-000: reply code */
-	bytes += qq_get8(&lrrp.result, data + bytes);
+	bytes += qq_get8(&packet.result, data + bytes);
 	/* 001-004: login uid */
-	bytes += qq_get32(&lrrp.uid, data + bytes);
+	bytes += qq_get32(&packet.uid, data + bytes);
 	/* 005-008: redirected new server IP */
-	bytes += qq_getIP(&lrrp.new_server_ip, data + bytes);
+	bytes += qq_getIP(&packet.new_server_ip, data + bytes);
 	/* 009-010: redirected new server port */
-	bytes += qq_get16(&lrrp.new_server_port, data + bytes);
+	bytes += qq_get16(&packet.new_server_port, data + bytes);
 
 	if (bytes != QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN) {
 		purple_debug_error("QQ",
 			   "Fail parsing login redirect packet, expect %d bytes, read %d bytes\n",
 			   QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN, bytes);
-		return QQ_LOGIN_REPLY_ERR_MISC;
+		return QQ_LOGIN_REPLY_ERR;
 	}
 
 	/* redirect to new server, do not disconnect or connect here
 	 * those connect should be called at packet_process */
-	qd->redirect_ip.s_addr = lrrp.new_server_ip.s_addr;
-	qd->redirect_port = lrrp.new_server_port;
+	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_send_packet_token(PurpleConnection *gc)
+void qq_request_token(PurpleConnection *gc)
 {
 	qq_data *qd;
 	guint8 buf[16] = {0};
@@ -267,7 +263,7 @@ void qq_send_packet_token(PurpleConnecti
 }
 
 /* send login packet to QQ server */
-void qq_send_packet_login(PurpleConnection *gc)
+void qq_request_login(PurpleConnection *gc)
 {
 	qq_data *qd;
 	guint8 *buf, *raw_data;
@@ -278,16 +274,8 @@ void qq_send_packet_login(PurpleConnecti
 	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
 	qd = (qq_data *) gc->proto_data;
 
-	g_return_if_fail(qd->token != NULL && qd->token_len > 0);
+	g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0);
 
-#ifdef DEBUG
-	memset(qd->inikey, 0x01, sizeof(qd->inikey));
-#else
-	for (bytes = 0; bytes < sizeof(qd->inikey); bytes++)	{
-		qd->inikey[bytes] = (guint8) (rand() & 0xff);
-	}
-#endif
-
 	raw_data = g_newa(guint8, QQ_LOGIN_DATA_LENGTH);
 	memset(raw_data, 0, QQ_LOGIN_DATA_LENGTH);
 
@@ -296,7 +284,7 @@ void qq_send_packet_login(PurpleConnecti
 	bytes = 0;
 	/* now generate the encrypted data
 	 * 000-015 use password_twice_md5 as key to encrypt empty string */
-	encrypted_len = qq_encrypt(raw_data + bytes, (guint8 *) "", 0, qd->password_twice_md5);
+	encrypted_len = qq_encrypt(raw_data + bytes, (guint8 *) "", 0, qd->ld.pwd_twice_md5);
 	g_return_if_fail(encrypted_len == 16);
 	bytes += encrypted_len;
 
@@ -313,26 +301,26 @@ void qq_send_packet_login(PurpleConnecti
 	/* 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->token_len);
+	bytes += qq_put8(raw_data + bytes, qd->ld.token_len);
 	/* 070-093, login token, normally 24 bytes */
-	bytes += qq_putdata(raw_data + bytes, qd->token, qd->token_len);
+	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 */
 
-	encrypted_len = qq_encrypt(encrypted_data, raw_data, QQ_LOGIN_DATA_LENGTH, qd->inikey);
+	encrypted_len = qq_encrypt(encrypted_data, raw_data, QQ_LOGIN_DATA_LENGTH, qd->ld.init_key);
 
 	buf = g_newa(guint8, MAX_PACKET_SIZE);
 	memset(buf, 0, MAX_PACKET_SIZE);
 	bytes = 0;
-	bytes += qq_putdata(buf + bytes, qd->inikey, QQ_KEY_LENGTH);
+	bytes += qq_putdata(buf + bytes, qd->ld.init_key, QQ_KEY_LENGTH);
 	bytes += qq_putdata(buf + bytes, encrypted_data, encrypted_len);
 
 	qd->send_seq++;
 	qq_send_cmd_encrypted(gc, QQ_CMD_LOGIN, qd->send_seq, buf, bytes, TRUE);
 }
 
-guint8 qq_process_token_reply(PurpleConnection *gc, guint8 *buf, gint buf_len)
+guint8 qq_process_token(PurpleConnection *gc, guint8 *buf, gint buf_len)
 {
 	qq_data *qd;
 	guint8 ret;
@@ -346,7 +334,7 @@ guint8 qq_process_token_reply(PurpleConn
 
 	ret = buf[0];
 
-	if (ret != QQ_TOKEN_REPLY_OK) {
+	if (ret != QQ_LOGIN_REPLY_OK) {
 		purple_debug_error("QQ", "Failed to request token: %d\n", buf[0]);
 		qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ",
 				buf, buf_len,
@@ -357,7 +345,7 @@ guint8 qq_process_token_reply(PurpleConn
 		}
 		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_msg);
 		g_free(error_msg);
-		return ret;
+		return QQ_LOGIN_REPLY_ERR;
 	}
 
 	token_len = buf_len-2;
@@ -365,7 +353,7 @@ guint8 qq_process_token_reply(PurpleConn
 		error_msg = g_strdup_printf( _("Invalid token len, %d"), token_len);
 		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_msg);
 		g_free(error_msg);
-		return -1;
+		return QQ_LOGIN_REPLY_ERR;
 	}
 
 	if (buf[1] != token_len) {
@@ -376,34 +364,39 @@ guint8 qq_process_token_reply(PurpleConn
 			buf+2, token_len,
 			"<<< got a token -> [default] decrypt and dump");
 
-	qd->token = g_new0(guint8, token_len);
-	qd->token_len = token_len;
-	g_memmove(qd->token, buf + 2, qd->token_len);
+	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_send_packet_logout(PurpleConnection *gc)
+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->password_twice_md5, QQ_KEY_LENGTH);
+		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 */
 }
 
 /* process the login reply packet */
-guint8 qq_process_login_reply( PurpleConnection *gc, guint8 *data, gint data_len)
+guint8 qq_process_login( PurpleConnection *gc, guint8 *data, gint data_len)
 {
 	qq_data *qd;
 	guint8 ret = data[0];
 	gchar *server_reply, *server_reply_utf8;
 	gchar *error_msg;
 
-	g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR_MISC);
+	g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR);
 
 	qd = (qq_data *) gc->proto_data;
 
@@ -472,7 +465,7 @@ guint8 qq_process_login_reply( PurpleCon
 }
 
 /* send keep-alive packet to QQ server (it is a heart-beat) */
-void qq_send_packet_keep_alive(PurpleConnection *gc)
+void qq_request_keep_alive(PurpleConnection *gc)
 {
 	qq_data *qd;
 	guint8 raw_data[16] = {0};
@@ -505,8 +498,8 @@ gboolean qq_process_keep_alive(guint8 *d
 			return TRUE;
 
 	/* segments[0] and segment[1] are all 0x30 ("0") */
-	qd->total_online = strtol(segments[2], NULL, 10);
-	if(0 == qd->total_online) {
+	qd->online_total = strtol(segments[2], NULL, 10);
+	if(0 == qd->online_total) {
 		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				_("Keep alive error"));
 	}
@@ -519,3 +512,341 @@ gboolean qq_process_keep_alive(guint8 *d
 	g_strfreev(segments);
 	return TRUE;
 }
+
+/* For QQ2007/2008 */
+void qq_request_get_server(PurpleConnection *gc)
+{
+	qq_data *qd;
+	guint8 *buf, *raw_data;
+	gint bytes;
+	guint8 *encrypted_data;
+	gint encrypted_len;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	raw_data = g_newa(guint8, QQ_LOGIN_DATA_LENGTH);
+	memset(raw_data, 0, QQ_LOGIN_DATA_LENGTH);
+
+	encrypted_data = g_newa(guint8, QQ_LOGIN_DATA_LENGTH + 16);	/* 16 bytes more */
+
+	bytes = 0;
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)&qd->redirect_data, sizeof(qd->redirect_data));
+
+	encrypted_len = qq_encrypt(encrypted_data, raw_data, QQ_LOGIN_DATA_LENGTH, qd->ld.init_key);
+
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+	memset(buf, 0, MAX_PACKET_SIZE);
+	bytes = 0;
+	bytes += qq_putdata(buf + bytes, qd->ld.init_key, QQ_KEY_LENGTH);
+	bytes += qq_putdata(buf + bytes, encrypted_data, encrypted_len);
+
+	qd->send_seq++;
+	qq_send_cmd_encrypted(gc, QQ_CMD_LOGIN, qd->send_seq, buf, bytes, TRUE);
+}
+
+guint16 qq_process_get_server(PurpleConnection *gc, guint8 *rcved, gint rcved_len)
+{
+	qq_data *qd;
+	guint8 *data;
+	gint data_len;
+	qq_redirect_data *redirect;
+
+	g_return_val_if_fail (gc != NULL && gc->proto_data != NULL, QQ_LOGIN_REPLY_ERR);
+	qd = (qq_data *) gc->proto_data;
+
+	data = g_newa(guint8, rcved_len);
+	/* May use password_twice_md5 in the past version like QQ2005*/
+	data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.init_key);
+	if (data_len < 0) {
+		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Can not decrypt get server reply"));
+		return QQ_LOGIN_REPLY_ERR;
+	}
+
+	if (data_len < sizeof(qq_redirect_data)) {
+		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Can not decrypt get server reply"));
+		return QQ_LOGIN_REPLY_ERR;
+	}
+
+	redirect = (qq_redirect_data *)data;
+	if (redirect->ret == 0) {
+		memset(&qd->redirect_data, 0, sizeof(qd->redirect_data));
+		qd->redirect_ip.s_addr = 0;
+		return QQ_LOGIN_REPLY_OK;
+	}
+
+	g_memmove(&qd->redirect_data, redirect, sizeof(qd->redirect_data));
+	g_memmove(&qd->redirect_ip, &redirect->ip, sizeof(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_data;
+	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_data = 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_data, raw_data, QQ_LOGIN_DATA_LENGTH, qd->ld.init_key);
+
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+	memset(buf, 0, MAX_PACKET_SIZE);
+	bytes = 0;
+	bytes += qq_putdata(buf + bytes, qd->ld.init_key, QQ_KEY_LENGTH);
+	bytes += qq_putdata(buf + bytes, encrypted_data, 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_data;
+	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_data = 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_data, raw_data, QQ_LOGIN_DATA_LENGTH, qd->ld.init_key);
+
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+	memset(buf, 0, MAX_PACKET_SIZE);
+	bytes = 0;
+	bytes += qq_putdata(buf + bytes, qd->ld.init_key, QQ_KEY_LENGTH);
+	bytes += qq_putdata(buf + bytes, encrypted_data, 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_data;
+	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_data = 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->captcha.token_len); 	/* captcha token */
+	bytes += qq_putdata(raw_data + bytes, qd->captcha.token, qd->captcha.token_len);
+
+	encrypted_len = qq_encrypt(encrypted_data, raw_data, QQ_LOGIN_DATA_LENGTH, qd->ld.init_key);
+
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+	memset(buf, 0, MAX_PACKET_SIZE);
+	bytes = 0;
+	bytes += qq_putdata(buf + bytes, qd->ld.init_key, QQ_KEY_LENGTH);
+	bytes += qq_putdata(buf + bytes, encrypted_data, 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 code of  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 verify"));
+}
+
+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 Verifing"),
+		_("QQ Captcha Verifing"),
+		_("Please fill code according to 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 *buf, gint buf_len)
+{
+	qq_data *qd;
+	int bytes;
+	guint8 ret;
+	guint8 sub_cmd;
+	guint8 reply;
+	guint16 captcha_len;
+	guint8 curr_index;
+
+	g_return_val_if_fail(buf != NULL && buf_len != 0, -1);
+
+	g_return_val_if_fail(gc != NULL  && gc->proto_data != NULL, -1);
+	qd = (qq_data *) gc->proto_data;
+
+	ret = buf[0];
+
+	bytes = 0;
+	bytes += qq_get8(&sub_cmd, buf + bytes);
+	bytes += 2;
+	bytes += qq_get8(&reply, buf + bytes);
+
+	bytes += qq_get16(&(qd->ld.token_ex_len), buf + bytes);
+	if (qd->ld.token_ex != NULL)	g_free(qd->ld.token_ex);
+	qd->ld.token_ex = g_new0(guint8, qd->ld.token_ex_len);
+	bytes += qq_getdata(qd->ld.token_ex, qd->ld.token_ex_len , buf + bytes);
+
+	if(reply != 1)
+	{
+		purple_debug_info("QQ", "All captchaes is verified\n");
+		return QQ_LOGIN_REPLY_OK;
+	}
+
+	bytes += qq_get16(&captcha_len, buf + 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, buf + bytes);
+	qd->captcha.data_len += captcha_len;
+
+	bytes += qq_get8(&curr_index, buf + bytes);
+	bytes += qq_get8(&qd->captcha.next_index, buf + bytes);
+
+	bytes += qq_get16(&qd->captcha.token_len, buf + bytes);
+	qd->captcha.token = g_new0(guint8, qd->captcha.token_len);
+	bytes += qq_getdata(qd->captcha.token, qd->captcha.token_len, buf + bytes);
+
+	if(qd->captcha.next_index > 0)
+	{
+		return QQ_LOGIN_REPLY_NEXT_TOKEN_EX;
+	}
+
+	return QQ_LOGIN_REPLY_CAPTCHA_DLG;
+}
============================================================
--- libpurple/protocols/qq/qq_base.h	1be7d89ee372ed31923b785525b7966caa34db3f
+++ libpurple/protocols/qq/qq_base.h	ea7caf396b13c3400b8d302a782cef8aa3761425
@@ -28,29 +28,40 @@
 #include <glib.h>
 #include "connection.h"
 
-#define QQ_TOKEN_REPLY_OK 	0x00
-
 #define QQ_LOGIN_REPLY_OK							0x00
 #define QQ_LOGIN_REPLY_REDIRECT				0x01
 #define QQ_LOGIN_REPLY_ERR_PWD					0x05
 #define QQ_LOGIN_REPLY_NEED_REACTIVE		0x06
 #define QQ_LOGIN_REPLY_REDIRECT_EX			0x0A
-#define QQ_LOGIN_REPLY_ERR_MISC				0xff	/* defined by myself */
+/* defined by myself */
+#define QQ_LOGIN_REPLY_CAPTCHA_DLG			0xfc
+#define QQ_LOGIN_REPLY_NEXT_TOKEN_EX		0xfd
+#define QQ_LOGIN_REPLY_ERR_DECRYPT			0xfe
+#define QQ_LOGIN_REPLY_ERR							0xff
 
-#define QQ_LOGIN_MODE_NORMAL        0x0a
-#define QQ_LOGIN_MODE_AWAY	    0x1e
-#define QQ_LOGIN_MODE_HIDDEN        0x28
+#define QQ_LOGIN_MODE_NORMAL		0x0a
+#define QQ_LOGIN_MODE_AWAY	    	0x1e
+#define QQ_LOGIN_MODE_HIDDEN		0x28
 
 #define QQ_UPDATE_ONLINE_INTERVAL   300	/* in sec */
 
-void qq_send_packet_token(PurpleConnection *gc);
-guint8 qq_process_token_reply(PurpleConnection *gc, guint8 *buf, gint buf_len);
+void qq_request_token(PurpleConnection *gc);
+guint8 qq_process_token(PurpleConnection *gc, guint8 *buf, gint buf_len);
 
-void qq_send_packet_login(PurpleConnection *gc);
-guint8 qq_process_login_reply( PurpleConnection *gc, guint8 *data, gint data_len);
+void qq_request_token_ex(PurpleConnection *gc);
+void qq_request_token_ex_next(PurpleConnection *gc);
+guint8 qq_process_token_ex(PurpleConnection *gc, guint8 *buf, gint buf_len);
+void qq_captcha_input_dialog(PurpleConnection *gc,qq_captcha_data *captcha);
 
-void qq_send_packet_logout(PurpleConnection *gc);
+void qq_request_login(PurpleConnection *gc);
+guint8 qq_process_login( PurpleConnection *gc, guint8 *data, gint data_len);
 
-void qq_send_packet_keep_alive(PurpleConnection *gc);
+void qq_request_logout(PurpleConnection *gc);
+
+void qq_request_keep_alive(PurpleConnection *gc);
 gboolean qq_process_keep_alive(guint8 *data, gint data_len, PurpleConnection *gc);
+
+/* for QQ2007 */
+void qq_request_get_server(PurpleConnection *gc);
+guint16 qq_process_get_server(PurpleConnection *gc, guint8 *rcved, gint rcved_len);
 #endif
============================================================
--- libpurple/protocols/qq/qq_define.c	2a513cf15a10847e24922596fcd0d05c5f2412d2
+++ libpurple/protocols/qq/qq_define.c	b0b876be933b82e32bcef47e673b4fabe5ab339a
@@ -1,5 +1,5 @@
 /**
- * @file header_info.c
+ * @file qq_define.c
  *
  * purple
  *
@@ -24,7 +24,7 @@
 
 #include "internal.h"
 
-#include "header_info.h"
+#include "qq_define.h"
 
 #define QQ_CLIENT_062E 0x062e	/* GB QQ2000c build 0630 */
 #define QQ_CLIENT_072E 0x072e	/* EN QQ2000c build 0305 */
@@ -51,7 +51,6 @@
 #define QQ_CLIENT_0F4B 0x0F4B	/* QQ2006 Beta 3  */
 
 #define QQ_CLIENT_1105 0x1105	/* QQ2007 beta4*/
-#define QQ_CLIENT_111D 0x111D	/* QQ2007 */
 #define QQ_CLIENT_115B 0x115B	/* QQ2008 */
 #define QQ_CLIENT_1203 0x1203	/* QQ2008 */
 #define QQ_CLIENT_1205 0x1205	/* QQ2008 */
@@ -94,6 +93,7 @@ const gchar *qq_get_ver_desc(gint source
 		return "QQ2005 beta1";
 	case QQ_CLIENT_0D51:
 		return "QQ2005 beta2";
+	case QQ_CLIENT_0D55:
 	case QQ_CLIENT_0D61:
 		return "QQ2005";
 	case QQ_CLIENT_0E1B:
============================================================
--- libpurple/protocols/qq/qq_define.h	8241e8f0df57be7a021443c7796c1c249c539b5f
+++ libpurple/protocols/qq/qq_define.h	5048b85b1fc951d59bfc35da16f22c9f10cabfd2
@@ -1,5 +1,5 @@
 /**
- * @file header_info.h
+ * @file qq_define.h
  *
  * purple
  *
@@ -30,10 +30,11 @@
 #define QQ_UDP_HEADER_LENGTH    7
 #define QQ_TCP_HEADER_LENGTH    9
 
-#define QQ_PACKET_TAG           0x02	/* all QQ text packets starts with it */
-#define QQ_PACKET_TAIL          0x03	/* all QQ text packets end with it */
+#define QQ_PACKET_TAG			0x02	/* all QQ text packets starts with it */
+#define QQ_PACKET_TAIL			0x03	/* all QQ text packets end with it */
 
-#define QQ_CLIENT       0x0d55
+#define QQ_CLIENT_0D55 0x0d55	/* QQ2005 used by openq before */
+#define QQ_CLIENT_111D 0x111D	/* QQ2007 */
 
 const gchar *qq_get_ver_desc(gint source);
 
@@ -64,6 +65,13 @@ enum {
 	QQ_CMD_TOKEN  = 0x0062, 		/* get login token */
 	QQ_CMD_RECV_MSG_SYS = 0x0080,			/* receive a system message */
 	QQ_CMD_BUDDY_CHANGE_STATUS = 0x0081,	/* buddy change status */
+	/* for QQ2007*/
+	QQ_CMD_GET_SERVER = 0x0091,					/* select login server */
+	QQ_CMD_TOKEN_EX = 0x00BA,						/* get LOGIN token */
+	QQ_CMD_CHECK_PWD = 0x00DD,				/* Password verify */
+	QQ_CMD_GET_CAPTCHA = 0x00AE,				/* the request verification of information */
+	QQ_CMD_BUDDY_ADD_NO_AUTH_EX = 0x00A7,			/* add friend without auth */
+	QQ_CMD_BUDDY_ADD_AUTH_EX = 0x00A8, 				/* add buddy with auth */
 };
 
 const gchar *qq_get_cmd_desc(gint type);
============================================================
--- libpurple/protocols/qq/qq_network.c	a8e9da018f66d0d8ce5641e76142356bb0cd03a3
+++ libpurple/protocols/qq/qq_network.c	dbde8647a131927c84dbcbf99f73ea6a03605ca2
@@ -30,7 +30,7 @@
 #include "group_info.h"
 #include "group_free.h"
 #include "qq_crypt.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "qq_base.h"
 #include "buddy_list.h"
 #include "packet_parse.h"
@@ -160,7 +160,7 @@ static gboolean connect_check(gpointer d
 		qd->connect_watcher = 0;
 	}
 
-	if (qd->fd >= 0 && qd->token != NULL && qd->token_len >= 0) {
+	if (qd->fd >= 0 && qd->ld.token != NULL && qd->ld.token_len > 0) {
 		purple_debug_info("QQ", "Connect ok\n");
 		return FALSE;
 	}
@@ -228,6 +228,19 @@ gboolean qq_connect_later(gpointer data)
 	return FALSE;	/* timeout callback stops */
 }
 
+static void redirect_server(PurpleConnection *gc)
+{
+	qq_data *qd;
+	qd = (qq_data *) gc->proto_data;
+
+	if (qd->check_watcher > 0) {
+			purple_timeout_remove(qd->check_watcher);
+			qd->check_watcher = 0;
+	}
+	if (qd->connect_watcher > 0)	purple_timeout_remove(qd->connect_watcher);
+	qd->connect_watcher = purple_timeout_add_seconds(QQ_CONNECT_INTERVAL, qq_connect_later, gc);
+}
+
 /* process the incoming packet from qq_pending */
 static gboolean packet_process(PurpleConnection *gc, guint8 *buf, gint buf_len)
 {
@@ -242,6 +255,7 @@ static gboolean packet_process(PurpleCon
 	guint32 room_id;
 	gint update_class;
 	guint32 ship32;
+	int ret;
 
 	qq_transaction *trans;
 
@@ -292,20 +306,13 @@ static gboolean packet_process(PurpleCon
 
 	switch (cmd) {
 		case QQ_CMD_TOKEN:
-			if (qq_process_token_reply(gc, buf + bytes, bytes_not_read) == QQ_TOKEN_REPLY_OK) {
-				qq_send_packet_login(gc);
-			}
-			break;
+		case QQ_CMD_GET_SERVER:
 		case QQ_CMD_LOGIN:
-			qq_proc_login_cmd(gc, buf + bytes, bytes_not_read);
-			/* check is redirect or not, and do it now */
-			if (qd->redirect_ip.s_addr != 0) {
-				if (qd->check_watcher > 0) {
-					purple_timeout_remove(qd->check_watcher);
-					qd->check_watcher = 0;
+			ret = qq_proc_login_cmds(gc, cmd, seq, buf + bytes, bytes_not_read, update_class, ship32);
+			if (ret != QQ_LOGIN_REPLY_OK) {
+				if (ret == QQ_LOGIN_REPLY_REDIRECT) {
+					redirect_server(gc);
 				}
-				if (qd->connect_watcher > 0)	purple_timeout_remove(qd->connect_watcher);
-				qd->connect_watcher = purple_timeout_add_seconds(QQ_CONNECT_INTERVAL, qq_connect_later, gc);
 				return FALSE;	/* do nothing after this function and return now */
 			}
 			break;
@@ -316,10 +323,10 @@ static gboolean packet_process(PurpleCon
 			purple_debug_info("QQ", "%s (0x%02X) for room %d, len %d\n",
 					qq_get_room_cmd_desc(room_cmd), room_cmd, room_id, buf_len);
 #endif
-			qq_proc_room_cmd(gc, seq, room_cmd, room_id, buf + bytes, bytes_not_read, update_class, ship32);
+			qq_proc_room_cmds(gc, seq, room_cmd, room_id, buf + bytes, bytes_not_read, update_class, ship32);
 			break;
 		default:
-			qq_proc_client_cmd(gc, cmd, seq, buf + bytes, bytes_not_read, update_class, ship32);
+			qq_proc_client_cmds(gc, cmd, seq, buf + bytes, bytes_not_read, update_class, ship32);
 			break;
 	}
 
@@ -645,7 +652,7 @@ static gboolean network_timeout(gpointer
 	qd->itv_count.keep_alive--;
 	if (qd->itv_count.keep_alive <= 0) {
 		qd->itv_count.keep_alive = qd->itv_config.keep_alive;
-		qq_send_packet_keep_alive(gc);
+		qq_request_keep_alive(gc);
 		return TRUE;
 	}
 
@@ -663,10 +670,9 @@ static gboolean network_timeout(gpointer
 	return TRUE;		/* if return FALSE, timeout callback stops */
 }
 
-static void do_request_token(PurpleConnection *gc)
+static void set_all_keys(PurpleConnection *gc)
 {
 	qq_data *qd;
-	gchar *conn_msg;
 	const gchar *passwd;
 
 	/* _qq_show_socket("Got login socket", source); */
@@ -682,24 +688,27 @@ static void do_request_token(PurpleConne
 	qd->channel = 1;
 	qd->uid = strtol(purple_account_get_username(purple_connection_get_account(gc)), NULL, 10);
 
+#ifdef DEBUG
+	memset(qd->ld.init_key, 0x01, sizeof(qd->ld.init_key));
+	memset(qd->ld.captcha_key, 0x02, sizeof(qd->ld.captcha_key));
+#else
+	for (bytes = 0; bytes < sizeof(qd->ld.init_key); bytes++)	{
+		qd->ld.init_key[bytes] = (guint8) (rand() & 0xff);
+	}
+	for (bytes = 0; bytes < sizeof(qd->captcha_key); bytes++)	{
+		qd->captcha_key[bytes] = (guint8) (rand() & 0xff);
+	}
+#endif
+
 	/* now generate md5 processed passwd */
 	passwd = purple_account_get_password(purple_connection_get_account(gc));
 
 	/* use twice-md5 of user password as session key since QQ 2003iii */
-	qq_get_md5(qd->password_twice_md5, sizeof(qd->password_twice_md5),
+	qq_get_md5(qd->ld.pwd_twice_md5, sizeof(qd->ld.pwd_twice_md5),
 		(guint8 *)passwd, strlen(passwd));
-	qq_get_md5(qd->password_twice_md5, sizeof(qd->password_twice_md5),
-		qd->password_twice_md5, sizeof(qd->password_twice_md5));
+	qq_get_md5(qd->ld.pwd_twice_md5, sizeof(qd->ld.pwd_twice_md5),
+		qd->ld.pwd_twice_md5, sizeof(qd->ld.pwd_twice_md5));
 
-	g_return_if_fail(qd->network_watcher == 0);
-	qd->network_watcher = purple_timeout_add_seconds(qd->itv_config.resend, network_timeout, gc);
-
-	/* Update the login progress status display */
-	conn_msg = g_strdup_printf(_("Request token"));
-	purple_connection_update_progress(gc, conn_msg, 2, QQ_CONNECT_STEPS);
-	g_free(conn_msg);
-
-	qq_send_packet_token(gc);
 }
 
 /* the callback function after socket is built
@@ -744,7 +753,19 @@ static void connect_cb(gpointer data, gi
 		conn->input_handler = purple_input_add(source, PURPLE_INPUT_READ, udp_pending, gc);
 	}
 
-	do_request_token( gc );
+	g_return_if_fail(qd->network_watcher == 0);
+	qd->network_watcher = purple_timeout_add_seconds(qd->itv_config.resend, network_timeout, gc);
+
+	set_all_keys( gc );
+
+	if (qd->is_above_2007) {
+		purple_connection_update_progress(gc, _("Get server ..."), 2, QQ_CONNECT_STEPS);
+		qq_request_get_server(gc);
+		return;
+	}
+
+	purple_connection_update_progress(gc, _("Request token"), 2, QQ_CONNECT_STEPS);
+	qq_request_token(gc);
 }
 
 #ifndef purple_proxy_connect_udp
@@ -888,7 +909,6 @@ gboolean connect_to_server(PurpleConnect
 {
 	PurpleAccount *account ;
 	qq_data *qd;
-	gchar *conn_msg;
 
 	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, FALSE);
 	account = purple_connection_get_account(gc);
@@ -900,9 +920,7 @@ gboolean connect_to_server(PurpleConnect
 		return FALSE;
 	}
 
-	conn_msg = g_strdup_printf( _("Connecting server %s, retries %d"), server, port);
-	purple_connection_update_progress(gc, conn_msg, 1, QQ_CONNECT_STEPS);
-	g_free(conn_msg);
+	purple_connection_update_progress(gc, _("Connecting server ..."), 1, QQ_CONNECT_STEPS);
 
 	purple_debug_info("QQ", "Connect to %s:%d\n", server, port);
 
@@ -963,7 +981,7 @@ void qq_disconnect(PurpleConnection *gc)
 
 	/* finish  all I/O */
 	if (qd->fd >= 0 && qd->is_login) {
-		qq_send_packet_logout(gc);
+		qq_request_logout(gc);
 	}
 
 	/* not connected */
@@ -988,14 +1006,9 @@ void qq_disconnect(PurpleConnection *gc)
 
 	qq_trans_remove_all(gc);
 
-	if (qd->token) {
-		purple_debug_info("QQ", "free token\n");
-		g_free(qd->token);
-		qd->token = NULL;
-		qd->token_len = 0;
-	}
-	memset(qd->inikey, 0, sizeof(qd->inikey));
-	memset(qd->password_twice_md5, 0, sizeof(qd->password_twice_md5));
+	memset(qd->ld.init_key, 0, sizeof(qd->ld.init_key));
+	memset(qd->ld.captcha_key, 0, sizeof(qd->ld.captcha_key));
+	memset(qd->ld.pwd_twice_md5, 0, sizeof(qd->ld.pwd_twice_md5));
 	memset(qd->session_key, 0, sizeof(qd->session_key));
 	memset(qd->session_md5, 0, sizeof(qd->session_md5));
 
@@ -1019,7 +1032,7 @@ static gint packet_encap(qq_data *qd, gu
 	}
 	/* now comes the normal QQ packet as UDP */
 	bytes += qq_put8(buf + bytes, QQ_PACKET_TAG);
-	bytes += qq_put16(buf + bytes, QQ_CLIENT);
+	bytes += qq_put16(buf + bytes, qd->client_version);
 	bytes += qq_put16(buf + bytes, cmd);
 
 	bytes += qq_put16(buf + bytes, seq);
============================================================
--- libpurple/protocols/qq/qq_process.c	587c83dd9bd715ffdc1da1e0a6cce725971355ef
+++ libpurple/protocols/qq/qq_process.c	3d29b82d408d5a9504ea576ebac6eed88032156e
@@ -42,7 +42,7 @@
 #include "group_join.h"
 #include "group_opt.h"
 
-#include "header_info.h"
+#include "qq_define.h"
 #include "qq_base.h"
 #include "im.h"
 #include "qq_process.h"
@@ -176,8 +176,7 @@ static void process_server_msg(guint8 *d
 			_qq_process_msg_sys_notice(gc, from, to, msg_utf8);
 			break;
 		case QQ_SERVER_NEW_CLIENT:
-			purple_debug_warning("QQ",
-				   "QQ Server has newer client than %s\n", qq_get_ver_desc(QQ_CLIENT));
+			purple_debug_warning("QQ", "QQ Server has newer client version\n");
 			break;
 		default:
 			purple_debug_warning("QQ", "Recv unknown sys msg code: %s\nMsg: %s\n", code, msg_utf8);
@@ -407,6 +406,10 @@ void qq_update_online(PurpleConnection *
 
 void qq_update_online(PurpleConnection *gc, guint16 cmd)
 {
+	qq_data *qd;
+	g_return_if_fail (gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
 	switch (cmd) {
 		case 0:
 			qq_request_get_buddies_online(gc, 0, QQ_CMD_CLASS_UPDATE_ONLINE);
@@ -414,13 +417,14 @@ void qq_update_online(PurpleConnection *
 		case QQ_CMD_GET_BUDDIES_ONLINE:
 			/* last command */
 			update_all_rooms_online(gc, 0, 0);
+			qd->online_last_update = time(NULL);
 			break;
 		default:
 			break;
 	}
 }
 
-void qq_proc_room_cmd(PurpleConnection *gc, guint16 seq,
+void qq_proc_room_cmds(PurpleConnection *gc, guint16 seq,
 		guint8 room_cmd, guint32 room_id, guint8 *rcved, gint rcved_len,
 		gint update_class, guint32 ship32)
 {
@@ -567,55 +571,108 @@ void qq_proc_room_cmd(PurpleConnection *
 	}
 }
 
-void qq_proc_login_cmd(PurpleConnection *gc, guint8 *rcved, gint rcved_len)
+guint8 qq_proc_login_cmds(PurpleConnection *gc,  guint16 cmd, guint16 seq,
+		guint8 *rcved, gint rcved_len, gint update_class, guint32 ship32)
 {
 	qq_data *qd;
-	guint8 *data;
-	gint data_len;
-	guint ret_8;
+	guint8 *data = NULL;
+	gint data_len = 0;
+	guint ret_8 = QQ_LOGIN_REPLY_ERR;
 
-	g_return_if_fail (gc != NULL && gc->proto_data != NULL);
+	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(rcved_len > 0, QQ_LOGIN_REPLY_ERR);
 	data = g_newa(guint8, rcved_len);
-	/* May use password_twice_md5 in the past version like QQ2005*/
-	data_len = qq_decrypt(data, rcved, rcved_len, qd->inikey);
-	if (data_len >= 0) {
+
+	switch (cmd) {
+		case QQ_CMD_TOKEN:
+			if (qq_process_token(gc, rcved, rcved_len) == QQ_LOGIN_REPLY_OK) {
+				if (qd->is_above_2007) {
+					qq_request_token_ex(gc);
+				} else {
+					qq_request_login(gc);
+				}
+				return QQ_LOGIN_REPLY_OK;
+			}
+			return QQ_LOGIN_REPLY_ERR;
+		case QQ_CMD_GET_SERVER:
+		case QQ_CMD_TOKEN_EX:
+			data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.init_key);
+			break;
+		default:
+			/* May use password_twice_md5 in the past version like QQ2005 */
+			data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.init_key);
+			if (data_len >= 0) {
+				purple_debug_warning("QQ", "Decrypt login packet by init_key, %d bytes\n", data_len);
+			} else {
+				data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.pwd_twice_md5);
+				if (data_len >= 0) {
+					purple_debug_warning("QQ", "Decrypt login packet by password_twice_md5, %d bytes\n", data_len);
+				}
+			}
+			break;
+	}
+
+	if (data_len < 0) {
 		purple_debug_warning("QQ",
-				"Decrypt login reply packet with inikey, %d bytes\n", data_len);
-	} else {
-		data_len = qq_decrypt(data, rcved, rcved_len, qd->password_twice_md5);
-		if (data_len >= 0) {
-			purple_debug_warning("QQ",
-				"Decrypt login reply packet with password_twice_md5, %d bytes\n", data_len);
-		} else {
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				"Can not decrypt login cmd, [%05d], 0x%04X %s, len %d\n",
+				seq, cmd, qq_get_cmd_desc(cmd), rcved_len);
+		qq_show_packet("Can not decrypted", rcved, rcved_len);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				_("Can not decrypt login reply"));
-			return;
-		}
+		return QQ_LOGIN_REPLY_ERR_DECRYPT;
 	}
 
-	ret_8 = qq_process_login_reply(gc, data, data_len);
-	if (ret_8 != QQ_LOGIN_REPLY_OK) {
-		return;
-	}
+	switch (cmd) {
+		case QQ_CMD_GET_SERVER:
+			ret_8 = qq_process_get_server(gc, data, data_len);
+			if ( ret_8 == QQ_LOGIN_REPLY_OK) {
+				qq_request_token(gc);
+			} else if ( ret_8 == QQ_LOGIN_REPLY_REDIRECT) {
+				return QQ_LOGIN_REPLY_REDIRECT;
+			}
+			break;
+		case QQ_CMD_TOKEN_EX:
+			ret_8 = qq_process_token_ex(gc, data, data_len);
+			if (ret_8 == QQ_LOGIN_REPLY_OK) {
+				//qq_request_check_password(gc);
+			} else if (ret_8 == QQ_LOGIN_REPLY_NEXT_TOKEN_EX) {
+				qq_request_token_ex_next(gc);
+			} else if (ret_8 == QQ_LOGIN_REPLY_CAPTCHA_DLG) {
+				qq_captcha_input_dialog(gc, &(qd->captcha));
+				g_free(qd->captcha.token);
+				g_free(qd->captcha.data);
+				memset(&qd->captcha, 0, sizeof(qd->captcha));
+			}
+			break;
+		case QQ_CMD_LOGIN:
+			ret_8 = qq_process_login(gc, data, data_len);
+			if (ret_8 != QQ_LOGIN_REPLY_OK) {
+				return  ret_8;
+			}
 
-	purple_debug_info("QQ", "Login repliess OK; everything is fine\n");
+			purple_debug_info("QQ", "Login repliess OK; everything is fine\n");
+			purple_connection_set_state(gc, PURPLE_CONNECTED);
+			qd->is_login = TRUE;	/* must be defined after sev_finish_login */
 
-	purple_connection_set_state(gc, PURPLE_CONNECTED);
-	qd->is_login = TRUE;	/* must be defined after sev_finish_login */
+			/* now initiate QQ Qun, do it first as it may take longer to finish */
+			qq_group_init(gc);
 
-	/* now initiate QQ Qun, do it first as it may take longer to finish */
-	qq_group_init(gc);
+			/* is_login, but we have packets before login */
+			qq_trans_process_remained(gc);
 
-	/* is_login, but we have packets before login */
-	qq_trans_process_remained(gc);
-
-	qq_update_all(gc, 0);
-	return;
+			qq_update_all(gc, 0);
+			break;
+		default:
+			process_cmd_unknow(gc, _("Unknow LOGIN CMD"), data, data_len, cmd, seq);
+			return QQ_LOGIN_REPLY_ERR;
+	}
+	return QQ_LOGIN_REPLY_OK;
 }
 
-void qq_proc_client_cmd(PurpleConnection *gc, guint16 cmd, guint16 seq,
+void qq_proc_client_cmds(PurpleConnection *gc, guint16 cmd, guint16 seq,
 		guint8 *rcved, gint rcved_len, gint update_class, guint32 ship32)
 {
 	qq_data *qd;
@@ -710,7 +767,7 @@ void qq_proc_client_cmd(PurpleConnection
 			purple_debug_info("QQ", "All buddies and groups received\n");
 			break;
 		default:
-			process_cmd_unknow(gc, _("Unknow reply CMD"), data, data_len, cmd, seq);
+			process_cmd_unknow(gc, _("Unknow CLIENT CMD"), data, data_len, cmd, seq);
 			is_unknow = TRUE;
 			break;
 	}
============================================================
--- libpurple/protocols/qq/qq_process.h	8c9135998e632d7d4b13a3373fde6fee15053896
+++ libpurple/protocols/qq/qq_process.h	785653af694fe209081308333c2cb8f5a8065bbc
@@ -38,10 +38,11 @@ enum {
 	QQ_CMD_CLASS_UPDATE_ROOM,
 };
 
-void qq_proc_login_cmd(PurpleConnection *gc, guint8 *rcved, gint rcved_len);
-void qq_proc_client_cmd(PurpleConnection *gc, guint16 cmd, guint16 seq,
+guint8 qq_proc_login_cmds(PurpleConnection *gc,  guint16 cmd, guint16 seq,
 		guint8 *rcved, gint rcved_len, gint update_class, guint32 ship32);
-void qq_proc_room_cmd(PurpleConnection *gc, guint16 seq,
+void qq_proc_client_cmds(PurpleConnection *gc, guint16 cmd, guint16 seq,
+		guint8 *rcved, gint rcved_len, gint update_class, guint32 ship32);
+void qq_proc_room_cmds(PurpleConnection *gc, guint16 seq,
 		guint8 room_cmd, guint32 room_id, guint8 *rcved, gint rcved_len,
 		gint update_class, guint32 ship32);
 
============================================================
--- libpurple/protocols/qq/qq_trans.c	f513eebd952e6d6aaefa38b76dbc42f2bca7f3a5
+++ libpurple/protocols/qq/qq_trans.c	685b1c333f6f5f72e93b6572a0eabb6c5f7f98bf
@@ -30,7 +30,7 @@
 #include "prefs.h"
 #include "request.h"
 
-#include "header_info.h"
+#include "qq_define.h"
 #include "qq_network.h"
 #include "qq_process.h"
 #include "qq_trans.h"
============================================================
--- libpurple/protocols/qq/send_file.c	9e43dc2a0a6cb55e09ba358583550243846eec45
+++ libpurple/protocols/qq/send_file.c	591962a2446a469711b3abaf0c80d7a4b76faf81
@@ -31,7 +31,7 @@
 
 #include "buddy_list.h"
 #include "file_trans.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "im.h"
 #include "qq_base.h"
 #include "packet_parse.h"
@@ -54,8 +54,8 @@ static int _qq_in_same_lan(ft_info *info
 static int _qq_in_same_lan(ft_info *info)
 {
 	if (info->remote_internet_ip == info->local_internet_ip) return 1;
-	purple_debug_info("QQ", 
-			"Not in the same LAN, remote internet ip[%x], local internet ip[%x]\n",  
+	purple_debug_info("QQ",
+			"Not in the same LAN, remote internet ip[%x], local internet ip[%x]\n",
 			info->remote_internet_ip
 			, info->local_internet_ip);
 	return 0;
@@ -87,7 +87,7 @@ static ssize_t _qq_xfer_udp_recv(guint8 
 	info = (ft_info *) xfer->data;
 	sinlen = sizeof(sin);
 	r = recvfrom(info->recv_fd, buf, len, 0, (struct sockaddr *) &sin, &sinlen);
-	purple_debug_info("QQ", 
+	purple_debug_info("QQ",
 			"==> recv %d bytes from File UDP Channel, remote ip[%s], remote port[%d]\n",
 			r, inet_ntoa(sin.sin_addr), g_ntohs(sin.sin_port));
 	return r;
@@ -301,7 +301,7 @@ static gint _qq_create_packet_file_heade
 	/* 004-007: sender uid */
 	bytes += qq_put32 (raw_data + bytes, to_uid);
 	/* 008-009: sender client version */
-	bytes += qq_put16 (raw_data + bytes, QQ_CLIENT);
+	bytes += qq_put16 (raw_data + bytes, qd->client_version);
 	/* 010-013: receiver uid */
 	bytes += qq_put32 (raw_data + bytes, qd->uid);
 	/* 014-017: sender uid */
@@ -380,7 +380,7 @@ static void _qq_xfer_init_socket(PurpleX
 
 static void _qq_xfer_init_socket(PurpleXfer *xfer)
 {
-	gint sockfd, listen_port = 0, i; 
+	gint sockfd, listen_port = 0, i;
 	socklen_t sin_len;
 	struct sockaddr_in sin;
 	ft_info *info;
@@ -389,7 +389,7 @@ static void _qq_xfer_init_socket(PurpleX
 	g_return_if_fail(xfer->data != NULL);
 	info = (ft_info *) xfer->data;
 
-	/* debug 
+	/* debug
 	info->local_real_ip = 0x7f000001;
 	*/
 	info->local_real_ip = g_ntohl(inet_addr(purple_network_get_my_ip(-1)));
@@ -460,7 +460,7 @@ static void _qq_send_packet_file_request
 	raw_data = g_newa(guint8, packet_len);
 	bytes = 0;
 
-	bytes += _qq_create_packet_file_header(raw_data + bytes, to_uid, 
+	bytes += _qq_create_packet_file_header(raw_data + bytes, to_uid,
 			QQ_FILE_TRANS_REQ, qd, FALSE);
 	bytes += qq_fill_conn_info(raw_data + bytes, info);
 	/* 079: 0x20 */
@@ -682,7 +682,7 @@ static void _qq_xfer_recv_init(PurpleXfe
 }
 
 /* process reject im for file transfer request */
-void qq_process_recv_file_reject (guint8 *data, gint data_len, 
+void qq_process_recv_file_reject (guint8 *data, gint data_len,
 		guint32 sender_uid, PurpleConnection *gc)
 {
 	gchar *msg, *filename;
@@ -711,7 +711,7 @@ void qq_process_recv_file_reject (guint8
 }
 
 /* process cancel im for file transfer request */
-void qq_process_recv_file_cancel (guint8 *data, gint data_len, 
+void qq_process_recv_file_cancel (guint8 *data, gint data_len,
 		guint32 sender_uid, PurpleConnection *gc)
 {
 	gchar *msg, *filename;
@@ -785,7 +785,7 @@ void qq_process_recv_file_request(guint8
 	info->local_internet_port = qd->my_port;
 	info->local_real_ip = 0x00000000;
 	info->to_uid = sender_uid;
-	
+
 	if (data_len <= 2 + 30 + QQ_CONN_INFO_LEN) {
 		purple_debug_warning("QQ", "Received file request message is empty\n");
 		return;
@@ -822,18 +822,18 @@ void qq_process_recv_file_request(guint8
 				q_bud->status = QQ_BUDDY_ONLINE_INVISIBLE;
 				qq_update_buddy_contact(gc, q_bud);
 			}
-			else 
+			else
 				purple_debug_info("QQ", "buddy %d is already online\n", sender_uid);
 
 		}
-		else 
+		else
 			purple_debug_warning("QQ", "buddy %d is not in list\n", sender_uid);
 
-		g_free(sender_name);	    
+		g_free(sender_name);
 		g_strfreev(fileinfo);
 		return;
 	}
-	
+
 	xfer = purple_xfer_new(purple_connection_get_account(gc),
 			PURPLE_XFER_RECEIVE,
 			sender_name);
@@ -875,7 +875,7 @@ static void _qq_xfer_send_notify_ip_ack(
 	*/
 }
 
-void qq_process_recv_file_notify(guint8 *data, gint data_len, 
+void qq_process_recv_file_notify(guint8 *data, gint data_len,
 		guint32 sender_uid, PurpleConnection *gc)
 {
 	gint bytes;
@@ -892,7 +892,7 @@ void qq_process_recv_file_notify(guint8 
 		purple_debug_warning("QQ", "Received file notify message is empty\n");
 		return;
 	}
-	
+
 	bytes = 0;
 	bytes += qq_get16(&(info->send_seq), data + bytes);
 


More information about the Commits mailing list