changeset 11369:ab0fa7cd61cc

[gaim-migrate @ 13593] Bring all the changes to OSCAR file transfers from oldstatus to HEAD. Add documentation resulting from my Summer of Code project to CVS including the original editable files (in OpenOffice.org 2.0 format). committer: Tailor Script <tailor@pidgin.im>
author Jonathan Clark <ardentlygnarly>
date Tue, 30 Aug 2005 05:21:58 +0000
parents 26e316f9e136
children 2c3fb4a0f785
files doc/oscar/On Sending Files via OSCAR.odt doc/oscar/On Sending Files via OSCAR.pdf doc/oscar/Receive Codepaths.odg doc/oscar/Send Codepaths.odg src/protocols/oscar/aim.h src/protocols/oscar/ft.c src/protocols/oscar/im.c src/protocols/oscar/oscar.c src/protocols/oscar/txqueue.c
diffstat 9 files changed, 1425 insertions(+), 389 deletions(-) [+]
line wrap: on
line diff
Binary file doc/oscar/On Sending Files via OSCAR.odt has changed
Binary file doc/oscar/On Sending Files via OSCAR.pdf has changed
Binary file doc/oscar/Receive Codepaths.odg has changed
Binary file doc/oscar/Send Codepaths.odg has changed
--- a/src/protocols/oscar/aim.h	Tue Aug 30 05:21:30 2005 +0000
+++ b/src/protocols/oscar/aim.h	Tue Aug 30 05:21:58 2005 +0000
@@ -55,7 +55,7 @@
  */
 #ifdef WIN32_INDLL
 #define faim_export __declspec(dllexport)
-#else
+#else 
 #define faim_export __declspec(dllimport)
 #endif /* WIN32_INDLL */
 #define faim_internal
@@ -293,9 +293,54 @@
 #define AIM_CONN_TYPE_EMAIL		0x0018
 
 /* they start getting arbitrary for rendezvous stuff =) */
+#define AIM_CONN_TYPE_RENDEZVOUS_PROXY	0xfffd /* these speak a strange language */
 #define AIM_CONN_TYPE_RENDEZVOUS	0xfffe /* these do not speak FLAP! */
 #define AIM_CONN_TYPE_LISTENER		0xffff /* socket waiting for accept() */
 
+/* Command types for doing a rendezvous proxy login
+ * Thanks to Keith Lea and the Joust project for documenting these commands well */
+#define AIM_RV_PROXY_PACKETVER_DFLT	0x044a
+#define AIM_RV_PROXY_ERROR		0x0001
+#define AIM_RV_PROXY_INIT_SEND		0x0002 /* First command sent when creating a connection */
+#define AIM_RV_PROXY_INIT_RECV		0x0004 /* First command sent when receiving existing connection */
+#define AIM_RV_PROXY_ACK		0x0003
+#define AIM_RV_PROXY_READY		0x0005
+
+/* Number of bytes expected in each of the above packets, including the 2 bytes specifying length */
+#define AIM_RV_PROXY_ERROR_LEN		14
+#define AIM_RV_PROXY_INIT_SEND_LEN	55
+#define AIM_RV_PROXY_INIT_RECV_LEN	57
+#define AIM_RV_PROXY_ACK_LEN		18
+#define AIM_RV_PROXY_READY_LEN		12
+#define AIM_RV_PROXY_HDR_LEN		12	/* Bytes in just the header alone */
+
+/* Default values for unknown/unused values in rendezvous proxy negotiation packets */
+#define AIM_RV_PROXY_SERVER_FLAGS	0x0220		/* Default flags sent by proxy server */
+#define AIM_RV_PROXY_CLIENT_FLAGS	0x0000		/* Default flags sent by client */
+#define AIM_RV_PROXY_UNKNOWNA_DFLT	0x00000000	/* Default value for an unknown block */
+#define AIM_RV_PROXY_SERVER_URL		"ars.oscar.aol.com"
+#define AIM_RV_PROXY_CONNECT_PORT	5190		/* The port we should always connect to */
+
+/* What is the purpose of this transfer? (Who will end up with a new file?)
+ * These values are used in oft_info->send_or_recv */
+#define AIM_XFER_SEND			0x0001
+#define AIM_XFER_RECV			0x0002
+
+/* Via what method is the data getting routed?
+ * These values are used in oft_info->method */
+#define AIM_XFER_DIRECT			0x0001	/* Direct connection; receiver connects to sender */
+#define AIM_XFER_REDIR			0x0002	/* Redirected connection; sender connects to receiver */
+#define AIM_XFER_PROXY			0x0003	/* Proxied connection */
+
+/* Who requested the proxy?
+ * The difference between a stage 2 and stage 3 proxied transfer is that the receiver does the
+ * initial login for a stage 2, but the sender must do it for a stage 3.
+ * These values are used in oft_info->stage */
+#define AIM_XFER_PROXY_NONE		0x0001
+#define AIM_XFER_PROXY_STG1		0x0002	/* Sender requested a proxy be used (stage1) */
+#define AIM_XFER_PROXY_STG2		0x0003	/* Receiver requested a proxy be used (stage2) */
+#define AIM_XFER_PROXY_STG3		0x0004	/* Receiver requested a proxy be used (stage3) */
+
 /*
  * Subtypes, we need these for OFT stuff.
  */
@@ -303,7 +348,7 @@
 #define AIM_CONN_SUBTYPE_OFT_GETFILE	0x0002
 #define AIM_CONN_SUBTYPE_OFT_SENDFILE	0x0003
 #define AIM_CONN_SUBTYPE_OFT_BUDDYICON	0x0004
-#define AIM_CONN_SUBTYPE_OFT_VOICE		0x0005
+#define AIM_CONN_SUBTYPE_OFT_VOICE	0x0005
 
 /*
  * Status values returned from aim_conn_new().  ORed together.
@@ -326,7 +371,7 @@
 	void *priv; /* misc data the client may want to store */
 	void *internal; /* internal conn-specific libfaim data */
 	time_t lastactivity; /* time of last transmit */
-	int forcedlatency;
+	int forcedlatency; 
 	void *handlerlist;
 	void *sessv; /* pointer to parent session */
 	void *inside; /* only accessible from inside libfaim */
@@ -356,7 +401,7 @@
 typedef struct aim_frame_s {
 	fu8_t hdrtype; /* defines which piece of the union to use */
 	union {
-		struct {
+		struct { 
 			fu8_t channel;
 			flap_seqnum_t seqnum;
 		} flap;
@@ -412,7 +457,7 @@
 	 * These are only used when you don't use your own lowlevel
 	 * I/O.  I don't suggest that you use libfaim's internal I/O.
 	 * Its really bad and the API/event model is quirky at best.
-	 *
+	 *  
 	 */
 	aim_frame_t *queue_outgoing;
 	aim_frame_t *queue_incoming;
@@ -471,23 +516,21 @@
 } aim_session_t;
 
 /* Valid for calling aim_icq_setstatus() and for aim_userinfo_t->icqinfo.status */
-#define AIM_ICQ_STATE_NORMAL			0x00000000
-#define AIM_ICQ_STATE_AWAY				0x00000001
-#define AIM_ICQ_STATE_DND				0x00000002
-#define AIM_ICQ_STATE_OUT				0x00000004
-#define AIM_ICQ_STATE_BUSY				0x00000010
-#define AIM_ICQ_STATE_CHAT				0x00000020
-#define AIM_ICQ_STATE_INVISIBLE			0x00000100
-#define AIM_ICQ_STATE_WEBAWARE			0x00010000
-#define AIM_ICQ_STATE_HIDEIP			0x00020000
-#define AIM_ICQ_STATE_BIRTHDAY			0x00080000
+#define AIM_ICQ_STATE_NORMAL		0x00000000
+#define AIM_ICQ_STATE_AWAY		0x00000001
+#define AIM_ICQ_STATE_DND		0x00000002
+#define AIM_ICQ_STATE_OUT		0x00000004
+#define AIM_ICQ_STATE_BUSY		0x00000010
+#define AIM_ICQ_STATE_CHAT		0x00000020
+#define AIM_ICQ_STATE_INVISIBLE		0x00000100
+#define AIM_ICQ_STATE_WEBAWARE		0x00010000
+#define AIM_ICQ_STATE_HIDEIP		0x00020000
+#define AIM_ICQ_STATE_BIRTHDAY		0x00080000
 #define AIM_ICQ_STATE_DIRECTDISABLED	0x00100000
-#define AIM_ICQ_STATE_ICQHOMEPAGE		0x00200000
+#define AIM_ICQ_STATE_ICQHOMEPAGE	0x00200000
 #define AIM_ICQ_STATE_DIRECTREQUIREAUTH	0x10000000
 #define AIM_ICQ_STATE_DIRECTCONTACTLIST	0x20000000
 
-
-
 /*
  * Get command from connections
  *
@@ -587,14 +630,10 @@
 faim_export aim_conn_t *aim_getconn_type_all(aim_session_t *, int type);
 faim_export aim_conn_t *aim_getconn_fd(aim_session_t *, int fd);
 
-
-
 /* 0x0001 - service.c */
 faim_export int aim_srv_setavailmsg(aim_session_t *sess, const char *msg);
 faim_export int aim_srv_setidle(aim_session_t *sess, fu32_t idletime);
 
-
-
 /* misc.c */
 
 #define AIM_VISIBILITYCHANGE_PERMITADD    0x05
@@ -684,7 +723,7 @@
 #define AIM_IMFLAGS_BUDDYREQ			0x0010 /* buddy icon requested */
 #define AIM_IMFLAGS_HASICON				0x0020 /* already has icon */
 #define AIM_IMFLAGS_SUBENC_MACINTOSH	0x0040 /* damn that Steve Jobs! */
-#define AIM_IMFLAGS_CUSTOMFEATURES		0x0080 /* features field present */
+#define AIM_IMFLAGS_CUSTOMFEATURES 		0x0080 /* features field present */
 #define AIM_IMFLAGS_EXTDATA				0x0100
 #define AIM_IMFLAGS_X					0x0200
 #define AIM_IMFLAGS_MULTIPART			0x0400 /* ->mpmsg section valid */
@@ -778,7 +817,7 @@
 	/* Always provided */
 	aim_mpmsg_t mpmsg;
 	fu32_t icbmflags; /* some flags apply only to ->msg, not all mpmsg */
-
+	
 	/* Only provided if message has a human-readable section */
 	gchar *msg;
 	int msglen;
@@ -840,6 +879,9 @@
 			fu16_t totfiles;
 			fu32_t totsize;
 			char *filename;
+			/* reqnum: 0x0001 usually; 0x0002 = reply request for stage 2 proxy transfer */
+			fu16_t reqnum;
+			fu8_t use_proxy; /* Did the user request that we use a rv proxy? */
 		} sendfile;
 	} info;
 	void *destructor; /* used internally only */
@@ -875,43 +917,56 @@
 /* 0x0008 */ faim_export int aim_im_warn(aim_session_t *sess, aim_conn_t *conn, const char *destsn, fu32_t flags);
 /* 0x000b */ faim_export int aim_im_denytransfer(aim_session_t *sess, const char *sender, const fu8_t *cookie, fu16_t code);
 /* 0x0014 */ faim_export int aim_im_sendmtn(aim_session_t *sess, fu16_t type1, const char *sn, fu16_t type2);
-
+faim_export void aim_im_makecookie(char* cookie);
 
 
 /* ft.c */
 struct aim_fileheader_t {
 #if 0
-	char magic[4];			/* 0 */
-	fu16_t hdrlen;			/* 4 */
-	fu16_t hdrtype;			/* 6 */
+	char magic[4];		/* 0 */
+	fu16_t hdrlen;		/* 4 */
+	fu16_t hdrtype;		/* 6 */
 #endif
-	guchar bcookie[8];		/* 8 */
-	fu16_t encrypt;			/* 16 */
-	fu16_t compress;		/* 18 */
-	fu16_t totfiles;		/* 20 */
-	fu16_t filesleft;		/* 22 */
-	fu16_t totparts;		/* 24 */
-	fu16_t partsleft;		/* 26 */
-	fu32_t totsize;			/* 28 */
-	fu32_t size;			/* 32 */
-	fu32_t modtime;			/* 36 */
-	fu32_t checksum;		/* 40 */
-	fu32_t rfrcsum;			/* 44 */
-	fu32_t rfsize;			/* 48 */
-	fu32_t cretime;			/* 52 */
-	fu32_t rfcsum;			/* 56 */
-	fu32_t nrecvd;			/* 60 */
-	fu32_t recvcsum;		/* 64 */
-	char idstring[32];		/* 68 */
-	fu8_t flags;			/* 100 */
-	fu8_t lnameoffset;		/* 101 */
-	fu8_t lsizeoffset;		/* 102 */
-	guchar dummy[69];		/* 103 */
-	guchar macfileinfo[16];	/* 172 */
-	fu16_t nencode;			/* 188 */
-	fu16_t nlanguage;		/* 190 */
-	char name[64];			/* 192 */
-							/* 256 */
+	char bcookie[8];	/* 8 */
+	fu16_t encrypt;		/* 16 */
+	fu16_t compress;	/* 18 */
+	fu16_t totfiles;	/* 20 */
+	fu16_t filesleft;	/* 22 */
+	fu16_t totparts;	/* 24 */
+	fu16_t partsleft;	/* 26 */
+	fu32_t totsize;		/* 28 */
+	fu32_t size;		/* 32 */
+	fu32_t modtime;		/* 36 */
+	fu32_t checksum;	/* 40 */
+	fu32_t rfrcsum;		/* 44 */
+	fu32_t rfsize;		/* 48 */
+	fu32_t cretime;		/* 52 */
+	fu32_t rfcsum;		/* 56 */
+	fu32_t nrecvd;		/* 60 */
+	fu32_t recvcsum;	/* 64 */
+	fu8_t idstring[32];	/* 68 */
+	fu8_t flags;		/* 100 */
+	fu8_t lnameoffset;	/* 101 */
+	fu8_t lsizeoffset;	/* 102 */
+	char dummy[69];		/* 103 */
+	char macfileinfo[16];	/* 172 */
+	fu16_t nencode;		/* 188 */
+	fu16_t nlanguage;	/* 190 */
+	char name[64];		/* 192 */
+				/* 256 */
+};
+
+struct aim_rv_proxy_info {
+	fu16_t packet_ver;
+	fu16_t cmd_type;
+	fu16_t flags;
+	char* ip; /* IP address sent along with this packet */
+	fu16_t port; /* This is NOT the port we should use to connect. Always connect to 5190 */
+	fu8_t cookie[8];
+	fu32_t unknownA;
+	fu16_t err_code; /* Valid only for cmd_type of AIM_RV_PROXY_ERROR */
+	aim_conn_t *conn;
+	aim_session_t *sess;
 };
 
 struct aim_oft_info {
@@ -921,11 +976,19 @@
 	char *clientip;
 	char *verifiedip;
 	fu16_t port;
+	
+	int send_or_recv; /* Send or receive */
+	int method; /* What method is being used to transfer this file? DIRECT, REDIR, or PROXY */
+	int stage; /* At what stage was a proxy requested? NONE, STG1, STG2*/
+	int xfer_reffed; /* There are many timers, but we should only ref the xfer once */
+	fu32_t res_bytes; /* The bytes already received for resuming a transfer */
+	
 	aim_conn_t *conn;
 	aim_session_t *sess;
 	int success; /* Was the connection successful? Used for timing out the transfer. */
 	struct aim_fileheader_t fh;
 	struct aim_oft_info *next;
+	struct aim_rv_proxy_info *proxy_info;
 };
 
 faim_export fu32_t aim_oft_checksum_chunk(const fu8_t *buffer, int bufferlen, fu32_t prevcheck);
@@ -940,12 +1003,21 @@
                                          const fu8_t *localip, fu16_t port, const fu8_t *mycookie);
 faim_export aim_conn_t *aim_odc_connect(aim_session_t *sess, const char *sn, const char *addr, const fu8_t *cookie);
 
-faim_export struct aim_oft_info *aim_oft_createinfo(aim_session_t *sess, const fu8_t *cookie, const char *sn, const char *ip, fu16_t port, fu32_t size, fu32_t modtime, char *filename);
+faim_export struct aim_oft_info *aim_oft_createinfo(aim_session_t *sess, const fu8_t *cookie, const char *sn,
+	const char *ip, fu16_t port, fu32_t size, fu32_t modtime, char *filename, int send_or_recv,
+	int method, int stage);
 faim_export int aim_oft_destroyinfo(struct aim_oft_info *oft_info);
+faim_export struct aim_rv_proxy_info *aim_rv_proxy_createinfo(aim_session_t *sess, const fu8_t *cookie, fu16_t port);
 faim_export int aim_sendfile_listen(aim_session_t *sess, struct aim_oft_info *oft_info, int listenfd);
 faim_export int aim_oft_sendheader(aim_session_t *sess, fu16_t type, struct aim_oft_info *oft_info);
 
+faim_export int aim_rv_proxy_init_recv(struct aim_rv_proxy_info *proxy_info);
+faim_export int aim_rv_proxy_init_send(struct aim_rv_proxy_info *proxy_info);
 
+faim_export int aim_sendfile_listen(aim_session_t *sess, struct aim_oft_info *oft_info, int listenfd);
+faim_export int aim_oft_sendheader(aim_session_t *sess, fu16_t type, struct aim_oft_info *oft_info);
+int aim_bstream_send(aim_bstream_t *bs, aim_conn_t *conn, size_t count);
+faim_internal struct aim_rv_proxy_info *aim_rv_proxy_read(aim_session_t *sess, aim_conn_t *conn);
 
 /* 0x0002 - locate.c */
 /*
@@ -1081,8 +1153,8 @@
 faim_export void aim_locate_dorequest(aim_session_t *sess);
 
 /* 0x0002 */ faim_export int aim_locate_reqrights(aim_session_t *sess);
+/* 0x0004 */ faim_export int aim_locate_setcaps(aim_session_t *sess, fu32_t caps);
 /* 0x0004 */ faim_export int aim_locate_setprofile(aim_session_t *sess, const char *profile_encoding, const gchar *profile, const int profile_len, const char *awaymsg_encoding, const gchar *awaymsg, const int awaymsg_len);
-/* 0x0004 */ faim_export int aim_locate_setcaps(aim_session_t *sess, fu32_t caps);
 /* 0x0005 */ faim_export int aim_locate_getinfo(aim_session_t *sess, const char *, fu16_t);
 /* 0x0009 */ faim_export int aim_locate_setdirinfo(aim_session_t *sess, const char *first, const char *middle, const char *last, const char *maiden, const char *nickname, const char *street, const char *city, const char *state, const char *zip, int country, fu16_t privacy);
 /* 0x000b */ faim_export int aim_locate_000b(aim_session_t *sess, const char *sn);
--- a/src/protocols/oscar/ft.c	Tue Aug 30 05:21:30 2005 +0000
+++ b/src/protocols/oscar/ft.c	Tue Aug 30 05:21:58 2005 +0000
@@ -2,40 +2,40 @@
  * Oscar File transfer (OFT) and Oscar Direct Connect (ODC).
  * (ODC is also referred to as DirectIM and IM Image.)
  *
- * There are a few static helper functions at the top, then
+ * There are a few static helper functions at the top, then 
  * ODC stuff, then ft stuff.
  *
- * I feel like this is a good place to explain OFT, so I'm going to
- * do just that.  Each OFT packet has a header type.  I guess this
- * is pretty similar to the subtype of a SNAC packet.  The type
- * basically tells the other client the meaning of the OFT packet.
- * There are two distinct types of file transfer, which I usually
- * call "sendfile" and "getfile."  Sendfile is when you send a file
- * to another AIM user.  Getfile is when you share a group of files,
+ * I feel like this is a good place to explain OFT, so I'm going to 
+ * do just that.  Each OFT packet has a header type.  I guess this 
+ * is pretty similar to the subtype of a SNAC packet.  The type 
+ * basically tells the other client the meaning of the OFT packet.  
+ * There are two distinct types of file transfer, which I usually 
+ * call "sendfile" and "getfile."  Sendfile is when you send a file 
+ * to another AIM user.  Getfile is when you share a group of files, 
  * and other users request that you send them the files.
  *
  * A typical sendfile file transfer goes like this:
- *   1) Sender sends a channel 2 ICBM telling the other user that
- *      we want to send them a file.  At the same time, we open a
- *      listener socket (this should be done before sending the
- *      ICBM) on some port, and wait for them to connect to us.
- *      The ICBM we sent should contain our IP address and the port
+ *   1) Sender sends a channel 2 ICBM telling the other user that 
+ *      we want to send them a file.  At the same time, we open a 
+ *      listener socket (this should be done before sending the 
+ *      ICBM) on some port, and wait for them to connect to us.  
+ *      The ICBM we sent should contain our IP address and the port 
  *      number that we're listening on.
- *   2) The receiver connects to the sender on the given IP address
- *      and port.  After the connection is established, the receiver
+ *   2) The receiver connects to the sender on the given IP address 
+ *      and port.  After the connection is established, the receiver 
  *      sends an ICBM signifying that we are ready and waiting.
- *   3) The sender sends an OFT PROMPT message over the OFT
+ *   3) The sender sends an OFT PROMPT message over the OFT 
  *      connection.
- *   4) The receiver of the file sends back an exact copy of this
- *      OFT packet, except the cookie is filled in with the cookie
- *      from the ICBM.  I think this might be an attempt to verify
- *      that the user that is connected is actually the guy that
+ *   4) The receiver of the file sends back an exact copy of this 
+ *      OFT packet, except the cookie is filled in with the cookie 
+ *      from the ICBM.  I think this might be an attempt to verify 
+ *      that the user that is connected is actually the guy that 
  *      we sent the ICBM to.  Oh, I've been calling this the ACK.
- *   5) The sender starts sending raw data across the connection
+ *   5) The sender starts sending raw data across the connection 
  *      until the entire file has been sent.
- *   6) The receiver knows the file is finished because the sender
- *      sent the file size in an earlier OFT packet.  So then the
- *      receiver sends the DONE thingy (after filling in the
+ *   6) The receiver knows the file is finished because the sender 
+ *      sent the file size in an earlier OFT packet.  So then the 
+ *      receiver sends the DONE thingy (after filling in the 
  *      "received" checksum and size) and closes the connection.
  */
 
@@ -112,18 +112,18 @@
 /**
  * Calculate oft checksum of buffer
  *
- * Prevcheck should be 0xFFFF0000 when starting a checksum of a file.  The
- * checksum is kind of a rolling checksum thing, so each time you get bytes
- * of a file you just call this puppy and it updates the checksum.  You can
- * calculate the checksum of an entire file by calling this in a while or a
+ * Prevcheck should be 0xFFFF0000 when starting a checksum of a file.  The 
+ * checksum is kind of a rolling checksum thing, so each time you get bytes 
+ * of a file you just call this puppy and it updates the checksum.  You can 
+ * calculate the checksum of an entire file by calling this in a while or a 
  * for loop, or something.
  *
- * Thanks to Graham Booker for providing this improved checksum routine,
- * which is simpler and should be more accurate than Josh Myer's original
+ * Thanks to Graham Booker for providing this improved checksum routine, 
+ * which is simpler and should be more accurate than Josh Myer's original 
  * code. -- wtm
  *
- * This algorithm works every time I have tried it.  The other fails
- * sometimes.  So, AOL who thought this up?  It has got to be the weirdest
+ * This algorithm works every time I have tried it.  The other fails 
+ * sometimes.  So, AOL who thought this up?  It has got to be the weirdest 
  * checksum I have ever seen.
  *
  * @param buffer Buffer of data to checksum.  Man I'd like to buff her...
@@ -144,7 +144,7 @@
 			val = buffer[i] << 8;
 		check -= val;
 		/*
-		 * The following appears to be necessary.... It happens
+		 * The following appears to be necessary.... It happens 
 		 * every once in a while and the checksum doesn't fail.
 		 */
 		if (check > oldcheck)
@@ -244,7 +244,7 @@
  *
  * @param sess The session.
  * @param conn The already-connected ODC connection.
- * @param typing If 0x0002, sends a "typing" message, 0x0001 sends "typed," and
+ * @param typing If 0x0002, sends a "typing" message, 0x0001 sends "typed," and 
  *        0x0000 sends "stopped."
  * @return Return 0 if no errors, otherwise return the error number.
  */
@@ -317,7 +317,7 @@
 /**
  * Send client-to-client IM over an established direct connection.
  * Call this just like you would aim_send_im, to send a directim.
- *
+ * 
  * @param sess The session.
  * @param conn The already-connected ODC connection.
  * @param msg Null-terminated string to send.
@@ -384,7 +384,7 @@
 
 	/* end of hdr2 */
 
-#if 0 /* XXX - this is how you send buddy icon info... */
+#if 0 /* XXX - this is how you send buddy icon info... */	
 	aimbs_put16(hdrbs, 0x0008);
 	aimbs_put16(hdrbs, 0x000c);
 	aimbs_put16(hdrbs, 0x0000);
@@ -446,7 +446,7 @@
  *
  * @param sess The session.
  * @param sn The screen name of the buddy whose direct connection you want to find.
- * @return The conn for the direct connection with the given buddy, or NULL if no
+ * @return The conn for the direct connection with the given buddy, or NULL if no 
  *         connection was found.
  */
 faim_export aim_conn_t *aim_odc_getconn(aim_session_t *sess, const char *sn)
@@ -534,7 +534,7 @@
  *
  * This is a wrapper for aim_newconn.
  *
- * If addr is NULL, the socket is not created, but the connection is
+ * If addr is NULL, the socket is not created, but the connection is 
  * allocated and setup to connect.
  *
  * @param sess The Godly session.
@@ -631,7 +631,7 @@
 		while (payloadlength - recvd) {
 			if (payloadlength - recvd >= 1024)
 				i = aim_recv(conn->fd, &msg[recvd], 1024);
-			else
+			else 
 				i = aim_recv(conn->fd, &msg[recvd], payloadlength - recvd);
 			if (i <= 0) {
 				free(msg);
@@ -642,7 +642,7 @@
 			if ((userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_IMAGETRANSFER)))
 				ret = userfunc(sess, &fr, snptr, (double)recvd / payloadlength);
 		}
-
+		
 		if ((userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMINCOMING)))
 			ret = userfunc(sess, &fr, snptr, msg, payloadlength, encoding, isawaymsg);
 
@@ -654,7 +654,7 @@
 	return ret;
 }
 
-faim_export struct aim_oft_info *aim_oft_createinfo(aim_session_t *sess, const fu8_t *cookie, const char *sn, const char *ip, fu16_t port, fu32_t size, fu32_t modtime, char *filename)
+faim_export struct aim_oft_info *aim_oft_createinfo(aim_session_t *sess, const fu8_t *cookie, const char *sn, const char *ip, fu16_t port, fu32_t size, fu32_t modtime, char *filename, int send_or_recv, int method, int stage)
 {
 	struct aim_oft_info *new;
 
@@ -667,11 +667,21 @@
 	new->sess = sess;
 	if (cookie)
 		memcpy(new->cookie, cookie, 8);
+	else
+		aim_im_makecookie(new->cookie);
 	if (ip)
 		new->clientip = strdup(ip);
+	else
+		new->clientip = NULL;
 	if (sn)
 		new->sn = strdup(sn);
+	else
+		new->sn = NULL;
+	new->method = method;
+	new->send_or_recv = send_or_recv;
+	new->stage = stage;
 	new->port = port;
+	new->xfer_reffed = FALSE;
 	new->success = FALSE;
 	new->fh.totfiles = 1;
 	new->fh.filesleft = 1;
@@ -696,8 +706,27 @@
 	return new;
 }
 
+faim_export struct aim_rv_proxy_info *aim_rv_proxy_createinfo(aim_session_t *sess, const fu8_t *cookie,
+	fu16_t port)
+{
+	struct aim_rv_proxy_info *proxy_info;
+	
+	if (!(proxy_info = (struct aim_rv_proxy_info*)calloc(1, sizeof(struct aim_rv_proxy_info))))
+		return NULL;
+	
+	proxy_info->sess = sess;
+	proxy_info->port = port;
+	proxy_info->packet_ver = AIM_RV_PROXY_PACKETVER_DFLT;
+	proxy_info->unknownA = AIM_RV_PROXY_UNKNOWNA_DFLT;
+	
+	if (cookie)
+		memcpy(proxy_info->cookie, cookie, 8);
+	
+	return proxy_info;
+}
+
 /**
- * Remove the given oft_info struct from the oft_info linked list, and
+ * Remove the given oft_info struct from the oft_info linked list, and 
  * then free its memory.
  *
  * @param sess The session.
@@ -732,13 +761,13 @@
 /**
  * Creates a listener socket so the other dude can connect to us.
  *
- * You'll want to set up some kind of watcher on this socket.
- * When the state changes, call aim_handlerendconnection with
- * the connection returned by this.  aim_handlerendconnection
+ * You'll want to set up some kind of watcher on this socket.  
+ * When the state changes, call aim_handlerendconnection with 
+ * the connection returned by this.  aim_handlerendconnection 
  * will accept the pending connection and stop listening.
  *
  * @param sess The session.
- * @param oft_info File transfer information associated with this
+ * @param oft_info File transfer information associated with this 
  *        connection.
  * @return Return 0 if no errors, otherwise return the error number.
  */
@@ -802,7 +831,7 @@
 	fh->name[63] = '\0';
 
 	return fh;
-}
+} 
 
 /**
  * Fills a buffer with network-order fh data
@@ -812,7 +841,7 @@
  * @return Return non-zero on error.
  */
 static int aim_oft_buildheader(aim_bstream_t *bs, struct aim_fileheader_t *fh)
-{
+{ 
 	fu8_t *hdr;
 
 	if (!bs || !fh)
@@ -857,7 +886,7 @@
  *
  * @param sess The session.
  * @param type The subtype of the OFT packet we're sending.
- * @param oft_info The aim_oft_info struct with the connection and OFT
+ * @param oft_info The aim_oft_info struct with the connection and OFT 
  *        info we're sending.
  * @return Return 0 if no errors, otherwise return the error number.
  */
@@ -870,8 +899,8 @@
 
 #if 0
 	/*
-	 * If you are receiving a file, the cookie should be null, if you are sending a
-	 * file, the cookie should be the same as the one used in the ICBM negotiation
+	 * If you are receiving a file, the cookie should be null, if you are sending a 
+	 * file, the cookie should be the same as the one used in the ICBM negotiation 
 	 * SNACs.
 	 */
 	fh->lnameoffset = 0x1a;
@@ -901,13 +930,142 @@
 }
 
 /**
- * Handle incoming data on a rendezvous connection.  This is analogous to the
- * consumesnac function in rxhandlers.c, and I really think this should probably
+ * Create a rendezvous "init recv" packet and send it on its merry way.
+ * This is the first packet sent to the proxy server by the second client
+ * involved in this rendezvous proxy session.
+ *
+ * @param sess The session.
+ * @param proxy_info Changable pieces of data for this packet
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+faim_export int aim_rv_proxy_init_recv(struct aim_rv_proxy_info *proxy_info)
+{
+	//aim_tlvlist_t *tlvlist_sendfile;
+	aim_bstream_t bs;
+	fu8_t *bs_raw;
+	fu16_t packet_len;
+	fu8_t sn_len;
+	int err;
+	
+	err = 0;
+	
+	if (!proxy_info)
+		return -EINVAL;
+
+	sn_len = strlen(proxy_info->sess->sn);
+	packet_len = 2 + 2	/* packet_len, packet_ver */
+		+ 2 + 4		/* cmd_type,  unknownA */
+		+ 2		/* flags */
+		+ 1 + sn_len	/* Length/value pair for screenname */
+		+ 8		/* ICBM Cookie */
+		+ 2		/* port */
+		+ 2 + 2 + 16;	/* TLV for Filesend capability block */
+	
+	if (!(bs_raw = malloc(packet_len)))
+		return -ENOMEM;
+		
+	aim_bstream_init(&bs, bs_raw, packet_len);
+	aimbs_put16(&bs, packet_len - 2); /* Length includes only packets after length marker */
+	aimbs_put16(&bs, proxy_info->packet_ver);
+	aimbs_put16(&bs, AIM_RV_PROXY_INIT_RECV);
+	aimbs_put32(&bs, proxy_info->unknownA);
+	aimbs_put16(&bs, proxy_info->flags);
+	aimbs_put8(&bs, sn_len);
+	aimbs_putraw(&bs, proxy_info->sess->sn, sn_len);
+	aimbs_put16(&bs, proxy_info->port);
+	aimbs_putraw(&bs, proxy_info->cookie, 8);
+	
+	aimbs_put16(&bs, 0x0001);		/* Type */
+	aimbs_put16(&bs, 16);			/* Length */
+	aimbs_putcaps(&bs, AIM_CAPS_SENDFILE);	/* Value */
+	
+	// TODO: Use built-in TLV 
+	//aim_tlvlist_add_caps(&tlvlist_sendfile, 0x0001, AIM_CAPS_SENDFILE);
+	//aim_tlvlist_write(&bs, &tlvlist_sendfile);
+	
+	aim_bstream_rewind(&bs);
+	if (aim_bstream_send(&bs, proxy_info->conn, packet_len) != packet_len)
+		err = errno;
+	proxy_info->conn->lastactivity = time(NULL);
+	
+	//aim_tlvlist_free(tlvlist_sendfile);
+	free(bs_raw);
+
+	return err;
+}
+
+
+/**
+ * Create a rendezvous "init send" packet and send it on its merry way.
+ * This is the first packet sent to the proxy server by the client
+ * first indicating that this will be a proxied connection
+ *
+ * @param sess The session.
+ * @param proxy_info Changable pieces of data for this packet
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+faim_export int aim_rv_proxy_init_send(struct aim_rv_proxy_info *proxy_info)
+{
+	//aim_tlvlist_t *tlvlist_sendfile;
+	aim_bstream_t bs;
+	fu8_t *bs_raw;
+	fu16_t packet_len;
+	fu8_t sn_len;
+	int err;
+	
+	err = 0;
+	
+	if (!proxy_info)
+		return -EINVAL;
+
+	sn_len = strlen(proxy_info->sess->sn);
+	packet_len = 2 + 2	/* packet_len, packet_ver */
+		+ 2 + 4		/* cmd_type,  unknownA */
+		+ 2		/* flags */
+		+ 1 + sn_len	/* Length/value pair for screenname */
+		+ 8		/* ICBM Cookie */
+		+ 2 + 2 + 16;	/* TLV for Filesend capability block */
+	
+	if (!(bs_raw = malloc(packet_len)))
+		return -ENOMEM;
+		
+	aim_bstream_init(&bs, bs_raw, packet_len);
+	aimbs_put16(&bs, packet_len - 2); /* Length includes only packets after length marker */
+	aimbs_put16(&bs, proxy_info->packet_ver);
+	aimbs_put16(&bs, AIM_RV_PROXY_INIT_SEND);
+	aimbs_put32(&bs, proxy_info->unknownA);
+	aimbs_put16(&bs, proxy_info->flags);
+	aimbs_put8(&bs, sn_len);
+	aimbs_putraw(&bs, proxy_info->sess->sn, sn_len);
+	aimbs_putraw(&bs, proxy_info->cookie, 8);
+	
+	aimbs_put16(&bs, 0x0001);		/* Type */
+	aimbs_put16(&bs, 16);			/* Length */
+	aimbs_putcaps(&bs, AIM_CAPS_SENDFILE);	/* Value */
+	
+	// TODO: Use built-in TLV 
+	//aim_tlvlist_add_caps(&tlvlist_sendfile, 0x0001, AIM_CAPS_SENDFILE);
+	//aim_tlvlist_write(&bs, &tlvlist_sendfile);
+	
+	aim_bstream_rewind(&bs);
+	if (aim_bstream_send(&bs, proxy_info->conn, packet_len) != packet_len)
+		err = errno;
+	proxy_info->conn->lastactivity = time(NULL);
+	
+	//aim_tlvlist_free(tlvlist_sendfile);
+	free(bs_raw);
+
+	return err;
+}
+
+/**
+ * Handle incoming data on a rendezvous connection.  This is analogous to the 
+ * consumesnac function in rxhandlers.c, and I really think this should probably 
  * be in rxhandlers.c as well, but I haven't finished cleaning everything up yet.
  *
  * @param sess The session.
  * @param fr The frame allocated for the incoming data.
- * @return Return 0 if the packet was handled correctly, otherwise return the
+ * @return Return 0 if the packet was handled correctly, otherwise return the 
  *         error number.
  */
 faim_internal int aim_rxdispatch_rendezvous(aim_session_t *sess, aim_frame_t *fr)
@@ -937,3 +1095,101 @@
 
 	return ret;
 }
+
+/**
+ * Handle incoming data on a rendezvous proxy connection.  This is similar to
+ * aim_rxdispatch_rendezvous above and should probably be kept with that function.
+ *
+ * @param sess The session.
+ * @param fr The frame allocated for the incoming data.
+ * @return Return 0 if the packet was handled correctly, otherwise return the 
+ *         error number.
+ */
+faim_internal struct aim_rv_proxy_info *aim_rv_proxy_read(aim_session_t *sess, aim_conn_t *conn)
+{
+	aim_bstream_t bs_hdr;
+	fu8_t hdr_buf[AIM_RV_PROXY_HDR_LEN];
+	aim_bstream_t bs_body; /* The body (everything but the header) of the packet */
+	fu8_t *body_buf = NULL;
+	fu8_t body_len;
+	
+	char str_ip[30] = {""};
+	fu8_t ip_temp[4];
+	
+	fu16_t len;
+	struct aim_rv_proxy_info *proxy_info;
+	
+	if(!(proxy_info = malloc(sizeof(struct aim_rv_proxy_info))))
+		return NULL;
+
+	aim_bstream_init(&bs_hdr, hdr_buf, AIM_RV_PROXY_HDR_LEN);
+	if (aim_bstream_recv(&bs_hdr, conn->fd, AIM_RV_PROXY_HDR_LEN) == AIM_RV_PROXY_HDR_LEN) {
+		aim_bstream_rewind(&bs_hdr);
+		len = aimbs_get16(&bs_hdr);
+		proxy_info->packet_ver = aimbs_get16(&bs_hdr);
+		proxy_info->cmd_type = aimbs_get16(&bs_hdr);
+		proxy_info->unknownA = aimbs_get32(&bs_hdr);
+		proxy_info->flags = aimbs_get16(&bs_hdr);
+		if(proxy_info->cmd_type == AIM_RV_PROXY_READY) {
+			/* Do a little victory dance
+			 * A ready packet contains no additional information */
+		} else if(proxy_info->cmd_type == AIM_RV_PROXY_ERROR) {
+			if(len == AIM_RV_PROXY_ERROR_LEN - 2) {
+				body_len = AIM_RV_PROXY_ERROR_LEN - AIM_RV_PROXY_HDR_LEN;
+				body_buf = malloc(body_len);
+				aim_bstream_init(&bs_body, body_buf, body_len);
+				if (aim_bstream_recv(&bs_body, conn->fd, body_len) == body_len) {
+					aim_bstream_rewind(&bs_body);
+					proxy_info->err_code = aimbs_get16(&bs_body);
+				} else {
+					gaim_debug_warning("oscar","error reading rv proxy error packet\n");
+					aim_conn_close(conn);
+					free(proxy_info);
+					proxy_info = NULL;
+				}
+			} else {
+				gaim_debug_warning("oscar","invalid length for proxy error packet\n");
+				free(proxy_info);
+				proxy_info = NULL;
+			}
+		} else if(proxy_info->cmd_type == AIM_RV_PROXY_ACK) {
+			if(len == AIM_RV_PROXY_ACK_LEN - 2) {
+				body_len = AIM_RV_PROXY_ACK_LEN - AIM_RV_PROXY_HDR_LEN;
+				body_buf = malloc(body_len);
+				aim_bstream_init(&bs_body, body_buf, body_len);
+				if (aim_bstream_recv(&bs_body, conn->fd, body_len) == body_len) {
+					aim_bstream_rewind(&bs_body);
+					proxy_info->port = aimbs_get16(&bs_body);
+					int i;
+					for(i=0; i<4; i++)
+						ip_temp[i] = aimbs_get8(&bs_body);
+					snprintf(str_ip, sizeof(str_ip), "%hhu.%hhu.%hhu.%hhu",
+						ip_temp[0], ip_temp[1],
+						ip_temp[2], ip_temp[3]);
+					proxy_info->ip = strdup(str_ip);
+				} else {
+					gaim_debug_warning("oscar","error reading rv proxy error packet\n");
+					aim_conn_close(conn);
+					free(proxy_info);
+					proxy_info = NULL;
+				}
+			} else {
+				gaim_debug_warning("oscar","invalid length for proxy error packet\n");
+				free(proxy_info);
+				proxy_info = NULL;
+			}
+		} else {
+			gaim_debug_warning("oscar","unknown type for aim rendezvous proxy packet\n");
+		}	
+	} else {
+		gaim_debug_warning("oscar","error reading header of rv proxy packet\n");
+		aim_conn_close(conn);
+		free(proxy_info);
+		proxy_info = NULL;
+	}
+	if(body_buf) {
+		free(body_buf);
+		body_buf = NULL;
+	}
+	return proxy_info;
+}
--- a/src/protocols/oscar/im.c	Tue Aug 30 05:21:30 2005 +0000
+++ b/src/protocols/oscar/im.c	Tue Aug 30 05:21:58 2005 +0000
@@ -83,11 +83,11 @@
 		fu8_t data[10];
 	} fingerprints[] = {
 		/* AOL Mobile Communicator, WinAIM 1.0.414 */
-		{ AIM_CLIENTTYPE_MC,
+		{ AIM_CLIENTTYPE_MC, 
 		  3, {0x01, 0x01, 0x01}},
 
 		/* WinAIM 2.0.847, 2.1.1187, 3.0.1464, 4.3.2229, 4.4.2286 */
-		{ AIM_CLIENTTYPE_WINAIM,
+		{ AIM_CLIENTTYPE_WINAIM, 
 		  3, {0x01, 0x01, 0x02}},
 
 		/* WinAIM 4.1.2010, libfaim */
@@ -144,10 +144,10 @@
 	aimbs_put16(&fr->data, 0x0000);
 
 	/* These are all read-write */
-	aimbs_put32(&fr->data, params->flags);
+	aimbs_put32(&fr->data, params->flags); 
 	aimbs_put16(&fr->data, params->maxmsglen);
-	aimbs_put16(&fr->data, params->maxsenderwarn);
-	aimbs_put16(&fr->data, params->maxrecverwarn);
+	aimbs_put16(&fr->data, params->maxsenderwarn); 
+	aimbs_put16(&fr->data, params->maxrecverwarn); 
 	aimbs_put32(&fr->data, params->minmsginterval);
 
 	aim_tx_enqueue(sess, fr);
@@ -184,7 +184,7 @@
 	params.maxsenderwarn = aimbs_get16(bs);
 	params.maxrecverwarn = aimbs_get16(bs);
 	params.minmsginterval = aimbs_get32(bs);
-
+	
 	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
 		return userfunc(sess, rx, &params);
 
@@ -282,7 +282,7 @@
 	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, msgtlvlen+128)))
 		return -ENOMEM;
 
-	/* XXX - should be optional */
+	/* XXX - should be optional */	
 	snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, args->destsn, strlen(args->destsn)+1);
 	aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
 
@@ -432,7 +432,7 @@
 	fu8_t *hdr;
 	int hdrlen;
 	aim_bstream_t hdrbs;
-
+	
 	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
 		return -EINVAL;
 
@@ -490,7 +490,7 @@
 	aim_tlvlist_add_str(&itl, 0x000c, msg);
 	aim_tlvlist_add_chatroom(&itl, 0x2711, exchange, roomname, instance);
 	aim_tlvlist_write(&hdrbs, &itl);
-
+	
 	aim_tlvlist_add_raw(&otl, 0x0005, aim_bstream_curpos(&hdrbs), hdr);
 
 	aim_tlvlist_write(&fr->data, &otl);
@@ -498,7 +498,7 @@
 	free(hdr);
 	aim_tlvlist_free(&itl);
 	aim_tlvlist_free(&otl);
-
+	
 	aim_tx_enqueue(sess, fr);
 
 	return 0;
@@ -731,7 +731,7 @@
 	aim_tlvlist_add_raw(&itl, 0x0003, 4, ip);
 	aim_tlvlist_add_16(&itl, 0x0005, port);
 	aim_tlvlist_add_noval(&itl, 0x000f);
-
+	
 	aim_tlvlist_write(&hdrbs, &itl);
 
 	aim_tlvlist_add_raw(&tl, 0x0005, aim_bstream_curpos(&hdrbs), hdr);
@@ -747,6 +747,22 @@
 	return 0;
 }
 
+/*
+ * Extracted from aim_im_sendch2_sendfile_ask
+ * Generates a random ICBM cookie in a character array of length 8
+ * and copies it into the variable passed as cookie
+ */
+faim_export void aim_im_makecookie(char* cookie) {
+	int i;
+	char gen_cookie[8];
+	
+	/* XXX - Should be like "21CBF95" and null terminated */
+	for (i = 0; i < 7; i++)
+		gen_cookie[i] = 0x30 + ((fu8_t)rand() % 10);
+	gen_cookie[7] = '\0';
+	memcpy(cookie, gen_cookie, 8);
+}
+
 /**
  * Subtype 0x0006 - Send an "I want to send you this file" message
  *
@@ -757,55 +773,112 @@
 	aim_frame_t *fr;
 	aim_snacid_t snacid;
 	aim_tlvlist_t *tl=NULL, *subtl=NULL;
-	int i;
 
 	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)) || !oft_info)
 		return -EINVAL;
 
-	/* XXX - Should be like "21CBF95" and null terminated */
-	for (i = 0; i < 7; i++)
-		oft_info->cookie[i] = 0x30 + ((fu8_t)rand() % 10);
-	oft_info->cookie[7] = '\0';
+	/* The cookie must already have been generated by this point */
 
 	{ /* Create the subTLV chain */
 		fu8_t *buf;
 		int buflen;
 		aim_bstream_t bs;
-
-		aim_tlvlist_add_16(&subtl, 0x000a, 0x0001);
-		aim_tlvlist_add_noval(&subtl, 0x000f);
+		fu8_t ip[4];
+		fu8_t ip_comp[4]; /* The bitwise complement of the ip */
+		char *nexttoken;
+		int i;
+
+		/* In a stage 2 proxied transfer & a transfer redirect, we send a second "reply request"
+		 * Being the second request for this transfer, its request number is 2
+		 * You can fill in the blank for a stage 3's request number... */
+		if( (oft_info->send_or_recv == AIM_XFER_RECV && oft_info->stage == AIM_XFER_PROXY_STG2)
+			|| (oft_info->send_or_recv == AIM_XFER_RECV
+				&& oft_info->stage == AIM_XFER_PROXY_STG3)
+			|| oft_info->method == AIM_XFER_REDIR)
+			aim_tlvlist_add_16(&subtl, 0x000a, 0x0002);
+		else if(oft_info->send_or_recv == AIM_XFER_SEND && oft_info->stage == AIM_XFER_PROXY_STG3)
+			aim_tlvlist_add_16(&subtl, 0x000a, 0x0003);
+		else
+			aim_tlvlist_add_16(&subtl, 0x000a, 0x0001);
+			
+		/* This is usually necessary, but ruins a redirect and a stg3 proxy request */
+		if(!(oft_info->send_or_recv == AIM_XFER_RECV
+			&& (oft_info->method == AIM_XFER_REDIR || oft_info->stage == AIM_XFER_PROXY_STG3))) {
+			aim_tlvlist_add_noval(&subtl, 0x000f);
+		}
+		
+		/* If the following is ever enabled, ensure that it is not sent with a receive redirect
+		 * or stage 3 proxy redirect for a file receive (same conditions for sending 0x000f above) */
 /*		aim_tlvlist_add_raw(&subtl, 0x000e, 2, "en");
 		aim_tlvlist_add_raw(&subtl, 0x000d, 8, "us-ascii");
 		aim_tlvlist_add_raw(&subtl, 0x000c, 24, "Please accept this file."); */
 		/* XXX - Change oft_info->clientip to an array of 4 bytes */
 		if (oft_info->clientip) {
-			fu8_t ip[4];
-			char *nexttoken;
-			int i = 0;
+			i = 0;
 			nexttoken = strtok(oft_info->clientip, ".");
 			while (nexttoken && i<4) {
 				ip[i] = atoi(nexttoken);
+				ip_comp[i] = ~ip[i];
 				nexttoken = strtok(NULL, ".");
 				i++;
 			}
+			
+			/* If there is no proxyip, we must fill it in with the clientip */
+			if(!oft_info->proxyip) {
+				aim_tlvlist_add_raw(&subtl, 0x0002, 4, ip);
+				aim_tlvlist_add_raw(&subtl, 0x0016, 4, ip_comp); /* check? value */
+			}
+			
 			aim_tlvlist_add_raw(&subtl, 0x0003, 4, ip);
 		}
-		aim_tlvlist_add_16(&subtl, 0x0005, oft_info->port);
-
-		/* TLV t(2711) */
-		buflen = 2+2+4+strlen(oft_info->fh.name)+1;
-		buf = malloc(buflen);
-		aim_bstream_init(&bs, buf, buflen);
-		aimbs_put16(&bs, (oft_info->fh.totfiles > 1) ? 0x0002 : 0x0001);
-		aimbs_put16(&bs, oft_info->fh.totfiles);
-		aimbs_put32(&bs, oft_info->fh.totsize);
-
-		/* Filename - NULL terminated, for some odd reason */
-		aimbs_putstr(&bs, oft_info->fh.name);
-		aimbs_put8(&bs, 0x00);
-
-		aim_tlvlist_add_raw(&subtl, 0x2711, bs.len, bs.data);
-		free(buf);
+		
+		/* Don't send the proxyip & accompanying info during a receive redirect or stg3 proxy request */
+		if(!(oft_info->send_or_recv == AIM_XFER_RECV
+			&& (oft_info->method == AIM_XFER_REDIR || oft_info->stage == AIM_XFER_PROXY_STG3))) {
+			if (oft_info->proxyip) { /* Generate the proxyip */
+				i = 0;
+				nexttoken = strtok(oft_info->proxyip, ".");
+				while (nexttoken && i<4) {
+					ip[i] = atoi(nexttoken);
+					ip_comp[i] = ~ip[i];
+					nexttoken = strtok(NULL, ".");
+					i++;
+				}
+				aim_tlvlist_add_raw(&subtl, 0x0002, 4, ip);
+				/* This zero-length TLV specifies a proxy will be used */
+				aim_tlvlist_add_noval(&subtl, 0x0010);
+				
+				/* Proxied transfers fail without this next (check?) value */
+				aim_tlvlist_add_raw(&subtl, 0x0016, 4, ip_comp);
+			}
+		}
+		
+		/* Don't send the port & its check during a stage 3 proxy request */
+		if(!(oft_info->send_or_recv == AIM_XFER_RECV && oft_info->stage == AIM_XFER_PROXY_STG3)) {
+			aim_tlvlist_add_16(&subtl, 0x0005, oft_info->port);
+			
+			/* Check value? Bitwise complement of the port */
+			aim_tlvlist_add_16(&subtl, 0x0017, ~(oft_info->port));
+		}
+
+		/* winAIM gets mad at us if we send too much info during a send redirect or stg3 proxy request */
+		if(!(oft_info->send_or_recv == AIM_XFER_RECV
+			&& (oft_info->method == AIM_XFER_REDIR || oft_info->stage == AIM_XFER_PROXY_STG3))) {
+			/* TLV t(2711) */
+			buflen = 2+2+4+strlen(oft_info->fh.name)+1;
+			buf = malloc(buflen);
+			aim_bstream_init(&bs, buf, buflen);
+			aimbs_put16(&bs, (oft_info->fh.totfiles > 1) ? 0x0002 : 0x0001);
+			aimbs_put16(&bs, oft_info->fh.totfiles);
+			aimbs_put32(&bs, oft_info->fh.totsize);
+	
+			/* Filename - NULL terminated, for some odd reason */
+			aimbs_putstr(&bs, oft_info->fh.name);
+			aimbs_put8(&bs, 0x00);
+	
+			aim_tlvlist_add_raw(&subtl, 0x2711, bs.len, bs.data);
+			free(buf);
+		}
 	}
 
 	{ /* Create the main TLV chain */
@@ -1143,7 +1216,7 @@
 		flag1 = aimbs_get16(&mbs);
 		flag2 = aimbs_get16(&mbs);
 
-		msg = aimbs_getraw(&mbs, msglen);
+		msg = aimbs_getstr(&mbs, msglen);
 	}
 
 	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
@@ -1202,8 +1275,8 @@
 
 static int mpmsg_addsection(aim_session_t *sess, aim_mpmsg_t *mpm, fu16_t charset, fu16_t charsubset, gchar *data, fu16_t datalen)
 {
-	aim_mpmsg_section_t *sec;
-
+	aim_mpmsg_section_t *sec; 
+	
 	if (!(sec = malloc(sizeof(aim_mpmsg_section_t))))
 		return -1;
 
@@ -1249,7 +1322,7 @@
 {
 	gchar *dup;
 
-	if (!(dup = strdup(ascii)))
+	if (!(dup = strdup(ascii))) 
 		return -1;
 
 	if (mpmsg_addsection(sess, mpm, 0x0000, 0x0000, dup, strlen(ascii)) == -1) {
@@ -1279,7 +1352,7 @@
 		free(buf);
 		return -1;
 	}
-
+	
 	return 0;
 }
 
@@ -1289,13 +1362,13 @@
 
 	for (cur = mpm->parts; cur; ) {
 		aim_mpmsg_section_t *tmp;
-
+		
 		tmp = cur->next;
 		free(cur->data);
 		free(cur);
 		cur = tmp;
 	}
-
+	
 	mpm->numparts = 0;
 	mpm->parts = NULL;
 
@@ -1733,6 +1806,9 @@
 		/* There is sometimes more after the null-terminated filename, 
 		 * but I'm unsure of its format. */
 		/* I don't believe him. */
+		/* There is sometimes a null byte inside a unicode filename,
+		 * but as far as I can tell the filename is the last
+		 * piece of data that will be in this message. --Jonathan */
 	}
 
 	return;
@@ -1847,8 +1923,15 @@
 	 * 0x0002 - "I will accept this file from you"
 	 * 0x0002 - Also used in ICQ Lite Beta 4.0 URLs
 	 */
+	 /*
+	  * This is what I call the request number of the file transfer
+	  * 0x0001 - Initial file transfer request for no proxy or stage 1 proxy
+	  * 0x0002 - "Reply request" for a stage 2 proxy (receiver wants to use proxy)
+	  * 0x0003 - A third request has been sent; applies only to stage 3 proxied transfers
+	  * -- Jonathan
+	  */
 	if (aim_tlv_gettlv(list2, 0x000a, 1))
-		;
+		args.info.sendfile.reqnum = aim_tlv_get16(list2, 0x000a, 1);
 
 	/*
 	 * Error code.
@@ -1869,7 +1952,7 @@
 	 */
 	if (aim_tlv_gettlv(list2, 0x000d, 1))
 		args.encoding = aim_tlv_getstr(list2, 0x000d, 1);
-
+	
 	/*
 	 * Language.
 	 */
@@ -1882,16 +1965,18 @@
 	 * Maybe means we should connect directly to transfer the file?
 	 * Also used in ICQ Lite Beta 4.0 URLs.  Also empty.
 	 */
+	 /* I don't think this indicates a direct transfer; this flag is
+	  * also present in a stage 1 proxied file send request -- Jonathan */
 	if (aim_tlv_gettlv(list2, 0x000f, 1))
 		;
 
 	/*
-	 * Unknown -- no value
-	 *
-	 * Maybe means we should proxy the file transfer through an AIM server?
+	 * Flag meaning we should proxy the file transfer through an AIM server
 	 */
 	if (aim_tlv_gettlv(list2, 0x0010, 1))
-		;
+		args.info.sendfile.use_proxy = TRUE;
+	else
+		args.info.sendfile.use_proxy = FALSE;
 
 	if (strlen(proxyip))
 		args.proxyip = (char *)proxyip;
@@ -2100,7 +2185,7 @@
 	snacid = aim_cachesnac(sess, 0x0004, 0x0008, 0x0000, sn, strlen(sn)+1);
 	aim_putsnac(&fr->data, 0x0004, 0x0008, 0x0000, snacid);
 
-	aimbs_put16(&fr->data, (flags & AIM_WARN_ANON) ? 0x0001 : 0x0000);
+	aimbs_put16(&fr->data, (flags & AIM_WARN_ANON) ? 0x0001 : 0x0000); 
 	aimbs_put8(&fr->data, strlen(sn));
 	aimbs_putstr(&fr->data, sn);
 
@@ -2117,7 +2202,7 @@
 	fu16_t channel, nummissed, reason;
 	aim_userinfo_t userinfo;
 
-	while (aim_bstream_empty(bs)) {
+	while (aim_bstream_empty(bs)) {	
 
 		channel = aimbs_get16(bs);
 		aim_info_extract(sess, bs, &userinfo);
@@ -2140,7 +2225,7 @@
  *    AIM_TRANSFER_DENY_NOTSUPPORTED -- "client does not support"
  *    AIM_TRANSFER_DENY_DECLINE -- "client has declined transfer"
  *    AIM_TRANSFER_DENY_NOTACCEPTING -- "client is not accepting transfers"
- *
+ * 
  */
 faim_export int aim_im_denytransfer(aim_session_t *sess, const char *sender, const fu8_t *cookie, fu16_t code)
 {
@@ -2148,7 +2233,7 @@
 	aim_frame_t *fr;
 	aim_snacid_t snacid;
 	aim_tlvlist_t *tl = NULL;
-
+	
 	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
 		return -EINVAL;
 
@@ -2157,7 +2242,7 @@
 
 	snacid = aim_cachesnac(sess, 0x0004, 0x000b, 0x0000, NULL, 0);
 	aim_putsnac(&fr->data, 0x0004, 0x000b, 0x0000, snacid);
-
+	
 	aimbs_putraw(&fr->data, cookie, 8);
 
 	aimbs_put16(&fr->data, 0x0002); /* channel */
--- a/src/protocols/oscar/oscar.c	Tue Aug 30 05:21:30 2005 +0000
+++ b/src/protocols/oscar/oscar.c	Tue Aug 30 05:21:58 2005 +0000
@@ -68,8 +68,11 @@
 #define OSCAR_DEFAULT_HIDE_IP TRUE
 #define OSCAR_DEFAULT_WEB_AWARE FALSE
 
-/* Seconds each file transfer ip address will be given to make a connection */
-#define FT_IP_TIMEOUT	15
+/* Milliseconds each file transfer ip address will be given to make a connection. */
+#define FT_CLIENTIP_TIMEOUT	1000	/* 5000 */
+#define FT_VERIFIEDIP_TIMEOUT	5000	/* 15000 */
+#define FT_REDIR_TIMEOUT	10000	/* 20000 */	/* Time to wait for redirected transfer */
+#define FT_PROXYIP_TIMEOUT	300000	/* 120000 */	/* Time to create a checksum for VERY large files */
 
 static int caps_aim = AIM_CAPS_CHAT | AIM_CAPS_BUDDYICON | AIM_CAPS_DIRECTIM | AIM_CAPS_SENDFILE | AIM_CAPS_INTEROPERATE | AIM_CAPS_ICHAT;
 static int caps_icq = AIM_CAPS_BUDDYICON | AIM_CAPS_DIRECTIM | AIM_CAPS_SENDFILE | AIM_CAPS_ICQUTF8 | AIM_CAPS_INTEROPERATE | AIM_CAPS_ICHAT;
@@ -103,7 +106,7 @@
 	gboolean chpass;
 	char *oldp;
 	char *newp;
-
+	
 	GSList *oscar_chats;
 	GSList *direct_ims;
 	GSList *file_transfers;
@@ -274,6 +277,7 @@
 static int gaim_icqalias         (aim_session_t *, aim_frame_t *, ...);
 static int gaim_icqinfo          (aim_session_t *, aim_frame_t *, ...);
 static int gaim_popup            (aim_session_t *, aim_frame_t *, ...);
+
 static int gaim_ssi_parseerr     (aim_session_t *, aim_frame_t *, ...);
 static int gaim_ssi_parserights  (aim_session_t *, aim_frame_t *, ...);
 static int gaim_ssi_parselist    (aim_session_t *, aim_frame_t *, ...);
@@ -298,10 +302,12 @@
 
 static gboolean gaim_icon_timerfunc(gpointer data);
 static void oscar_callback(gpointer data, gint source, GaimInputCondition condition);
+static void oscar_xfer_init_recv(GaimXfer *xfer);
+static void oscar_xfer_init_send(GaimXfer *xfer);
+
 static void oscar_direct_im_initiate(GaimConnection *gc, const char *who, const guchar *cookie);
+static void recent_buddies_cb(const char *name, GaimPrefType type, gpointer value, gpointer data);
 static void oscar_set_info(GaimConnection *gc, const char *info);
-static void recent_buddies_cb(const char *name, GaimPrefType type, gpointer value, gpointer data);
-static void oscar_xfer_init_recv(GaimXfer *xfer);
 
 static void oscar_free_name_data(struct name_data *data) {
 	g_free(data->name);
@@ -320,8 +326,8 @@
 	int i = 0;
 	int charset = AIM_CHARSET_ASCII;
 
-	/* Determine how we can send this message.  Per the warnings elsewhere
-	 * in this file, these little checks determine the simplest encoding
+	/* Determine how we can send this message.  Per the warnings elsewhere 
+	 * in this file, these little checks determine the simplest encoding 
 	 * we can use for a given message send using it. */
 	while (utf8[i]) {
 		if ((unsigned char)utf8[i] > 0x7f) {
@@ -351,7 +357,7 @@
 
 /*
  * Take a string of the form charset="bleh" where bleh is
- * one of us-ascii, utf-8, iso-8859-1, or unicode-2-0, and
+ * one of us-ascii, utf-8, iso-8859-1, or unicode-2-0, and 
  * return a newly allocated string containing bleh.
  */
 static gchar *oscar_encoding_extract(const char *encoding)
@@ -469,7 +475,7 @@
 		charsetstr1 = "UCS-2BE";
 		charsetstr2 = "UTF-8";
 	} else if (charset == AIM_CHARSET_CUSTOM) {
-		if ((sourcesn != NULL) && aim_sn_is_icq(sourcesn))
+		if ((sourcesn != NULL) && isdigit(sourcesn[0]))
 			charsetstr1 = gaim_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING);
 		else
 			charsetstr1 = "ISO-8859-1";
@@ -739,8 +745,7 @@
 	if (b == NULL)
 		b = gaim_find_buddy(account, userinfo->sn);
 
-	if (b != NULL)
-	{
+	if (b != NULL) {
 		g = gaim_find_buddys_group(b);
 		presence = gaim_buddy_get_presence(b);
 		status = gaim_presence_get_active_status(presence);
@@ -811,7 +816,7 @@
 
 	if (!name)
 		return NULL;
-
+	
 	x = strchr(name, '-');
 
 	if (!x) return NULL;
@@ -955,7 +960,8 @@
 		g_snprintf(buf, sizeof buf, _("Direct IM with %s failed"), dim->name);
 
 	conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, dim->name,
-											   gaim_connection_get_account(dim->gc));
+		gaim_connection_get_account(dim->gc));
+        
 	if (conv) {
 		gaim_conversation_write(conv, NULL, buf, GAIM_MESSAGE_SYSTEM, time(NULL));
 		gaim_conversation_update_progress(conv, 0);
@@ -1207,7 +1213,7 @@
 	}
 
 	c = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, sn,
-											gaim_connection_get_account(gc));
+		gaim_connection_get_account(gc));
 	if (c != NULL)
 		gaim_conversation_update_progress(c, percent);
 	dim->watcher = gaim_input_add(dim->conn->fd, GAIM_INPUT_READ,
@@ -1606,14 +1612,17 @@
 	aim_session_t *sess = aim_conn_getsess(conn);
 	GaimConnection *gc = sess ? sess->aux_data : NULL;
 	OscarData *od;
-	
+
 	if (!gc) {
 		gaim_debug_info("oscar",
 				   "oscar callback for closed connection (1).\n");
 		return;
 	}
-
-	od = (OscarData *)gc->proto_data;
+      
+	if( !(od = (OscarData *)gc->proto_data) ) {
+		gaim_debug_warning("oscar","NULL od in oscar_callback; conn closed?\n");
+		return;
+	}
 
 	if (!g_list_find(gaim_connections_get_all(), gc)) {
 		/* oh boy. this is probably bad. i guess the only thing we 
@@ -1643,7 +1652,7 @@
 				}
 			} else {
 				if ((conn->type == AIM_CONN_TYPE_BOS) ||
-					   !(aim_getconn_type(od->sess, AIM_CONN_TYPE_BOS)))
+					!(aim_getconn_type(od->sess, AIM_CONN_TYPE_BOS)))
 				{
 					gaim_debug_error("oscar", "Major connection error.  i.e. "
 						"invalid data was received on the oscar TCP stream\n");
@@ -1652,7 +1661,6 @@
 					struct chat_connection *cc = find_oscar_chat_by_conn(gc, conn);
 					GaimConversation *conv = gaim_find_chat(gc, cc->id);
 					char *buf;
-
 					gaim_debug_info("oscar", "Lost connection "
 									"to chat room %s\n", cc->name);
 
@@ -1663,9 +1671,9 @@
 					else
 						gaim_notify_error(gc, NULL, buf, NULL);
 					g_free(buf);
-
+					
 					oscar_chat_kill(gc, cc);
-
+					
 				} else if (conn->type == AIM_CONN_TYPE_CHATNAV) {
 					if (od->cnpa > 0)
 						gaim_input_remove(od->cnpa);
@@ -1682,6 +1690,7 @@
 										  _("Chat is currently unavailable"),
 										  NULL);
 					}
+					gaim_debug_info("oscar","killing rendezvous connection\n");
 					aim_conn_kill(od->sess, &conn);
 				} else if (conn->type == AIM_CONN_TYPE_AUTH) {
 					if (od->paspa > 0)
@@ -1766,7 +1775,7 @@
 	primitive = gaim_status_type_get_primitive(status_type);
 
 	gaim_debug_misc("oscar", "oscar_login: gc = %p\n", gc);
-
+	
 	if (primitive == GAIM_STATUS_OFFLINE)
 		return;
 
@@ -1788,7 +1797,7 @@
 	sess = g_new0(aim_session_t, 1);
 	aim_session_init(sess, TRUE);
 	/*
-	 * We need an immediate queue because we don't use a while-loop
+	 * We need an immediate queue because we don't use a while-loop 
 	 * to see if things need to be sent.
 	 */
 	aim_tx_setenqueue(sess, AIM_TX_IMMEDIATE, NULL);
@@ -1879,7 +1888,7 @@
 	if (od->getinfotimer > 0)
 		gaim_timeout_remove(od->getinfotimer);
 	gaim_prefs_disconnect_by_handle(gc);
-
+		
 	aim_session_kill(od->sess);
 	g_free(od->sess);
 	od->sess = NULL;
@@ -1945,8 +1954,17 @@
  *  -They begin to send us lots of raw data.
  *  -When they finish sending data we send an AIM_CB_OFT_DONE and then close 
  *   the connection.
+ *
+ * Update August 2005:
+ * The series of events for transfers has been seriously complicated by the addition
+ * of transfer redirects and proxied connections. I could throw a whole lot of words
+ * at trying to explain things here, but it probably wouldn't do much good. To get
+ * a better idea of what happens, take a look at the diagrams and documentation
+ * from my Summer of Code project. -- Jonathan Clark
  */
 static void oscar_sendfile_connected(gpointer data, gint source, GaimInputCondition condition);
+static void oscar_xfer_proxylogin(gpointer data, gint source, GaimInputCondition condition);
+static void oscar_send_file_request(GaimXfer *xfer);
 
 /*
  * Miscellaneous xfer functions
@@ -1987,6 +2005,11 @@
 	return NULL;
 }
 
+/*
+ * We're done sending/receiving raw data through the connection.
+ * If we're the receiver, send an OFT header with the bytes received
+ * filled in to indicate this party's over.
+ */
 static void oscar_xfer_end(GaimXfer *xfer)
 {
 	struct aim_oft_info *oft_info = xfer->data;
@@ -2011,99 +2034,188 @@
  */
 
 /*
- * This is a gaim timeout callback for when the clientip looks to be useless (after verifiedip has been tried)
- * It gives up on the file transfer completely if it doesn't approve of the file transfer status
- */
-static gboolean oscar_clientip_timeout(gpointer data) {
+ * This is a gaim timeout callback called X milliseconds after a connection is attempted
+ * By this point, we've lost faith in the connection method we just tried and want to
+ * try something new. Hopefully, that new connection method will be more successful,
+ * otherwise, we'll end up here again and again until the connection is successful
+ * or we've tried every method... if that happens we just throw our hands up
+ * and inform the user of his bad karma.
+*/
+static gboolean oscar_xfer_ip_timeout(gpointer data) {
 	GaimXfer *xfer;
 	struct aim_oft_info *oft_info;
 	char *msg = NULL;
 	
-	gaim_debug_info("oscar","AAA - in oscar_clientip_timeout\n");
+	gaim_debug_info("oscar","AAA - in oscar_xfer_ip_timeout\n");
+
 	xfer = (GaimXfer*) data;
 	if(xfer->data) {
 		oft_info = (struct aim_oft_info*) xfer->data;
-		
+			
 		/* Check to see if the clientip has produced any results */
 		if(!oft_info->success) {
-			msg = g_strdup_printf(_("Transfer of file %s timed out."),gaim_xfer_get_filename(xfer));
-			gaim_xfer_conversation_write(xfer, msg, TRUE);
-			g_free(msg);
-			gaim_xfer_unref(xfer);
-			gaim_xfer_cancel_local(xfer);
+			/* This connection has worn out its welcome. Goodbye. */
+			if(oft_info->conn) {
+				close(oft_info->conn->fd);
+				aim_conn_kill(oft_info->sess, &oft_info->conn);
+			}
+		
+			if(oft_info->method == AIM_XFER_DIRECT || oft_info->method == AIM_XFER_REDIR) {		
+				/* If (we're currently using the verified ip)
+				* In case clientip & verifiedip are the same,
+				* we must prevent an infinite loop */
+				if(xfer->remote_ip && oft_info->verifiedip
+					&& g_ascii_strcasecmp(xfer->remote_ip, oft_info->verifiedip) == 0
+					&& g_ascii_strcasecmp(oft_info->clientip, oft_info->verifiedip) != 0 )
+				{
+					/* The verifiedip timed out */
+					if(oft_info->method == AIM_XFER_DIRECT) {
+						/* clientip & verifiedip failed, request a redirect
+						* that is, we want the sender to connect to us */
+						
+						/* Let the user not to lose hope quite yet*/
+						msg = g_strdup_printf(_("Attempting connection redirect..."));
+						gaim_xfer_conversation_write(xfer, msg, FALSE);
+						g_free(msg);
+						
+						gaim_timeout_add(FT_REDIR_TIMEOUT,
+							oscar_xfer_ip_timeout, xfer);
+						oft_info->method = AIM_XFER_REDIR;
+						g_free(oft_info->proxyip);
+						oft_info->proxyip = NULL;
+						oft_info->clientip = g_strdup( gaim_network_get_my_ip(
+							oft_info->conn ? oft_info->conn->fd : -1));
+						oscar_xfer_init_send(xfer);
+					} else {
+						/* clientip, verifiedip, and redirect all failed. */
+						gaim_debug_info("oscar",
+							"redirect timed out. requesting stg3 proxy\n");
+							
+						/* Kill our listener */
+						gaim_input_remove(xfer->watcher);
+						aim_conn_kill(oft_info->sess, &oft_info->conn);
+				
+						/* Instead of failing here, request a stage 3 proxy */
+						g_free(oft_info->clientip);
+						g_free(oft_info->verifiedip);
+						oft_info->clientip = NULL;
+						oft_info->verifiedip = NULL;
+						oft_info->port = 0;
+						oft_info->conn->type = AIM_CONN_TYPE_RENDEZVOUS;
+						oft_info->method = AIM_XFER_PROXY;
+						oft_info->stage = AIM_XFER_PROXY_STG3;
+						
+						aim_im_sendch2_sendfile_ask(oft_info->sess, oft_info);
+					}
+				} else {
+					/* clientip timed out, now try verifiedip */
+					g_free(xfer->remote_ip);
+					xfer->remote_ip = g_strdup(oft_info->verifiedip);
+					gaim_debug_info("oscar","attempting connection using verifiedip\n");
+					oscar_xfer_init_recv(xfer);
+				}
+			} else if(oft_info->method == AIM_XFER_PROXY) {
+				/* proxyip timed out
+				 * Yes, it's a bit odd to ask the user to enable proxied file transfers
+				 * when it's a proxied transfer that timed out. It is possible that a
+				 * stage 1/2 proxied transfer might work when a stage 3 will not. */
+				msg = g_strdup_printf(_("Transfer of file %s timed out.\n Try enabling proxy servers for file transfers in Tools->Preferences->AIM/ICQ."),
+					gaim_xfer_get_filename(xfer));
+				gaim_xfer_conversation_write(xfer, msg, TRUE);
+				g_free(msg);
+				gaim_xfer_cancel_local(xfer);
+				if(oft_info->xfer_reffed) {
+					oft_info->xfer_reffed = FALSE;
+					gaim_xfer_unref(xfer);
+				}
+			} else {
+				gaim_debug_warning("oscar","unknown xfer method encountered in timout\n");
+			}
 		} else {
-			gaim_debug_info("oscar","connection successful; no action taken\n");
+			if(oft_info->xfer_reffed) {
+				oft_info->xfer_reffed = FALSE;
+				gaim_xfer_unref(xfer);
+			}
+			gaim_debug_info("oscar","connection successful; timeout off\n");
 		}
+	} else {
+		gaim_debug_info("oscar","transfer already done; nothing to do\n");
 	}
 	return FALSE;
 }
 
 /*
- * This is a gaim timeout callback for when the verifiedip looks to be useless 
- * It tries the file transfer again using the clientip
- *
- * BBB
+ * Connect to another client or a file transfer proxy server.
+ * Though this function has traditionally only been used during file receives,
+ * it is now called to make any sort of file transfer connection via gaim_proxy_connect.
  */
-static gboolean oscar_verifiedip_timeout(gpointer data) {
-	GaimXfer *xfer;
-	struct aim_oft_info *oft_info;
-	
-	gaim_debug_info("oscar","AAA - in oscar_verifiedip_timeout\n");
-	xfer = (GaimXfer*) data;
-	if(xfer->data) {
-		oft_info = (struct aim_oft_info*) xfer->data;
-		
-		/* Check to see if the verifiedip has produced any results */
-		if(!oft_info->success) {
-			/* gaim_xfer_conversation_write(xfer,
-				"Attempting file transfer via secondary IP address...", FALSE); */
-		
-			/* The verifiedip connection has worn out its welcome. Goodbye. */
-			close(oft_info->conn->fd);
-			aim_conn_kill(oft_info->sess, &oft_info->conn);
-			
-			/* Try the file transfer again with the clientip */
-			g_free(xfer->remote_ip);
-			xfer->remote_ip = g_strdup(oft_info->clientip);
-			gaim_debug_info("oscar","attempting connection using clientip: %s\n", xfer->remote_ip);
-			oscar_xfer_init_recv(xfer);
-		} else {
-			gaim_debug_info("oscar","connection successful; no action taken\n");
-		}
-	}
-	return FALSE;
-}
-
 static void oscar_xfer_init_recv(GaimXfer *xfer)
 {
-	struct aim_oft_info *oft_info = xfer->data;
-	GaimConnection *gc = oft_info->sess->aux_data;
-	OscarData *od = gc->proto_data;
-
-	gaim_debug_info("oscar", "AAA - in oscar_xfer_recv_init\n");
+	struct aim_oft_info *oft_info;
+	struct aim_rv_proxy_info *proxy_info;
+	GaimConnection *gc;
+	OscarData *od;
+	GaimInputFunction nextstop_cb;
+	int rc;
+
+	g_return_if_fail(xfer != NULL);
+	g_return_if_fail(xfer->data != NULL);
+
+	oft_info = xfer->data;
+	proxy_info = oft_info->proxy_info;
+	gc = oft_info->sess->aux_data;
+	od = gc->proto_data;
+
+	gaim_debug_info("oscar", "AAA - in oscar_xfer_init_recv\n");
 	
 	/* Start a timer for this ip address
-	 * If the verifiedip fails, try the clientip
-	 * If clientip fails, declare the whole file transfer dead
-	 * This xfer reference will be released in oscar_clientip_timeout */
-	if(xfer->data) {
+	 * If the clientip fails, try the verifiedip
+	 * If that fails, wait for the transfer to redirect
+	 * This xfer reference will be released in oscar_xfer_ip_timeout */
+	if(!oft_info->xfer_reffed) {
+		oft_info->xfer_reffed = TRUE;
 		gaim_xfer_ref(xfer);
-		/* If clientip & verifiedip are the same, we must prevent an infinite loop */
-		if(g_ascii_strcasecmp(xfer->remote_ip, oft_info->verifiedip) == 0
+	}
+	
+	if(oft_info->method != AIM_XFER_PROXY) {
+		/* If (we're currently using the verified ip)
+		 * In case clientip & verifiedip are the same, we must prevent an infinite loop */
+		if(xfer->remote_ip && oft_info->verifiedip
+			&& g_ascii_strcasecmp(xfer->remote_ip, oft_info->verifiedip) == 0
 			&& g_ascii_strcasecmp(oft_info->clientip, oft_info->verifiedip) != 0 ) {
-			gaim_timeout_add(FT_IP_TIMEOUT * 1000, oscar_verifiedip_timeout, xfer);
+			gaim_timeout_add(FT_VERIFIEDIP_TIMEOUT, oscar_xfer_ip_timeout, xfer);
 		} else {
-			gaim_timeout_add(FT_IP_TIMEOUT * 1000, oscar_clientip_timeout, xfer);
+			gaim_timeout_add(FT_CLIENTIP_TIMEOUT, oscar_xfer_ip_timeout, xfer);
 		}
-	}
-
+	} else {
+		gaim_timeout_add(FT_PROXYIP_TIMEOUT, oscar_xfer_ip_timeout, xfer);
+	}
 	oft_info->conn = aim_newconn(od->sess, AIM_CONN_TYPE_RENDEZVOUS);
+	
+	/* If we're routing this transfer through a AOL proxy server, do the special login
+	 * before telling the other client we're ready for action.
+	 * Note, firststop_cb is the first function called after gaim has made a connection
+	 * Also, the connection type is changed until the proxy login is complete */
+	if(oft_info->method == AIM_XFER_PROXY) {
+		if(proxy_info)
+			proxy_info->conn = oft_info->conn;
+		else {
+			gaim_debug_warning("oscar","NULL proxy_info\n");
+			gaim_xfer_cancel_local(xfer);
+		}
+		nextstop_cb = oscar_xfer_proxylogin;
+		oft_info->conn->type = AIM_CONN_TYPE_RENDEZVOUS_PROXY;
+	} else {
+		nextstop_cb = oscar_sendfile_connected;
+	}
+	
 	if (oft_info->conn) {
 		oft_info->conn->subtype = AIM_CONN_SUBTYPE_OFT_SENDFILE;
-		aim_conn_addhandler(od->sess, oft_info->conn, AIM_CB_FAM_OFT, AIM_CB_OFT_PROMPT, oscar_sendfile_prompt, 0);
-		oft_info->conn->fd = xfer->fd = gaim_proxy_connect(gaim_connection_get_account(gc),
-					xfer->remote_ip, xfer->remote_port, oscar_sendfile_connected, xfer);
-		if (xfer->fd == -1) {
+		aim_conn_addhandler(od->sess, oft_info->conn, AIM_CB_FAM_OFT, AIM_CB_OFT_PROMPT,
+			oscar_sendfile_prompt, 0);
+		rc = gaim_proxy_connect(gaim_connection_get_account(gc),
+					xfer->remote_ip, xfer->remote_port, nextstop_cb, xfer);
+		if (rc == -1) {
 			gaim_xfer_error(GAIM_XFER_RECEIVE, gaim_xfer_get_account(xfer), xfer->who,
 							_("Unable to establish file descriptor."));
 			gaim_xfer_cancel_local(xfer);
@@ -2114,21 +2226,16 @@
 		gaim_xfer_cancel_local(xfer);
 		/* Try a different port? Ask them to connect to us? /join #gaim and whine? */
 	}
-
-}
-
+}
+
+/*
+ * "Never mind. This transfer wasn't such a great idea after all."
+ */
 static void oscar_xfer_cancel_recv(GaimXfer *xfer)
 {
-	struct aim_oft_info *oft_info;
-	GaimConnection *gc;
-	OscarData *od;
-
-	g_return_if_fail(xfer != NULL);
-	g_return_if_fail(xfer->data != NULL);
-
-	oft_info = xfer->data;
-	gc = oft_info->sess->aux_data;
-	od = gc->proto_data;
+	struct aim_oft_info *oft_info = xfer->data;
+	GaimConnection *gc = oft_info->sess->aux_data;
+	OscarData *od = gc->proto_data;
 
 	gaim_debug_info("oscar", "AAA - in oscar_xfer_cancel_recv\n");
 
@@ -2141,6 +2248,9 @@
 	od->file_transfers = g_slist_remove(od->file_transfers, xfer);
 }
 
+/*
+ * Called after every data packet we receive
+ */
 static void oscar_xfer_ack_recv(GaimXfer *xfer, const guchar *buffer, size_t size)
 {
 	struct aim_oft_info *oft_info = xfer->data;
@@ -2150,9 +2260,299 @@
 }
 
 /*
+ * xfer functions used for proxied file transfers
+ */
+
+/*
+ * Called by oscar_send_proxylogin_cb when we receive a ready packet
+ * BBB
+ */
+void oscar_xfer_proxylogin_ready(GaimXfer *xfer, gint fd) {
+	struct aim_oft_info *oft_info;
+	struct aim_rv_proxy_info *proxy_info;
+	
+	gaim_debug_info("oscar","AAA - in oscar_xfer_proxylogin_ready\n");
+	if (!(oft_info = xfer->data)) {
+		gaim_debug_warning("oscar","NULL oft_info; aborting\n");
+		gaim_xfer_cancel_local(xfer);
+		return;
+	}
+	if (!(proxy_info = oft_info->proxy_info)) {
+		gaim_debug_warning("oscar","NULL proxy_info; aborting\n");
+		gaim_xfer_cancel_local(xfer);
+		return;
+	}
+	
+	/* Remove the rv proxy watcher and put the connection type back the way we found it */
+	gaim_input_remove(xfer->watcher);
+	xfer->watcher = 0;
+	oft_info->conn->type = AIM_CONN_TYPE_RENDEZVOUS;
+
+	if(oft_info->send_or_recv == AIM_XFER_SEND) {
+	
+		if(oft_info->stage == AIM_XFER_PROXY_STG2) {
+			aim_im_sendch2_sendfile_accept(oft_info->sess, oft_info);
+			
+			/* For stage 2, both file headers are filled in */
+			memcpy(&oft_info->fh.bcookie, oft_info->cookie, 8);
+		}
+		
+		/* The following is taken from oscar_sendfile_estblsh */
+		aim_conn_addhandler(oft_info->sess, oft_info->conn, AIM_CB_FAM_OFT, AIM_CB_OFT_ACK,
+			oscar_sendfile_ack, 0);
+		aim_conn_addhandler(oft_info->sess, oft_info->conn, AIM_CB_FAM_OFT, AIM_CB_OFT_DONE,
+			oscar_sendfile_done, 0);
+		xfer->watcher = gaim_input_add(oft_info->conn->fd, GAIM_INPUT_READ, oscar_callback,
+			oft_info->conn);
+		
+		/* Inform the other user that we are connected and ready to transfer */
+		aim_oft_sendheader(oft_info->sess, AIM_CB_OFT_PROMPT, oft_info);
+	} else if(oft_info->send_or_recv == AIM_XFER_RECV) {
+		oscar_sendfile_connected(xfer, fd, GAIM_INPUT_READ);
+	} else {
+		gaim_debug_warning("oscar","no value for send_or_recv; aborting transfer\n");
+		gaim_xfer_cancel_local(xfer);
+	}	
+}
+
+/*
+ * Called by oscar_sendfile_proxylogin_cb when we receive an ack packet in reply to an init_send
+ * BBB
+ */
+void oscar_xfer_proxylogin_ack(GaimXfer *xfer) {
+	struct aim_oft_info *oft_info;
+	struct aim_rv_proxy_info *proxy_info;
+	
+	gaim_debug_info("oscar","AAA - in oscar_xfer_proxylogin_ack\n");
+	if (!(oft_info = xfer->data)) {
+		gaim_debug_warning("oscar","NULL oft_info; aborting\n");
+		gaim_xfer_cancel_local(xfer);
+		return;
+	}
+	if (!(proxy_info = oft_info->proxy_info)) {
+		gaim_debug_warning("oscar","NULL proxy_info; aborting\n");
+		gaim_xfer_cancel_local(xfer);
+		return;
+	}
+	
+	/* Use the proxy "port" we just ACK-quired (hah) so that the proxy will love us */
+	oft_info->port = proxy_info->port;
+	oft_info->proxyip = g_strdup(proxy_info->ip);
+	gaim_debug_info("oscar","received client ip and port: %s:%d\n",
+		oft_info->proxyip, oft_info->port);
+		
+	if(oft_info->send_or_recv == AIM_XFER_SEND) {
+		oscar_send_file_request(xfer);
+	} else if(oft_info->send_or_recv == AIM_XFER_RECV) {
+		strncpy(oft_info->fh.name, xfer->filename, 64);
+		oft_info->fh.name[63] = '\0';
+		oft_info->fh.totsize = gaim_xfer_get_size(xfer);
+		oft_info->fh.size = gaim_xfer_get_size(xfer);
+		
+		/* Calculating the checksum can take a very long time for large files */
+		gaim_debug_info("oscar","calculating file checksum\n");
+ 		oft_info->fh.checksum = aim_oft_checksum_file(xfer->local_filename);
+		gaim_debug_info("oscar","checksum calculated\n");
+	
+		aim_im_sendch2_sendfile_ask(oft_info->sess, oft_info);
+	} else {
+		gaim_debug_warning("oscar","no value for send_or_recv; aborting transfer\n");
+		gaim_xfer_cancel_local(xfer);
+	}
+}
+
+/*
+ * This is called whenever we receive data while negotiating a rendezvous proxy connection
+ * BBB
+ */
+static void oscar_xfer_proxylogin_cb(gpointer data, gint source, GaimInputCondition condition) {
+	GaimXfer *xfer;
+	struct aim_oft_info *oft_info;
+	
+	gaim_debug_info("oscar","AAA - in oscar_xfer_proxylogin_cb\n");
+	if (!(xfer = data)) {
+		gaim_debug_warning("oscar","NULL xfer; aborting\n");
+		gaim_xfer_cancel_local(xfer);
+		return;
+	}	
+	if (!(oft_info = xfer->data)) {
+		gaim_debug_warning("oscar","NULL oft_info; aborting\n");
+		gaim_xfer_cancel_local(xfer);
+		return;
+	}
+	
+	if( (oft_info->proxy_info = aim_rv_proxy_read(oft_info->sess, oft_info->conn)) ) {
+		
+		switch(oft_info->proxy_info->cmd_type) {
+			case AIM_RV_PROXY_READY:
+				oscar_xfer_proxylogin_ready(xfer, source);
+				free(oft_info->proxy_info);
+				oft_info->proxy_info = NULL;
+				break;
+			case AIM_RV_PROXY_ACK:
+				oscar_xfer_proxylogin_ack(xfer);
+				free(oft_info->proxy_info);
+				oft_info->proxy_info = NULL;
+				break;
+			case AIM_RV_PROXY_ERROR:
+				gaim_debug_info("oscar","error logging into rendezvous proxy; err code is %x\n",
+					oft_info->proxy_info->err_code);
+				gaim_input_remove(xfer->watcher);
+				xfer->watcher = 0;
+				free(oft_info->proxy_info);
+				oft_info->proxy_info = NULL;
+				gaim_xfer_cancel_remote(xfer);
+				break;
+			/* We should never get here */
+			default:
+				gaim_debug_info("oscar","proxylogin switch defaulted unexpectedly\n");
+		}
+	} else {
+		gaim_debug_info("oscar","could not read rv proxy packet\n");
+	}
+}
+
+/*
+ * Called to send necessary login data to a rendezvous proxy server once we're connected
+ * Takes xfer is data and fd as source
+ */
+static void oscar_xfer_proxylogin(gpointer data, gint source, GaimInputCondition condition)
+{
+	GaimXfer *xfer;
+	struct aim_oft_info *oft_info;
+	struct aim_rv_proxy_info *proxy_info;
+	int err;
+	
+	gaim_debug_info("oscar","AAA - in oscar_xfer_proxylogin\n");
+	if (!(xfer = data)) {
+		gaim_debug_warning("oscar","NULL xfer; aborting\n");
+		gaim_xfer_cancel_local(xfer);
+		return;
+	}	
+	if (!(oft_info = xfer->data)) {
+		gaim_debug_warning("oscar","NULL oft_info; aborting\n");
+		gaim_xfer_cancel_local(xfer);
+		return;
+	}
+	if (!(proxy_info = oft_info->proxy_info)) {
+		gaim_debug_warning("oscar","NULL proxy_info; aborting\n");
+		gaim_xfer_cancel_local(xfer);
+		return;
+	}
+	if(oft_info->success) {
+		gaim_debug_info("oscar","connection already successful, ignoring 2nd conn\n");
+		return;
+	}
+
+	xfer->fd = source;
+	oft_info->conn->fd = source;
+		
+	proxy_info->conn = oft_info->conn;
+	proxy_info->flags = AIM_RV_PROXY_CLIENT_FLAGS;
+	memcpy(proxy_info->cookie, oft_info->cookie, 8);
+	
+	if(oft_info->send_or_recv == AIM_XFER_SEND) {
+		if(oft_info->stage == AIM_XFER_PROXY_STG1 || oft_info->stage == AIM_XFER_PROXY_STG3) {
+			gaim_debug_info("oscar","sending INIT SEND for stage 1/3 rv proxied send\n");
+			if( (err = aim_rv_proxy_init_send(proxy_info)) ) {
+				gaim_xfer_error(GAIM_XFER_SEND, gaim_xfer_get_account(xfer), xfer->who,
+								_("Unable to log into file transfer proxy."));
+				gaim_debug_info("oscar", "error while sending INIT SEND rv proxy packet: %s\n",
+					strerror(err));
+				gaim_xfer_cancel_local(xfer);
+			}
+		} else if(oft_info->stage == AIM_XFER_PROXY_STG2) {
+			gaim_debug_info("oscar","sending INIT RECV for stage 2 rv proxied send\n");
+			if( (err = aim_rv_proxy_init_recv(proxy_info)) ) {
+				gaim_xfer_error(GAIM_XFER_SEND, gaim_xfer_get_account(xfer), xfer->who,
+								_("Unable to log into file transfer proxy."));
+				gaim_debug_info("oscar", "error while sending INIT RECV rv proxy packet: %s\n",
+					strerror(err));
+				gaim_xfer_cancel_local(xfer);
+			}
+		} else {
+			gaim_debug_warning("oscar","no proxy type specified; aborting transfer\n");
+			gaim_xfer_cancel_local(xfer);
+		}
+	} else if(oft_info->send_or_recv == AIM_XFER_RECV) {
+		if(oft_info->stage == AIM_XFER_PROXY_STG2) {
+			gaim_debug_info("oscar","sending INIT SEND for stage 2 rv proxied receive\n");
+			if( (err = aim_rv_proxy_init_send(proxy_info)) ) {
+				gaim_xfer_error(GAIM_XFER_SEND, gaim_xfer_get_account(xfer), xfer->who,
+								_("Unable to log into file transfer proxy."));
+				gaim_debug_info("oscar", "error while sending INIT SEND rv proxy packet: %s\n",
+					strerror(err));
+				gaim_xfer_cancel_local(xfer);
+			}
+		} else if(oft_info->stage == AIM_XFER_PROXY_STG1
+				|| oft_info->stage == AIM_XFER_PROXY_STG3) {
+			gaim_debug_info("oscar","sending INIT RECV for stage 1/3 rv proxied receive\n");
+			if( (err = aim_rv_proxy_init_recv(proxy_info)) ) {
+				gaim_xfer_error(GAIM_XFER_SEND, gaim_xfer_get_account(xfer), xfer->who,
+								_("Unable to log into file transfer proxy."));
+				gaim_debug_info("oscar", "error while sending INIT RECV rv proxy packet: %s\n",
+					strerror(err));
+				gaim_xfer_cancel_local(xfer);
+			}
+		} else {
+			gaim_debug_warning("oscar","no proxy type specified; aborting transfer\n");
+			gaim_xfer_cancel_local(xfer);
+		}
+	} else {
+		gaim_debug_warning("oscar","no send_or_recv value specified; aborting\n");
+		gaim_xfer_cancel_local(xfer);
+	}
+	free(proxy_info);
+	oft_info->proxy_info = NULL;
+	
+	xfer->watcher = gaim_input_add(xfer->fd, GAIM_INPUT_READ, oscar_xfer_proxylogin_cb, xfer);
+}
+
+ 
+ /*
  * xfer functions used when sending files
  */
 
+/*
+ * Send a request to another client notifying them we want to sent a file
+ */
+static void oscar_send_file_request(GaimXfer *xfer)
+{
+ 	struct aim_oft_info *oft_info = xfer->data;
+	GaimConnection *gc = oft_info->sess->aux_data;
+ 	OscarData *od = gc->proto_data;
+	
+	gaim_debug_info("oscar", "AAA - in oscar_send_file_request\n");
+	
+	if (oft_info->conn) {
+		xfer->filename = g_path_get_basename(xfer->local_filename);
+		strncpy(oft_info->fh.name, xfer->filename, 64);
+		oft_info->fh.name[63] = '\0';
+		oft_info->fh.totsize = gaim_xfer_get_size(xfer);
+		oft_info->fh.size = gaim_xfer_get_size(xfer);
+		
+		/* Calculating the checksum can take a very long time for large files */
+		gaim_debug_info("oscar","calculating file checksum\n");
+		oft_info->fh.checksum = aim_oft_checksum_file(xfer->local_filename);
+		gaim_debug_info("oscar","checksum calculated\n");
+
+		memcpy(&oft_info->fh.bcookie, oft_info->cookie, 8);
+
+		aim_im_sendch2_sendfile_ask(od->sess, oft_info);
+		aim_conn_addhandler(od->sess, oft_info->conn, AIM_CB_FAM_OFT,
+			AIM_CB_OFT_ESTABLISHED, oscar_sendfile_estblsh, 0);
+	} else {
+		gaim_xfer_error(GAIM_XFER_SEND, gaim_xfer_get_account(xfer), xfer->who,
+						_("Unable to establish listener socket or no AOL proxy connection present."));
+		gaim_xfer_cancel_local(xfer);
+	}
+}
+
+
+/*
+ * Opens a listener socket in preparation for sending a file
+ * This is not called if we are using a rendezvous proxy server
+ */
 static void oscar_xfer_init_send(GaimXfer *xfer)
 {
 	struct aim_oft_info *oft_info = xfer->data;
@@ -2160,15 +2560,8 @@
 	OscarData *od = gc->proto_data;
 	int listenfd;
 
-	gaim_debug_info("oscar", "AAA - in oscar_xfer_send_init\n");
-
-	xfer->filename = g_path_get_basename(xfer->local_filename);
-	strncpy(oft_info->fh.name, xfer->filename, 64);
-	oft_info->fh.name[63] = '\0';
-	oft_info->fh.totsize = gaim_xfer_get_size(xfer);
-	oft_info->fh.size = gaim_xfer_get_size(xfer);
-	oft_info->fh.checksum = aim_oft_checksum_file(xfer->local_filename);
-
+	gaim_debug_info("oscar", "AAA - in oscar_xfer_init_send\n");
+	
 	/* Create a listening socket and an associated libfaim conn */
 	if ((listenfd = gaim_network_listen_range(5190, 5199)) < 0) {
 		gaim_xfer_cancel_local(xfer);
@@ -2183,17 +2576,19 @@
 	gaim_debug_misc("oscar",
 			   "port is %hu, ip is %s\n",
 			   xfer->local_port, oft_info->clientip);
-	if (oft_info->conn) {
-		xfer->watcher = gaim_input_add(oft_info->conn->fd, GAIM_INPUT_READ, oscar_callback, oft_info->conn);
-		aim_im_sendch2_sendfile_ask(od->sess, oft_info);
-		aim_conn_addhandler(od->sess, oft_info->conn, AIM_CB_FAM_OFT, AIM_CB_OFT_ESTABLISHED, oscar_sendfile_estblsh, 0);
-	} else {
-		gaim_xfer_error(GAIM_XFER_SEND, gaim_xfer_get_account(xfer), xfer->who,
-						_("Unable to establish listener socket."));
-		gaim_xfer_cancel_local(xfer);
-	}
-}
-
+			   
+	if(oft_info->conn)
+		xfer->watcher = gaim_input_add(oft_info->conn->fd, GAIM_INPUT_READ, oscar_callback,
+			oft_info->conn);
+	else
+		gaim_debug_info("oscar","NULL oft_info->conn; not adding watcher\n");
+		
+	oscar_send_file_request(xfer);
+}
+
+/*
+ * "On second thought, you don't deserve this file."
+ */
 static void oscar_xfer_cancel_send(GaimXfer *xfer)
 {
 	struct aim_oft_info *oft_info = xfer->data;
@@ -2205,12 +2600,19 @@
 	if (gaim_xfer_get_status(xfer) != GAIM_XFER_STATUS_CANCEL_REMOTE)
 		aim_im_sendch2_sendfile_cancel(oft_info->sess, oft_info);
 
-	aim_conn_kill(oft_info->sess, &oft_info->conn);
-	aim_oft_destroyinfo(oft_info);
+	/* Added a few sanity checks to prevent segfaulting */
+	if(oft_info) {
+		if(oft_info->sess && oft_info->conn)
+			aim_conn_kill(oft_info->sess, &oft_info->conn);
+		aim_oft_destroyinfo(oft_info);
+	}
 	xfer->data = NULL;
 	od->file_transfers = g_slist_remove(od->file_transfers, xfer);
 }
 
+/*
+ * Called when we send some data to the other client
+ */
 static void oscar_xfer_ack_send(GaimXfer *xfer, const guchar *buffer, size_t size)
 {
 	struct aim_oft_info *oft_info = xfer->data;
@@ -2239,6 +2641,10 @@
 	}
 }
 
+/*
+ * Called by the Gaim core to determine whether or not we're allowed to send a file
+ * to this user.
+ */
 static gboolean oscar_can_receive_file(GaimConnection *gc, const char *who) {
 	gboolean can_receive = FALSE;
 	OscarData *od = gc->proto_data;
@@ -2253,13 +2659,21 @@
 	return can_receive;
 }
 
+/*
+ * Called by the Gaim core when the user indicates that a file is to be sent to
+ * a special someone. 
+ */
 static void oscar_send_file(GaimConnection *gc, const char *who, const char *file) {
-
 	OscarData *od;
 	GaimXfer *xfer;
 	struct aim_oft_info *oft_info;
 	const char *ip;
-
+	gboolean use_rv_proxy;
+
+	use_rv_proxy = gaim_prefs_get_bool("/plugins/prpl/oscar/use_rv_proxy");
+	if(use_rv_proxy) 
+		gaim_debug_info("oscar","using stage 1 proxied transfer\n");
+	
 	od = (OscarData *)gc->proto_data;
 
 	/* You want to send a file to someone else, you're so generous */
@@ -2268,12 +2682,28 @@
 	xfer = gaim_xfer_new(gc->account, GAIM_XFER_SEND, who);
 
 	/* Create the oscar-specific data */
-	ip = gaim_network_get_my_ip(od->conn ? od->conn->fd : -1);
-	oft_info = aim_oft_createinfo(od->sess, NULL, who, ip, 0, 0, 0, NULL);
+	if (use_rv_proxy) {
+		/* This hostname will be resolved by gaim_proxy_connect */
+		xfer->remote_ip = g_strdup(AIM_RV_PROXY_SERVER_URL);
+		xfer->remote_port = AIM_RV_PROXY_CONNECT_PORT;
+		oft_info = aim_oft_createinfo(od->sess, NULL /*cookie*/, who, 0 /*ip*/, 0, 0, 0, NULL,
+			AIM_XFER_SEND, AIM_XFER_PROXY, AIM_XFER_PROXY_STG1);
+		oft_info->proxy_info = aim_rv_proxy_createinfo(oft_info->sess, NULL, 0);
+		/* We must create a cookie before the request is sent
+		* so that it can be sent to the proxy */
+		aim_im_makecookie(oft_info->cookie);
+	} else {
+		ip = gaim_network_get_my_ip(od->conn ? od->conn->fd : -1);
+		oft_info = aim_oft_createinfo(od->sess, NULL, who, ip, 0, 0, 0, NULL,
+			AIM_XFER_SEND, AIM_XFER_DIRECT, AIM_XFER_PROXY_NONE);
+	}
 	xfer->data = oft_info;
 
-	 /* Setup our I/O op functions */
-	gaim_xfer_set_init_fnc(xfer, oscar_xfer_init_send);
+	/* Setup our I/O op functions */
+	if (use_rv_proxy)
+		gaim_xfer_set_init_fnc(xfer, oscar_xfer_init_recv); 
+	else
+		gaim_xfer_set_init_fnc(xfer, oscar_xfer_init_send);
 	gaim_xfer_set_end_fnc(xfer, oscar_xfer_end);
 	gaim_xfer_set_cancel_send_fnc(xfer, oscar_xfer_cancel_send);
 	gaim_xfer_set_request_denied_fnc(xfer, oscar_xfer_cancel_send);
@@ -2356,7 +2786,7 @@
 	} else {
 		gaim_debug_misc("oscar", "Email is NULL\n");
 	}
-
+	
 	gaim_debug_misc("oscar", "BOSIP: %s\n", info->bosip);
 	gaim_debug_info("oscar",
 			   "Closing auth connection...\n");
@@ -3067,7 +3497,7 @@
 	va_start(ap, fr);
 	info = va_arg(ap, aim_userinfo_t *);
 	va_end(ap);
-
+	
 	g_return_val_if_fail(info != NULL, 1);
 	g_return_val_if_fail(info->sn != NULL, 1);
 
@@ -3076,9 +3506,8 @@
 
 	if (info->present & AIM_USERINFO_PRESENT_FLAGS) {
 		if (info->flags & AIM_FLAG_AWAY)
-		  buddy_is_away = TRUE;
-	}
-
+			buddy_is_away = TRUE;
+	}
 	if (info->present & AIM_USERINFO_PRESENT_ICQEXTSTATUS) {
 		type = (info->icqinfo.status << 16);
 		if (!(info->icqinfo.status & AIM_ICQ_STATE_CHAT) &&
@@ -3205,7 +3634,7 @@
 	info = va_arg(ap, aim_userinfo_t *);
 	va_end(ap);
 
-	gaim_prpl_got_user_status(account, info->sn, OSCAR_STATUS_ID_OFFLINE, NULL);
+	gaim_prpl_got_user_status(account, info->sn, OSCAR_STATUS_ID_OFFLINE, NULL);	
 
 	g_hash_table_remove(od->buddyinfo, gaim_normalize(gc->account, info->sn));
 
@@ -3214,9 +3643,10 @@
 
 /* BBB */
 /*
- * This is called after a remote AIM user has connected to us.  We 
- * want to do some voodoo with the socket file descriptors, add a 
- * callback or two, and then send the AIM_CB_OFT_PROMPT.
+ * This is called after a remote AIM user has connected to us.
+ * If not using a rendezvous proxy, then we want to do some
+ * voodoo with the socket file descriptors. Then we always
+ * add a callback or two, and then send the AIM_CB_OFT_PROMPT.
  */
 static int oscar_sendfile_estblsh(aim_session_t *sess, aim_frame_t *fr, ...) {
 	GaimConnection *gc = sess->aux_data;
@@ -3226,39 +3656,63 @@
 	va_list ap;
 	aim_conn_t *conn, *listenerconn;
 
-	gaim_debug_info("oscar",
-			   "AAA - in oscar_sendfile_estblsh\n");
+	gaim_debug_info("oscar", "AAA - in oscar_sendfile_estblsh\n");
+	
 	va_start(ap, fr);
 	conn = va_arg(ap, aim_conn_t *);
 	listenerconn = va_arg(ap, aim_conn_t *);
 	va_end(ap);
 
-	if (!(xfer = oscar_find_xfer_by_conn(od->file_transfers, listenerconn)))
-		return 1;
-
-	if (!(oft_info = xfer->data))
+	/* Finding by conn will work for proxied connections only
+	 * Finding by listenerconn will work for direct connections only */
+	if (!(xfer = oscar_find_xfer_by_conn(od->file_transfers, conn))) {
+		if(!(xfer = oscar_find_xfer_by_conn(od->file_transfers, listenerconn))) {
+			gaim_debug_warning("oscar","xfer not found via connection\n");
+			return 1;
+		}
+	}
+	if (!(oft_info = xfer->data)) {
+		gaim_debug_warning("oscar","NULL data\n");
 		return 1;
-
-	/* Stop watching listener conn; watch transfer conn instead */
-	gaim_input_remove(xfer->watcher);
-	aim_conn_kill(sess, &listenerconn);
-
-	oft_info->conn = conn;
-	xfer->fd = oft_info->conn->fd;
-
-	aim_conn_addhandler(sess, oft_info->conn, AIM_CB_FAM_OFT, AIM_CB_OFT_ACK, oscar_sendfile_ack, 0);
-	aim_conn_addhandler(sess, oft_info->conn, AIM_CB_FAM_OFT, AIM_CB_OFT_DONE, oscar_sendfile_done, 0);
+	}
+		
+	/* Mark connection as success so further connections aren't attempted
+	 * This is important here since some receive file code paths pass through here */
+	oft_info->success = TRUE;
+
+	if(oft_info->method != AIM_XFER_PROXY) {
+		/* Stop watching listener conn; watch transfer conn instead */
+		gaim_input_remove(xfer->watcher);
+		
+		aim_conn_kill(sess, &listenerconn);
+	
+		oft_info->conn = conn;
+		xfer->fd = oft_info->conn->fd;
+	}
+	
 	xfer->watcher = gaim_input_add(oft_info->conn->fd, GAIM_INPUT_READ, oscar_callback, oft_info->conn);
-
-	/* Inform the other user that we are connected and ready to transfer */
-	aim_oft_sendheader(sess, AIM_CB_OFT_PROMPT, oft_info);
-
+	
+	if(oft_info->send_or_recv == AIM_XFER_SEND) {
+		aim_conn_addhandler(oft_info->sess, oft_info->conn, AIM_CB_FAM_OFT, AIM_CB_OFT_ACK,
+			oscar_sendfile_ack, 0);
+		aim_conn_addhandler(oft_info->sess, oft_info->conn, AIM_CB_FAM_OFT, AIM_CB_OFT_DONE,
+			oscar_sendfile_done, 0);
+
+		/* Inform the other user that we are connected and ready to transfer */
+		aim_oft_sendheader(sess, AIM_CB_OFT_PROMPT, oft_info);
+	}
+	
+	/* For a file send, we'll hopefully end up in oscar_sendfile_ack next
+	 * For a file receive, oscar_sendfile_prompt */
+	
 	return 0;
 }
 
 /*
  * This is the gaim callback passed to gaim_proxy_connect when connecting to another AIM 
- * user in order to transfer a file.
+ * user in order to transfer a file
+ * Takes xfer as data and fd as source 
+ * BBB
  */
 static void oscar_sendfile_connected(gpointer data, gint source, GaimInputCondition condition) {
 	GaimXfer *xfer;
@@ -3270,35 +3724,42 @@
 		return;
 	if (!(oft_info = xfer->data))
 		return;
+	if(oft_info->success) {
+		gaim_debug_info("oscar","connection already successful; ignoring 2nd conn\n");
+		return;
+	}		
 	if (source < 0) {
-		/* This will also be called 3 minutes after the verifiedip times out.
-		 * However, we might have made a successful connection with the clientip
-		 * so we need to make sure the verifiedip's failures aren't taken out
-		 * on the poor little clientip, which might actually have been a success. */
-		if(oft_info->success) {
-			gaim_debug_info("oscar","fd of %d for verifiedip, but clientip succeeded; ignoring\n",
-				source);
-			return;
-		} else {
-			gaim_debug_info("oscar","received fd of %d; aborting transfer\n", source);
-			gaim_xfer_cancel_remote(xfer);
-			return;
-		}
-	}
+		gaim_debug_info("oscar","received fd of %d; aborting transfer\n", source);
+		gaim_xfer_cancel_remote(xfer);
+		return;
+	}
+	oft_info->success = TRUE; /* Mark this connection as successful before it times out */
 	
-	gaim_debug_info("oscar","marking connection as success; fd is %d\n", source);
-	oft_info->success = TRUE; /* Mark this connection as successful before it times out */
-
+	/* We might have already set these in oscar_sendfile_proxylogin, but it won't
+	 * hurt to do it again since it is rather necessary */
 	xfer->fd = source;
 	oft_info->conn->fd = source;
-
+	
 	aim_conn_completeconnect(oft_info->sess, oft_info->conn);
+	
 	xfer->watcher = gaim_input_add(xfer->fd, GAIM_INPUT_READ, oscar_callback, oft_info->conn);
 
-	/* Inform the other user that we are connected and ready to transfer */
-	aim_im_sendch2_sendfile_accept(oft_info->sess, oft_info);
-
-	return;
+	/* Inform the other user that we are connected and accept the transfer
+	 * Except for a stage 2 receive, then we'll be the ones receiving this accept message */
+	if(oft_info->stage != AIM_XFER_PROXY_STG2)
+		aim_im_sendch2_sendfile_accept(oft_info->sess, oft_info);
+		
+	/* Don't wait around if this is a redirected send */
+	if(oft_info->send_or_recv == AIM_XFER_SEND) {
+		/* We should only get here if this is a redirected file send */
+		aim_conn_addhandler(oft_info->sess, oft_info->conn, AIM_CB_FAM_OFT, AIM_CB_OFT_ACK,
+			oscar_sendfile_ack, 0);
+		aim_conn_addhandler(oft_info->sess, oft_info->conn, AIM_CB_FAM_OFT, AIM_CB_OFT_DONE,
+			oscar_sendfile_done, 0);
+		
+		/* Inform the other user that we are ready to transfer */
+		aim_oft_sendheader(oft_info->sess, AIM_CB_OFT_PROMPT, oft_info);
+	}
 }
 
 /*
@@ -3316,9 +3777,10 @@
 	aim_conn_t *conn;
 	fu8_t *cookie;
 	struct aim_fileheader_t *fh;
-
+	
 	gaim_debug_info("oscar",
 			   "AAA - in oscar_sendfile_prompt\n");
+			   
 	va_start(ap, fr);
 	conn = va_arg(ap, aim_conn_t *);
 	cookie = va_arg(ap, fu8_t *);
@@ -3392,6 +3854,7 @@
 	aim_conn_t *conn;
 	fu8_t *cookie;
 	struct aim_fileheader_t *fh;
+	struct aim_oft_info *oft_info;
 
 	gaim_debug_info("oscar", "AAA - in oscar_sendfile_done\n");
 	va_start(ap, fr);
@@ -3400,8 +3863,16 @@
 	fh = va_arg(ap, struct aim_fileheader_t *);
 	va_end(ap);
 
-	if (!(xfer = oscar_find_xfer_by_conn(od->file_transfers, conn)))
+	if (!(xfer = oscar_find_xfer_by_conn(od->file_transfers, conn))) {
+		gaim_debug_warning("oscar","xfer not found\n");
 		return 1;
+	}
+	if(!(oft_info = xfer->data)) {
+		gaim_debug_warning("oscar","NULL oft_info\n");
+		return 1;
+	}
+	if(fh->nrecvd == fh->size)
+		gaim_xfer_set_completed(xfer, TRUE);
 
 	xfer->fd = conn->fd;
 	gaim_xfer_end(xfer);
@@ -3420,7 +3891,7 @@
 	gchar *tmp;
 	aim_mpmsg_section_t *curpart;
 
-	gaim_debug_misc("oscar", "Received IM from %s with %d parts\n",
+	gaim_debug_misc("oscar", "Recived IM from %s with %d parts\n",
 					userinfo->sn, args->mpmsg.numparts);
 
 	if (args->mpmsg.numparts == 0)
@@ -3484,7 +3955,7 @@
 	curpart = args->mpmsg.parts;
 	while (curpart != NULL) {
 		tmp = gaim_plugin_oscar_decode_im_part(account, userinfo->sn, curpart->charset,
-				curpart->charsubset, curpart->data, curpart->datalen);
+-				curpart->charsubset, curpart->data, curpart->datalen);
 		if (tmp != NULL) {
 			g_string_append(message, tmp);
 			g_free(tmp);
@@ -3576,21 +4047,25 @@
 			g_free(name);
 	} else if (args->reqclass & AIM_CAPS_SENDFILE) {
 /* BBB */
-		if (args->status == AIM_RENDEZVOUS_PROPOSE) {
+		/* This is the first sendfile request where we need to notify the user that someone
+		 * wants to send a file */
+		if (args->status == AIM_RENDEZVOUS_PROPOSE
+			&& (args->info.sendfile.reqnum == 0x0001)) {
 			/* Someone wants to send a file (or files) to us */
 			GaimXfer *xfer;
 			struct aim_oft_info *oft_info;
-
-			if (!args->cookie || !args->port || !args->verifiedip || 
+			struct aim_rv_proxy_info *proxy_info = NULL;
+			gboolean use_rv_proxy;
+			int proxy_stage;
+			int xfer_method;
+			const char *proxy_ip = NULL;
+			
+			if (!args->cookie || !args->port ||
 			    !args->info.sendfile.filename || !args->info.sendfile.totsize || 
 			    !args->info.sendfile.totfiles || !args->reqclass) {
 				gaim_debug_warning("oscar",
 						   "%s tried to send you a file with incomplete "
 						   "information.\n", userinfo->sn);
-				if (args->proxyip)
-					gaim_debug_warning("oscar",
-							   "IP for a proxy server was given.  Gaim "
-							   "does not support this yet.\n");
 				g_free(message);
 				return 1;
 			}
@@ -3610,9 +4085,37 @@
 
 			/* Build the file transfer handle */
 			xfer = gaim_xfer_new(gc->account, GAIM_XFER_RECEIVE, userinfo->sn);
-			xfer->remote_ip = g_strdup(args->verifiedip);
-			xfer->remote_port = args->port;
-
+			
+			use_rv_proxy = gaim_prefs_get_bool("/plugins/prpl/oscar/use_rv_proxy");
+			
+			if(args->info.sendfile.use_proxy) {
+				/* The sender requested (stage 1) that we use a rendezvous proxy */
+				xfer_method = AIM_XFER_PROXY;
+				proxy_stage = AIM_XFER_PROXY_STG1;
+				gaim_debug_info("oscar","using stage 1 proxy with ip: %s\n",
+					args->proxyip, args->port);
+				xfer->remote_ip = g_strdup(args->proxyip);
+				xfer->remote_port = AIM_RV_PROXY_CONNECT_PORT;
+				proxy_info = aim_rv_proxy_createinfo(od->sess, args->cookie, args->port);
+			} else if(use_rv_proxy) {
+				/* If the local user indicated that a rendezvous proxy is necessary
+				 * start a stage 2 proxied transfer */
+				gaim_debug_info("oscar","using stage 2 proxied transfer\n");
+				xfer_method = AIM_XFER_PROXY;
+				proxy_stage = AIM_XFER_PROXY_STG2;
+				/* This hostname will be resolved by gaim_proxy_connect */
+				xfer->remote_ip = g_strdup(AIM_RV_PROXY_SERVER_URL);
+				xfer->remote_port = AIM_RV_PROXY_CONNECT_PORT;
+				proxy_info = aim_rv_proxy_createinfo(od->sess, args->cookie, 0);
+			} else {
+				/* We are receiving a file directly with no rendezvous proxy */
+				xfer_method = AIM_XFER_DIRECT;
+				proxy_stage = AIM_XFER_PROXY_NONE;
+				xfer->remote_ip = g_strdup(args->clientip);
+				xfer->remote_port = args->port;
+			}
+			
+			/* Use UTF8 so that the world will be a happier place */
 			if (g_utf8_validate(args->info.sendfile.filename, -1,
 						NULL)) {
 				gaim_xfer_set_filename(xfer,
@@ -3626,20 +4129,30 @@
 
 			gaim_xfer_set_size(xfer, args->info.sendfile.totsize);
 			
-			/* Ignore <ICQ_COOL_FT> XML that is sent along with ICQ sendfile requests */
-			if(g_ascii_strncasecmp(message,"<ICQ_COOL_FT>",13)) {
-				gaim_debug_info("oscar","Ignoring ICQ file transfer message: %s\n", message);
+			/* Ignore messages that start with <ICQ_COOL_FT> (XML that is sent along
+			 * with ICQ sendfile requests) & <HTML> message that is sent with AOL file
+			 * transfers (Note: this latter message is ignored only if whole message
+			 * is <HTML>, but not if it starts with <HTML> */
+			if(message && ( g_ascii_strncasecmp(message,"<ICQ_COOL_FT>",13) < 0
+				|| g_ascii_strcasecmp(message,"<HTML>") == 0) ) {
+				gaim_debug_info("oscar","Ignoring file transfer message: %s\n", message);
 				g_free(message);
 				message = NULL;
 			}
 			gaim_xfer_set_message(xfer, message);
 
 			/* Create the oscar-specific data */
-			oft_info = aim_oft_createinfo(od->sess, args->cookie, userinfo->sn, args->clientip, xfer->remote_port, 0, 0, NULL);
-			if (args->proxyip)
-				oft_info->proxyip = g_strdup(args->proxyip);
-			if (args->verifiedip)
-				oft_info->verifiedip = g_strdup(args->verifiedip);
+			oft_info = aim_oft_createinfo(od->sess, args->cookie, userinfo->sn, args->clientip,
+				xfer->remote_port, 0, 0, NULL, AIM_XFER_RECV, xfer_method, proxy_stage);
+			if(proxy_stage == AIM_XFER_PROXY_STG2 && proxy_ip) {
+				oft_info->proxyip = g_strdup(proxy_ip);
+			} else {
+				if (args->proxyip)
+					oft_info->proxyip = g_strdup(args->proxyip);
+				if (args->verifiedip)
+					oft_info->verifiedip = g_strdup(args->verifiedip);
+			}
+			oft_info->proxy_info = proxy_info;
 			xfer->data = oft_info;
 
 			 /* Setup our I/O op functions */
@@ -3651,9 +4164,116 @@
 
 			/* Keep track of this transfer for later */
 			od->file_transfers = g_slist_append(od->file_transfers, xfer);
-
+			
 			/* Now perform the request */
 			gaim_xfer_request(xfer);
+		/* A secondary request has been sent to negotiate the connection method */
+		} else if (args->status == AIM_RENDEZVOUS_PROPOSE && args->info.sendfile.reqnum == 0x0002) {
+			/* We have asked to send a file to someone else, but they sent us a reply request
+			 * asking us to use an alternative method of connecting */
+			GaimXfer *xfer;
+			struct aim_oft_info *oft_info;
+			
+			if ((xfer = oscar_find_xfer_by_cookie(od->file_transfers, args->cookie))) {
+				oft_info = xfer->data;
+				
+				/* Stop the listener connection */
+				gaim_input_remove(xfer->watcher);
+				aim_conn_kill(sess, &oft_info->conn); /* This is currently the listener */
+
+				if(args->info.sendfile.use_proxy) {
+					gaim_debug_info("oscar",
+						"received request for stage 2 rv proxy with ip: %s\n",
+						args->proxyip);
+					oft_info->method = AIM_XFER_PROXY;
+					oft_info->stage = AIM_XFER_PROXY_STG2;
+										
+					oft_info->proxy_info = aim_rv_proxy_createinfo(oft_info->sess,
+						args->cookie, args->port);
+					if(args->proxyip) {
+						if(xfer->remote_ip)
+							g_free(xfer->remote_ip);
+						xfer->remote_ip = g_strdup(args->proxyip);
+						xfer->remote_port = AIM_RV_PROXY_CONNECT_PORT;
+						oscar_xfer_init_recv(xfer);
+					} else {
+						gaim_debug_warning("oscar",
+							"stage 2 rv proxy file send: no proxy ip specified\n");
+					}
+				} else if(args->clientip
+						&& g_ascii_strcasecmp(args->clientip,"0.0.0.0") == 0)
+				{
+					gaim_debug_warning("oscar",
+						"other client wants us to send stage 3 proxy info\n");
+					oft_info->method = AIM_XFER_PROXY;
+					oft_info->stage = AIM_XFER_PROXY_STG3;
+					
+					/* Clean useless data from oft_info */
+					oft_info->clientip = NULL;
+					oft_info->verifiedip = NULL;
+					
+					/* This hostname will be resolved in gaim_proxy_connect */
+					xfer->remote_ip = g_strdup(AIM_RV_PROXY_SERVER_URL);
+					xfer->remote_port = AIM_RV_PROXY_CONNECT_PORT;
+					
+					oft_info->proxy_info
+						= aim_rv_proxy_createinfo(od->sess, args->cookie, 0);
+					oscar_xfer_init_recv(xfer);
+				} else {
+					gaim_debug_info("oscar","received request to redirect transfer; clientip/verifiedip: %s / %s\n",
+						args->clientip, args->verifiedip);
+					oft_info->method = AIM_XFER_REDIR;
+					if (args->verifiedip)
+						oft_info->verifiedip = g_strdup(args->verifiedip);
+					if (args->clientip) {
+						oft_info->clientip = g_strdup(args->clientip);
+						xfer->remote_ip = g_strdup(args->clientip);
+					}
+					xfer->remote_port = args->port;
+						
+					/* This file send will briefly follow file receive codepaths */
+					oscar_xfer_init_recv(xfer);
+				}
+			} else {
+				gaim_debug_warning("oscar","received file tranfer reply request: xfer not found\n");
+			}
+		/* A THIRD request has been sent trying to figure out what connection method will be used
+		 * to transfer this file */
+		} else if (args->status == AIM_RENDEZVOUS_PROPOSE && args->info.sendfile.reqnum == 0x0003) {
+			/* We are receiving a file from someone. We sent a request to use a stage 3
+			 * proxy. They did the initial proxy login and have sent us the info in a
+			 * third file transfer request. */
+			GaimXfer *xfer;
+			struct aim_oft_info *oft_info;
+			
+			if ((xfer = oscar_find_xfer_by_cookie(od->file_transfers, args->cookie))) {
+				oft_info = xfer->data;
+
+				/* We are receiving a file */
+				gaim_debug_info("oscar",
+					"other client sent us stage 3 proxy info\n");
+				
+				/* The following pieces of information should already have
+				 * been set in oscar_xfer_ip_timeout, but we'll list them
+				 * again just for clarity. */
+				oft_info->method = AIM_XFER_PROXY;
+				oft_info->stage = AIM_XFER_PROXY_STG3;
+				
+				oft_info->proxy_info = aim_rv_proxy_createinfo(oft_info->sess,
+					args->cookie, args->port);
+				if(args->proxyip) {
+					if(xfer->remote_ip)
+						g_free(xfer->remote_ip);
+					xfer->remote_ip = g_strdup(args->proxyip);
+					xfer->remote_port = AIM_RV_PROXY_CONNECT_PORT;
+					oscar_xfer_init_recv(xfer);
+				} else {
+					gaim_debug_warning("oscar",
+						"stage 3 rv proxy file receive: no proxy ip specified\n");
+				}
+			} else {
+				gaim_debug_warning("oscar","received file tranfer reply request: xfer not found\n");
+			}
 		} else if (args->status == AIM_RENDEZVOUS_CANCEL) {
 			/* The other user wants to cancel a file transfer */
 			GaimXfer *xfer;
@@ -3730,8 +4350,8 @@
 							  "the two computers and is necessary for IM "
 							  "Images.  Because your IP address will be "
 							  "revealed, this may be considered a privacy "
-							  "risk."),
-							GAIM_DEFAULT_ACTION_NONE, d, 2,
+							    "risk."),
+-							GAIM_DEFAULT_ACTION_NONE, d, 2,
 							_("Connect"), G_CALLBACK(accept_direct_im_request),
 							_("Cancel"), G_CALLBACK(destroy_direct_im_request));
 							/* FIXME: we should actually send a packet on cancel */
@@ -3961,9 +4581,8 @@
 				else
 					reason = g_strdup(_("No reason given."));
 
-				dialog_msg = g_strdup_printf(
-													_("The user %u wants to add %s to their buddy list for the following reason:\n%s"), 
-													args->uin, gaim_account_get_username(gc->account), reason);
+				dialog_msg = g_strdup_printf(_("The user %u wants to add %s to their buddy list for the following reason:\n%s"),
+					args->uin, gaim_account_get_username(gc->account), reason);
 				g_free(reason);
 				gaim_debug_info("oscar",
 						   "Received an authorization request from UIN %u\n",
@@ -4395,7 +5014,7 @@
 
 	if (destn == NULL)
 		return 1;
-
+	
 	buf = g_strdup_printf(_("User information not available: %s"), (reason < msgerrreasonlen) ? _(msgerrreason[reason]) : _("Unknown reason."));
 	if (!gaim_conv_present_error(destn, gaim_connection_get_account((GaimConnection*)sess->aux_data), buf)) {
 		g_free(buf);
@@ -4730,7 +5349,7 @@
 			gaim_notify_emails(gc, emailinfo->nummsgs, FALSE, NULL, NULL, (const char **)&to, (const char **)&emailinfo->url, NULL, NULL);
 		g_free(to);
 	}
-
+	
 	if (alertitle)
 		gaim_debug_misc("oscar", "Got an alert '%s' %s\n", alertitle, alerturl ? alerturl : "");
 
@@ -7707,7 +8326,6 @@
 	}
 }
 
-#if USE_PRPL_PREFERENCES
 static GaimPluginPrefFrame *
 get_plugin_pref_frame(GaimPlugin *plugin)
 {
@@ -7716,15 +8334,24 @@
 
 	frame = gaim_plugin_pref_frame_new();
 
+#if USE_PRPL_PREFERENCES
 	ppref = gaim_plugin_pref_new_with_name_and_label("/plugins/prpl/oscar/recent_buddies", _("Use recent buddies group"));
 	gaim_plugin_pref_frame_add(frame, ppref);
 
 	ppref = gaim_plugin_pref_new_with_name_and_label("/plugins/prpl/oscar/show_idle", _("Show how long you have been idle"));
 	gaim_plugin_pref_frame_add(frame, ppref);
+#endif
+	
+	ppref = gaim_plugin_pref_new_with_label(_("File Transfers"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	ppref = gaim_plugin_pref_new_with_name_and_label(
+			    "/plugins/prpl/oscar/use_rv_proxy",
+			    _("Use AIM/ICQ proxy server (Slower/More Secure/Usually Works)"));
+	gaim_plugin_pref_frame_add(frame, ppref);
 
 	return frame;
 }
-#endif
 
 static const char *
 oscar_normalize(const GaimAccount *account, const char *str)
@@ -7815,11 +8442,9 @@
 	oscar_send_file			/* send_file */
 };
 
-#if USE_PRPL_PREFERENCES
 static GaimPluginUiInfo prefs_info = {
 	get_plugin_pref_frame
 };
-#endif
 
 static GaimPluginInfo info =
 {
@@ -7848,11 +8473,7 @@
 
 	NULL,                                             /**< ui_info        */
 	&prpl_info,                                       /**< extra_info     */
-#if USE_PRPL_PREFERENCES
-	NULL,                                             /**< prefs_info     */
-#else
-	NULL,                                             /**< prefs_info     */
-#endif
+	&prefs_info,
 	oscar_actions
 };
 
@@ -7870,9 +8491,11 @@
 	option = gaim_account_option_string_new(_("Encoding"), "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
+	/* Preferences */
 	gaim_prefs_add_none("/plugins/prpl/oscar");
 	gaim_prefs_add_bool("/plugins/prpl/oscar/recent_buddies", FALSE);
 	gaim_prefs_add_bool("/plugins/prpl/oscar/show_idle", FALSE);
+	gaim_prefs_add_bool("/plugins/prpl/oscar/use_rv_proxy", FALSE);
 }
 
 GAIM_INIT_PLUGIN(oscar, init_plugin, info);
--- a/src/protocols/oscar/txqueue.c	Tue Aug 30 05:21:30 2005 +0000
+++ b/src/protocols/oscar/txqueue.c	Tue Aug 30 05:21:58 2005 +0000
@@ -213,7 +213,7 @@
 	return cur;
 }
 
-static int aim_bstream_send(aim_bstream_t *bs, aim_conn_t *conn, size_t count)
+int aim_bstream_send(aim_bstream_t *bs, aim_conn_t *conn, size_t count)
 {
 	int wrote = 0;