view src/protocols/qq/send_file.c @ 13967:99b9b58b19dd

[gaim-migrate @ 16523] Fix a crazy MSN crash. Basically it's possible to have more than one slplink associated with a given switchboard, but our code did not allow for that. I think it happens when you're in a multi-user chat and you do stuff with multiple users that involves slplinks. Like maybe file transfer and buddy icon related stuff. Tracking this down took an ungodly amount of time, but thanks to Meebo for letting me do it :-) committer: Tailor Script <tailor@pidgin.im>
author Mark Doliner <mark@kingant.net>
date Thu, 20 Jul 2006 07:31:15 +0000
parents 2be9dfa9569b
children ef8490f9e823
line wrap: on
line source

/**
 * The QQ2003C protocol plugin
 *
 * for gaim
 *
 *	Author: Henry Ou <henry@linux.net>
 *
 * Copyright (C) 2004 Puzzlebird
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

//#include <network.h>,				beta2, gfhuang

#include "send_file.h"
#include "debug.h"
#include "notify.h"
#include "network.h"	//gaim_network_get_my_ip

#include "im.h"		//qq_create_packet_im_header
#include "packet_parse.h"
#include "crypt.h"
#include "header_info.h"
#include "send_core.h"
#include "utils.h"		// gaim_name_to_uid
#include "file_trans.h"	// qq_send_file_ctl
#include "qq.h"
#include "buddy_status.h" // by gfhuang
#include "keep_alive.h" //by gfhuang

enum
{
	QQ_FILE_TRANS_REQ = 0x0035,
	QQ_FILE_TRANS_ACC_UDP = 0x0037,
	QQ_FILE_TRANS_ACC_TCP = 0x0003,
	QQ_FILE_TRANS_DENY_UDP = 0x0039,
	QQ_FILE_TRANS_DENY_TCP = 0x0005,
	QQ_FILE_TRANS_NOTIFY = 0x003b,
	QQ_FILE_TRANS_NOTIFY_ACK = 0x003c,
	QQ_FILE_TRANS_CANCEL = 0x0049,
	QQ_FILE_TRANS_PASV = 0x003f
};

static int _qq_in_same_lan(ft_info *info)
{
	if (info->remote_internet_ip == info->local_internet_ip) return 1;
	gaim_debug(GAIM_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;
}

static int _qq_xfer_init_udp_channel(ft_info *info)
{
	struct sockaddr_in sin;
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	if (!_qq_in_same_lan(info)) {
		sin.sin_port = htons(info->remote_major_port);
		sin.sin_addr.s_addr = htonl(info->remote_internet_ip);
	} else {
		sin.sin_port = htons(info->remote_minor_port);
		sin.sin_addr.s_addr = htonl(info->remote_real_ip);
	}
	return 0;
//	return connect(info->sender_fd, (struct sockaddr *) &sin, sizeof(sin));
}

/* these 2 functions send and recv buffer from/to UDP channel */
static ssize_t _qq_xfer_udp_recv(char *buf, size_t len, GaimXfer *xfer)
{
	struct sockaddr_in sin;
	int sinlen;
	ft_info *info;
	int r;

	info = (ft_info *) xfer->data;
	sinlen = sizeof(sin);
	r = recvfrom(info->recv_fd, buf, len, 0, (struct sockaddr *) &sin, &sinlen);
	gaim_debug(GAIM_DEBUG_INFO, "QQ", "==> recv %d bytes from File UDP Channel, remote ip[%s], remote port[%d]\n",
			r, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
	return r;
}

/*
static ssize_t _qq_xfer_udp_send(const char *buf, size_t len, GaimXfer *xfer)
{
	ft_info *info;

	info = (ft_info *) xfer->data;
	return send(info->sender_fd, buf, len, 0);
}
*/
static ssize_t _qq_xfer_udp_send(const char *buf, size_t len, GaimXfer *xfer)
{
	struct sockaddr_in sin;
	ft_info *info;

	info = (ft_info *) xfer->data;
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	if (!_qq_in_same_lan(info)) {
		sin.sin_port = htons(info->remote_major_port);
		sin.sin_addr.s_addr = htonl(info->remote_internet_ip);
	} else if (info->use_major) {
		sin.sin_port = htons(info->remote_major_port);
		sin.sin_addr.s_addr = htonl(info->remote_real_ip);
	} else {
		sin.sin_port = htons(info->remote_minor_port);
		sin.sin_addr.s_addr = htonl(info->remote_real_ip);
	}
	gaim_debug(GAIM_DEBUG_INFO, "QQ", "sending to channel: %d.%d.%d.%d:%d\n",
			sin.sin_addr.s_addr & 0xff,
			(sin.sin_addr.s_addr >> 8) & 0xff,
			(sin.sin_addr.s_addr >> 16) & 0xff,
			sin.sin_addr.s_addr >> 24,
			ntohs(sin.sin_port)
		  );
	return sendto(info->sender_fd, buf, len, 0, (struct sockaddr *) &sin, sizeof(sin));
}

/* user-defined functions for gaim_xfer_read and gaim_xfer_write */

/*
static ssize_t _qq_xfer_read(char **buf, GaimXfer *xfer)
{
	*buf = g_newa(char, QQ_FILE_FRAGMENT_MAXLEN + 100);
	return _qq_xfer_udp_recv(*buf, QQ_FILE_FRAGMENT_MAXLEN + 100, xfer);
}
*/

gssize _qq_xfer_write(const guchar *buf, size_t len, GaimXfer *xfer) //gfhuang
{
	return _qq_xfer_udp_send(buf, len, xfer);
}

static void
_qq_xfer_recv_packet(gpointer data, gint source, GaimInputCondition condition)
{
	GaimXfer *xfer = (GaimXfer *) data;
	GaimAccount *account = gaim_xfer_get_account(xfer);
	GaimConnection *gc = gaim_account_get_connection(account);
	guint8 *buf;
	gint size;
	/* FIXME: It seems that the transfer never use a packet
	 * larger than 1500 bytes, so if it happened to be a
	 * larger packet, either error occured or protocol should
	 * be modified
	 */
	ft_info *info;
	info = xfer->data;
	g_return_if_fail (source == info->recv_fd);
	buf = g_newa(guint8, 1500);
	size = _qq_xfer_udp_recv(buf, 1500, xfer);
	qq_process_recv_file(gc, buf, size);
}
/*****************************************************************************/
// start file transfer process
static void
_qq_xfer_send_start (GaimXfer * xfer)
{
	GaimAccount *account;
	GaimConnection *gc;
	ft_info *info;

	account = gaim_xfer_get_account(xfer);
	gc = gaim_account_get_connection(account);
	info = (ft_info *) xfer->data;
}

static void
_qq_xfer_send_ack (GaimXfer *xfer, const char *buffer, size_t len)
{

	GaimAccount *account;
	GaimConnection *gc;

	account = gaim_xfer_get_account(xfer);
	gc = gaim_account_get_connection(account);
	qq_process_recv_file(gc, (guint8 *) buffer, len);
}

static void
_qq_xfer_recv_start(GaimXfer *xfer)
{
}

static void
_qq_xfer_end(GaimXfer *xfer)
{
	ft_info *info;
	g_return_if_fail(xfer != NULL && xfer->data != NULL);
	info = (ft_info *) xfer->data;

	qq_xfer_close_file(xfer);
	if (info->dest_fp != NULL) {
		fclose(info->dest_fp);
		gaim_debug(GAIM_DEBUG_INFO, "QQ", "file closed\n");
	}
	if (info->major_fd != 0) {
		close(info->major_fd);
		gaim_debug(GAIM_DEBUG_INFO, "QQ", "major port closed\n");
	}
	if (info->minor_fd != 0) {
		close(info->minor_fd);
		gaim_debug(GAIM_DEBUG_INFO, "QQ", "minor port closed\n");
	}
//	if (info->buffer != NULL) {
//		munmap(info->buffer, gaim_xfer_get_size(xfer));
//		gaim_debug(GAIM_DEBUG_INFO, "QQ", "file mapping buffer is freed.\n");
//	}
	g_free(info);
}

void qq_show_conn_info(ft_info *info)
{
	gchar *internet_ip_str, *real_ip_str;
	guint32 ip;

	ip = htonl(info->remote_real_ip);
	real_ip_str = gen_ip_str((guint8 *) &ip);
	ip = htonl(info->remote_internet_ip);
	internet_ip_str = gen_ip_str((guint8 *) &ip);
	gaim_debug(GAIM_DEBUG_INFO, "QQ", "remote internet ip[%s:%d], major port[%d], real ip[%s], minor port[%d]\n",
			internet_ip_str, info->remote_internet_port,
			info->remote_major_port, real_ip_str, info->remote_minor_port
		  );
	g_free(real_ip_str);
	g_free(internet_ip_str);
}

void qq_get_conn_info(guint8 *data, guint8 **cursor, gint data_len, ft_info *info)
{
	read_packet_data(data, cursor, data_len, info->file_session_key, 16);
	*cursor += 30;
	read_packet_b(data, cursor, data_len, &info->conn_method);
	read_packet_dw(data, cursor, data_len, &info->remote_internet_ip);
	read_packet_w(data, cursor, data_len, &info->remote_internet_port);
	read_packet_w(data, cursor, data_len, &info->remote_major_port);
	read_packet_dw(data, cursor, data_len, &info->remote_real_ip);
	read_packet_w(data, cursor, data_len, &info->remote_minor_port);
	qq_show_conn_info(info);
}

gint qq_fill_conn_info(guint8 *raw_data, guint8 ** cursor, ft_info *info)
{
	gint bytes;
	bytes = 0;
	// 064: connection method, UDP 0x00, TCP 0x03
	bytes += create_packet_b (raw_data, cursor, info->conn_method);
	// 065-068: outer ip address of sender (proxy address)
	bytes += create_packet_dw (raw_data, cursor, info->local_internet_ip);
	// 069-070: sender port
	bytes += create_packet_w (raw_data, cursor, info->local_internet_port);
	// 071-072: the first listening port(TCP doesn't have this part)
	bytes += create_packet_w (raw_data, cursor, info->local_major_port);
	// 073-076: real ip
	bytes += create_packet_dw (raw_data, cursor, info->local_real_ip);
	// 077-078: the second listening port
	bytes += create_packet_w (raw_data, cursor, info->local_minor_port);
	return bytes;
}


extern gchar *_gen_session_md5(gint uid, gchar *session_key);

/*****************************************************************************/
// fill in the common information of file transfer
static gint _qq_create_packet_file_header
(guint8 *raw_data, guint8 ** cursor, guint32 to_uid, guint16 message_type, qq_data *qd, gboolean seq_ack)
{
	gint bytes;
	time_t now;
	gchar *md5;
	guint16 seq;
	ft_info *info;

	bytes = 0;
	now = time(NULL);
	md5 = _gen_session_md5(qd->uid, qd->session_key);
	if (!seq_ack) seq = qd->send_seq;
	else {
		info = (ft_info *) qd->xfer->data;
		seq = info->send_seq;
	}

	// 000-003: receiver uid
	bytes += create_packet_dw (raw_data, cursor, qd->uid);
	// 004-007: sender uid
	bytes += create_packet_dw (raw_data, cursor, to_uid);
	// 008-009: sender client version
	bytes += create_packet_w (raw_data, cursor, QQ_CLIENT);
	// 010-013: receiver uid
	bytes += create_packet_dw (raw_data, cursor, qd->uid);
	// 014-017: sender uid
	bytes += create_packet_dw (raw_data, cursor, to_uid);
	// 018-033: md5 of (uid+session_key)
	bytes += create_packet_data (raw_data, cursor, md5, 16);
	// 034-035: message type
	bytes += create_packet_w (raw_data, cursor, message_type);
	// 036-037: sequence number
	bytes += create_packet_w (raw_data, cursor, seq);
	// 038-041: send time
	bytes += create_packet_dw (raw_data, cursor, (guint32) now);
	// 042-042: always 0x00
	bytes += create_packet_b (raw_data, cursor, 0x00);
	// 043-043: sender icon
	bytes += create_packet_b (raw_data, cursor, qd->my_icon);
	// 044-046: always 0x00
	bytes += create_packet_w (raw_data, cursor, 0x0000);
	bytes += create_packet_b (raw_data, cursor, 0x00);
	// 047-047: we use font attr
	bytes += create_packet_b (raw_data, cursor, 0x01);
	// 048-051: always 0x00
	bytes += create_packet_dw (raw_data, cursor, 0x00000000);

	// 052-062: always 0x00
	bytes += create_packet_dw (raw_data, cursor, 0x00000000);
	bytes += create_packet_dw (raw_data, cursor, 0x00000000);
	bytes += create_packet_w (raw_data, cursor, 0x0000);
	bytes += create_packet_b (raw_data, cursor, 0x00);
	// 063: transfer_type,  0x65: FILE 0x6b: FACE
	bytes += create_packet_b (raw_data, cursor, QQ_FILE_TRANSFER_FILE); /* FIXME by gfhuang */

	g_free (md5);
	return bytes;
}	//_qq_create_packet_file_header


#if 0
in_addr_t get_real_ip()
{
	char hostname[40];
	struct hostent *host;

	gethostname(hostname, sizeof(hostname));
	host = gethostbyname(hostname);
	return *(host->h_addr);
}


#include <sys/ioctl.h>
#include <net/if.h>

#define MAXINTERFACES 16
in_addr_t get_real_ip()
{
	int fd, intrface, i;
	struct ifconf ifc;
	struct ifreq buf[MAXINTERFACES];
	in_addr_t ret;

	if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) return 0;
	ifc.ifc_len = sizeof(buf);
	ifc.ifc_buf = (caddr_t) buf;
	if (ioctl(fd, SIOCGIFCONF, (char *) &ifc) < 0) return 0;
	intrface = ifc.ifc_len / sizeof(struct ifreq);
	for (i = 0; i < intrface; i++) {
		//buf[intrface].ifr_name
		if (ioctl(fd, SIOCGIFADDR, (char *) &buf[i]) >= 0)
		{
			ret = (((struct sockaddr_in *)(&buf[i].ifr_addr))->sin_addr).s_addr;
			if (ret == ntohl(0x7f000001)) continue;
			return ret;
		}
	}
	return 0;
}
#endif

static void
_qq_xfer_init_socket(GaimXfer *xfer)
{
	int sockfd, listen_port = 0, i, sin_len;
	struct sockaddr_in sin;
	ft_info *info;

	g_return_if_fail(xfer != NULL);
	g_return_if_fail(xfer->data != NULL);
	info = (ft_info *) xfer->data;

//	info->local_real_ip = ntohl(get_real_ip());
//debug
//	info->local_real_ip = 0x7f000001;
	info->local_real_ip = ntohl(inet_addr(gaim_network_get_my_ip(-1)));
	gaim_debug(GAIM_DEBUG_INFO, "QQ", "local real ip is %x", info->local_real_ip);

	for (i = 0; i < 2; i++) {
		sockfd = socket(PF_INET, SOCK_DGRAM, 0);
		g_return_if_fail(sockfd >= 0);

		memset(&sin, 0, sizeof(sin));
		sin.sin_family = AF_INET;
		sin.sin_port = 0;
		sin.sin_addr.s_addr = INADDR_ANY;
		sin_len = sizeof(sin);
		bind(sockfd, (struct sockaddr *) &sin, sin_len);
		getsockname(sockfd, (struct sockaddr *) &sin, &sin_len);
		listen_port = ntohs(sin.sin_port);

		switch (i) {
			case 0:
				info->local_major_port = listen_port;
				info->major_fd = sockfd;
				gaim_debug(GAIM_DEBUG_INFO, "QQ", "UDP Major Channel created on port[%d]\n",
						info->local_major_port);
				break;
			case 1:
				info->local_minor_port = listen_port;
				info->minor_fd = sockfd;
				gaim_debug(GAIM_DEBUG_INFO, "QQ", "UDP Minor Channel created on port[%d]\n",
						info->local_minor_port);
				break;
		}
	}

	if (_qq_in_same_lan(info)) {
		info->sender_fd = info->recv_fd = info->minor_fd;
	} else {
		info->sender_fd = info->recv_fd = info->major_fd;
	}

//	xfer->watcher = gaim_input_add(info->recv_fd, GAIM_INPUT_READ, _qq_xfer_recv_packet, xfer);
}

/*****************************************************************************/
// create the QQ_FILE_TRANS_REQ packet with file infomations
static void
_qq_send_packet_file_request (GaimConnection * gc, guint32 to_uid,
			      gchar * filename, gint filesize)
{
	qq_data *qd;
	guint8 *cursor, *raw_data;
	gchar *filelen_str;
	gint filename_len, filelen_strlen, packet_len, bytes;
	ft_info *info;

	qd = (qq_data *) gc->proto_data;

	info = g_new0(ft_info, 1);
	info->to_uid = to_uid;
	info->send_seq = qd->send_seq;
	info->local_internet_ip = ntohl(inet_addr(qd->my_ip));
	info->local_internet_port = qd->my_port;
	info->local_real_ip = 0x00000000;
	info->conn_method = 0x00;
	qd->xfer->data = info;

	filename_len = strlen (filename);
	filelen_str = g_strdup_printf ("%d ×Ö½Ú", filesize);
	filelen_strlen = strlen (filelen_str);

	packet_len = 82 + filename_len + filelen_strlen;
	raw_data = g_newa (guint8, packet_len);
	cursor = raw_data;

	bytes = _qq_create_packet_file_header(raw_data, &cursor, to_uid, QQ_FILE_TRANS_REQ, qd, FALSE);
	bytes += qq_fill_conn_info(raw_data, &cursor, info);
	// 079: 0x20
	bytes += create_packet_b (raw_data, &cursor, 0x20);
	// 080: 0x1f
	bytes += create_packet_b (raw_data, &cursor, 0x1f);
	// undetermined len: filename
	bytes += create_packet_data (raw_data, &cursor, filename,
				     filename_len);
	// 0x1f
	bytes += create_packet_b (raw_data, &cursor, 0x1f);
	// file length
	bytes += create_packet_data (raw_data, &cursor, filelen_str,
				     filelen_strlen);

	if (packet_len == bytes)
		qq_send_cmd (gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data,
			     cursor - raw_data);
	else
		gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file_request",
			    "%d bytes expected but got %d bytes\n",
			    packet_len, bytes);

	g_free (filelen_str);
}	//_qq_send_packet_file_request

/*****************************************************************************/
// tell the buddy we want to accept the file
static void
_qq_send_packet_file_accept(GaimConnection *gc, guint32 to_uid)
{
	qq_data *qd;
	guint8 *cursor, *raw_data;
	gint packet_len, bytes;
	ft_info *info;

	g_return_if_fail (gc != NULL && gc->proto_data != NULL);
	qd = (qq_data *) gc->proto_data;
	info = (ft_info *) qd->xfer->data;

	gaim_debug(GAIM_DEBUG_INFO, "QQ", "I've accepted the file transfer request from %d\n", to_uid);
	_qq_xfer_init_socket(qd->xfer);
	guint16 minor_port;
	guint32 real_ip;

	packet_len = 79;
	raw_data = g_newa (guint8, packet_len);
	cursor = raw_data;

	minor_port = info->local_minor_port;
	real_ip = info->local_real_ip;
	info->local_minor_port = 0;
	info->local_real_ip = 0;

	bytes = _qq_create_packet_file_header(raw_data, &cursor, to_uid, QQ_FILE_TRANS_ACC_UDP, qd, TRUE);
	bytes += qq_fill_conn_info(raw_data, &cursor, info);

	info->local_minor_port = minor_port;
	info->local_real_ip = real_ip;

	if (packet_len == bytes)
		qq_send_cmd (gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data,
			     cursor - raw_data);
	else
		gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file_accept",
			    "%d bytes expected but got %d bytes\n",
			    packet_len, bytes);
}	//_qq_send_packet_packet_accept

static void _qq_send_packet_file_notifyip(GaimConnection *gc, guint32 to_uid)
{
	GaimXfer *xfer;
	ft_info *info;
	qq_data *qd;
	guint8 *cursor, *raw_data;
	gint packet_len, bytes;

	qd = (qq_data *) gc->proto_data;
	xfer = qd->xfer;
	info = xfer->data;

	packet_len = 79;
	raw_data = g_newa (guint8, packet_len);
	cursor = raw_data;

	gaim_debug(GAIM_DEBUG_INFO, "QQ", "<== sending qq file notify ip packet\n");
	bytes = _qq_create_packet_file_header(raw_data, &cursor, to_uid, QQ_FILE_TRANS_NOTIFY, qd, TRUE);
	bytes += qq_fill_conn_info(raw_data, &cursor, info);
	if (packet_len == bytes)
		qq_send_cmd (gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data,
			     cursor - raw_data);
	else
		gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file_notify",
			    "%d bytes expected but got %d bytes\n",
			    packet_len, bytes);

	if (xfer->watcher) gaim_input_remove(xfer->watcher);
	xfer->watcher = gaim_input_add(info->recv_fd, GAIM_INPUT_READ, _qq_xfer_recv_packet, xfer);
	gaim_input_add(info->major_fd, GAIM_INPUT_READ, _qq_xfer_recv_packet, xfer);
//	gaim_input_add(info->minor_fd, GAIM_INPUT_READ, _qq_xfer_recv_packet, xfer);
}

/*****************************************************************************/
// tell the buddy we don't want the file
static void
_qq_send_packet_file_reject (GaimConnection *gc, guint32 to_uid)
{
	qq_data *qd;
	guint8 *cursor, *raw_data;
	gint packet_len, bytes;

	gaim_debug(GAIM_DEBUG_INFO, "_qq_send_packet_file_reject", "start");
	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
	qd = (qq_data *) gc->proto_data;

	packet_len = 64;
	raw_data = g_newa (guint8, packet_len);
	cursor = raw_data;
	bytes = 0;

	bytes = _qq_create_packet_file_header(raw_data, &cursor, to_uid, QQ_FILE_TRANS_DENY_UDP, qd, TRUE);

	if (packet_len == bytes)
		qq_send_cmd (gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data,
			     cursor - raw_data);
	else
		gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file",
			    "%d bytes expected but got %d bytes\n",
			    packet_len, bytes);

//	gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file_reject", "end\n");
}				// qq_send_packet_file_reject

/*****************************************************************************/
// tell the buddy to cancel transfer
static void
_qq_send_packet_file_cancel (GaimConnection *gc, guint32 to_uid)
{
	qq_data *qd;
	guint8 *cursor, *raw_data;
	gint packet_len, bytes;

	gaim_debug(GAIM_DEBUG_INFO, "_qq_send_packet_file_cancel", "start\n");
	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
	qd = (qq_data *) gc->proto_data;

	packet_len = 64;
	raw_data = g_newa (guint8, packet_len);
	cursor = raw_data;
	bytes = 0;

	gaim_debug(GAIM_DEBUG_INFO, "_qq_send_packet_file_cancel", "before create header\n");
	bytes = _qq_create_packet_file_header(raw_data, &cursor, to_uid, QQ_FILE_TRANS_CANCEL, qd, TRUE);
	gaim_debug(GAIM_DEBUG_INFO, "_qq_send_packet_file_cancel", "end create header\n");

	if (packet_len == bytes) {
		gaim_debug(GAIM_DEBUG_INFO, "_qq_send_packet_file_cancel", "before send cmd\n");
		qq_send_cmd (gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data,
			     cursor - raw_data);
	}
	else
		gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file",
			    "%d bytes expected but got %d bytes\n",
			    packet_len, bytes);

	gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file_cancel", "end\n");
}				// qq_send_packet_file_cancel

/*****************************************************************************/
// request to send a file
static void
_qq_xfer_init (GaimXfer * xfer)
{
	GaimConnection *gc;
	GaimAccount *account;
	guint32 to_uid;
	gchar *filename, *filename_without_path;

	g_return_if_fail (xfer != NULL);
	account = gaim_xfer_get_account(xfer);
	gc = gaim_account_get_connection(account);
	g_return_if_fail (gc != NULL && gc->proto_data != NULL);

	to_uid = gaim_name_to_uid (xfer->who);
	g_return_if_fail (to_uid != 0);

	filename = (gchar *) gaim_xfer_get_local_filename (xfer);
	g_return_if_fail (filename != NULL);

	filename_without_path = strrchr (filename, '/') + 1;

	_qq_send_packet_file_request (gc, to_uid, filename_without_path,
			gaim_xfer_get_size(xfer));
}				// qq_xfer_init

/*****************************************************************************/
// cancel the transfer of receiving files
static void
_qq_xfer_cancel(GaimXfer *xfer)
{
	GaimConnection *gc;
	GaimAccount *account;
	guint16 *seq;

	g_return_if_fail (xfer != NULL);
	seq = (guint16 *) xfer->data;
	account = gaim_xfer_get_account(xfer);
	gc = gaim_account_get_connection(account);

	switch (gaim_xfer_get_status(xfer)) {
		case GAIM_XFER_STATUS_CANCEL_LOCAL:
			_qq_send_packet_file_cancel(gc, gaim_name_to_uid(xfer->who));
			break;
		case GAIM_XFER_STATUS_CANCEL_REMOTE:
			_qq_send_packet_file_cancel(gc, gaim_name_to_uid(xfer->who));
			break;
		case GAIM_XFER_STATUS_NOT_STARTED:
			break;
		case GAIM_XFER_STATUS_UNKNOWN:
			_qq_send_packet_file_reject(gc, gaim_name_to_uid(xfer->who));
			break;
		case GAIM_XFER_STATUS_DONE:
			break;
		case GAIM_XFER_STATUS_ACCEPTED:
			break;
		case GAIM_XFER_STATUS_STARTED:
			break;
	}
}

/*****************************************************************************/
// init the transfer of receiving files
static void
_qq_xfer_recv_init(GaimXfer *xfer)
{
	GaimConnection *gc;
	GaimAccount *account;
	ft_info *info;

	g_return_if_fail (xfer != NULL && xfer->data != NULL);
	info = (ft_info *) xfer->data;
	account = gaim_xfer_get_account(xfer);
	gc = gaim_account_get_connection(account);

	_qq_send_packet_file_accept(gc, gaim_name_to_uid(xfer->who));
}

/*****************************************************************************/
// process reject im for file transfer request
void qq_process_recv_file_reject
	(guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid,
	 GaimConnection * gc)
{
	gchar *msg, *filename;
	qq_data *qd;

	g_return_if_fail (gc != NULL && data != NULL && data_len != 0);
	qd = (qq_data *) gc->proto_data;
	g_return_if_fail (qd != NULL && qd->xfer != NULL);

	if (*cursor >= (data + data_len - 1)) {
		gaim_debug (GAIM_DEBUG_WARNING, "QQ",
			    "Received file reject message is empty\n");
		return;
	}
	filename = strrchr(gaim_xfer_get_local_filename(qd->xfer), '/') + 1;
	msg = g_strdup_printf
		(_
		 ("Your request to send file[%s] has been rejected by buddy[%d]"),
		 filename, sender_uid);

	gaim_notify_warning (gc, _("File Send"), msg, NULL);
	gaim_xfer_request_denied(qd->xfer);
	qd->xfer = NULL;

	g_free (msg);
}				// qq_process_recv_file_reject

/*****************************************************************************/
// process cancel im for file transfer request
void qq_process_recv_file_cancel
	(guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid,
	 GaimConnection * gc)
{
	gchar *msg, *filename;
	qq_data *qd;

	g_return_if_fail (gc != NULL && data != NULL && data_len != 0);
	qd = (qq_data *) gc->proto_data;
	g_return_if_fail (qd != NULL && qd->xfer != NULL
			&& gaim_xfer_get_filename(qd->xfer) != NULL);

	if (*cursor >= (data + data_len - 1)) {
		gaim_debug (GAIM_DEBUG_WARNING, "QQ",
			    "Received file reject message is empty\n");
		return;
	}
	filename = strrchr(gaim_xfer_get_local_filename(qd->xfer), '/') + 1;
	msg = g_strdup_printf
		(_("The sending process of file[%s] has been cancaled by buddy[%d]"),
		 filename, sender_uid);

	gaim_notify_warning (gc, _("File Send"), msg, NULL);
	gaim_xfer_cancel_remote(qd->xfer);
	qd->xfer = NULL;

	g_free (msg);
}				// qq_process_recv_file_cancel

/*****************************************************************************/
// process accept im for file transfer request
void qq_process_recv_file_accept
	(guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid,
	 GaimConnection * gc)
{
	qq_data *qd;
	ft_info *info;
	GaimXfer *xfer;

	g_return_if_fail (gc != NULL && data != NULL && data_len != 0);
	qd = (qq_data *) gc->proto_data;
	xfer = qd->xfer;

	if (*cursor >= (data + data_len - 1)) {
		gaim_debug (GAIM_DEBUG_WARNING, "QQ",
			    "Received file reject message is empty\n");
		return;
	}

	info = (ft_info *) qd->xfer->data;

	*cursor = data + 18 + 12;
	qq_get_conn_info(data, cursor, data_len, info);
	_qq_xfer_init_socket(qd->xfer);

	_qq_xfer_init_udp_channel(info);
	_qq_send_packet_file_notifyip(gc, sender_uid);
}				// qq_process_recv_file_accept

/*****************************************************************************/
// process request from buddy's im for file transfer request
void qq_process_recv_file_request
	(guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid,
	 GaimConnection * gc)
{
	qq_data *qd;
	GaimXfer *xfer;
	gchar *sender_name;
	ft_info *info;
	GaimBuddy *b; //by gfhuang
	qq_buddy *q_bud;

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

	if (*cursor >= (data + data_len - 1)) {
		gaim_debug (GAIM_DEBUG_WARNING, "QQ",
			    "Received file reject message is empty\n");
		return;
	}

	info = g_new0(ft_info, 1);
	info->local_internet_ip = ntohl(inet_addr(qd->my_ip));
	info->local_internet_port = qd->my_port;
	info->local_real_ip = 0x00000000;
	info->to_uid = sender_uid;
	read_packet_w(data, cursor, data_len, &(info->send_seq));

	*cursor = data + 18 + 12;
	qq_get_conn_info(data, cursor, data_len, info);

	gchar **fileinfo;
	fileinfo = g_strsplit(data + 81 + 12, "\x1f", 2);
	g_return_if_fail (fileinfo != NULL && fileinfo[0] != NULL && fileinfo[1] != NULL);

	sender_name = uid_to_gaim_name(sender_uid);

	//FACE from IP detector, ignored by gfhuang
	if(g_ascii_strcasecmp(fileinfo[0], "FACE") == 0) {
		gaim_debug(GAIM_DEBUG_WARNING, "QQ",
			    "Received a FACE ip detect from qq-%d, so he/she must be online :)\n", sender_uid);

		b = gaim_find_buddy(gc->account, sender_name);
		q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
		if (q_bud) {
			if(0 != info->remote_real_ip) { //by gfhuang
				g_memmove(q_bud->ip, &info->remote_real_ip, 4);
				q_bud->port = info->remote_minor_port;
			}
			else if (0 != info->remote_internet_ip) {
				g_memmove(q_bud->ip, &info->remote_internet_ip, 4);
				q_bud->port = info->remote_major_port;
			}

			if(!is_online(q_bud->status)) {
				q_bud->status = QQ_BUDDY_ONLINE_INVISIBLE;
				qq_update_buddy_contact(gc, q_bud);
			}
			else 
				gaim_debug(GAIM_DEBUG_INFO, "QQ", "buddy %d is already online\n", sender_uid);

		}
		else 
			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "buddy %d is not in my friendlist\n", sender_uid);

		g_free(sender_name);	    
		g_strfreev(fileinfo);
		return;
	}
	
	xfer = gaim_xfer_new(gaim_connection_get_account(gc),
			GAIM_XFER_RECEIVE,
			sender_name);
	gaim_xfer_set_filename(xfer, fileinfo[0]);
	gaim_xfer_set_size(xfer, atoi(fileinfo[1]));

	gaim_xfer_set_init_fnc(xfer, _qq_xfer_recv_init);
	gaim_xfer_set_request_denied_fnc(xfer, _qq_xfer_cancel);
	gaim_xfer_set_cancel_recv_fnc(xfer, _qq_xfer_cancel);
	gaim_xfer_set_end_fnc(xfer, _qq_xfer_end);
	gaim_xfer_set_write_fnc(xfer, _qq_xfer_write);

	xfer->data = info;
	qd->xfer = xfer;

	gaim_xfer_request(xfer);

	g_free(sender_name);
	g_strfreev(fileinfo);

//	gaim_debug (GAIM_DEBUG_INFO, "qq_process_recv_file_request", "end\n");
}				// qq_process_recv_file_request

static void
_qq_xfer_send_notify_ip_ack(gpointer data, gint source, GaimInputCondition cond)
{
	GaimXfer *xfer = (GaimXfer *) data;
	GaimAccount *account = gaim_xfer_get_account(xfer);
	GaimConnection *gc = gaim_account_get_connection(account);
	ft_info *info = (ft_info *) xfer->data;

	gaim_input_remove(xfer->watcher);
	xfer->watcher = gaim_input_add(info->recv_fd, GAIM_INPUT_READ, _qq_xfer_recv_packet, xfer);
	qq_send_file_ctl_packet(gc, QQ_FILE_CMD_NOTIFY_IP_ACK, info->to_uid, 0);
//	info->use_major = TRUE;
//	qq_send_file_ctl_packet(gc, QQ_FILE_CMD_NOTIFY_IP_ACK, info->to_uid, 0);
//	info->use_major = FALSE;
}

void qq_process_recv_file_notify
	(guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid,
	 GaimConnection * gc)
{
	qq_data *qd;
	ft_info *info;
	GaimXfer *xfer;

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

	if (*cursor >= (data + data_len - 1)) {
		gaim_debug (GAIM_DEBUG_WARNING, "QQ",
			    "Received file notify message is empty\n");
		return;
	}

	xfer = qd->xfer;
	info = (ft_info *) qd->xfer->data;
	/* FIXME */
	read_packet_w(data, cursor, data_len, &(info->send_seq));

	*cursor = data + 18 + 12;
	qq_get_conn_info(data, cursor, data_len, info);

	_qq_xfer_init_udp_channel(info);

	xfer->watcher = gaim_input_add(info->sender_fd, GAIM_INPUT_WRITE, _qq_xfer_send_notify_ip_ack, xfer);
}

//temp placeholder until a working function can be implemented
gboolean qq_can_receive_file(GaimConnection *gc, const char *who)
{
	return TRUE;
}

void qq_send_file(GaimConnection *gc, const char *who, const char *file)
{
	qq_data *qd;
	GaimXfer *xfer;

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

	xfer = gaim_xfer_new (gc->account, GAIM_XFER_SEND,
			      who);
	gaim_xfer_set_init_fnc (xfer, _qq_xfer_init);
	gaim_xfer_set_cancel_send_fnc (xfer, _qq_xfer_cancel);
	gaim_xfer_set_write_fnc(xfer, _qq_xfer_write);

	qd->xfer = xfer;
	gaim_xfer_request (xfer);
}

static void qq_send_packet_request_key(GaimConnection *gc, guint8 key)
{
	qq_send_cmd(gc, QQ_CMD_REQUEST_KEY, TRUE, 0, TRUE, &key, 1);
}

static void qq_process_recv_request_key(GaimConnection *gc)
{
}