diff src/protocols/oscar/oscar.c @ 3630:9682c0e022c6

[gaim-migrate @ 3753] Yeah this will probably break a lot of shit knowing my luck. But hey, I really don't care what people thnk. committer: Tailor Script <tailor@pidgin.im>
author Rob Flynn <gaim@robflynn.com>
date Fri, 11 Oct 2002 03:14:01 +0000
parents f6a55922110a
children 424e5c4759d0
line wrap: on
line diff
--- a/src/protocols/oscar/oscar.c	Fri Oct 11 02:10:08 2002 +0000
+++ b/src/protocols/oscar/oscar.c	Fri Oct 11 03:14:01 2002 +0000
@@ -25,25 +25,35 @@
 #include <config.h>
 #endif
 
-
+#ifndef _WIN32
 #include <netdb.h>
-#include <unistd.h>
-#include <errno.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#else
+#include <winsock.h>
+#endif
+
+#include <errno.h>
 #include <ctype.h>
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <time.h>
-#include <sys/socket.h>
 #include <sys/stat.h>
+#include <signal.h>
+
 #include "multi.h"
 #include "prpl.h"
 #include "gaim.h"
 #include "aim.h"
 #include "proxy.h"
 
+#ifdef _WIN32
+#include "win32dep.h"
+#endif
+
 #include "pixmaps/protocols/oscar/ab.xpm"
 #include "pixmaps/protocols/oscar/admin_icon.xpm"
 #include "pixmaps/protocols/oscar/aol_icon.xpm"
@@ -73,7 +83,12 @@
 
 #define AIMHASHDATA "http://gaim.sourceforge.net/aim_data.php3"
 
-static int caps_aim = AIM_CAPS_CHAT | AIM_CAPS_BUDDYICON | AIM_CAPS_IMIMAGE;
+/* For win32 compatability */
+G_MODULE_IMPORT GSList *connections;
+G_MODULE_IMPORT int report_idle;
+
+static int caps_aim = AIM_CAPS_CHAT | AIM_CAPS_BUDDYICON |
+	AIM_CAPS_IMIMAGE | AIM_CAPS_SENDFILE;
 
 /* Set AIM caps, because Gaim can still do them over ICQ and 
  * Winicq doesn't mind. */
@@ -104,6 +119,7 @@
 
 	GSList *oscar_chats;
 	GSList *direct_ims;
+	GSList *file_transfers;
 	GSList *hasicons;
 	GHashTable *supports_tn;
 
@@ -156,6 +172,22 @@
 	fu8_t cookie[8];
 };
 
+struct oscar_file_transfer {
+	enum { OFT_SENDFILE_IN, OFT_SENDFILE_OUT } type;
+	aim_conn_t *conn;
+	struct file_transfer *xfer;
+	char *sn;
+	char ip[64];
+	fu16_t port;
+	fu8_t cookie[8];
+	int totsize;
+	int filesdone;
+	int totfiles;
+	int watcher;
+};
+
+static struct oscar_file_transfer *oft_listening;
+
 struct icon_req {
 	char *user;
 	time_t timestamp;
@@ -242,6 +274,55 @@
 	return c;
 }
 
+/* XXX there must be a better way than this.... -- wtm */
+static struct oscar_file_transfer *find_oft_by_conn(struct gaim_connection *gc,
+		aim_conn_t *conn) {
+	GSList *g = ((struct oscar_data *)gc->proto_data)->file_transfers;
+	struct oscar_file_transfer *f = NULL;
+
+	while (g) {
+		f = (struct oscar_file_transfer *)g->data;
+		if (f->conn == conn)
+			break;
+		g = g->next;
+		f = NULL;
+	}
+
+	return f;
+}
+
+static struct oscar_file_transfer *find_oft_by_xfer(struct gaim_connection *gc,
+		struct file_transfer *xfer) {
+	GSList *g = ((struct oscar_data *)gc->proto_data)->file_transfers;
+	struct oscar_file_transfer *f = NULL;
+
+	while (g) {
+		f = (struct oscar_file_transfer *)g->data;
+		if (f->xfer == xfer)
+			break;
+		g = g->next;
+		f = NULL;
+	}
+
+	return f;
+}
+
+static struct oscar_file_transfer *find_oft_by_cookie(struct gaim_connection *gc,
+		const char *cookie) {
+	GSList *g = ((struct oscar_data *)gc->proto_data)->file_transfers;
+	struct oscar_file_transfer *f = NULL;
+
+	while (g) {
+		f = (struct oscar_file_transfer *)g->data;
+		if (!strncmp(f->cookie, cookie, 8))
+			break;
+		g = g->next;
+		f = NULL;
+	}
+
+	return f;
+}
+
 static int gaim_parse_auth_resp  (aim_session_t *, aim_frame_t *, ...);
 static int gaim_parse_login      (aim_session_t *, aim_frame_t *, ...);
 static int gaim_handle_redirect  (aim_session_t *, aim_frame_t *, ...);
@@ -290,6 +371,14 @@
 static int gaim_directim_typing  (aim_session_t *, aim_frame_t *, ...);
 static int gaim_update_ui       (aim_session_t *, aim_frame_t *, ...);
 
+static int oscar_file_transfer_do(aim_session_t *, aim_frame_t *, ...);
+static void oscar_file_transfer_disconnect(aim_session_t *,
+		aim_conn_t *);
+static void oscar_cancel_transfer(struct gaim_connection *,
+		struct file_transfer *);
+static int oscar_sendfile_request(aim_session_t *sess,
+		struct oscar_file_transfer *oft);
+
 static char *msgerrreason[] = {
 	"Invalid error",
 	"Invalid SNAC",
@@ -319,6 +408,26 @@
 };
 static int msgerrreasonlen = 25;
 
+static void oscar_file_transfer_disconnect(aim_session_t *sess,
+		aim_conn_t *conn) {
+	struct gaim_connection *gc = sess->aux_data;
+	struct oscar_data *od = (struct oscar_data *)gc->proto_data;
+	struct oscar_file_transfer *oft = find_oft_by_conn(gc,
+			conn);
+
+	od->file_transfers = g_slist_remove(od->file_transfers, oft);
+
+	if (oft->watcher) {
+		gaim_input_remove(oft->watcher);
+		oft->watcher = 0;
+	}
+	
+	aim_conn_kill(sess, &conn);
+
+	g_free(oft->sn);
+	g_free(oft);
+}
+
 static void gaim_directim_disconnect(aim_session_t *sess, aim_conn_t *conn) {
 	struct gaim_connection *gc = sess->aux_data;
 	struct oscar_data *od = (struct oscar_data *)gc->proto_data;
@@ -425,6 +534,13 @@
 				} else if (conn->type == AIM_CONN_TYPE_RENDEZVOUS) {
 					if (conn->subtype == AIM_CONN_SUBTYPE_OFT_DIRECTIM)
 						gaim_directim_disconnect(odata->sess, conn);
+					else if (conn->subtype == AIM_CONN_SUBTYPE_OFT_SENDFILE) {
+						struct oscar_file_transfer *oft = find_oft_by_conn(gc, conn);
+						if (oft) {
+							transfer_abort(oft->xfer, _("Buddy canceled transfer"));
+						}
+						oscar_file_transfer_disconnect(odata->sess, conn);
+					}
 					else {
 						debug_printf("No handler for rendezvous disconnect (%d).\n",
 								source);
@@ -463,7 +579,11 @@
 	aim_conn_t *conn;
 
 	if (!g_slist_find(connections, gc)) {
+#ifndef _WIN32
 		close(source);
+#else
+		closesocket(source);
+#endif
 		return;
 	}
 
@@ -559,6 +679,13 @@
 		odata->direct_ims = g_slist_remove(odata->direct_ims, n);
 		g_free(n);
 	}
+	while (odata->file_transfers) {
+		struct oscar_file_transfer *n = odata->file_transfers->data;
+		if (n->watcher > 0)
+			gaim_input_remove(n->watcher);
+		odata->file_transfers = g_slist_remove(odata->file_transfers, n);
+		g_free(n);
+	}
 	while (odata->hasicons) {
 		struct icon_req *n = odata->hasicons->data;
 		g_free(n->user);
@@ -603,7 +730,11 @@
 	aim_conn_t *bosconn;
 
 	if (!g_slist_find(connections, gc)) {
+#ifndef _WIN32
 		close(source);
+#else
+		closesocket(source);
+#endif
 		return;
 	}
 
@@ -623,6 +754,33 @@
 	set_login_progress(gc, 4, _("Connection established, cookie sent"));
 }
 
+static void oscar_ask_send_file(struct gaim_connection *gc, char *destsn) {
+	struct oscar_data *od = (struct oscar_data *)gc->proto_data;
+	struct oscar_file_transfer *oft = oft_listening;
+
+	/* Kludge:  if we try to send a file to a client that doesn't
+	 * support it, the BOS server sends us back an error without
+	 * any information identifying which transfer was aborted.  So
+	 * we only allow one sendfile request at a time, to ensure that
+	 * the transfer referenced by an error is unambiguous.  It's ugly,
+	 * but what else can we do? -- wtm
+	 */
+	if (oft) {
+		do_error_dialog(_("Sorry, you already have an outgoing transfer pending.  Due to limitations of the Oscar protocol, only one outgoing transfer request is permitted at a time."), NULL, GAIM_ERROR);
+	}
+	else {
+		struct oscar_file_transfer *oft = g_new0(struct oscar_file_transfer,
+				1);
+
+		oft->type = OFT_SENDFILE_OUT;
+		oft->sn = g_strdup(destsn);
+
+		od->file_transfers = g_slist_append(od->file_transfers, oft);
+
+		oft->xfer = transfer_out_add(gc, oft->sn);
+	}
+}
+
 static int gaim_parse_auth_resp(aim_session_t *sess, aim_frame_t *fr, ...) {
 	va_list ap;
 	struct aim_authresp_info *info;
@@ -770,7 +928,11 @@
 	int x = 0;
 	unsigned char m[17];
 
+#ifndef _WIN32
 	while (read(pos->fd, &in, 1) == 1) {
+#else
+	while (recv(pos->fd, &in, 1, 0) == 1) {
+#endif
 		if (in == '\n')
 			x++;
 		else if (in != '\r')
@@ -784,18 +946,30 @@
 				_("You may be disconnected shortly.  You may want to use TOC until "
 				  "this is fixed.  Check " WEBSITE " for updates."), GAIM_WARNING);
 		gaim_input_remove(pos->inpa);
+#ifndef _WIN32
 		close(pos->fd);
+#else
+		closesocket(pos->fd);
+#endif
 		g_free(pos);
 		return;
 	}
+#ifndef _WIN32
 	read(pos->fd, m, 16);
+#else
+	recv(pos->fd, m, 16, 0);
+#endif
 	m[16] = '\0';
 	debug_printf("Sending hash: ");
 	for (x = 0; x < 16; x++)
 		debug_printf("%02x ", (unsigned char)m[x]);
 	debug_printf("\n");
 	gaim_input_remove(pos->inpa);
+#ifndef _WIN32
 	close(pos->fd);
+#else
+	closesocket(pos->fd);
+#endif
 	aim_sendmemblock(od->sess, pos->conn, 0, 16, m, AIM_SENDMEMBLOCK_FLAG_ISHASH);
 	g_free(pos);
 }
@@ -817,7 +991,11 @@
 	g_snprintf(buf, sizeof(buf), "GET " AIMHASHDATA
 			"?offset=%ld&len=%ld&modname=%s HTTP/1.0\n\n",
 			pos->offset, pos->len, pos->modname ? pos->modname : "");
+#ifndef _WIN32
 	write(pos->fd, buf, strlen(buf));
+#else
+	send(pos->fd, buf, strlen(buf), 0);
+#endif
 	if (pos->modname)
 		g_free(pos->modname);
 	pos->inpa = gaim_input_add(pos->fd, GAIM_INPUT_READ, damn_you, pos);
@@ -959,7 +1137,11 @@
 	aim_conn_t *tstconn;
 
 	if (!g_slist_find(connections, gc)) {
+#ifndef _WIN32
 		close(source);
+#else
+		closesocket(source);
+#endif
 		return;
 	}
 
@@ -987,7 +1169,11 @@
 	aim_conn_t *tstconn;
 
 	if (!g_slist_find(connections, gc)) {
+#ifndef _WIN32
 		close(source);
+#else
+		closesocket(source);
+#endif
 		return;
 	}
 
@@ -1016,7 +1202,11 @@
 	aim_conn_t *tstconn;
 
 	if (!g_slist_find(connections, gc)) {
+#ifndef _WIN32
 		close(source);
+#else
+		closesocket(source);
+#endif
 		g_free(ccon->show);
 		g_free(ccon->name);
 		g_free(ccon);
@@ -1278,6 +1468,193 @@
 				      oscar_callback, dim->conn);
 }
 
+static int oscar_sendfile_out_done(aim_session_t *sess, aim_frame_t *fr, ...) {
+	struct gaim_connection *gc = sess->aux_data;
+	va_list ap;
+	aim_conn_t *conn;
+	const char *cook;
+	struct oscar_file_transfer *oft;
+
+	va_start(ap, fr);
+	conn = va_arg(ap, aim_conn_t *);
+	cook = va_arg(ap, const char *);
+	va_end(ap);
+
+	oft = find_oft_by_cookie(gc, cook);
+	if (oft->filesdone == oft->totfiles)
+		oscar_file_transfer_disconnect(sess, conn);
+	else 
+		/* Send header for next file */
+		oscar_sendfile_request(sess, oft);
+
+	return 0;
+}
+
+/* Called once for each file before sending the raw data. */
+static int oscar_sendfile_request(aim_session_t *sess,
+		struct oscar_file_transfer *oft) {
+	char *name;
+	int size;
+
+	transfer_get_file_info(oft->xfer, &size, &name);
+	aim_oft_sendfile_request(sess, oft->conn, name, oft->filesdone,
+			oft->totfiles, size, oft->totsize);
+
+	return 0;
+}
+
+static int oscar_sendfile_accepted(aim_session_t *sess, aim_frame_t *fr, ...) {
+	struct gaim_connection *gc = sess->aux_data;
+	struct oscar_data *od = (struct oscar_data *)gc->proto_data;
+	struct oscar_file_transfer *oft;
+	va_list ap;
+	aim_conn_t *conn, *listenerconn;
+
+	alarm(0); /* reset timeout alarm */
+	va_start(ap, fr);
+	conn = va_arg(ap, aim_conn_t *);
+	listenerconn = va_arg(ap, aim_conn_t *);
+	va_end(ap);
+
+	oft = find_oft_by_conn(gc, listenerconn);
+	oft->conn = conn;
+	/* Stop watching listener conn; watch transfer conn instead */
+	gaim_input_remove(oft->watcher);
+	aim_conn_kill(sess, &listenerconn);
+	/* We no longer need to block other outgoing transfers. */
+	oft_listening = NULL;
+
+	aim_conn_addhandler(od->sess, oft->conn, AIM_CB_FAM_OFT,
+			AIM_CB_OFT_GETFILEFILESEND,
+			oscar_file_transfer_do,
+			0);
+	aim_conn_addhandler(sess, conn,
+			AIM_CB_FAM_OFT,
+			AIM_CB_OFT_GETFILECOMPLETE,
+			oscar_sendfile_out_done,
+			0);
+	oft->watcher = gaim_input_add(oft->conn->fd, GAIM_INPUT_READ,
+			oscar_callback, oft->conn);
+
+	oscar_sendfile_request(sess, oft);
+
+	return 0;
+}
+
+void oscar_sendfile_timeout(int sig)
+{
+	struct oscar_file_transfer *oft = oft_listening;
+
+	if (oft) {
+		aim_session_t *sess = aim_conn_getsess(oft->conn);
+		aim_conn_t *bosconn;
+		{
+			/* XXX is this valid? is there a better way? -- wtm */
+			struct gaim_connection *gc = sess->aux_data;
+			struct oscar_data *odata = (struct oscar_data *)gc->proto_data;
+			bosconn = odata->conn;
+		}
+
+		oft_listening = NULL;
+		aim_canceltransfer(sess, bosconn, oft->cookie,
+				oft->sn, AIM_CAPS_SENDFILE);
+
+		transfer_abort(oft->xfer, _("Transfer timed out"));
+		oscar_file_transfer_disconnect(sess, oft->conn);
+	}
+}
+
+/* Called once at the beginning of an outgoing transfer session. */
+static void oscar_start_transfer_out(struct gaim_connection *gc,
+		struct file_transfer *xfer, const char *name, int totfiles,
+		int totsize) {
+	struct oscar_data *od = (struct oscar_data *)gc->proto_data;
+	struct oscar_file_transfer *oft = find_oft_by_xfer(gc, xfer);
+
+	oft->xfer = xfer;
+	oft->totsize = totsize;
+	oft->totfiles = totfiles;
+	oft->filesdone = 0;
+	oft_listening = oft;
+
+	oft->conn = aim_sendfile_initiate(od->sess, oft->sn,
+			name, totfiles, oft->totsize, oft->cookie);
+	if (!oft->conn) {
+		do_error_dialog(_("Couldn't open listener to send file"),
+				_("File transfer aborted"),
+				GAIM_ERROR);
+		return;
+	}
+
+	{
+		/* XXX is there a good glib-oriented way of doing this?
+		 * -- wtm */
+		struct sigaction act;
+		act.sa_handler = oscar_sendfile_timeout;
+		act.sa_flags = SA_ONESHOT;
+		sigemptyset (&act.sa_mask);
+		sigaction(SIGALRM, &act, NULL);
+		alarm(OFT_TIMEOUT);
+	}
+
+	aim_conn_addhandler(od->sess, oft->conn, AIM_CB_FAM_OFT,
+			AIM_CB_OFT_GETFILEINITIATE,
+			oscar_sendfile_accepted,
+			0);
+	oft->watcher = gaim_input_add(oft->conn->fd, GAIM_INPUT_READ,
+			oscar_callback, oft->conn);
+}
+
+static void oscar_transfer_data_chunk(struct gaim_connection *gc,
+		struct file_transfer *xfer, const char *buf, int len)
+{
+	struct oscar_file_transfer *oft = find_oft_by_xfer(gc, xfer);
+	aim_session_t *sess = aim_conn_getsess(oft->conn);
+
+	if (oft->type == OFT_SENDFILE_IN)
+		aim_update_checksum(sess, oft->conn, buf, len);
+}
+
+static void oscar_start_transfer_in(struct gaim_connection *gc,
+		struct file_transfer *xfer, int offset) {
+	struct oscar_data *od = (struct oscar_data *)gc->proto_data;
+	struct oscar_file_transfer *oft = find_oft_by_xfer(gc, xfer);
+
+	oft->xfer = xfer;
+	oft->conn = aim_accepttransfer(od->sess, od->conn, oft->sn,
+			oft->cookie, oft->ip,
+			oft->port,
+			AIM_CAPS_SENDFILE);
+	if (!oft->conn) {
+		char *buf = g_strdup_printf("Couldn't connect to remote host");
+		do_error_dialog(buf, NULL, GAIM_ERROR);
+		g_free(buf);
+		return;
+	}
+
+	aim_conn_addhandler(od->sess, oft->conn, AIM_CB_FAM_OFT,
+			AIM_CB_OFT_GETFILEFILEREQ, oscar_file_transfer_do,
+			0);
+
+	oft->watcher = gaim_input_add(oft->conn->fd, GAIM_INPUT_READ,
+			oscar_callback, oft->conn);
+}
+
+static void oscar_cancel_transfer(struct gaim_connection *gc,
+		struct file_transfer *xfer) {
+	struct oscar_data *od = (struct oscar_data *)gc->proto_data;
+	struct oscar_file_transfer *oft = find_oft_by_xfer(gc, xfer);
+
+	if (oft->type == OFT_SENDFILE_IN)
+		aim_denytransfer(od->sess, oft->sn, oft->cookie,
+				AIM_TRANSFER_DENY_DECLINE);
+
+	od->file_transfers = g_slist_remove(od->file_transfers, oft);
+	aim_conn_kill(od->sess, &oft->conn);
+	g_free(oft->sn);
+	g_free(oft);
+}
+
 static int accept_direct_im(gpointer w, struct ask_direct *d) {
 	struct gaim_connection *gc = d->gc;
 	struct oscar_data *od = (struct oscar_data *)gc->proto_data;
@@ -1428,8 +1805,26 @@
 
 	debug_printf("rendezvous status %d (%s)\n", args->status, userinfo->sn);
 
-	if (args->status != AIM_RENDEZVOUS_PROPOSE)
+	
+	if (args->status == AIM_RENDEZVOUS_CANCEL) {
+		struct oscar_file_transfer *oft;
+		oft = find_oft_by_cookie(gc, args->cookie);
+		if (oft) {
+			transfer_abort(oft->xfer, _("Buddy canceled transfer"));
+			oscar_file_transfer_disconnect(sess, oft->conn);
+		}
+		return 0;
+	}
+	else if (args->status == AIM_RENDEZVOUS_ACCEPT) {
+		/* The user accepted our transfer request, but we don't
+		 * really need to do anything yet.
+		 * -- wtm
+		 */
+		return 0;
+	}
+	else if (args->status != AIM_RENDEZVOUS_PROPOSE) {
 		return 1;
+	}
 
 	if (args->reqclass & AIM_CAPS_CHAT) {
 		char *name = extract_name(args->info.chat.roominfo.name);
@@ -1446,6 +1841,39 @@
 		if (name)
 			g_free(name);
 	} else if (args->reqclass & AIM_CAPS_SENDFILE) {
+		struct oscar_file_transfer *oft;
+
+		if (!args->verifiedip) {
+			/* It seems that Trillian sends a message
+			 * with no file data during multiple-file
+			 * transfers.
+			 */
+			debug_printf("sendfile: didn't get any data\n");
+			return -1;
+		}
+
+		oft = g_new0(struct oscar_file_transfer, 1);
+
+		debug_printf("%s (%s) requests to send a file to %s\n",
+				userinfo->sn, args->verifiedip, gc->username);
+		
+		oft->type = OFT_SENDFILE_IN;
+		oft->sn = g_strdup(userinfo->sn);
+		strncpy(oft->ip, args->verifiedip, sizeof(oft->ip));
+		oft->port = args->port;
+		memcpy(oft->cookie, args->cookie, 8);
+
+		{ /* XXX ugly... */
+			struct gaim_connection *gc = sess->aux_data;
+			struct oscar_data *od = gc->proto_data;
+			od->file_transfers = g_slist_append(od->file_transfers, oft);
+		}
+
+		oft->xfer = transfer_in_add(gc, userinfo->sn, 
+				args->info.sendfile.filename,
+				args->info.sendfile.totsize,
+				args->info.sendfile.totfiles,
+				args->msg);
 	} else if (args->reqclass & AIM_CAPS_GETFILE) {
 	} else if (args->reqclass & AIM_CAPS_VOICE) {
 	} else if (args->reqclass & AIM_CAPS_BUDDYICON) {
@@ -1745,6 +2173,34 @@
 		return g_strdup_printf("Online");
 }
 
+static int gaim_parse_clientauto_rend(aim_session_t *sess,
+		const char *who, int reason, const char *cookie) {
+	struct gaim_connection *gc = sess->aux_data;
+	struct oscar_file_transfer *oft;
+	char *buf;
+
+	switch (reason) {
+		case 3: /* Decline sendfile. */
+			oft = find_oft_by_cookie(gc, cookie);
+
+			if (oft) {
+				buf = g_strdup_printf(_("%s has declined to receive a file from %s.\n"),
+						who, gc->username);
+				alarm(0); /* reset timeout alarm */
+				oft_listening = NULL;
+				transfer_abort(oft->xfer, buf);
+				g_free(buf);
+				oscar_file_transfer_disconnect(sess, oft->conn);
+			}
+			break;
+		default:
+			debug_printf("Received an unknown rendezvous client auto-response from %s.  Type 0x%04x\n", who, reason);
+
+	}
+
+	return 0;
+}
+
 static int gaim_parse_clientauto(aim_session_t *sess, aim_frame_t *fr, ...) {
 	struct gaim_connection *gc = sess->aux_data;
 	va_list ap;
@@ -1756,6 +2212,14 @@
 	who = va_arg(ap, char *);
 	reason = (fu16_t)va_arg(ap, unsigned int);
 
+	if (chan == 2) {
+		char *cookie = va_arg(ap, char *);
+		va_end(va);
+
+		return gaim_parse_clientauto_rend(sess, who, reason,
+				cookie);
+	}
+
 	switch(reason) {
 		case 0x0003: { /* Reply from an ICQ status message request */
 			int state = (int)va_arg(ap, fu32_t);
@@ -1827,12 +2291,27 @@
 	char *destn;
 	fu16_t reason;
 	char buf[1024];
+	struct oscar_file_transfer *oft = oft_listening;
 
 	va_start(ap, fr);
 	reason = (fu16_t)va_arg(ap, unsigned int);
 	destn = va_arg(ap, char *);
 	va_end(ap);
 
+	if (oft) {
+		/* If we try to send a file but it isn't supported, then
+		 * we get this error without any information identifying
+		 * the failed connection.  So we can only have one outgoing
+		 * transfer at a time.  Ugh. -- wtm
+		 */
+		oft_listening = NULL;
+		transfer_abort(oft->xfer,
+				(reason < msgerrreasonlen) ? msgerrreason[reason] : _("No reason was given."));
+
+		oscar_file_transfer_disconnect(sess, oft->conn);
+		return 1;
+	}
+
 	snprintf(buf, sizeof(buf), _("Your message to %s did not get sent:"), destn);
 	do_error_dialog(buf, (reason < msgerrreasonlen) ? msgerrreason[reason] : _("No reason was given."), GAIM_ERROR);
 
@@ -2806,7 +3285,12 @@
 	} else if (len != -1) {
 		/* Trying to send an IM image outside of a direct connection. */
 		oscar_ask_direct_im(gc, name);
+#ifndef _WIN32
 		return -ENOTCONN;
+#else
+		WSASetLastError( WSAENOTCONN );
+		return SOCKET_ERROR;
+#endif
 	}
 	if (imflags & IM_FLAG_AWAY) {
 		ret = aim_send_im(odata->sess, name, AIM_IMFLAGS_AWAY, message);
@@ -3538,6 +4022,80 @@
 	return NULL;
 }
 
+void oscar_transfer_nextfile(struct gaim_connection *gc,
+		struct file_transfer *xfer) {
+	struct oscar_file_transfer *oft = find_oft_by_xfer(gc, xfer);
+	aim_conn_t *conn = oft->conn;
+	aim_session_t *sess = aim_conn_getsess(conn);
+
+	oft->filesdone++;
+	oft->watcher = gaim_input_add(conn->fd, GAIM_INPUT_READ,
+			oscar_callback, conn);
+
+	/* If this is an incoming sendfile transfer, we send an OK
+	 * message to the sender; if this is an outgoing sendfile, we
+	 * will get an OK from the receiver that will be handled in
+	 * oscar_sendfile_out_done(), so we don't need to do anything
+	 * yet.
+	 */
+
+	if (oft->type == OFT_SENDFILE_IN)
+		aim_oft_end(sess, conn);
+}
+
+void oscar_transfer_done(struct gaim_connection *gc,
+		struct file_transfer *xfer) {
+	struct oscar_file_transfer *oft = find_oft_by_xfer(gc, xfer);
+	aim_conn_t *conn = oft->conn;
+	aim_session_t *sess = aim_conn_getsess(conn);
+
+	oft->filesdone++;
+	if (oft->type == OFT_SENDFILE_IN) {
+		aim_oft_end(sess, conn);
+		oscar_file_transfer_disconnect(sess, conn);
+	}
+	else if (oft->type == OFT_SENDFILE_OUT) { 
+#if 0
+		/* Wait for response before closing connection. */
+		oft->watcher = gaim_input_add(conn->fd, GAIM_INPUT_READ,
+				oscar_callback, conn);
+#else	
+		oscar_file_transfer_disconnect(sess, conn);
+#endif
+	}
+}
+
+static int oscar_file_transfer_do(aim_session_t *sess, aim_frame_t *fr, ...) {
+	struct gaim_connection *gc = sess->aux_data;
+	va_list ap;
+	aim_conn_t *conn;
+	struct oscar_file_transfer *oft;
+
+	va_start(ap, fr);
+	conn = va_arg(ap, aim_conn_t *);
+
+	oft = find_oft_by_conn(gc, conn);
+
+	/* Don't use the regular input handler for the raw data. */
+	gaim_input_remove(oft->watcher);
+	oft->watcher = 0;
+
+	if (oft->type == OFT_SENDFILE_IN) {
+		const char *name = va_arg(ap, const char *);
+		int size = va_arg(ap, int);
+		if (transfer_in_do(oft->xfer, conn->fd, name, size))
+			oscar_file_transfer_disconnect(sess, oft->conn);
+	}
+	else {
+		int offset = va_arg(ap, int);
+		if (transfer_out_do(oft->xfer, conn->fd, offset))
+			oscar_file_transfer_disconnect(sess, oft->conn);
+	}
+	va_end(ap);
+
+	return 0;
+}
+
 static int gaim_directim_initiate(aim_session_t *sess, aim_frame_t *fr, ...) {
 	va_list ap;
 	struct gaim_connection *gc = sess->aux_data;
@@ -3766,6 +4324,12 @@
 			pbm->gc = gc;
 			m = g_list_append(m, pbm);
 		}
+			
+		pbm = g_new0(struct proto_buddy_menu, 1);
+		pbm->label = _("Send File");
+		pbm->callback = oscar_ask_send_file;
+		pbm->gc = gc;
+		m = g_list_append(m, pbm);
 	}
 
 	pbm = g_new0(struct proto_buddy_menu, 1);
@@ -4015,7 +4579,7 @@
 
 static struct prpl *my_protocol = NULL;
 
-void oscar_init(struct prpl *ret) {
+G_MODULE_EXPORT void oscar_init(struct prpl *ret) {
 	struct proto_user_opt *puo;
 	ret->protocol = PROTO_OSCAR;
 	ret->options = OPT_PROTO_BUDDY_ICON | OPT_PROTO_IM_IMAGE;
@@ -4043,6 +4607,12 @@
 	ret->add_buddies = oscar_add_buddies;
 	ret->group_buddy = oscar_move_buddy;
 	ret->rename_group = oscar_rename_group;
+	ret->file_transfer_cancel = oscar_cancel_transfer;
+	ret->file_transfer_in = oscar_start_transfer_in;
+	ret->file_transfer_out = oscar_start_transfer_out;
+	ret->file_transfer_data_chunk = oscar_transfer_data_chunk;
+	ret->file_transfer_nextfile = oscar_transfer_nextfile;
+	ret->file_transfer_done = oscar_transfer_done;
 	ret->remove_buddy = oscar_remove_buddy;
 	ret->remove_buddies = oscar_remove_buddies;
 	ret->add_permit = oscar_add_permit;
@@ -4077,7 +4647,7 @@
 
 #ifndef STATIC
 
-void *gaim_prpl_init(struct prpl *prpl)
+G_MODULE_EXPORT void gaim_prpl_init(struct prpl *prpl)
 {
 	oscar_init(prpl);
 	prpl->plug->desc.api_version = PLUGIN_API_VERSION;