diff libgaim/protocols/qq/send_file.c @ 14192:60b1bc8dbf37

[gaim-migrate @ 16863] Renamed 'core' to 'libgaim' committer: Tailor Script <tailor@pidgin.im>
author Evan Schoenberg <evan.s@dreskin.net>
date Sat, 19 Aug 2006 01:50:10 +0000
parents
children 85f3808ca472
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgaim/protocols/qq/send_file.c	Sat Aug 19 01:50:10 2006 +0000
@@ -0,0 +1,944 @@
+/**
+ * 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 "send_file.h"
+#include "debug.h"
+#include "network.h"
+#include "notify.h"
+
+#include "buddy_status.h"
+#include "crypt.h"
+#include "file_trans.h"
+#include "header_info.h"
+#include "im.h"
+#include "keep_alive.h"
+#include "packet_parse.h"
+#include "qq.h"
+#include "send_core.h"
+#include "utils.h"
+
+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;
+}
+
+/* these 2 functions send and recv buffer from/to UDP channel */
+static ssize_t _qq_xfer_udp_recv(guint8 *buf, size_t len, GaimXfer *xfer)
+{
+	struct sockaddr_in sin;
+	socklen_t sinlen;
+	ft_info *info;
+	gint 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 guint8 *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 guint8 *buf, size_t len, GaimXfer *xfer)
+{
+	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);
+}
+
+static 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, guint8 *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 */
+
+	g_free (md5);
+	return bytes;
+}
+
+#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)
+{
+	gint sockfd, listen_port = 0, i; 
+	socklen_t 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;
+
+	/* 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, (guint8 *) filename,
+				     filename_len);
+	/* 0x1f */
+	bytes += create_packet_b (raw_data, &cursor, 0x1f);
+	/* file length */
+	bytes += create_packet_data (raw_data, &cursor, (guint8 *) 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);
+}
+
+/* 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;
+	guint16 minor_port;
+	guint32 real_ip;
+	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);
+
+	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);
+}
+
+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);
+}
+
+/* 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);
+}
+
+/* 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");
+}
+
+/* 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));
+}
+
+/* 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);
+}
+
+/* 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);
+}
+
+/* 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);
+}
+
+/* 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, **fileinfo;
+	ft_info *info;
+	GaimBuddy *b;
+	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);
+
+	fileinfo = g_strsplit((gchar *) (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) {
+				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);
+}
+
+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)
+{
+}