changeset 13200:33bef17125c2

[gaim-migrate @ 15563] This is the soon-to-be-infamous nonblocking network activity patch that I've been working on. Feel free to yell at me if this makes you unhappy. committer: Tailor Script <tailor@pidgin.im>
author Daniel Atallah <daniel.atallah@gmail.com>
date Thu, 09 Feb 2006 04:17:56 +0000
parents d8f238864c88
children 6ff5271afedc
files plugins/ChangeLog.API plugins/ssl/ssl-gnutls.c plugins/ssl/ssl-nss.c src/Makefile.am src/Makefile.mingw src/gaim_buffer.c src/gaim_buffer.h src/protocols/irc/dcc_send.c src/protocols/irc/irc.c src/protocols/irc/irc.h src/protocols/jabber/jabber.c src/protocols/jabber/jabber.h src/protocols/jabber/oob.c src/protocols/jabber/si.c src/protocols/msn/directconn.c src/protocols/msn/httpconn.c src/protocols/msn/httpconn.h src/protocols/msn/nexus.c src/protocols/msn/nexus.h src/protocols/msn/servconn.c src/protocols/msn/servconn.h src/protocols/napster/napster.c src/protocols/oscar/oscar.c src/protocols/simple/simple.c src/protocols/simple/simple.h src/protocols/yahoo/yahoo.c src/protocols/yahoo/yahoo.h src/protocols/yahoo/yahoo_filexfer.c src/protocols/yahoo/yahoo_packet.c src/protocols/yahoo/yahoo_packet.h src/protocols/yahoo/yahoo_picture.c src/protocols/yahoo/yahoochat.c src/protocols/yahoo/ycht.c src/protocols/yahoo/ycht.h src/proxy.c src/upnp.c src/util.c src/win32/libc_interface.c src/win32/wgaimerror.h
diffstat 39 files changed, 2592 insertions(+), 1079 deletions(-) [+]
line wrap: on
line diff
--- a/plugins/ChangeLog.API	Thu Feb 09 04:14:54 2006 +0000
+++ b/plugins/ChangeLog.API	Thu Feb 09 04:17:56 2006 +0000
@@ -109,6 +109,9 @@
 	* gaim_plugin_pref_add_choice(): label is now const char *
 	* struct proto_chat_entry: label is now const char *
 	* struct proto_chat_entry: identifier is now const char *
+	* All network activity has been updated to use non-blocking sockets.
+	  This means that plugins must be updated to expect such a socket from
+	  gaim_proxy_connect() and gaim_network_listen*().
 
 	Removed:
 	* gaim_gtk_sound_{get,set}_mute() (replaced by the /gaim/gtk/sound/mute
--- a/plugins/ssl/ssl-gnutls.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/plugins/ssl/ssl-gnutls.c	Thu Feb 09 04:17:56 2006 +0000
@@ -34,7 +34,7 @@
 typedef struct
 {
 	gnutls_session session;
-
+	guint handshake_handler;
 } GaimSslGnutlsData;
 
 #define GAIM_SSL_GNUTLS_DATA(gsc) ((GaimSslGnutlsData *)gsc->private_data)
@@ -48,7 +48,7 @@
 
 	gnutls_certificate_allocate_credentials(&xcred);
 	gnutls_certificate_set_x509_trust_file(xcred, "ca.pem",
-										   GNUTLS_X509_FMT_PEM);
+		GNUTLS_X509_FMT_PEM);
 }
 
 static gboolean
@@ -65,15 +65,48 @@
 	gnutls_certificate_free_credentials(xcred);
 }
 
+
+static void ssl_gnutls_handshake_cb(gpointer data, gint source,
+		GaimInputCondition cond)
+{
+	GaimSslConnection *gsc = data;
+	GaimSslGnutlsData *gnutls_data = GAIM_SSL_GNUTLS_DATA(gsc);
+	ssize_t ret;
+
+	gaim_debug_info("gnutls", "Handshaking\n");
+	ret = gnutls_handshake(gnutls_data->session);
+
+	if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
+		return;
+
+	gaim_input_remove(gnutls_data->handshake_handler);
+	gnutls_data->handshake_handler = 0;
+
+	if(ret != 0) {
+		gaim_debug_error("gnutls", "Handshake failed. Error %d\n", ret);
+
+		if(gsc->error_cb != NULL)
+			gsc->error_cb(gsc, GAIM_SSL_HANDSHAKE_FAILED,
+				gsc->connect_cb_data);
+
+		gaim_ssl_close(gsc);
+	} else {
+		gaim_debug_info("gnutls", "Handshake complete\n");
+
+		gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
+	}
+
+}
+
+
 static void
 ssl_gnutls_connect_cb(gpointer data, gint source, GaimInputCondition cond)
 {
 	GaimSslConnection *gsc = (GaimSslConnection *)data;
 	GaimSslGnutlsData *gnutls_data;
 	static const int cert_type_priority[2] = { GNUTLS_CRT_X509, 0 };
-	int ret;
 
-	if (source < 0) {
+	if(source < 0) {
 		if(gsc->error_cb != NULL)
 			gsc->error_cb(gsc, GAIM_SSL_CONNECT_FAILED, gsc->connect_cb_data);
 
@@ -90,37 +123,17 @@
 	gnutls_set_default_priority(gnutls_data->session);
 
 	gnutls_certificate_type_set_priority(gnutls_data->session,
-										 cert_type_priority);
+		cert_type_priority);
 
 	gnutls_credentials_set(gnutls_data->session, GNUTLS_CRD_CERTIFICATE,
-						   xcred);
+		xcred);
 
 	gnutls_transport_set_ptr(gnutls_data->session, GINT_TO_POINTER(source));
 
-
-	do
-	{
-		gaim_debug_info("gnutls", "Handshaking\n");
-		ret = gnutls_handshake(gnutls_data->session);
-	}
-	while ((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED));
-
-	if (ret < 0)
-	{
-		gaim_debug_error("gnutls", "Handshake failed. Error %d\n", ret);
+	gnutls_data->handshake_handler = gaim_input_add(gsc->fd,
+		GAIM_INPUT_READ, ssl_gnutls_handshake_cb, gsc);
 
-		if (gsc->error_cb != NULL)
-			gsc->error_cb(gsc, GAIM_SSL_HANDSHAKE_FAILED,
-						  gsc->connect_cb_data);
-
-		gaim_ssl_close(gsc);
-	}
-	else
-	{
-		gaim_debug_info("gnutls", "Handshake complete\n");
-
-		gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
-	}
+	ssl_gnutls_handshake_cb(gsc, gsc->fd, GAIM_INPUT_READ);
 }
 
 static void
@@ -131,27 +144,29 @@
 	if(!gnutls_data)
 		return;
 
+	if(gnutls_data->handshake_handler)
+		gaim_input_remove(gnutls_data->handshake_handler);
+
 	gnutls_bye(gnutls_data->session, GNUTLS_SHUT_RDWR);
 
 	gnutls_deinit(gnutls_data->session);
 
 	g_free(gnutls_data);
+	gsc->private_data = NULL;
 }
 
 static size_t
 ssl_gnutls_read(GaimSslConnection *gsc, void *data, size_t len)
 {
 	GaimSslGnutlsData *gnutls_data = GAIM_SSL_GNUTLS_DATA(gsc);
-	int s;
+	ssize_t s;
+
+	s = gnutls_record_recv(gnutls_data->session, data, len);
 
-	do
-	{
-		s = gnutls_record_recv(gnutls_data->session, data, len);
-	}
-	while ((s == GNUTLS_E_AGAIN) || (s == GNUTLS_E_INTERRUPTED));
-
-	if (s < 0)
-	{
+	if(s == GNUTLS_E_AGAIN || s == GNUTLS_E_INTERRUPTED) {
+		s = -1;
+		errno = EAGAIN;
+	} else if(s < 0) {
 		gaim_debug_error("gnutls", "receive failed: %d\n", s);
 		s = 0;
 	}
@@ -163,11 +178,20 @@
 ssl_gnutls_write(GaimSslConnection *gsc, const void *data, size_t len)
 {
 	GaimSslGnutlsData *gnutls_data = GAIM_SSL_GNUTLS_DATA(gsc);
-	size_t s = 0;
+	ssize_t s = 0;
 
+	/* XXX: when will gnutls_data be NULL? */
 	if(gnutls_data)
 		s = gnutls_record_send(gnutls_data->session, data, len);
 
+	if(s == GNUTLS_E_AGAIN || s == GNUTLS_E_INTERRUPTED) {
+		s = -1;
+		errno = EAGAIN;
+	} else if(s < 0) {
+		gaim_debug_error("gnutls", "send failed: %d\n", s);
+		s = 0;
+	}
+
 	return s;
 }
 
@@ -187,7 +211,7 @@
 plugin_load(GaimPlugin *plugin)
 {
 #ifdef HAVE_GNUTLS
-	if (!gaim_ssl_get_ops()) {
+	if(!gaim_ssl_get_ops()) {
 		gaim_ssl_set_ops(&ssl_ops);
 	}
 
@@ -204,7 +228,7 @@
 plugin_unload(GaimPlugin *plugin)
 {
 #ifdef HAVE_GNUTLS
-	if (gaim_ssl_get_ops() == &ssl_ops) {
+	if(gaim_ssl_get_ops() == &ssl_ops) {
 		gaim_ssl_set_ops(NULL);
 	}
 #endif
--- a/plugins/ssl/ssl-nss.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/plugins/ssl/ssl-nss.c	Thu Feb 09 04:17:56 2006 +0000
@@ -46,6 +46,7 @@
 {
 	PRFileDesc *fd;
 	PRFileDesc *in;
+	guint handshake_handler;
 
 } GaimSslNssData;
 
@@ -54,6 +55,54 @@
 static const PRIOMethods *_nss_methods = NULL;
 static PRDescIdentity _identity;
 
+/* Thank you, Evolution */
+static void
+set_errno(int code)
+{
+	/* FIXME: this should handle more. */
+	switch (code) {
+	case PR_INVALID_ARGUMENT_ERROR:
+		errno = EINVAL;
+		break;
+	case PR_PENDING_INTERRUPT_ERROR:
+		errno = EINTR;
+		break;
+	case PR_IO_PENDING_ERROR:
+		errno = EAGAIN;
+		break;
+	case PR_WOULD_BLOCK_ERROR:
+		errno = EAGAIN;
+		/*errno = EWOULDBLOCK; */
+		break;
+	case PR_IN_PROGRESS_ERROR:
+		errno = EINPROGRESS;
+		break;
+	case PR_ALREADY_INITIATED_ERROR:
+		errno = EALREADY;
+		break;
+	case PR_NETWORK_UNREACHABLE_ERROR:
+		errno = EHOSTUNREACH;
+		break;
+	case PR_CONNECT_REFUSED_ERROR:
+		errno = ECONNREFUSED;
+		break;
+	case PR_CONNECT_TIMEOUT_ERROR:
+	case PR_IO_TIMEOUT_ERROR:
+		errno = ETIMEDOUT;
+		break;
+	case PR_NOT_CONNECTED_ERROR:
+		errno = ENOTCONN;
+		break;
+	case PR_CONNECT_RESET_ERROR:
+		errno = ECONNRESET;
+		break;
+	case PR_IO_ERROR:
+	default:
+		errno = EIO;
+		break;
+	}
+}
+
 static void
 ssl_nss_init_nss(void)
 {
@@ -158,6 +207,36 @@
 }
 
 static void
+ssl_nss_handshake_cb(gpointer data, int fd, GaimInputCondition cond)
+{
+	GaimSslConnection *gsc = (GaimSslConnection *)data;
+	GaimSslNssData *nss_data = gsc->private_data;
+
+	/* I don't think this the best way to do this...
+	 * It seems to work because it'll eventually use the cached value
+	 */
+	if(SSL_ForceHandshake(nss_data->in) != SECSuccess) {
+		set_errno(PR_GetError());
+		if (errno == EAGAIN || errno == EWOULDBLOCK)
+			return;
+
+		gaim_debug_error("nss", "Handshake failed %u\n", PR_GetError());
+
+		if (gsc->error_cb != NULL)
+			gsc->error_cb(gsc, GAIM_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data);
+
+		gaim_ssl_close(gsc);
+
+		return;
+	}
+
+	gaim_input_remove(nss_data->handshake_handler);
+	nss_data->handshake_handler = 0;
+
+	gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
+}
+
+static void
 ssl_nss_connect_cb(gpointer data, gint source, GaimInputCondition cond)
 {
 	GaimSslConnection *gsc = (GaimSslConnection *)data;
@@ -183,9 +262,10 @@
 	}
 
 	socket_opt.option = PR_SockOpt_Nonblocking;
-	socket_opt.value.non_blocking = PR_FALSE;
+	socket_opt.value.non_blocking = PR_TRUE;
 
-	PR_SetSocketOption(nss_data->fd, &socket_opt);
+	if (PR_SetSocketOption(nss_data->fd, &socket_opt) != PR_SUCCESS)
+		gaim_debug_warning("nss", "unable to set socket into non-blocking mode: %u\n", PR_GetError());
 
 	nss_data->in = SSL_ImportFD(NULL, nss_data->fd);
 
@@ -212,21 +292,17 @@
 	if(gsc->host)
 		SSL_SetURL(nss_data->in, gsc->host);
 
+#if 0 /* This seems like it'd the be the correct way to implement the nonblocking stuff,
+	 but it doesn't seem to work */
+	SSL_HandshakeCallback(nss_data->in,
+		(SSLHandshakeCallback) ssl_nss_handshake_cb, gsc);
+#endif
 	SSL_ResetHandshake(nss_data->in, PR_FALSE);
 
-	if (SSL_ForceHandshake(nss_data->in))
-	{
-		gaim_debug_error("nss", "Handshake failed\n");
-
-		if (gsc->error_cb != NULL)
-			gsc->error_cb(gsc, GAIM_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data);
+	nss_data->handshake_handler = gaim_input_add(gsc->fd,
+		GAIM_INPUT_READ, ssl_nss_handshake_cb, gsc);
 
-		gaim_ssl_close(gsc);
-
-		return;
-	}
-
-	gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
+	ssl_nss_handshake_cb(gsc, gsc->fd, GAIM_INPUT_READ);
 }
 
 static void
@@ -240,26 +316,42 @@
 	if (nss_data->in) PR_Close(nss_data->in);
 	/* if (nss_data->fd) PR_Close(nss_data->fd); */
 
+	if (nss_data->handshake_handler)
+		gaim_input_remove(nss_data->handshake_handler);
+
 	g_free(nss_data);
+	gsc->private_data = NULL;
 }
 
 static size_t
 ssl_nss_read(GaimSslConnection *gsc, void *data, size_t len)
 {
+	ssize_t ret;
 	GaimSslNssData *nss_data = GAIM_SSL_NSS_DATA(gsc);
 
-	return PR_Read(nss_data->in, data, len);
+	ret = PR_Read(nss_data->in, data, len);
+
+	if (ret == -1)
+		set_errno(PR_GetError());
+
+	return ret;
 }
 
 static size_t
 ssl_nss_write(GaimSslConnection *gsc, const void *data, size_t len)
 {
+	ssize_t ret;
 	GaimSslNssData *nss_data = GAIM_SSL_NSS_DATA(gsc);
 
 	if(!nss_data)
 		return 0;
 
-	return PR_Write(nss_data->in, data, len);
+	ret = PR_Write(nss_data->in, data, len);
+
+	if (ret == -1)
+		set_errno(PR_GetError());
+
+	return ret;
 }
 
 static GaimSslOps ssl_ops =
--- a/src/Makefile.am	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/Makefile.am	Thu Feb 09 04:17:56 2006 +0000
@@ -80,6 +80,7 @@
 	desktopitem.c \
 	eventloop.c \
 	ft.c \
+	gaim_buffer.c \
 	idle.c \
 	imgstore.c \
 	log.c \
@@ -128,6 +129,7 @@
 	desktopitem.h \
 	eventloop.h \
 	ft.h \
+	gaim_buffer.h \
 	idle.h \
 	imgstore.h \
 	log.h \
--- a/src/Makefile.mingw	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/Makefile.mingw	Thu Feb 09 04:17:56 2006 +0000
@@ -94,6 +94,7 @@
 			dnssrv.c \
 			eventloop.c \
 			ft.c \
+			gaim_buffer.c \
 			gtkaccount.c \
 			gtkblist.c \
 			gtkconn.c \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gaim_buffer.c	Thu Feb 09 04:17:56 2006 +0000
@@ -0,0 +1,138 @@
+/*
+ * @file gaim_buffer.h Buffer Utility Functions
+ * @ingroup core
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include "internal.h"
+
+#include "gaim_buffer.h"
+
+#define DEFAULT_BUF_SIZE 256
+
+GaimCircBuffer *
+gaim_circ_buffer_new(gsize growsize) {
+	GaimCircBuffer *buf = g_new0(GaimCircBuffer, 1);
+	buf->growsize = growsize ? growsize : DEFAULT_BUF_SIZE;
+	return buf;
+}
+
+void gaim_circ_buffer_destroy(GaimCircBuffer *buf) {
+	g_free(buf->buffer);
+	g_free(buf);
+}
+
+static void grow_circ_buffer(GaimCircBuffer *buf, gsize len) {
+	int in_offset = 0, out_offset = 0;
+	int start_buflen = buf->buflen;
+
+	while ((buf->buflen - buf->bufused) < len)
+		buf->buflen += buf->growsize;
+
+	if (buf->inptr != NULL) {
+		in_offset = buf->inptr - buf->buffer;
+		out_offset = buf->outptr - buf->buffer;
+	}
+	buf->buffer = g_realloc(buf->buffer, buf->buflen);
+
+	/* adjust the fill and remove pointer locations */
+	if (buf->inptr == NULL) {
+		buf->inptr = buf->outptr = buf->buffer;
+	} else {
+		buf->inptr = buf->buffer + in_offset;
+		buf->outptr = buf->buffer + out_offset;
+	}
+
+	/* If the fill pointer is wrapped to before the remove
+	 * pointer, we need to shift the data */
+	if (in_offset < out_offset) {
+		int shift_n = MIN(buf->buflen - start_buflen,
+			in_offset);
+		memcpy(buf->buffer + start_buflen, buf->buffer,
+			shift_n);
+
+		/* If we couldn't fit the wrapped read buffer
+		 * at the end */
+		if (shift_n < in_offset) {
+			memcpy(buf->buffer,
+				buf->buffer + shift_n,
+				in_offset - shift_n);
+			buf->inptr = buf->buffer +
+				(in_offset - shift_n);
+		} else {
+			buf->inptr = buf->buffer +
+				start_buflen + in_offset;
+		}
+	}
+}
+
+void gaim_circ_buffer_append(GaimCircBuffer *buf, gconstpointer src, gsize len) {
+
+	int len_stored;
+
+	/* Grow the buffer, if necessary */
+	if ((buf->buflen - buf->bufused) < len)
+		grow_circ_buffer(buf, len);
+
+	/* If we may need to wrap */
+	if ((buf->inptr - buf->outptr) >= 0)
+		len_stored = MIN(len, buf->buflen
+			- (buf->inptr - buf->buffer));
+	else
+		len_stored = len;
+
+	memcpy(buf->inptr, src, len_stored);
+
+	if (len_stored < len) {
+		memcpy(buf->buffer, src + len_stored, len - len_stored);
+		buf->inptr = buf->buffer + (len - len_stored);
+	} else if ((buf->buffer - buf->inptr) == len_stored) {
+		buf->inptr = buf->buffer;
+	} else {
+		buf->inptr += len_stored;
+	}
+
+	buf->bufused += len;
+}
+
+gsize gaim_circ_buffer_get_max_read(GaimCircBuffer *buf) {
+	int max_read;
+
+	if (buf->bufused == 0)
+		max_read = 0;
+	else if ((buf->outptr - buf->inptr) >= 0)
+		max_read = buf->buflen - (buf->outptr - buf->buffer);
+	else
+		max_read = buf->inptr - buf->outptr;
+
+	return max_read;
+}
+
+gboolean gaim_circ_buffer_mark_read(GaimCircBuffer *buf, gsize len) {
+	g_return_val_if_fail(gaim_circ_buffer_get_max_read(buf) >= len, FALSE);
+
+	buf->outptr += len;
+	buf->bufused -= len;
+	/* wrap to the start if we're at the end */
+	if ((buf->outptr - buf->buffer) == buf->buflen)
+		buf->outptr = buf->buffer;
+
+	return TRUE;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gaim_buffer.h	Thu Feb 09 04:17:56 2006 +0000
@@ -0,0 +1,98 @@
+/*
+ * @file gaim_buffer.h Buffer Utility Functions
+ * @ingroup core
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef _GAIM_BUFFER_H
+#define _GAIM_BUFFER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct _GaimCircBuffer {
+	gchar *buffer;
+	gsize growsize;
+	gsize buflen;
+	gsize bufused;
+	gchar *inptr;
+	gchar *outptr;
+} GaimCircBuffer;
+
+/**
+ * Creates a new circular buffer.  This will not allocate any memory for the
+ * actual buffer until data is appended to it.
+ *
+ * @param growsize The size that the buffer should grow by the first time data
+ *                 is appended and every time more space is needed.
+ *
+ * @return The new GaimCircBuffer. This should be freed with
+ *         gaim_circ_buffer_destroy when you are done with it
+ */
+GaimCircBuffer *gaim_circ_buffer_new(gsize growsize);
+
+/**
+ * Dispose of the GaimCircBuffer and free any memory used by it (including any
+ * memory used by the internal buffer).
+ *
+ * @param buf The GaimCircBuffer to free
+ */
+void gaim_circ_buffer_destroy(GaimCircBuffer *buf);
+
+/**
+ * Append data to the GaimCircBuffer.  This will automatically grow the internal
+ * buffer to fit the added data.
+ *
+ * @param buf The GaimCircBuffer to which to append the data
+ * @param src pointer to the data to copy into the buffer
+ * @param len number of bytes to copy into the buffer
+ */
+void gaim_circ_buffer_append(GaimCircBuffer *buf, gconstpointer src, gsize len);
+
+/**
+ * Determine the maximum number of contiguous bytes that can be read from the
+ * GaimCircBuffer.
+ * Note: This may not be the total number of bytes that are buffered - a
+ * subsequent call after calling gaim_circ_buffer_mark_read() may indicate more
+ * data is available to read.
+ *
+ * @param buf the GaimCircBuffer for which to determine the maximum contiguous
+ *            bytes that can be read.
+ *
+ * @return the number of bytes that can be read from the GaimCircBuffer
+ */
+gsize gaim_circ_buffer_get_max_read(GaimCircBuffer *buf);
+
+/**
+ * Mark the number of bytes that have been read from the buffer.
+ *
+ * @param buf The GaimCircBuffer to mark bytes read from
+ * @param len The number of bytes to mark as read
+ *
+ * @return TRUE if we successfully marked the bytes as having been read, FALSE
+ *         otherwise.
+ */
+gboolean gaim_circ_buffer_mark_read(GaimCircBuffer *buf, gsize len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _GAIM_BUFFER_H */
--- a/src/protocols/irc/dcc_send.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/irc/dcc_send.c	Thu Feb 09 04:17:56 2006 +0000
@@ -174,7 +174,12 @@
 	char *buffer[16];
 	int len;
 
-	if ((len = read(source, buffer, sizeof(buffer))) <= 0) {
+	len = read(source, buffer, sizeof(buffer));
+
+	if (len < 0 && errno == EAGAIN)
+		return;
+	else if (len < 0) {
+		/* XXX: Shouldn't this be canceling the transfer? */
 		gaim_input_remove(xd->inpa);
 		xd->inpa = 0;
 		return;
@@ -209,20 +214,24 @@
 			gaim_xfer_end(xfer);
 			return;
 		}
-
-
 	}
 }
 
 static gssize irc_dccsend_send_write(const guchar *buffer, size_t size, GaimXfer *xfer)
 {
 	gssize s;
+	int ret;
 
 	s = MIN(gaim_xfer_get_bytes_remaining(xfer), size);
 	if (!s)
 		return 0;
 
-	return write(xfer->fd, buffer, s);
+	ret = write(xfer->fd, buffer, s);
+
+	if (ret < 0 && errno == EAGAIN)
+		ret = 0;
+
+	return ret;
 }
 
 static void irc_dccsend_send_connected(gpointer data, int source, GaimInputCondition cond) {
--- a/src/protocols/irc/irc.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/irc/irc.c	Thu Feb 09 04:17:56 2006 +0000
@@ -83,23 +83,79 @@
 	gaim_notify_formatted(gc, title, title, NULL, irc->motd->str, NULL, NULL);
 }
 
-int irc_send(struct irc_conn *irc, const char *buf)
+static int do_send(struct irc_conn *irc, const char *buf, gsize len)
 {
 	int ret;
 
 	if (irc->gsc) {
-		ret = gaim_ssl_write(irc->gsc, buf, strlen(buf));
+		ret = gaim_ssl_write(irc->gsc, buf, len);
 	} else {
-		if (irc->fd < 0)
-			return -1;
-		ret = write(irc->fd, buf, strlen(buf));
+		ret = write(irc->fd, buf, len);
+	}
+
+	return ret;
+}
+
+static void
+irc_send_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	struct irc_conn *irc = data;
+	int ret, writelen;
+
+	writelen = gaim_circ_buffer_get_max_read(irc->outbuf);
+
+	if (writelen == 0) {
+		gaim_input_remove(irc->writeh);
+		irc->writeh = 0;
+		return;
+	}
+
+	ret = do_send(irc, irc->outbuf->outptr, writelen);
+
+	if (ret < 0 && errno == EAGAIN)
+		return;
+	else if (ret <= 0) {
+		gaim_connection_error(gaim_account_get_connection(irc->account),
+			      _("Server has disconnected"));
+		return;
+	}
+
+	gaim_circ_buffer_mark_read(irc->outbuf, ret);
+
+#if 0
+	/* We *could* try to write more if we wrote it all */
+	if (ret == write_len) {
+		irc_send_cb(data, source, cond);
+	}
+#endif
+}
+
+int irc_send(struct irc_conn *irc, const char *buf)
+{
+	int ret, buflen = strlen(buf);
+
+	/* If we're not buffering writes, try to send immediately */
+	if (!irc->writeh)
+		ret = do_send(irc, buf, buflen);
+	else {
+		ret = -1;
+		errno = EAGAIN;
 	}
 
 	/* gaim_debug(GAIM_DEBUG_MISC, "irc", "sent%s: %s",
 		irc->gsc ? " (ssl)" : "", buf); */
-	if (ret < 0) {
+	if (ret <= 0 && errno != EAGAIN) {
 		gaim_connection_error(gaim_account_get_connection(irc->account),
 				      _("Server has disconnected"));
+	} else if (ret < buflen) {
+		if (ret < 0)
+			ret = 0;
+		if (!irc->writeh)
+			irc->writeh = gaim_input_add(
+				irc->gsc ? irc->gsc->fd : irc->fd,
+				GAIM_INPUT_WRITE, irc_send_cb, irc);
+		gaim_circ_buffer_append(irc->outbuf, buf + ret,
+			buflen - ret);
 	}
 
 	return ret;
@@ -240,6 +296,7 @@
 	gc->proto_data = irc = g_new0(struct irc_conn, 1);
 	irc->fd = -1;
 	irc->account = account;
+	irc->outbuf = gaim_circ_buffer_new(512);
 
 	userparts = g_strsplit(username, "@", 2);
 	gaim_connection_set_display_name(gc, userparts[0]);
@@ -262,6 +319,7 @@
 					irc_login_cb_ssl, irc_ssl_connect_failure, gc);
 		} else {
 			gaim_connection_error(gc, _("SSL support unavailable"));
+			return;
 		}
 	}
 
@@ -288,7 +346,7 @@
 	if (pass && *pass) {
 		buf = irc_format(irc, "vv", "PASS", pass);
 		if (irc_send(irc, buf) < 0) {
-			gaim_connection_error(gc, "Error sending password");
+/*			gaim_connection_error(gc, "Error sending password"); */
 			g_free(buf);
 			return FALSE;
 		}
@@ -302,14 +360,14 @@
 	buf = irc_format(irc, "vvvv:", "USER", strlen(username) ? username : g_get_user_name(), hostname, irc->server,
 			      strlen(realname) ? realname : IRC_DEFAULT_ALIAS);
 	if (irc_send(irc, buf) < 0) {
-		gaim_connection_error(gc, "Error registering with server");
+/*		gaim_connection_error(gc, "Error registering with server");*/
 		g_free(buf);
 		return FALSE;
 	}
 	g_free(buf);
 	buf = irc_format(irc, "vn", "NICK", gaim_connection_get_display_name(gc));
 	if (irc_send(irc, buf) < 0) {
-		gaim_connection_error(gc, "Error sending nickname");
+/*		gaim_connection_error(gc, "Error sending nickname");*/
 		g_free(buf);
 		return FALSE;
 	}
@@ -404,6 +462,12 @@
 	if (irc->motd)
 		g_string_free(irc->motd, TRUE);
 	g_free(irc->server);
+
+	if (irc->writeh)
+		gaim_input_remove(irc->writeh);
+
+	gaim_circ_buffer_destroy(irc->outbuf);
+
 	g_free(irc);
 }
 
@@ -443,7 +507,7 @@
 	const char *status_id = gaim_status_get_id(status);
 
 	if (gc)
-	  irc = gc->proto_data;
+		irc = gc->proto_data;
 
 	if (!gaim_status_is_active(status))
 		return;
@@ -527,8 +591,13 @@
 		irc->inbuflen += IRC_INITIAL_BUFSIZE;
 		irc->inbuf = g_realloc(irc->inbuf, irc->inbuflen);
 	}
-	
-	if ((len = gaim_ssl_read(gsc, irc->inbuf + irc->inbufused, IRC_INITIAL_BUFSIZE - 1)) < 0) {
+
+	len = gaim_ssl_read(gsc, irc->inbuf + irc->inbufused, IRC_INITIAL_BUFSIZE - 1);
+
+	if (len < 0 && errno == EAGAIN) {
+		/* Try again later */
+		return;
+	} else if (len < 0) {
 		gaim_connection_error(gc, _("Read error"));
 		return;
 	} else if (len == 0) {
@@ -550,7 +619,10 @@
 		irc->inbuf = g_realloc(irc->inbuf, irc->inbuflen);
 	}
 
-	if ((len = read(irc->fd, irc->inbuf + irc->inbufused, IRC_INITIAL_BUFSIZE - 1)) < 0) {
+	len = read(irc->fd, irc->inbuf + irc->inbufused, IRC_INITIAL_BUFSIZE - 1);
+	if (len < 0 && errno == EAGAIN) {
+		return;
+	} else if (len < 0) {
 		gaim_connection_error(gc, _("Read error"));
 		return;
 	} else if (len == 0) {
--- a/src/protocols/irc/irc.h	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/irc/irc.h	Thu Feb 09 04:17:56 2006 +0000
@@ -1,10 +1,10 @@
 /**
  * @file irc.h
- * 
+ *
  * gaim
  *
  * Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu>
- * 
+ *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
@@ -26,6 +26,7 @@
 #include <glib.h>
 
 #include "ft.h"
+#include "gaim_buffer.h"
 #include "roomlist.h"
 #include "sslconn.h"
 
@@ -77,6 +78,9 @@
 	GaimSslConnection *gsc;
 
 	gboolean quitting;
+
+	GaimCircBuffer *outbuf;
+	guint writeh;
 };
 
 struct irc_buddy {
--- a/src/protocols/jabber/jabber.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/jabber/jabber.c	Thu Feb 09 04:17:56 2006 +0000
@@ -194,6 +194,42 @@
 	}
 }
 
+static int jabber_do_send(JabberStream *js, const char *data, int len)
+{
+	int ret;
+
+	if (js->gsc)
+		ret = gaim_ssl_write(js->gsc, data, len);
+	else
+		ret = write(js->fd, data, len);
+
+	return ret;
+}
+
+static void jabber_send_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	JabberStream *js = data;
+	int ret, writelen;
+	writelen = gaim_circ_buffer_get_max_read(js->write_buffer);
+
+	if (writelen == 0) {
+		gaim_input_remove(js->writeh);
+		js->writeh = -1;
+		return;
+	}
+
+	ret = jabber_do_send(js, js->write_buffer->outptr, writelen);
+
+	if (ret < 0 && errno == EAGAIN)
+		return;
+	else if (ret <= 0) {
+		gaim_connection_error(js->gc, _("Write error"));
+		return;
+	}
+
+	gaim_circ_buffer_mark_read(js->write_buffer, ret);
+}
+
 void jabber_send_raw(JabberStream *js, const char *data, int len)
 {
 	int ret;
@@ -228,27 +264,53 @@
 			sasl_encode(js->sasl, &data[pos], towrite, &out, &olen);
 			pos += towrite;
 
-			if (js->gsc)
-				ret = gaim_ssl_write(js->gsc, out, olen);
-			else
-				ret = write(js->fd, out, olen);
-			if (ret < 0)
+			if (js->writeh != -1)
+				ret = jabber_do_send(js, out, olen);
+			else {
+				ret = -1;
+				errno = EAGAIN;
+			}
+
+			if (ret < 0 && errno != EAGAIN)
 				gaim_connection_error(js->gc, _("Write error"));
+			else if (ret < olen) {
+				if (ret < 0)
+					ret = 0;
+				if (js->writeh == -1)
+					js->writeh = gaim_input_add(
+						js->gsc ? js->gsc->fd : js->fd,
+						GAIM_INPUT_WRITE,
+						jabber_send_cb, js);
+				gaim_circ_buffer_append(js->write_buffer,
+					out + ret, olen - ret);
+			}
 		}
 		return;
 	}
 #endif
-	
-	if(js->gsc) {
-		ret = gaim_ssl_write(js->gsc, data, len == -1 ? strlen(data) : len);
-	} else {
-		if(js->fd < 0)
-			return;
-		ret = write(js->fd, data, len == -1 ? strlen(data) : len);
+
+	if (len == -1)
+		len = strlen(data);
+
+	if (js->writeh == -1)
+		ret = jabber_do_send(js, data, len);
+	else {
+		ret = -1;
+		errno = EAGAIN;
 	}
 
-	if(ret < 0)
+	if (ret < 0 && errno != EAGAIN)
 		gaim_connection_error(js->gc, _("Write error"));
+	else if (ret < len) {
+		if (ret < 0)
+			ret = 0;
+		if (js->writeh == -1)
+			js->writeh = gaim_input_add(
+				js->gsc ? js->gsc->fd : js->fd,
+				GAIM_INPUT_WRITE, jabber_send_cb, js);
+		gaim_circ_buffer_append(js->write_buffer,
+			data + ret, len - ret);
+	}
 
 }
 
@@ -285,7 +347,9 @@
 		buf[len] = '\0';
 		gaim_debug(GAIM_DEBUG_INFO, "jabber", "Recv (ssl)(%d): %s\n", len, buf);
 		jabber_parser_process(js, buf, len);
-	} else {
+	} else if(errno == EAGAIN)
+		return;
+	else {
 		gaim_connection_error(gc, _("Read Error"));
 	}
 }
@@ -317,6 +381,8 @@
 		buf[len] = '\0';
 		gaim_debug(GAIM_DEBUG_INFO, "jabber", "Recv (%d): %s\n", len, buf);
 		jabber_parser_process(js, buf, len);
+	} else if(errno == EAGAIN) {
+		return;
 	} else {
 		gaim_connection_error(gc, _("Read Error"));
 	}
@@ -446,6 +512,8 @@
 	js->chat_servers = g_list_append(NULL, g_strdup("conference.jabber.org"));
 	js->user = jabber_id_new(gaim_account_get_username(account));
 	js->next_id = g_random_int();
+	js->write_buffer = gaim_circ_buffer_new(512);
+	js->writeh = -1;
 
 	if(!js->user) {
 		gaim_connection_error(gc, _("Invalid Jabber ID"));
@@ -789,6 +857,9 @@
 		return;
 	}
 
+	js->write_buffer = gaim_circ_buffer_new(512);
+	js->writeh = -1;
+
 	if(!js->user->resource) {
 		char *me;
 		js->user->resource = g_strdup("Home");
@@ -871,6 +942,9 @@
 		jabber_id_free(js->user);
 	if(js->avatar_hash)
 		g_free(js->avatar_hash);
+	gaim_circ_buffer_destroy(js->write_buffer);
+	if(js->writeh)
+		gaim_input_remove(js->writeh);
 #ifdef HAVE_CYRUS_SASL
 	if(js->sasl)
 		sasl_dispose(&js->sasl);
--- a/src/protocols/jabber/jabber.h	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/jabber/jabber.h	Thu Feb 09 04:17:56 2006 +0000
@@ -26,6 +26,7 @@
 #include "connection.h"
 #include "roomlist.h"
 #include "sslconn.h"
+#include "gaim_buffer.h"
 
 #include "jutil.h"
 #include "xmlnode.h"
@@ -108,6 +109,9 @@
 	char *avatar_hash;
 	GSList *pending_avatar_requests;
 
+	GaimCircBuffer *write_buffer;
+	guint writeh;
+
 	/* OK, this stays at the end of the struct, so plugins can depend
 	 * on the rest of the stuff being in the right place
 	 */
--- a/src/protocols/jabber/oob.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/jabber/oob.c	Thu Feb 09 04:17:56 2006 +0000
@@ -33,12 +33,15 @@
 	char *page;
 
 	GString *headers;
-	gboolean newline;
 
 	char *iq_id;
 
 	JabberStream *js;
 
+	gchar *write_buffer;
+	gsize written_len;
+	guint writeh;
+
 } JabberOOBXfer;
 
 static void jabber_oob_xfer_init(GaimXfer *xfer)
@@ -57,6 +60,9 @@
 	g_free(jox->address);
 	g_free(jox->page);
 	g_free(jox->iq_id);
+	g_free(jox->write_buffer);
+	if(jox->writeh)
+		gaim_input_remove(jox->writeh);
 	g_free(jox);
 
 	xfer->data = NULL;
@@ -76,41 +82,72 @@
 	jabber_oob_xfer_free(xfer);
 }
 
+static void jabber_oob_xfer_request_send(gpointer data, gint source, GaimInputCondition cond) {
+	GaimXfer *xfer = data;
+	JabberOOBXfer *jox = xfer->data;
+	int len, total_len = strlen(jox->write_buffer);
+
+	len = write(xfer->fd, jox->write_buffer + jox->written_len,
+		total_len - jox->written_len);
+
+	if(len < 0 && errno == EAGAIN)
+		return;
+	else if(len < 0) {
+		gaim_debug(GAIM_DEBUG_ERROR, "jabber", "Write error on oob xfer!\n");
+		gaim_input_remove(jox->writeh);
+		gaim_xfer_cancel_local(xfer);
+	}
+	jox->written_len += len;
+
+	if(jox->written_len == total_len) {
+		gaim_input_remove(jox->writeh);
+		g_free(jox->write_buffer);
+		jox->write_buffer = NULL;
+	}
+}
+
 static void jabber_oob_xfer_start(GaimXfer *xfer)
 {
 	JabberOOBXfer *jox = xfer->data;
 
-	char *buf = g_strdup_printf("GET /%s HTTP/1.1\r\nHost: %s\r\n\r\n",
+	if(jox->write_buffer == NULL) {
+		jox->write_buffer = g_strdup_printf(
+			"GET /%s HTTP/1.1\r\nHost: %s\r\n\r\n",
 			jox->page, jox->address);
-	write(xfer->fd, buf, strlen(buf));
-	g_free(buf);
+		jox->written_len = 0;
+	}
+
+	jox->writeh = gaim_input_add(xfer->fd, GAIM_INPUT_WRITE,
+		jabber_oob_xfer_request_send, xfer);
+
+	jabber_oob_xfer_request_send(xfer, xfer->fd, GAIM_INPUT_WRITE);
 }
 
 static gssize jabber_oob_xfer_read(guchar **buffer, GaimXfer *xfer) {
 	JabberOOBXfer *jox = xfer->data;
-	char test;
-	int size;
+	char test[2048];
+	char *tmp, *lenstr;
+	int len;
 
-	if(read(xfer->fd, &test, sizeof(test)) > 0) {
-		jox->headers = g_string_append_c(jox->headers, test);
-		if(test == '\r')
-			return 0;
-		if(test == '\n') {
-			if(jox->newline) {
-				gchar *lenstr = strstr(jox->headers->str, "Content-Length: ");
-				if(lenstr) {
-					sscanf(lenstr, "Content-Length: %d", &size);
-					gaim_xfer_set_size(xfer, size);
-				}
-				gaim_xfer_set_read_fnc(xfer, NULL);
-				return 0;
-			} else
-				jox->newline = TRUE;
-				return 0;
+	if((len = read(xfer->fd, test, sizeof(test))) > 0) {
+		jox->headers = g_string_append_len(jox->headers, test, len);
+		if((tmp = strstr(jox->headers->str, "\r\n\r\n"))) {
+			*tmp = '\0';
+			lenstr = strstr(jox->headers->str, "Content-Length: ");
+			if(lenstr) {
+				int size;
+				sscanf(lenstr, "Content-Length: %d", &size);
+				gaim_xfer_set_size(xfer, size);
 			}
-		jox->newline = FALSE;
+			gaim_xfer_set_read_fnc(xfer, NULL);
+
+			tmp += 4;
+
+			*buffer = g_strdup(tmp);
+			return strlen(tmp);
+		}
 		return 0;
-	} else {
+	} else if (errno != EAGAIN) {
 		gaim_debug(GAIM_DEBUG_ERROR, "jabber", "Read error on oob xfer!\n");
 		gaim_xfer_cancel_local(xfer);
 	}
--- a/src/protocols/jabber/si.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/jabber/si.c	Thu Feb 09 04:17:56 2006 +0000
@@ -63,6 +63,7 @@
 
 	char *rxqueue;
 	size_t rxlen;
+	gsize rxmaxlen;
 } JabberSIXfer;
 
 static GaimXfer*
@@ -218,6 +219,39 @@
 	jabber_si_bytestreams_attempt_connect(xfer);
 }
 
+
+static void
+jabber_si_xfer_bytestreams_send_read_again_resp_cb(gpointer data, gint source,
+		GaimInputCondition cond)
+{
+	GaimXfer *xfer = data;
+	JabberSIXfer *jsx = xfer->data;
+	int len;
+
+	len = write(source, jsx->rxqueue + jsx->rxlen, jsx->rxmaxlen - jsx->rxlen);
+	if (len < 0 && errno == EAGAIN)
+		return;
+	else if (len < 0) {
+		gaim_input_remove(xfer->watcher);
+		xfer->watcher = 0;
+		g_free(jsx->rxqueue);
+		jsx->rxqueue = NULL;
+		close(source);
+		gaim_xfer_cancel_remote(xfer);
+	}
+	jsx->rxlen += len;
+
+	if (jsx->rxlen < jsx->rxmaxlen)
+		return;
+
+	gaim_input_remove(xfer->watcher);
+	xfer->watcher = 0;
+	g_free(jsx->rxqueue);
+	jsx->rxqueue = NULL;
+
+	gaim_xfer_start(xfer, source, NULL, -1);
+}
+
 static void
 jabber_si_xfer_bytestreams_send_read_again_cb(gpointer data, gint source,
 		GaimInputCondition cond)
@@ -235,7 +269,10 @@
 
 	if(jsx->rxlen < 5) {
 		gaim_debug_info("jabber", "reading the first 5 bytes\n");
-		if((len = read(source, buffer, 5 - jsx->rxlen)) <= 0) {
+		len = read(source, buffer, 5 - jsx->rxlen);
+		if(len < 0 && errno == EAGAIN)
+			return;
+		else if(len < 0) {
 			gaim_input_remove(xfer->watcher);
 			xfer->watcher = 0;
 			close(source);
@@ -256,7 +293,10 @@
 		return;
 	} else if(jsx->rxlen - 5 <  jsx->rxqueue[4] + 2) {
 		gaim_debug_info("jabber", "reading umpteen more bytes\n");
-		if((len = read(source, buffer, jsx->rxqueue[4] + 5 + 2 - jsx->rxlen)) <= 0) {
+		len = read(source, buffer, jsx->rxqueue[4] + 5 + 2 - jsx->rxlen);
+		if(len < 0 && errno == EAGAIN)
+			return;
+		else if(len < 0) {
 			gaim_input_remove(xfer->watcher);
 			xfer->watcher = 0;
 			close(source);
@@ -294,20 +334,64 @@
 		return;
 	}
 
+	g_free(jsx->rxqueue);
 	host = gaim_network_get_my_ip(jsx->js->fd);
 
-	buffer[0] = 0x05;
-	buffer[1] = 0x00;
-	buffer[2] = 0x00;
-	buffer[3] = 0x03;
-	buffer[4] = strlen(host);
-	memcpy(buffer + 5, host, strlen(host));
-	buffer[5+strlen(host)] = 0x00;
-	buffer[6+strlen(host)] = 0x00;
+	jsx->rxmaxlen = 5 + strlen(host) + 2;
+	jsx->rxqueue = g_malloc(jsx->rxmaxlen);
+	jsx->rxlen = 0;
+
+	jsx->rxqueue[0] = 0x05;
+	jsx->rxqueue[1] = 0x00;
+	jsx->rxqueue[2] = 0x00;
+	jsx->rxqueue[3] = 0x03;
+	jsx->rxqueue[4] = strlen(host);
+	memcpy(jsx->rxqueue + 5, host, strlen(host));
+	jsx->rxqueue[5+strlen(host)] = 0x00;
+	jsx->rxqueue[6+strlen(host)] = 0x00;
+
+	xfer->watcher = gaim_input_add(source, GAIM_INPUT_WRITE,
+		jabber_si_xfer_bytestreams_send_read_again_resp_cb, xfer);
+	jabber_si_xfer_bytestreams_send_read_again_resp_cb(xfer, source,
+		GAIM_INPUT_WRITE);
+}
+
+static void
+jabber_si_xfer_bytestreams_send_read_response_cb(gpointer data, gint source,
+		GaimInputCondition cond)
+{
+	GaimXfer *xfer = data;
+	JabberSIXfer *jsx = xfer->data;
+	int len;
 
-	write(source, buffer, strlen(host)+7);
+	len = write(source, jsx->rxqueue + jsx->rxlen, jsx->rxmaxlen - jsx->rxlen);
+	if (len < 0 && errno == EAGAIN)
+		return;
+	else if (len < 0) {
+		gaim_input_remove(xfer->watcher);
+		xfer->watcher = 0;
+		g_free(jsx->rxqueue);
+		jsx->rxqueue = NULL;
+		close(source);
+		gaim_xfer_cancel_remote(xfer);
+	}
+	jsx->rxlen += len;
 
-	gaim_xfer_start(xfer, source, NULL, -1);
+	if (jsx->rxlen < jsx->rxmaxlen)
+		return;
+
+	gaim_input_remove(xfer->watcher);
+	xfer->watcher = 0;
+
+	if (jsx->rxqueue[1] == 0x00) {
+		xfer->watcher = gaim_input_add(source, GAIM_INPUT_READ,
+			jabber_si_xfer_bytestreams_send_read_again_cb, xfer);
+		g_free(jsx->rxqueue);
+		jsx->rxqueue = NULL;
+	} else {
+		close(source);
+		gaim_xfer_cancel_remote(xfer);
+	}
 }
 
 static void
@@ -326,7 +410,10 @@
 
 	if(jsx->rxlen < 2) {
 		gaim_debug_info("jabber", "reading those first two bytes\n");
-		if((len = read(source, buffer, 2 - jsx->rxlen)) <= 0) {
+		len = read(source, buffer, 2 - jsx->rxlen);
+		if(len < 0 && errno == EAGAIN)
+			return;
+		else if(len < 0) {
 			gaim_input_remove(xfer->watcher);
 			xfer->watcher = 0;
 			close(source);
@@ -339,7 +426,10 @@
 		return;
 	} else if(jsx->rxlen - 2 <  jsx->rxqueue[1]) {
 		gaim_debug_info("jabber", "reading the next umpteen bytes\n");
-		if((len = read(source, buffer, jsx->rxqueue[1] + 2 - jsx->rxlen)) <= 0) {
+		len = read(source, buffer, jsx->rxqueue[1] + 2 - jsx->rxlen);
+		if(len < 0 && errno == EAGAIN)
+			return;
+		else if(len < 0) {
 			gaim_input_remove(xfer->watcher);
 			xfer->watcher = 0;
 			close(source);
@@ -357,7 +447,6 @@
 	gaim_input_remove(xfer->watcher);
 	xfer->watcher = 0;
 
-
 	gaim_debug_info("jabber", "checking to make sure we're socks FIVE\n");
 
 	if(jsx->rxqueue[0] != 0x05) {
@@ -372,26 +461,33 @@
 
 		gaim_debug_info("jabber", "testing %hhu\n", jsx->rxqueue[i+2]);
 		if(jsx->rxqueue[i+2] == 0x00) {
-			buffer[0] = 0x05;
-			buffer[1] = 0x00;
-			write(source, buffer, 2);
-			xfer->watcher = gaim_input_add(source, GAIM_INPUT_READ,
-					jabber_si_xfer_bytestreams_send_read_again_cb, xfer);
 			g_free(jsx->rxqueue);
+			jsx->rxlen = 0;
+			jsx->rxmaxlen = 2;
+			jsx->rxqueue = g_malloc(jsx->rxmaxlen);
+			jsx->rxqueue[0] = 0x05;
+			jsx->rxqueue[1] = 0x00;
+			xfer->watcher = gaim_input_add(source, GAIM_INPUT_WRITE,
+				jabber_si_xfer_bytestreams_send_read_response_cb,
+				xfer);
+			jabber_si_xfer_bytestreams_send_read_response_cb(xfer,
+				source, GAIM_INPUT_WRITE);
 			jsx->rxqueue = NULL;
 			jsx->rxlen = 0;
 			return;
 		}
 	}
 
-	buffer[0] = 0x05;
-	buffer[1] = 0xFF;
-	write(source, buffer, 2);
-	close(source);
 	g_free(jsx->rxqueue);
-	jsx->rxqueue = NULL;
 	jsx->rxlen = 0;
-	gaim_xfer_cancel_remote(xfer);
+	jsx->rxmaxlen = 2;
+	jsx->rxqueue = g_malloc(jsx->rxmaxlen);
+	jsx->rxqueue[0] = 0x05;
+	jsx->rxqueue[1] = 0xFF;
+	xfer->watcher = gaim_input_add(source, GAIM_INPUT_WRITE,
+		jabber_si_xfer_bytestreams_send_read_response_cb, xfer);
+	jabber_si_xfer_bytestreams_send_read_response_cb(xfer,
+		source, GAIM_INPUT_WRITE);
 }
 
 static void
@@ -403,7 +499,10 @@
 
 	gaim_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_connected_cb\n");
 
-	if((acceptfd = accept(source, NULL, 0)) == -1) {
+	acceptfd = accept(source, NULL, 0);
+	if(acceptfd == -1 && (errno == EAGAIN || errno == EWOULDBLOCK))
+		return;
+	else if(acceptfd == -1) {
 		gaim_debug_warning("jabber", "accept: %s\n", strerror(errno));
 		return;
 	}
@@ -584,6 +683,7 @@
 	g_free(jsx->stream_id);
 	g_free(jsx->iq_id);
 	/* XXX: free other stuff */
+	g_free(jsx->rxqueue);
 	g_free(jsx);
 	xfer->data = NULL;
 }
--- a/src/protocols/msn/directconn.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/msn/directconn.c	Thu Feb 09 04:17:56 2006 +0000
@@ -460,8 +460,8 @@
 
 	directconn->fd = fd;
 
-	directconn->inpa = gaim_input_add(fd, GAIM_INPUT_READ,
-									  connect_cb, directconn);
+	directconn->inpa = gaim_input_add(fd, GAIM_INPUT_READ, connect_cb,
+		directconn);
 
 	directconn->port = port;
 	directconn->c = 0;
--- a/src/protocols/msn/httpconn.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/msn/httpconn.c	Thu Feb 09 04:17:56 2006 +0000
@@ -25,16 +25,7 @@
 #include "debug.h"
 #include "httpconn.h"
 
-typedef struct
-{
-	MsnHttpConn *httpconn;
-	char *data;
-	size_t size;
-
-} MsnHttpQueueData;
-
 static void read_cb(gpointer data, gint source, GaimInputCondition cond);
-void msn_httpconn_process_queue(MsnHttpConn *httpconn);
 gboolean msn_httpconn_parse_data(MsnHttpConn *httpconn, const char *buf,
 								 size_t size, char **ret_buf, size_t *ret_size,
 								 gboolean *error);
@@ -55,6 +46,9 @@
 
 	httpconn->servconn = servconn;
 
+	httpconn->tx_buf = gaim_circ_buffer_new(MSN_BUF_LEN);
+	httpconn->tx_handler = -1;
+
 	return httpconn;
 }
 
@@ -68,14 +62,15 @@
 	if (httpconn->connected)
 		msn_httpconn_disconnect(httpconn);
 
-	if (httpconn->full_session_id != NULL)
-		g_free(httpconn->full_session_id);
+	g_free(httpconn->full_session_id);
+
+	g_free(httpconn->session_id);
 
-	if (httpconn->session_id != NULL)
-		g_free(httpconn->session_id);
+	g_free(httpconn->host);
 
-	if (httpconn->host != NULL)
-		g_free(httpconn->host);
+	gaim_circ_buffer_destroy(httpconn->tx_buf);
+	if (httpconn->tx_handler > 0)
+		gaim_input_remove(httpconn->tx_handler);
 
 	g_free(httpconn);
 }
@@ -114,49 +109,70 @@
 	return auth;
 }
 
-static ssize_t
-write_raw(MsnHttpConn *httpconn, const char *header,
-		  const char *body, size_t body_len)
+static void
+httpconn_write_cb(gpointer data, gint source, GaimInputCondition cond)
 {
-	char *buf;
-	size_t buf_len;
+	MsnHttpConn *httpconn = data;
+	int ret, writelen;
+
+	if (httpconn->waiting_response)
+		return;
+
+	writelen = gaim_circ_buffer_get_max_read(httpconn->tx_buf);
+
+	if (writelen == 0) {
+		gaim_input_remove(httpconn->tx_handler);
+		httpconn->tx_handler = -1;
+		return;
+	}
 
-	ssize_t s;
+	ret = write(httpconn->fd, httpconn->tx_buf->outptr, writelen);
+
+	if (ret < 0 && errno == EAGAIN)
+		return;
+	else if (ret <= 0) {
+		msn_servconn_got_error(httpconn->servconn,
+			MSN_SERVCONN_ERROR_WRITE);
+		return;
+	}
+
+	gaim_circ_buffer_mark_read(httpconn->tx_buf, ret);
+}
+
+static ssize_t
+write_raw(MsnHttpConn *httpconn, const char *data, size_t data_len)
+{
 	ssize_t res; /* result of the write operation */
 
 #ifdef MSN_DEBUG_HTTP
 	gaim_debug_misc("msn", "Writing HTTP (header): {%s}\n", header);
 #endif
 
-	buf = g_strdup_printf("%s\r\n", header);
-	buf_len = strlen(buf);
 
-	if (body != NULL)
+	if (httpconn->tx_handler == -1 && !httpconn->waiting_response)
+		res = write(httpconn->fd, data, data_len);
+	else
 	{
-		buf = g_realloc(buf, buf_len + body_len);
-		memcpy(buf + buf_len, body, body_len);
-		buf_len += body_len;
+		res = -1;
+		errno = EAGAIN;
 	}
 
-	s = 0;
-
-	do
+	if (res <= 0 && errno != EAGAIN)
 	{
-		res = write(httpconn->fd, buf + s, buf_len - s);
-		if (res >= 0)
-		{
-			s += res;
-		}
-		else if (errno != EAGAIN)
-		{
-			msn_servconn_got_error(httpconn->servconn, MSN_SERVCONN_ERROR_WRITE);
-			return -1;
-		}
-	} while (s < buf_len);
+		msn_servconn_got_error(httpconn->servconn,
+			MSN_SERVCONN_ERROR_WRITE);
+		return -1;
+	} else if (res < data_len) {
+		if (res < 0)
+			res = 0;
+		if (httpconn->tx_handler == -1)
+			httpconn->tx_handler = gaim_input_add(httpconn->fd,
+				GAIM_INPUT_WRITE, httpconn_write_cb, httpconn);
+		gaim_circ_buffer_append(httpconn->tx_buf, data + res,
+			data_len - res);
+	}
 
-	g_free(buf);
-
-	return s;
+	return res;
 }
 
 static void
@@ -169,11 +185,14 @@
 	g_return_if_fail(httpconn != NULL);
 
 	if (httpconn->waiting_response ||
-		httpconn->queue != NULL)
+		httpconn->tx_handler > 0)
 	{
 		return;
 	}
 
+	/* It is OK if this is buffered because it will only be buffered if
+	   nothing else is in the buffer */
+
 	auth = msn_httpconn_proxy_auth(httpconn);
 
 	header = g_strdup_printf(
@@ -187,20 +206,19 @@
 		"Connection: Keep-Alive\r\n"
 		"Pragma: no-cache\r\n"
 		"Content-Type: application/x-msn-messenger\r\n"
-		"Content-Length: 0\r\n",
+		"Content-Length: 0\r\n\r\n",
 		httpconn->host,
 		httpconn->full_session_id,
 		httpconn->host,
 		auth ? auth : "");
 
-	if (auth != NULL)
-		g_free(auth);
+	g_free(auth);
 
-	r = write_raw(httpconn, header, NULL, -1);
+	r = write_raw(httpconn, header, strlen(header));
 
 	g_free(header);
 
-	if (r > 0)
+	if (r >= 0)
 	{
 		httpconn->waiting_response = TRUE;
 		httpconn->dirty = FALSE;
@@ -242,12 +260,13 @@
 	if (source > 0)
 	{
 		httpconn->inpa = gaim_input_add(httpconn->fd, GAIM_INPUT_READ,
-										read_cb, data);
+			read_cb, data);
 
 		httpconn->timer = gaim_timeout_add(2000, do_poll, httpconn);
 
 		httpconn->waiting_response = FALSE;
-		msn_httpconn_process_queue(httpconn);
+		if (httpconn->tx_handler > 0)
+			httpconn_write_cb(httpconn, source, GAIM_INPUT_WRITE);
 	}
 	else
 	{
@@ -269,8 +288,7 @@
 		msn_httpconn_disconnect(httpconn);
 
 	r = gaim_proxy_connect(httpconn->session->account,
-						   "gateway.messenger.hotmail.com", 80, connect_cb,
-						   httpconn);
+		"gateway.messenger.hotmail.com", 80, connect_cb, httpconn);
 
 	if (r == 0)
 	{
@@ -329,7 +347,9 @@
 
 	len = read(httpconn->fd, buf, sizeof(buf) - 1);
 
-	if (len <= 0)
+	if (len < 0 && errno == EAGAIN)
+		return;
+	else if (len <= 0)
 	{
 		gaim_debug_error("msn", "HTTP: Read error\n");
 		msn_servconn_got_error(httpconn->servconn, MSN_SERVCONN_ERROR_READ);
@@ -444,37 +464,12 @@
 	g_free(old_rx_buf);
 }
 
-void
-msn_httpconn_process_queue(MsnHttpConn *httpconn)
-{
-	if (httpconn->queue != NULL)
-	{
-		MsnHttpQueueData *queue_data;
-
-		queue_data = (MsnHttpQueueData *)httpconn->queue->data;
-
-		httpconn->queue = g_list_remove(httpconn->queue, queue_data);
-
-		msn_httpconn_write(queue_data->httpconn,
-						   queue_data->data,
-						   queue_data->size);
-
-		g_free(queue_data->data);
-		g_free(queue_data);
-	}
-	else
-	{
-		httpconn->dirty = TRUE;
-	}
-}
-
 size_t
-msn_httpconn_write(MsnHttpConn *httpconn, const char *data, size_t size)
+msn_httpconn_write(MsnHttpConn *httpconn, const char *body, size_t size)
 {
 	char *params;
-	char *header;
+	char *data;
 	char *auth;
-	gboolean first;
 	const char *server_types[] = { "NS", "SB" };
 	const char *server_type;
 	size_t r; /* result of the write operation */
@@ -484,31 +479,14 @@
 	/* TODO: remove http data from servconn */
 
 	g_return_val_if_fail(httpconn != NULL, 0);
-	g_return_val_if_fail(data     != NULL, 0);
-	g_return_val_if_fail(size      > 0,    0);
+	g_return_val_if_fail(body != NULL, 0);
+	g_return_val_if_fail(size > 0, 0);
 
 	servconn = httpconn->servconn;
 
-	if (httpconn->waiting_response)
-	{
-		MsnHttpQueueData *queue_data = g_new0(MsnHttpQueueData, 1);
-
-		queue_data->httpconn = httpconn;
-		queue_data->data     = g_memdup(data, size);
-		queue_data->size     = size;
-
-		httpconn->queue = g_list_append(httpconn->queue, queue_data);
-		/* httpconn->dirty = TRUE; */
-
-		/* servconn->processing = TRUE; */
-
-		return size;
-	}
-
-	first = httpconn->virgin;
 	server_type = server_types[servconn->type];
 
-	if (first)
+	if (httpconn->virgin)
 	{
 		host = "gateway.messenger.hotmail.com";
 
@@ -516,6 +494,7 @@
 		params = g_strdup_printf("Action=open&Server=%s&IP=%s",
 								 server_type,
 								 servconn->host);
+		httpconn->virgin = FALSE;
 	}
 	else
 	{
@@ -529,12 +508,12 @@
 		}
 
 		params = g_strdup_printf("SessionID=%s",
-								 httpconn->full_session_id);
+			httpconn->full_session_id);
 	}
 
 	auth = msn_httpconn_proxy_auth(httpconn);
 
-	header = g_strdup_printf(
+	data = g_strdup_printf(
 		"POST http://%s/gateway/gateway.dll?%s HTTP/1.1\r\n"
 		"Accept: */*\r\n"
 		"Accept-Language: en-us\r\n"
@@ -545,25 +524,26 @@
 		"Connection: Keep-Alive\r\n"
 		"Pragma: no-cache\r\n"
 		"Content-Type: application/x-msn-messenger\r\n"
-		"Content-Length: %d\r\n",
+		"Content-Length: %d\r\n\r\n"
+		"%s",
 		host,
 		params,
 		host,
 		auth ? auth : "",
-		(int)size);
+		(int) size,
+		body ? body : "");
+
 
 	g_free(params);
 
-	if (auth != NULL)
-		g_free(auth);
+	g_free(auth);
 
-	r = write_raw(httpconn, header, data, size);
+	r = write_raw(httpconn, data, strlen(data));
 
-	g_free(header);
+	g_free(data);
 
-	if (r > 0)
+	if (r >= 0)
 	{
-		httpconn->virgin = FALSE;
 		httpconn->waiting_response = TRUE;
 		httpconn->dirty = FALSE;
 	}
@@ -629,7 +609,9 @@
 			*ret_buf = g_strdup("");
 			*ret_size = 0;
 
-			msn_httpconn_process_queue(httpconn);
+			if (httpconn->tx_handler > 0)
+				httpconn_write_cb(httpconn, httpconn->fd,
+					GAIM_INPUT_WRITE);
 
 			return TRUE;
 		}
@@ -781,7 +763,8 @@
 	*ret_buf  = body;
 	*ret_size = body_len;
 
-	msn_httpconn_process_queue(httpconn);
+	if (httpconn->tx_handler > 0)
+		httpconn_write_cb(httpconn, httpconn->fd, GAIM_INPUT_WRITE);
 
 	return TRUE;
 }
--- a/src/protocols/msn/httpconn.h	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/msn/httpconn.h	Thu Feb 09 04:17:56 2006 +0000
@@ -27,6 +27,7 @@
 typedef struct _MsnHttpConn MsnHttpConn;
 
 #include "servconn.h"
+#include "gaim_buffer.h"
 
 /**
  * An HTTP Connection.
@@ -50,13 +51,15 @@
 								 connect to. */
 
 	char *host; /**< The HTTP gateway host. */
-	GList *queue; /**< The queue of data chunks to write. */
 
 	int fd; /**< The connection's file descriptor. */
-	int inpa; /**< The connection's input handler. */
+	guint inpa; /**< The connection's input handler. */
 
 	char *rx_buf; /**< The receive buffer. */
-	int rx_len; /**< The receive buffer lenght. */
+	int rx_len; /**< The receive buffer length. */
+
+	GaimCircBuffer *tx_buf;
+	guint tx_handler;
 };
 
 /**
--- a/src/protocols/msn/nexus.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/msn/nexus.c	Thu Feb 09 04:17:56 2006 +0000
@@ -36,8 +36,8 @@
 
 	nexus = g_new0(MsnNexus, 1);
 	nexus->session = session;
-	nexus->challenge_data = g_hash_table_new_full(g_str_hash, g_str_equal,
-												  g_free, g_free);
+	nexus->challenge_data = g_hash_table_new_full(g_str_hash,
+		g_str_equal, g_free, g_free);
 
 	return nexus;
 }
@@ -45,15 +45,18 @@
 void
 msn_nexus_destroy(MsnNexus *nexus)
 {
-	if (nexus->login_host != NULL)
-		g_free(nexus->login_host);
+	g_free(nexus->login_host);
 
-	if (nexus->login_path != NULL)
-		g_free(nexus->login_path);
+	g_free(nexus->login_path);
 
 	if (nexus->challenge_data != NULL)
 		g_hash_table_destroy(nexus->challenge_data);
 
+	if (nexus->input_handler > 0)
+		gaim_input_remove(nexus->input_handler);
+	g_free(nexus->write_buf);
+	g_free(nexus->read_buf);
+
 	g_free(nexus);
 }
 
@@ -61,27 +64,58 @@
  * Util
  **************************************************************************/
 
-static size_t
-msn_ssl_read(GaimSslConnection *gsc, char **dest_buffer)
+static gssize
+msn_ssl_read(MsnNexus *nexus)
 {
-	gssize size = 0, s;
-	char *buffer = NULL;
+	gssize len;
 	char temp_buf[4096];
 
-	while ((s = gaim_ssl_read(gsc, temp_buf, sizeof(temp_buf))) > 0)
+	if ((len = gaim_ssl_read(nexus->gsc, temp_buf,
+			sizeof(temp_buf))) > 0)
 	{
-		buffer = g_realloc(buffer, size + s + 1);
-
-		strncpy(buffer + size, temp_buf, s);
-
-		buffer[size + s] = '\0';
-
-		size += s;
+		nexus->read_buf = g_realloc(nexus->read_buf,
+			nexus->read_len + len + 1);
+		strncpy(nexus->read_buf + nexus->read_len, temp_buf, len);
+		nexus->read_len += len;
+		nexus->read_buf[nexus->read_len] = '\0';
 	}
 
-	*dest_buffer = buffer;
+	return len;
+}
+
+static void
+nexus_write_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	MsnNexus *nexus = data;
+	int len, total_len;
+
+	total_len = strlen(nexus->write_buf);
+
+	len = gaim_ssl_write(nexus->gsc,
+		nexus->write_buf + nexus->written_len,
+		total_len - nexus->written_len);
 
-	return size;
+	if (len < 0 && errno == EAGAIN)
+		return;
+	else if (len <= 0) {
+		gaim_input_remove(nexus->input_handler);
+		nexus->input_handler = -1;
+		/* TODO: notify of the error */
+		return;
+	}
+	nexus->written_len += len;
+
+	if (nexus->written_len < total_len)
+		return;
+
+	gaim_input_remove(nexus->input_handler);
+	nexus->input_handler = -1;
+
+	g_free(nexus->write_buf);
+	nexus->write_buf = NULL;
+	nexus->written_len = 0;
+
+	nexus->written_cb(nexus, source, 0);
 }
 
 /**************************************************************************
@@ -89,6 +123,10 @@
  **************************************************************************/
 
 static void
+login_connect_cb(gpointer data, GaimSslConnection *gsc,
+				 GaimInputCondition cond);
+
+static void
 login_error_cb(GaimSslConnection *gsc, GaimSslErrorType error, void *data)
 {
 	MsnNexus *nexus;
@@ -106,6 +144,157 @@
 }
 
 static void
+nexus_login_written_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	MsnNexus *nexus = data;
+	MsnSession *session;
+	int len;
+
+	session = nexus->session;
+	g_return_if_fail(session != NULL);
+
+	if (nexus->input_handler == -1)
+		nexus->input_handler = gaim_input_add(nexus->gsc->fd,
+			GAIM_INPUT_READ, nexus_login_written_cb, nexus);
+
+
+	len = msn_ssl_read(nexus);
+
+	if (len < 0 && errno == EAGAIN)
+		return;
+	else if (len < 0) {
+		gaim_input_remove(nexus->input_handler);
+		nexus->input_handler = -1;
+		g_free(nexus->read_buf);
+		nexus->read_buf = NULL;
+		nexus->read_len = 0;
+		/* TODO: error handling */
+		return;
+	}
+
+	if (g_strstr_len(nexus->read_buf, nexus->read_len,
+			"\r\n\r\n") == NULL)
+		return;
+
+	gaim_input_remove(nexus->input_handler);
+	nexus->input_handler = -1;
+
+	gaim_ssl_close(nexus->gsc);
+	nexus->gsc = NULL;
+
+	gaim_debug_misc("msn", "ssl buffer: {%s}", nexus->read_buf);
+
+	if (strstr(nexus->read_buf, "HTTP/1.1 302") != NULL)
+	{
+		/* Redirect. */
+		char *location, *c;
+
+		location = strstr(nexus->read_buf, "Location: ");
+		if (location == NULL)
+		{
+			g_free(nexus->read_buf);
+			nexus->read_buf = NULL;
+			nexus->read_len = 0;
+
+			return;
+		}
+		location = strchr(location, ' ') + 1;
+
+		if ((c = strchr(location, '\r')) != NULL)
+			*c = '\0';
+
+		/* Skip the http:// */
+		if ((c = strchr(location, '/')) != NULL)
+			location = c + 2;
+
+		if ((c = strchr(location, '/')) != NULL)
+		{
+			g_free(nexus->login_path);
+			nexus->login_path = g_strdup(c);
+
+			*c = '\0';
+		}
+
+		g_free(nexus->login_host);
+		nexus->login_host = g_strdup(location);
+
+		gaim_ssl_connect(session->account, nexus->login_host,
+			GAIM_SSL_DEFAULT_PORT, login_connect_cb,
+			login_error_cb, nexus);
+	}
+	else if (strstr(nexus->read_buf, "HTTP/1.1 401 Unauthorized") != NULL)
+	{
+		const char *error;
+
+		if ((error = strstr(nexus->read_buf, "WWW-Authenticate")) != NULL)
+		{
+			if ((error = strstr(error, "cbtxt=")) != NULL)
+			{
+				const char *c;
+				char *temp;
+
+				error += strlen("cbtxt=");
+
+				if ((c = strchr(error, '\n')) == NULL)
+					c = error + strlen(error);
+
+				temp = g_strndup(error, c - error);
+				error = gaim_url_decode(temp);
+				g_free(temp);
+			}
+		}
+
+		msn_session_set_error(session, MSN_ERROR_AUTH, error);
+	}
+	else if (strstr(nexus->read_buf, "HTTP/1.1 200 OK"))
+	{
+		char *base, *c;
+		char *login_params;
+
+#if 0
+		/* All your base are belong to us. */
+		base = buffer;
+
+		/* For great cookie! */
+		while ((base = strstr(base, "Set-Cookie: ")) != NULL)
+		{
+			base += strlen("Set-Cookie: ");
+
+			c = strchr(base, ';');
+
+			session->login_cookies =
+				g_list_append(session->login_cookies,
+							  g_strndup(base, c - base));
+		}
+#endif
+
+		base  = strstr(nexus->read_buf, "Authentication-Info: ");
+
+		g_return_if_fail(base != NULL);
+
+		base  = strstr(base, "from-PP='");
+		base += strlen("from-PP='");
+		c     = strchr(base, '\'');
+
+		login_params = g_strndup(base, c - base);
+
+		msn_got_login_params(session, login_params);
+
+		g_free(login_params);
+
+		msn_nexus_destroy(nexus);
+		session->nexus = NULL;
+		return;
+	}
+
+	g_free(nexus->read_buf);
+	nexus->read_buf = NULL;
+	nexus->read_len = 0;
+
+}
+
+
+void
 login_connect_cb(gpointer data, GaimSslConnection *gsc,
 				 GaimInputCondition cond)
 {
@@ -115,7 +304,6 @@
 	char *request_str, *head, *tail;
 	char *buffer = NULL;
 	guint32 ctint;
-	size_t s;
 
 	nexus = data;
 	g_return_if_fail(nexus != NULL);
@@ -123,6 +311,8 @@
 	session = nexus->session;
 	g_return_if_fail(session != NULL);
 
+	nexus->gsc = gsc;
+
 	msn_session_set_login_step(session, MSN_LOGIN_STEP_GET_COOKIE);
 
 	username =
@@ -169,168 +359,64 @@
 	g_free(username);
 	g_free(password);
 
-	if ((s = gaim_ssl_write(gsc, request_str, strlen(request_str))) <= 0)
-	{
-		g_free(request_str);
+	nexus->write_buf = request_str;
+	nexus->written_len = 0;
+
+	nexus->read_len = 0;
+
+	nexus->written_cb = nexus_login_written_cb;
+
+	nexus->input_handler = gaim_input_add(gsc->fd, GAIM_INPUT_WRITE,
+		nexus_write_cb, nexus);
+
+	nexus_write_cb(nexus, gsc->fd, GAIM_INPUT_WRITE);
+
+	return;
+
+
+}
 
+static void
+nexus_connect_written_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	MsnNexus *nexus = data;
+	int len;
+	char *da_login;
+	char *base, *c;
+
+	if (nexus->input_handler == -1)
+		nexus->input_handler = gaim_input_add(nexus->gsc->fd,
+			GAIM_INPUT_READ, nexus_connect_written_cb, nexus);
+
+	/* Get the PassportURLs line. */
+	len = msn_ssl_read(nexus);
+
+	if (len < 0 && errno == EAGAIN)
+		return;
+	else if (len < 0) {
+		gaim_input_remove(nexus->input_handler);
+		nexus->input_handler = -1;
+		g_free(nexus->read_buf);
+		nexus->read_buf = NULL;
+		nexus->read_len = 0;
+		/* TODO: error handling */
 		return;
 	}
 
-	g_free(request_str);
-
-	if ((s = msn_ssl_read(gsc, &buffer)) <= 0)
+	if (g_strstr_len(nexus->read_buf, nexus->read_len,
+			"\r\n\r\n") == NULL)
 		return;
 
-	gaim_ssl_close(gsc);
-
-	gaim_debug_misc("msn", "ssl buffer: {%s}", buffer);
-
-	if (strstr(buffer, "HTTP/1.1 302") != NULL)
-	{
-		/* Redirect. */
-		char *location, *c;
-
-		location = strstr(buffer, "Location: ");
-		if (location == NULL)
-		{
-			g_free(buffer);
-
-			return;
-		}
-		location = strchr(location, ' ') + 1;
-
-		if ((c = strchr(location, '\r')) != NULL)
-			*c = '\0';
-
-		/* Skip the http:// */
-		if ((c = strchr(location, '/')) != NULL)
-			location = c + 2;
-
-		if ((c = strchr(location, '/')) != NULL)
-		{
-			g_free(nexus->login_path);
-			nexus->login_path = g_strdup(c);
-
-			*c = '\0';
-		}
-
-		g_free(nexus->login_host);
-		nexus->login_host = g_strdup(location);
-
-		gaim_ssl_connect(session->account, nexus->login_host,
-						 GAIM_SSL_DEFAULT_PORT, login_connect_cb,
-						 login_error_cb, nexus);
-	}
-	else if (strstr(buffer, "HTTP/1.1 401 Unauthorized") != NULL)
-	{
-		const char *error;
-
-		if ((error = strstr(buffer, "WWW-Authenticate")) != NULL)
-		{
-			if ((error = strstr(error, "cbtxt=")) != NULL)
-			{
-				const char *c;
-				char *temp;
-
-				error += strlen("cbtxt=");
-
-				if ((c = strchr(error, '\n')) == NULL)
-					c = error + strlen(error);
-
-				temp = g_strndup(error, c - error);
-				error = gaim_url_decode(temp);
-				g_free(temp);
-			}
-		}
-
-		msn_session_set_error(session, MSN_ERROR_AUTH, error);
-	}
-	else if (strstr(buffer, "HTTP/1.1 200 OK"))
-	{
-		char *base, *c;
-		char *login_params;
-
-#if 0
-		/* All your base are belong to us. */
-		base = buffer;
+	gaim_input_remove(nexus->input_handler);
+	nexus->input_handler = -1;
 
-		/* For great cookie! */
-		while ((base = strstr(base, "Set-Cookie: ")) != NULL)
-		{
-			base += strlen("Set-Cookie: ");
-
-			c = strchr(base, ';');
-
-			session->login_cookies =
-				g_list_append(session->login_cookies,
-							  g_strndup(base, c - base));
-		}
-#endif
-
-		base  = strstr(buffer, "Authentication-Info: ");
-
-		g_return_if_fail(base != NULL);
-
-		base  = strstr(base, "from-PP='");
-		base += strlen("from-PP='");
-		c     = strchr(base, '\'');
-
-		login_params = g_strndup(base, c - base);
-
-		msn_got_login_params(session, login_params);
-
-		g_free(login_params);
-
-		msn_nexus_destroy(nexus);
-		session->nexus = NULL;
-	}
-
-	g_free(buffer);
-}
-
-/**************************************************************************
- * Connect
- **************************************************************************/
-
-static void
-nexus_connect_cb(gpointer data, GaimSslConnection *gsc,
-				 GaimInputCondition cond)
-{
-	MsnNexus *nexus;
-	MsnSession *session;
-	char *request_str;
-	char *da_login;
-	char *base, *c;
-	char *buffer = NULL;
-	size_t s;
-
-	nexus = data;
-	g_return_if_fail(nexus != NULL);
-
-	session = nexus->session;
-	g_return_if_fail(session != NULL);
-
-	msn_session_set_login_step(session, MSN_LOGIN_STEP_AUTH);
-
-	request_str = g_strdup_printf("GET /rdr/pprdr.asp\r\n\r\n");
-
-	if ((s = gaim_ssl_write(gsc, request_str, strlen(request_str))) <= 0)
-	{
-		g_free(request_str);
-		return;
-	}
-
-	g_free(request_str);
-
-	/* Get the PassportURLs line. */
-	if ((s = msn_ssl_read(gsc, &buffer)) <= 0)
-		return;
-
-	base = strstr(buffer, "PassportURLs");
+	base = strstr(nexus->read_buf, "PassportURLs");
 
 	if (base == NULL)
 	{
-		g_free(buffer);
+		g_free(nexus->read_buf);
+		nexus->read_buf = NULL;
+		nexus->read_len = 0;
 		return;
 	}
 
@@ -345,27 +431,64 @@
 		if ((c = strchr(da_login, '/')) != NULL)
 		{
 			nexus->login_path = g_strdup(c);
-
 			*c = '\0';
 		}
 
 		nexus->login_host = g_strdup(da_login);
 	}
 
-	g_free(buffer);
+	g_free(nexus->read_buf);
+	nexus->read_buf = NULL;
+	nexus->read_len = 0;
 
-	gaim_ssl_close(gsc);
+	gaim_ssl_close(nexus->gsc);
+	nexus->gsc = NULL;
 
 	/* Now begin the connection to the login server. */
-	gaim_ssl_connect(session->account, nexus->login_host,
-					 GAIM_SSL_DEFAULT_PORT, login_connect_cb,
-					 login_error_cb, nexus);
+	gaim_ssl_connect(nexus->session->account, nexus->login_host,
+		GAIM_SSL_DEFAULT_PORT, login_connect_cb, login_error_cb,
+		nexus);
+}
+
+
+/**************************************************************************
+ * Connect
+ **************************************************************************/
+
+static void
+nexus_connect_cb(gpointer data, GaimSslConnection *gsc,
+				 GaimInputCondition cond)
+{
+	MsnNexus *nexus;
+	MsnSession *session;
+
+	nexus = data;
+	g_return_if_fail(nexus != NULL);
+
+	session = nexus->session;
+	g_return_if_fail(session != NULL);
+
+	nexus->gsc = gsc;
+
+	msn_session_set_login_step(session, MSN_LOGIN_STEP_AUTH);
+
+	nexus->write_buf = g_strdup("GET /rdr/pprdr.asp\r\n\r\n");
+	nexus->written_len = 0;
+
+	nexus->read_len = 0;
+
+	nexus->written_cb = nexus_connect_written_cb;
+
+	nexus->input_handler = gaim_input_add(gsc->fd, GAIM_INPUT_WRITE,
+		nexus_write_cb, nexus);
+
+	nexus_write_cb(nexus, gsc->fd, GAIM_INPUT_WRITE);
 }
 
 void
 msn_nexus_connect(MsnNexus *nexus)
 {
 	gaim_ssl_connect(nexus->session->account, "nexus.passport.com",
-					 GAIM_SSL_DEFAULT_PORT, nexus_connect_cb,
-					 login_error_cb, nexus);
+		GAIM_SSL_DEFAULT_PORT, nexus_connect_cb,
+		login_error_cb, nexus);
 }
--- a/src/protocols/msn/nexus.h	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/msn/nexus.h	Thu Feb 09 04:17:56 2006 +0000
@@ -26,8 +26,6 @@
 
 typedef struct _MsnNexus MsnNexus;
 
-#include "nexus.h"
-
 struct _MsnNexus
 {
 	MsnSession *session;
@@ -35,6 +33,16 @@
 	char *login_host;
 	char *login_path;
 	GHashTable *challenge_data;
+	GaimSslConnection *gsc;
+
+	guint input_handler;
+
+	char *write_buf;
+	gsize written_len;
+	GaimInputFunction written_cb;
+
+	char *read_buf;
+	gsize read_len;
 };
 
 void msn_nexus_connect(MsnNexus *nexus);
--- a/src/protocols/msn/servconn.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/msn/servconn.c	Thu Feb 09 04:17:56 2006 +0000
@@ -50,6 +50,9 @@
 
 	servconn->num = session->servconns_count++;
 
+	servconn->tx_buf = gaim_circ_buffer_new(MSN_BUF_LEN);
+	servconn->tx_handler = -1;
+
 	return servconn;
 }
 
@@ -73,8 +76,11 @@
 	if (servconn->httpconn != NULL)
 		msn_httpconn_destroy(servconn->httpconn);
 
-	if (servconn->host != NULL)
-		g_free(servconn->host);
+	g_free(servconn->host);
+
+	gaim_circ_buffer_destroy(servconn->tx_buf);
+	if (servconn->tx_handler > 0)
+		gaim_input_remove(servconn->tx_handler);
 
 	msn_cmdproc_destroy(servconn->cmdproc);
 	g_free(servconn);
@@ -181,7 +187,7 @@
 		/* Someone wants to know we connected. */
 		servconn->connect_cb(servconn);
 		servconn->inpa = gaim_input_add(servconn->fd, GAIM_INPUT_READ,
-										read_cb, data);
+			read_cb, data);
 	}
 	else
 	{
@@ -227,7 +233,7 @@
 	}
 
 	r = gaim_proxy_connect(session->account, host, port, connect_cb,
-						   servconn);
+		servconn);
 
 	if (r == 0)
 	{
@@ -279,30 +285,72 @@
 		servconn->disconnect_cb(servconn);
 }
 
+static void
+servconn_write_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	MsnServConn *servconn = data;
+	int ret, writelen;
+
+	writelen = gaim_circ_buffer_get_max_read(servconn->tx_buf);
+
+	if (writelen == 0) {
+		gaim_input_remove(servconn->tx_handler);
+		servconn->tx_handler = -1;
+		return;
+	}
+
+	ret = write(servconn->fd, servconn->tx_buf->outptr, writelen);
+
+	if (ret < 0 && errno == EAGAIN)
+		return;
+	else if (ret <= 0) {
+		msn_servconn_got_error(servconn, MSN_SERVCONN_ERROR_WRITE);
+		return;
+	}
+
+	gaim_circ_buffer_mark_read(servconn->tx_buf, ret);
+}
+
 size_t
 msn_servconn_write(MsnServConn *servconn, const char *buf, size_t len)
 {
-	size_t ret = FALSE;
+	size_t ret = 0;
 
 	g_return_val_if_fail(servconn != NULL, 0);
 
 	if (!servconn->session->http_method)
 	{
-		switch (servconn->type)
-		{
-			case MSN_SERVCONN_NS:
-			case MSN_SERVCONN_SB:
-				ret = write(servconn->fd, buf, len);
-				break;
+		if (servconn->tx_handler == -1) {
+			switch (servconn->type)
+			{
+				case MSN_SERVCONN_NS:
+				case MSN_SERVCONN_SB:
+					ret = write(servconn->fd, buf, len);
+					break;
 #if 0
-			case MSN_SERVCONN_DC:
-				ret = write(servconn->fd, &buf, sizeof(len));
-				ret = write(servconn->fd, buf, len);
-				break;
+				case MSN_SERVCONN_DC:
+					ret = write(servconn->fd, &buf, sizeof(len));
+					ret = write(servconn->fd, buf, len);
+					break;
 #endif
-			default:
-				ret = write(servconn->fd, buf, len);
-				break;
+				default:
+					ret = write(servconn->fd, buf, len);
+					break;
+			}
+		} else {
+			ret = -1;
+			errno = EAGAIN;
+		}
+
+		if (ret < 0 && errno == EAGAIN)
+			ret = 0;
+		if (ret < len) {
+			if (servconn->tx_handler == -1)
+				servconn->tx_handler = gaim_input_add(
+					servconn->fd, GAIM_INPUT_WRITE,
+					servconn_write_cb, servconn);
+			gaim_circ_buffer_append(servconn->tx_buf, buf + ret,
+				len - ret);
 		}
 	}
 	else
@@ -332,7 +380,9 @@
 
 	len = read(servconn->fd, buf, sizeof(buf) - 1);
 
-	if (len <= 0)
+	if (len < 0 && errno == EAGAIN)
+		return;
+	else if (len <= 0)
 	{
 		gaim_debug_error("msn", "servconn read error, len: %d error: %s\n", len, strerror(errno));
 		msn_servconn_got_error(servconn, MSN_SERVCONN_ERROR_READ);
--- a/src/protocols/msn/servconn.h	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/msn/servconn.h	Thu Feb 09 04:17:56 2006 +0000
@@ -84,6 +84,9 @@
 						  It's only set when we've received a command that
 						  has a payload. */
 
+	GaimCircBuffer *tx_buf;
+	guint tx_handler;
+
 	void (*connect_cb)(MsnServConn *); /**< The callback to call when connecting. */
 	void (*disconnect_cb)(MsnServConn *); /**< The callback to call when disconnecting. */
 	void (*destroy_cb)(MsnServConn *); /**< The callback to call when destroying. */
--- a/src/protocols/napster/napster.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/napster/napster.c	Thu Feb 09 04:17:56 2006 +0000
@@ -508,6 +508,11 @@
 		return;
 	}
 
+	/* Clear the nonblocking flag
+	   This protocol should be updated to support nonblocking I/O if
+	   anyone is going to actually use it */
+	fcntl(source, F_SETFL, 0);
+
 	ndata->fd = source;
 
 	/* Update the login progress status display */
--- a/src/protocols/oscar/oscar.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/oscar/oscar.c	Thu Feb 09 04:17:56 2006 +0000
@@ -51,8 +51,8 @@
 #define OSCAR_STATUS_ID_OFFLINE		"offline"
 #define OSCAR_STATUS_ID_AVAILABLE	"available"
 #define OSCAR_STATUS_ID_AWAY		"away"
-#define OSCAR_STATUS_ID_DND			"dnd"
-#define OSCAR_STATUS_ID_NA			"na"
+#define OSCAR_STATUS_ID_DND		"dnd"
+#define OSCAR_STATUS_ID_NA		"na"
 #define OSCAR_STATUS_ID_OCCUPIED	"occupied"
 #define OSCAR_STATUS_ID_FREE4CHAT	"free4chat"
 #define OSCAR_STATUS_ID_CUSTOM		"custom"
@@ -1018,6 +1018,9 @@
 
 	g_return_if_fail(gc != NULL);
 
+	/* XXX:NBIO remove when nonblocking I/O implemented for oscar */
+	fcntl(source, F_SETFL, 0);
+
 	dim->gpc_pend = FALSE;
 	if (dim->killme) {
 		oscar_direct_im_destroy(od, dim);
@@ -1780,6 +1783,9 @@
 		return;
 	}
 
+	/* XXX:NBIO remove when nonblocking I/O implemented for oscar */
+	fcntl(source, F_SETFL, 0);
+
 	od = gc->proto_data;
 	sess = od->sess;
 	conn = aim_getconn_type_all(sess, AIM_CONN_TYPE_AUTH);
@@ -1949,6 +1955,9 @@
 		return;
 	}
 
+	/* XXX:NBIO remove when nonblocking I/O implemented for oscar */
+	fcntl(source, F_SETFL, 0);
+
 	aim_conn_completeconnect(sess, bosconn);
 	gc->inpa = gaim_input_add(bosconn->fd, GAIM_INPUT_READ, oscar_callback, bosconn);
 
@@ -2310,6 +2319,9 @@
 	struct aim_oft_info *oft_info;
 	struct aim_rv_proxy_info *proxy_info;
 
+	/* XXX:NBIO remove when nonblocking I/O implemented for oscar */
+	fcntl(fd, F_SETFL, 0);
+
 	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");
@@ -3063,6 +3075,9 @@
 		return;
 	}
 
+	/* XXX:NBIO remove when nonblocking I/O implemented for oscar */
+	fcntl(source, F_SETFL, 0);
+
 	buf = g_strdup_printf("GET " AIMHASHDATA "?offset=%ld&len=%ld&modname=%s HTTP/1.0\n\n",
 			pos->offset, pos->len, pos->modname ? pos->modname : "");
 	write(pos->fd, buf, strlen(buf));
@@ -3256,6 +3271,9 @@
 		return;
 	}
 
+	/* XXX:NBIO remove when nonblocking I/O implemented for oscar */
+	fcntl(source, F_SETFL, 0);
+
 	aim_conn_completeconnect(sess, tstconn);
 	od->cnpa = gaim_input_add(tstconn->fd, GAIM_INPUT_READ, oscar_callback, tstconn);
 	gaim_debug_info("oscar", "chatnav: connected\n");
@@ -3285,6 +3303,9 @@
 		return;
 	}
 
+	/* XXX:NBIO remove when nonblocking I/O implemented for oscar */
+	fcntl(source, F_SETFL, 0);
+
 	aim_conn_completeconnect(sess, tstconn);
 	od->paspa = gaim_input_add(tstconn->fd, GAIM_INPUT_READ, oscar_callback, tstconn);
 	gaim_debug_info("oscar", "admin: connected\n");
@@ -3319,6 +3340,9 @@
 		return;
 	}
 
+	/* XXX:NBIO remove when nonblocking I/O implemented for oscar */
+	fcntl(source, F_SETFL, 0);
+
 	aim_conn_completeconnect(sess, ccon->conn);
 	ccon->inpa = gaim_input_add(tstconn->fd, GAIM_INPUT_READ, oscar_callback, tstconn);
 	od->oscar_chats = g_slist_append(od->oscar_chats, ccon);
@@ -3347,6 +3371,9 @@
 		return;
 	}
 
+	/* XXX:NBIO remove when nonblocking I/O implemented for oscar */
+	fcntl(source, F_SETFL, 0);
+
 	aim_conn_completeconnect(sess, tstconn);
 	od->emlpa = gaim_input_add(tstconn->fd, GAIM_INPUT_READ, oscar_callback, tstconn);
 	gaim_debug_info("oscar",
@@ -3376,6 +3403,9 @@
 		return;
 	}
 
+	/* XXX:NBIO remove when nonblocking I/O implemented for oscar */
+	fcntl(source, F_SETFL, 0);
+
 	aim_conn_completeconnect(sess, tstconn);
 	od->icopa = gaim_input_add(tstconn->fd, GAIM_INPUT_READ, oscar_callback, tstconn);
 	gaim_debug_info("oscar", "icon: connected\n");
@@ -3817,14 +3847,18 @@
 	if(oft_info->success) {
 		gaim_debug_info("oscar","connection already successful; ignoring 2nd conn\n");
 		return;
-	}		
+	}
+
 	if (source < 0) {
 		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 */
-	
+
+	/* XXX:NBIO remove when nonblocking I/O implemented for oscar */
+	fcntl(source, F_SETFL, 0);
+
 	/* 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;
--- a/src/protocols/simple/simple.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/simple/simple.c	Thu Feb 09 04:17:56 2006 +0000
@@ -69,7 +69,7 @@
 	struct simple_account_data *sip = gc->proto_data;
 	if(sip->udp) { /* in case of UDP send a packet only with a 0 byte to
 			 remain in the NAT table */
-		gchar buf[2]={0,0};
+		gchar buf[2] = {0, 0};
 		gaim_debug_info("simple", "sending keep alive\n");
 		sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
 	}
@@ -163,7 +163,7 @@
 }
 
 static struct sip_connection *connection_create(struct simple_account_data *sip, int fd) {
-	struct sip_connection *ret = g_new0(struct sip_connection,1);
+	struct sip_connection *ret = g_new0(struct sip_connection, 1);
 	ret->fd = fd;
 	sip->openconns = g_slist_append(sip->openconns, ret);
 	return ret;
@@ -191,25 +191,25 @@
 {
 	struct simple_account_data *sip = (struct simple_account_data *)gc->proto_data;
 	struct simple_buddy *b;
-	if(strncmp("sip:", buddy->name,4)) {
-		gchar *buf = g_strdup_printf("sip:%s",buddy->name);
+	if(strncmp("sip:", buddy->name, 4)) {
+		gchar *buf = g_strdup_printf("sip:%s", buddy->name);
 		gaim_blist_rename_buddy(buddy, buf);
 		g_free(buf);
 	}
 	if(!g_hash_table_lookup(sip->buddies, buddy->name)) {
 		b = g_new0(struct simple_buddy, 1);
-		gaim_debug_info("simple","simple_add_buddy %s\n",buddy->name);
+		gaim_debug_info("simple", "simple_add_buddy %s\n", buddy->name);
 		b->name = g_strdup(buddy->name);
 		g_hash_table_insert(sip->buddies, b->name, b);
 	} else {
-		gaim_debug_info("simple","buddy %s already in internal list\n", buddy->name);
+		gaim_debug_info("simple", "buddy %s already in internal list\n", buddy->name);
 	}
 }
 
 static void simple_get_buddies(GaimConnection *gc) {
 	GaimBlistNode *gnode, *cnode, *bnode;
 
-	gaim_debug_info("simple","simple_get_buddies\n");
+	gaim_debug_info("simple", "simple_get_buddies\n");
 
 	for(gnode = gaim_get_blist()->root; gnode; gnode = gnode->next) {
 		if(!GAIM_BLIST_NODE_IS_GROUP(gnode)) continue;
@@ -250,7 +250,8 @@
 	return types;
 }
 
-static gchar *auth_header(struct simple_account_data *sip, struct sip_auth *auth, gchar *method, gchar *target) {
+static gchar *auth_header(struct simple_account_data *sip,
+		struct sip_auth *auth, const gchar *method, const gchar *target) {
 	gchar noncecount[9];
 	gchar *response;
 	gchar *ret;
@@ -261,7 +262,7 @@
 	authdomain = gaim_account_get_string(sip->account, "authdomain", "");
 	authuser = gaim_account_get_string(sip->account, "authuser", sip->username);
 
-	if(!authuser || strlen(authuser)<1) {
+	if(!authuser || strlen(authuser) < 1) {
 		authuser = sip->username;
 	}
 
@@ -297,7 +298,7 @@
 	return ret;
 }
 
-static char * parse_attribute(const char *attrname, char *source) {
+static char *parse_attribute(const char *attrname, char *source) {
 	char *tmp, *tmp2, *retval = NULL;
 	int len = strlen(attrname);
 
@@ -314,14 +315,14 @@
 }
 
 static void fill_auth(struct simple_account_data *sip, gchar *hdr, struct sip_auth *auth) {
-	int i=0;
+	int i = 0;
 	const char *authuser;
 	char *tmp;
 	gchar **parts;
 
 	authuser = gaim_account_get_string(sip->account, "authuser", sip->username);
 
-	if(!authuser || strlen(authuser)<1) {
+	if(!authuser || strlen(authuser) < 1) {
 		authuser = sip->username;
 	}
 
@@ -334,11 +335,11 @@
 		gaim_debug_info("simple", "found NTLM\n");
 		auth->type = 2;
 		if(!strstr(hdr, "gssapi-data")) {
-			gaim_debug_info("simple","here");
+			gaim_debug_info("simple", "here");
 			parts = g_strsplit(hdr+5, "\", ", 0);
 			i = 0;
 			while(parts[i]) {
-			gaim_debug_info("simple","parts[i] %s\n",parts[i]);
+			gaim_debug_info("simple", "parts[i] %s\n", parts[i]);
 				if((tmp = parse_attribute("targetname=\"",
 						parts[i]))) {
 					auth->target = tmp;
@@ -388,7 +389,34 @@
 	auth->digest_session_key = gaim_cipher_http_digest_calculate_session_key(
 			"md5", authuser, auth->realm, sip->password, auth->nonce, NULL);
 
-	auth->nc=1;
+	auth->nc = 1;
+}
+
+static void simple_canwrite_cb(gpointer data, gint source, GaimInputCondition cond) {
+	GaimConnection *gc = data;
+	struct simple_account_data *sip = gc->proto_data;
+	gsize max_write;
+	gssize written;
+
+	max_write = gaim_circ_buffer_get_max_read(sip->txbuf);
+
+	if(max_write == 0) {
+		gaim_input_remove(sip->tx_handler);
+		sip->tx_handler = 0;
+		return;
+	}
+
+	written = write(sip->fd, sip->txbuf->outptr, max_write);
+
+	if(written < 0 && errno == EAGAIN)
+		written = 0;
+	else if(written <= 0) {
+		/*TODO: do we really want to disconnect on a failure to write?*/
+		gaim_connection_error(gc, _("Could not write"));
+		return;
+	}
+
+	gaim_circ_buffer_mark_read(sip->txbuf, written);
 }
 
 static void simple_input_cb(gpointer data, gint source, GaimInputCondition cond);
@@ -398,18 +426,23 @@
 	struct simple_account_data *sip = gc->proto_data;
 	struct sip_connection *conn;
 
-	if( source < 0 ) {
-		gaim_connection_error(gc,"Could not connect");
+	if(source < 0) {
+		gaim_connection_error(gc, _("Could not connect"));
 		return;
 	}
 
 	sip->fd = source;
 	sip->connecting = FALSE;
-	write(sip->fd, sip->sendlater, strlen(sip->sendlater));
+
+	simple_canwrite_cb(gc, sip->fd, GAIM_INPUT_WRITE);
+
+	/* If there is more to write now, we need to register a handler */
+	if(sip->txbuf->bufused > 0)
+		sip->tx_handler = gaim_input_add(sip->fd, GAIM_INPUT_WRITE,
+			simple_canwrite_cb, gc);
+
 	conn = connection_create(sip, source);
 	conn->inputhandler = gaim_input_add(sip->fd, GAIM_INPUT_READ, simple_input_cb, gc);
-	g_free(sip->sendlater);
-	sip->sendlater = NULL;
 }
 
 
@@ -417,44 +450,65 @@
 	struct simple_account_data *sip = gc->proto_data;
 	int error = 0;
 	if(!sip->connecting) {
-		gaim_debug_info("simple","connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
+		gaim_debug_info("simple", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
 		error = gaim_proxy_connect(sip->account, sip->realhostname, sip->realport, send_later_cb, gc);
 		if(error) {
 			gaim_connection_error(gc, _("Couldn't create socket"));
 		}
 		sip->connecting = TRUE;
 	}
-	if(sip->sendlater) {
-		gchar *old = sip->sendlater;
-		sip->sendlater = g_strdup_printf("%s\r\n%s",old, buf);
-		g_free(old);
-	} else {
-		sip->sendlater = g_strdup(buf);
-	}
+
+	if(gaim_circ_buffer_get_max_read(sip->txbuf) > 0)
+		gaim_circ_buffer_append(sip->txbuf, "\r\n", 2);
+
+	gaim_circ_buffer_append(sip->txbuf, buf, strlen(buf));
 }
 
-static int sendout_pkt(GaimConnection *gc, const char *buf) {
+static void sendout_pkt(GaimConnection *gc, const char *buf) {
 	struct simple_account_data *sip = gc->proto_data;
 	time_t currtime = time(NULL);
-	int ret = 0;
+	int writelen = strlen(buf);
 
 	gaim_debug(GAIM_DEBUG_MISC, "simple", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
 	if(sip->udp) {
-		if(sendto(sip->fd, buf, strlen(buf), 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < strlen(buf)) {
+		if(sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
 			gaim_debug_info("simple", "could not send packet\n");
 		}
 	} else {
-		if(sip->fd <0 ) {
+		int ret;
+		if(sip->fd < 0) {
 			sendlater(gc, buf);
-			return 0;
+			return;
 		}
-		ret = write(sip->fd, buf, strlen(buf));
-		if(ret < 0) {
-			sendlater(gc,buf);
-			return 0;
+
+		if(sip->tx_handler) {
+			ret = -1;
+			errno = EAGAIN;
+		} else
+			ret = write(sip->fd, buf, writelen);
+
+		if (ret < 0 && errno == EAGAIN)
+			ret = 0;
+		else if(ret <= 0) { /* XXX: When does this happen legitimately? */
+			sendlater(gc, buf);
+			return;
+		}
+
+		if (ret < writelen) {
+			if(!sip->tx_handler)
+				sip->tx_handler = gaim_input_add(sip->fd,
+					GAIM_INPUT_WRITE, simple_canwrite_cb,
+					gc);
+
+			/* XXX: is it OK to do this? You might get part of a request sent
+			   with part of another. */
+			if(sip->txbuf->bufused > 0)
+				gaim_circ_buffer_append(sip->txbuf, "\r\n", 2);
+
+			gaim_circ_buffer_append(sip->txbuf, buf + ret,
+				writelen - ret);
 		}
 	}
-	return ret;
 }
 
 static void sendout_sipmsg(struct simple_account_data *sip, struct sipmsg *msg) {
@@ -487,7 +541,7 @@
 	if(body) {
 		gchar len[12];
 		sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
-		sipmsg_add_header(msg, "Content-Length",len);
+		sipmsg_add_header(msg, "Content-Length", len);
 	}
 	else
 		sipmsg_add_header(msg, "Content-Length", "0");
@@ -532,18 +586,20 @@
 		transactions = transactions->next;
 	}
 
-	return (struct transaction *)NULL;
+	return NULL;
 }
 
-static void send_sip_request(GaimConnection *gc, gchar *method, gchar *url, gchar *to, gchar *addheaders, gchar *body, struct sip_dialog *dialog, TransCallback tc) {
+static void send_sip_request(GaimConnection *gc, const gchar *method,
+		const gchar *url, const gchar *to, const gchar *addheaders,
+		const gchar *body, struct sip_dialog *dialog, TransCallback tc) {
 	struct simple_account_data *sip = gc->proto_data;
 	char *callid = dialog ? g_strdup(dialog->callid) : gencallid();
 	char *auth = "";
-	char *addh = "";
+	const char *addh = "";
 	gchar *branch = genbranch();
 	char *buf;
 
-	if(!strcmp(method,"REGISTER")) {
+	if(!strcmp(method, "REGISTER")) {
 		if(sip->regcallid) {
 			g_free(callid);
 			callid = g_strdup(sip->regcallid);
@@ -552,14 +608,14 @@
 	}
 
 	if(addheaders) addh = addheaders;
-	if(sip->registrar.type && !strcmp(method,"REGISTER")) {
+	if(sip->registrar.type && !strcmp(method, "REGISTER")) {
 		buf = auth_header(sip, &sip->registrar, method, url);
 		auth = g_strdup_printf("Authorization: %s", buf);
 		g_free(buf);
 		gaim_debug(GAIM_DEBUG_MISC, "simple", "header %s", auth);
 	}
 
-	if(sip->proxy.type && strcmp(method,"REGISTER")) {
+	if(sip->proxy.type && strcmp(method, "REGISTER")) {
 		buf = auth_header(sip, &sip->proxy, method, url);
 		auth = g_strdup_printf("Proxy-Authorization: %s", buf);
 		g_free(buf);
@@ -603,7 +659,7 @@
 
 	transactions_add_buf(sip, buf, tc);
 
-	sendout_pkt(gc,buf);
+	sendout_pkt(gc, buf);
 
 	g_free(buf);
 }
@@ -613,8 +669,8 @@
 }
 
 static void do_register_exp(struct simple_account_data *sip, int expire) {
-	char *uri = g_strdup_printf("sip:%s",sip->servername);
-	char *to = g_strdup_printf("sip:%s@%s",sip->username,sip->servername);
+	char *uri = g_strdup_printf("sip:%s", sip->servername);
+	char *to = g_strdup_printf("sip:%s@%s", sip->username, sip->servername);
 	char *contact = get_contact(sip);
 	char *hdr = g_strdup_printf("Contact: %s\r\nExpires: %d\r\n", contact, expire);
 	g_free(contact);
@@ -626,7 +682,10 @@
 	} else {
 		sip->reregister = time(NULL) + 600;
 	}
-	send_sip_request(sip->gc,"REGISTER",uri,to, hdr, "", NULL, process_register_response);
+
+	send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
+		process_register_response);
+
 	g_free(hdr);
 	g_free(uri);
 	g_free(to);
@@ -641,15 +700,15 @@
 	gchar *tmp;
 
 	if(!from) return NULL;
-	gaim_debug_info("simple", "parsing address out of %s\n",from);
+	gaim_debug_info("simple", "parsing address out of %s\n", from);
 	tmp = strchr(from, '<');
 
 	/* i hate the different SIP UA behaviours... */
 	if(tmp) { /* sip address in <...> */
 		from = tmp+1;
-		tmp = strchr(from,'>');
+		tmp = strchr(from, '>');
 		if(tmp) {
-			from = g_strndup(from,tmp-from);
+			from = g_strndup(from, tmp-from);
 		} else {
 			gaim_debug_info("simple", "found < without > in From\n");
 			return NULL;
@@ -657,19 +716,19 @@
 	} else {
 		tmp = strchr(from, ';');
 		if(tmp) {
-			from = g_strndup(from,tmp-from);
+			from = g_strndup(from, tmp-from);
 		} else {
 			from = g_strdup(from);
 		}
 	}
-	gaim_debug_info("simple", "got %s\n",from);
+	gaim_debug_info("simple", "got %s\n", from);
 	return from;
 }
 
 static gboolean process_subscribe_response(struct simple_account_data *sip, struct sipmsg *msg, struct transaction *tc) {
-	gchar *to = parse_from(sipmsg_find_header(tc->msg,"To")); /* cant be NULL since it is our own msg */
+	gchar *to = parse_from(sipmsg_find_header(tc->msg, "To")); /* cant be NULL since it is our own msg */
 
-	if(msg->response==200 || msg->response==202) {
+	if(msg->response == 200 || msg->response == 202) {
 		return TRUE;
 	}
 
@@ -684,15 +743,21 @@
 	gchar *contact = "Expires: 300\r\nAccept: application/pidf+xml, application/xpidf+xml\r\nEvent: presence\r\n";
 	gchar *to;
 	gchar *tmp;
-	if(strstr(buddy->name,"sip:")) to = g_strdup(buddy->name);
-	else to = g_strdup_printf("sip:%s",buddy->name);
+
+	if(strstr(buddy->name,"sip:"))
+		to = g_strdup(buddy->name);
+	else
+		to = g_strdup_printf("sip:%s", buddy->name);
+
 	tmp = get_contact(sip);
 	contact = g_strdup_printf("%sContact: %s\r\n", contact, tmp);
 	g_free(tmp);
+
 	/* subscribe to buddy presence
 	 * we dont need to know the status so we do not need a callback */
 
-	send_sip_request(sip->gc, "SUBSCRIBE",to, to, contact, "", NULL, process_subscribe_response);
+	send_sip_request(sip->gc, "SUBSCRIBE", to, to, contact, "", NULL,
+		process_subscribe_response);
 
 	g_free(to);
 	g_free(contact);
@@ -704,9 +769,9 @@
 
 static void simple_buddy_resub(char *name, struct simple_buddy *buddy, struct simple_account_data *sip) {
 	time_t curtime = time(NULL);
-	gaim_debug_info("simple","buddy resub\n");
+	gaim_debug_info("simple", "buddy resub\n");
 	if(buddy->resubscribe < curtime) {
-		gaim_debug(GAIM_DEBUG_MISC, "simple", "simple_buddy_resub %s\n",name);
+		gaim_debug(GAIM_DEBUG_MISC, "simple", "simple_buddy_resub %s\n", name);
 		simple_subscribe(sip, buddy);
 	}
 }
@@ -755,16 +820,16 @@
 	return TRUE;
 }
 
-static void simple_send_message(struct simple_account_data *sip, char *to, char *msg, char *type) {
+static void simple_send_message(struct simple_account_data *sip, const char *to, const char *msg, const char *type) {
 	gchar *hdr;
 	gchar *fullto;
-	if(strncmp("sip:",to,4)) {
-		fullto = g_strdup_printf("sip:%s",to);
+	if(strncmp("sip:", to, 4)) {
+		fullto = g_strdup_printf("sip:%s", to);
 	} else {
 		fullto = g_strdup(to);
 	}
 	if(type) {
-		hdr = g_strdup_printf("Content-Type: %s\r\n",type);
+		hdr = g_strdup_printf("Content-Type: %s\r\n", type);
 	} else {
 		hdr = g_strdup("Content-Type: text/plain\r\n");
 	}
@@ -800,26 +865,26 @@
 		send_sip_response(sip->gc, msg, 200, "OK", NULL);
 		found = TRUE;
 	}
-	if(!strncmp(contenttype, "application/im-iscomposing+xml",30)) {
+	if(!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
 		xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
 		xmlnode *state;
 		gchar *statedata;
 
 		if(!isc) {
-			gaim_debug_info("simple","process_incoming_message: can not parse iscomposing\n");
+			gaim_debug_info("simple", "process_incoming_message: can not parse iscomposing\n");
 			return;
 		}
 
 		state = xmlnode_get_child(isc, "state");
 
 		if(!state) {
-			gaim_debug_info("simple","process_incoming_message: no state found\n");
+			gaim_debug_info("simple", "process_incoming_message: no state found\n");
 			return;
 		}
 
 		statedata = xmlnode_get_data(state);
 		if(statedata) {
-			if(strstr(statedata,"active")) serv_got_typing(sip->gc, from, 0, GAIM_TYPING);
+			if(strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, GAIM_TYPING);
 			else serv_got_typing_stopped(sip->gc, from);
 		}
 		xmlnode_free(isc);
@@ -839,12 +904,12 @@
 	gaim_debug(GAIM_DEBUG_MISC, "simple", "in process register response response: %d\n", msg->response);
 	switch (msg->response) {
 		case 200:
-			if(sip->registerstatus<3) { /* registered */
+			if(sip->registerstatus < 3) { /* registered */
 				if(gaim_account_get_bool(sip->account, "dopublish", TRUE)) {
 					send_publish(sip);
 				}
 			}
-			sip->registerstatus=3;
+			sip->registerstatus = 3;
 			gaim_connection_set_state(sip->gc, GAIM_CONNECTED);
 
 			/* get buddies from blist */
@@ -853,15 +918,15 @@
 			subscribe_timeout(sip);
 			break;
 		case 401:
-			if(sip->registerstatus!=2) {
-				gaim_debug_info("simple","REGISTER retries %d\n",sip->registrar.retries);
-				if(sip->registrar.retries>3) {
-					gaim_connection_error(sip->gc,"Wrong Password");
+			if(sip->registerstatus != 2) {
+				gaim_debug_info("simple", "REGISTER retries %d\n", sip->registrar.retries);
+				if(sip->registrar.retries > 3) {
+					gaim_connection_error(sip->gc, _("Wrong Password"));
 					return TRUE;
 				}
 				tmp = sipmsg_find_header(msg, "WWW-Authenticate");
 				fill_auth(sip, tmp, &sip->registrar);
-				sip->registerstatus=2;
+				sip->registerstatus = 2;
 				do_register(sip);
 			}
 			break;
@@ -877,28 +942,28 @@
 	xmlnode *basicstatus;
 	gboolean isonline = FALSE;
 
-	fromhdr = sipmsg_find_header(msg,"From");
+	fromhdr = sipmsg_find_header(msg, "From");
 	from = parse_from(fromhdr);
 	if(!from) return;
 
 	pidf = xmlnode_from_str(msg->body, msg->bodylen);
 
 	if(!pidf) {
-		gaim_debug_info("simple","process_incoming_notify: no parseable pidf\n");
+		gaim_debug_info("simple", "process_incoming_notify: no parseable pidf\n");
 		return;
 	}
 
-	basicstatus = xmlnode_get_child(xmlnode_get_child(xmlnode_get_child(pidf,"tuple"),"status"), "basic");
+	basicstatus = xmlnode_get_child(xmlnode_get_child(xmlnode_get_child(pidf, "tuple"), "status"), "basic");
 
 	if(!basicstatus) {
-		gaim_debug_info("simple","process_incoming_notify: no basic found\n");
+		gaim_debug_info("simple", "process_incoming_notify: no basic found\n");
 		return;
 	}
 
 	tmp2 = xmlnode_get_data(basicstatus);
 
 	if(!tmp2) {
-		gaim_debug_info("simple","process_incoming_notify: no basic data found\n");
+		gaim_debug_info("simple", "process_incoming_notify: no basic data found\n");
 		return;
 	}
 
@@ -1013,7 +1078,10 @@
 static void send_publish(struct simple_account_data *sip) {
 	gchar *uri = g_strdup_printf("sip:%s@%s", sip->username, sip->servername);
 	gchar *doc = gen_pidf(sip);
-	send_sip_request(sip->gc, "PUBLISH", uri, uri, "Expires: 600\r\nEvent: presence\r\nContent-Type: application/pidf+xml\r\n", doc, NULL, process_publish_response);
+	send_sip_request(sip->gc, "PUBLISH", uri, uri,
+		"Expires: 600\r\nEvent: presence\r\n"
+		"Content-Type: application/pidf+xml\r\n",
+		doc, NULL, process_publish_response);
 	sip->republish = time(NULL) + 500;
 	g_free(doc);
 }
@@ -1106,7 +1174,7 @@
 			if(msg->response == 407) {
 				gchar *resend, *auth, *ptmp;
 
-				if(sip->proxy.retries>3) return;
+				if(sip->proxy.retries > 3) return;
 				sip->proxy.retries++;
 				/* do proxy authentication */
 
@@ -1124,7 +1192,7 @@
 			} else {
 				if(msg->response == 100) {
 					/* ignore provisional response */
-					gaim_debug_info("simple","got trying response\n");
+					gaim_debug_info("simple", "got trying response\n");
 				} else {
 					sip->proxy.retries = 0;
 					if(msg->response == 401) sip->registrar.retries++;
@@ -1142,7 +1210,7 @@
 		}
 	}
 	if(!found) {
-		gaim_debug(GAIM_DEBUG_MISC, "simple", "received a unknown sip message with method %s and response %d\n",msg->method, msg->response);
+		gaim_debug(GAIM_DEBUG_MISC, "simple", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
 	}
 }
 
@@ -1160,33 +1228,33 @@
 	}
 	if(cur != conn->inbuf) {
 		memmove(conn->inbuf, cur, conn->inbufused-(cur-conn->inbuf));
-		conn->inbufused=strlen(conn->inbuf);
+		conn->inbufused = strlen(conn->inbuf);
 	}
 
 	/* Received a full Header? */
-	if((cur = strstr(conn->inbuf, "\r\n\r\n"))!=NULL) {
+	if((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL) {
 		time_t currtime = time(NULL);
 		cur += 2;
 		cur[0] = '\0';
-		gaim_debug_info("simple","\n\nreceived - %s\n######\n%s\n#######\n\n",ctime(&currtime), conn->inbuf);
+		gaim_debug_info("simple", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
 		msg = sipmsg_parse_header(conn->inbuf);
 		cur[0] = '\r';
 		cur += 2;
 		restlen = conn->inbufused - (cur-conn->inbuf);
-		if(restlen>=msg->bodylen) {
-			dummy = g_malloc(msg->bodylen+1);
+		if(restlen >= msg->bodylen) {
+			dummy = g_malloc(msg->bodylen + 1);
 			memcpy(dummy, cur, msg->bodylen);
-			dummy[msg->bodylen]='\0';
+			dummy[msg->bodylen] = '\0';
 			msg->body = dummy;
-			cur+=msg->bodylen;
-			memmove(conn->inbuf, cur, conn->inbuflen-(cur-conn->inbuf));
-			conn->inbufused=strlen(conn->inbuf);
+			cur += msg->bodylen;
+			memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
+			conn->inbufused = strlen(conn->inbuf);
 		} else {
 			sipmsg_free(msg);
 			return;
 		}
 		gaim_debug(GAIM_DEBUG_MISC, "simple", "in process response response: %d\n", msg->response);
-		process_input_message(sip,msg);
+		process_input_message(sip, msg);
 	} else {
 		gaim_debug(GAIM_DEBUG_MISC, "simple", "received a incomplete sip msg: %s\n", conn->inbuf);
 	}
@@ -1202,7 +1270,7 @@
 	static char buffer[65536];
 	if((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
 		buffer[len] = '\0';
-		gaim_debug_info("simple","\n\nreceived - %s\n######\n%s\n#######\n\n",ctime(&currtime), buffer);
+		gaim_debug_info("simple", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
 		msg = sipmsg_parse_msg(buffer);
 		if(msg) process_input_message(sip, msg);
 	}
@@ -1219,25 +1287,24 @@
 		return;
 	}
 
-	if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
+	if(conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
 		conn->inbuflen += SIMPLE_BUF_INC;
 		conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
 	}
 
-	if ((len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1)) <= 0) {
-		gaim_debug_info("simple","simple_input_cb: read error\n");
+	len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
+
+	if(len < 0 && errno == EAGAIN)
+		return;
+	else if(len <= 0) {
+		gaim_debug_info("simple", "simple_input_cb: read error\n");
 		connection_remove(sip, source);
 		if(sip->fd == source) sip->fd = -1;
 		return;
 	}
-	if(len == 0) {
-		/* connection was closed */
-		connection_remove(sip, source);
-		if(sip->fd == source) sip->fd = -1;
-	}
 
 	conn->inbufused += len;
-	conn->inbuf[conn->inbufused]='\0';
+	conn->inbuf[conn->inbufused] = '\0';
 
 	process_input(sip, conn);
 }
@@ -1260,8 +1327,8 @@
 	struct simple_account_data *sip = gc->proto_data;
 	struct sip_connection *conn;
 
-	if( source < 0 ) {
-		gaim_connection_error(gc,"Could not connect");
+	if(source < 0) {
+		gaim_connection_error(gc, _("Could not connect"));
 		return;
 	}
 
@@ -1351,7 +1418,7 @@
 	sip->listenport = gaim_network_get_port_from_fd(sip->listenfd);
 	sip->listenpa = gaim_input_add(sip->listenfd, GAIM_INPUT_READ,
 			simple_newconn_cb, sip->gc);
-	gaim_debug_info("simple","connecting to %s port %d\n",
+	gaim_debug_info("simple", "connecting to %s port %d\n",
 			sip->realhostname, sip->realport);
 	/* open tcp connection to the server */
 	error = gaim_proxy_connect(sip->account, sip->realhostname,
@@ -1367,7 +1434,6 @@
 	gchar *hostname;
 	int port = gaim_account_get_int(sip->account, "port", 0);
 
-
 	/* find the host to connect to */
 	if(results) {
 		hostname = g_strdup(resp->hostname);
@@ -1385,8 +1451,9 @@
 	sip->realhostname = hostname;
 	sip->realport = port;
 	if(!sip->realport) sip->realport = 5060;
+
 	/* TCP case */
-	if(! sip->udp) {
+	if(!sip->udp) {
 		/* create socket for incoming connections */
 		if(!gaim_network_listen_range(5060, 5160, SOCK_STREAM,
 					simple_tcp_connect_listen_cb, sip)) {
@@ -1408,20 +1475,24 @@
 	gchar *hosttoconnect;
 
 	const char *username = gaim_account_get_username(account);
+	gc = gaim_account_get_connection(account);
 
-	gc = gaim_account_get_connection(account);
-	gc->proto_data = sip = g_new0(struct simple_account_data,1);
-	sip->gc=gc;
-	sip->account = account;
-	sip->registerexpire = 900;
-	sip->udp = gaim_account_get_bool(account, "udp", FALSE);
 	if (strpbrk(username, " \t\v\r\n") != NULL) {
 		gaim_connection_error(gc, _("SIP usernames may not contain whitespaces or @ symbols"));
 		return;
 	}
 
+	gc->proto_data = sip = g_new0(struct simple_account_data, 1);
+	sip->gc = gc;
+	sip->account = account;
+	sip->registerexpire = 900;
+	sip->udp = gaim_account_get_bool(account, "udp", FALSE);
+	/* TODO: is there a good default grow size? */
+	if(!sip->udp)
+		sip->txbuf = gaim_circ_buffer_new(0);
+
 	userserver = g_strsplit(username, "@", 2);
-	gaim_connection_set_display_name(gc,userserver[0]);
+	gaim_connection_set_display_name(gc, userserver[0]);
 	sip->username = g_strdup(userserver[0]);
 	sip->servername = g_strdup(userserver[1]);
 	sip->password = g_strdup(gaim_connection_get_password(gc));
@@ -1441,10 +1512,10 @@
 	}
 
 	/* TCP case */
-	if(! sip->udp) {
-		gaim_srv_resolve("sip","tcp",hosttoconnect,srvresolved, sip);
+	if(!sip->udp) {
+		gaim_srv_resolve("sip", "tcp", hosttoconnect, srvresolved, sip);
 	} else { /* UDP */
-		gaim_srv_resolve("sip","udp",hosttoconnect,srvresolved, sip);
+		gaim_srv_resolve("sip", "udp", hosttoconnect, srvresolved, sip);
 	}
 	g_free(hosttoconnect);
 }
@@ -1470,12 +1541,13 @@
 		g_free(sip->proxy.target);
 		g_free(sip->proxy.realm);
 		g_free(sip->proxy.digest_session_key);
-		g_free(sip->sendlater);
+		if(sip->txbuf)
+			gaim_circ_buffer_destroy(sip->txbuf);
 		g_free(sip->realhostname);
 		if(sip->listenpa) gaim_input_remove(sip->listenpa);
+		if(sip->tx_handler) gaim_input_remove(sip->tx_handler);
 		if(sip->resendtimeout) gaim_timeout_remove(sip->resendtimeout);
 		if(sip->registertimeout) gaim_timeout_remove(sip->registertimeout);
-		sip->servername = sip->username = sip->password = sip->registrar.nonce = sip->registrar.realm = sip->proxy.nonce = sip->proxy.realm = sip->sendlater = sip->realhostname = NULL;
 	}
 	g_free(gc->proto_data);
 	gc->proto_data = NULL;
@@ -1597,7 +1669,6 @@
 	option = gaim_account_option_int_new(_("Connect port"), "port", 0);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
-
 	option = gaim_account_option_bool_new(_("Use UDP"), "udp", FALSE);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 	option = gaim_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
--- a/src/protocols/simple/simple.h	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/simple/simple.h	Thu Feb 09 04:17:56 2006 +0000
@@ -27,6 +27,7 @@
 #include <time.h>
 
 #include <cipher.h>
+#include <gaim_buffer.h>
 #include <prpl.h>
 
 #include "sipmsg.h"
@@ -84,7 +85,8 @@
 	guint resendtimeout;
 	gboolean connecting;
 	GaimAccount *account;
-	gchar *sendlater;
+	GaimCircBuffer *txbuf;
+	guint tx_handler;
 	gchar *regcallid;
 	GSList *transactions;
 	GSList *watcher;
--- a/src/protocols/yahoo/yahoo.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/yahoo/yahoo.c	Thu Feb 09 04:17:56 2006 +0000
@@ -2286,19 +2286,34 @@
 	GaimConnection *gc = data;
 	GaimAccount *account = gaim_connection_get_account(gc);
 	struct yahoo_data *yd = gc->proto_data;
-	char buf[2048], *i = buf;
+	char bufread[2048], *i = bufread, *buf = bufread;
 	int len;
 	GString *s;
 
-	len = read(source, buf, sizeof(buf)-1);
-	if (len <= 0  || (strncmp(buf, "HTTP/1.0 302", strlen("HTTP/1.0 302")) &&
+	len = read(source, bufread, sizeof(bufread) - 1);
+	if (len < 0 && errno == EAGAIN)
+		return;
+	else if (len <= 0) {
+		gaim_connection_error(gc, _("Unable to read"));
+		return;
+	}
+
+	if (yd->rxlen > 0 || !g_strstr_len(buf, len, "\r\n\r\n")) {
+		yd->rxqueue = g_realloc(yd->rxqueue, yd->rxlen + len + 1);
+		memcpy(yd->rxqueue + yd->rxlen, buf, len);
+		yd->rxlen += len;
+		i = buf = yd->rxqueue;
+		len = yd->rxlen;
+	}
+	buf[len] = '\0';
+
+	if ((strncmp(buf, "HTTP/1.0 302", strlen("HTTP/1.0 302")) &&
 			  strncmp(buf, "HTTP/1.1 302", strlen("HTTP/1.1 302")))) {
 		gaim_connection_error(gc, _("Unable to read"));
 		return;
 	}
 
 	s = g_string_sized_new(len);
-	buf[sizeof(buf)-1] = '\0';
 
 	while ((i = strstr(i, "Set-Cookie: "))) {
 		i += strlen("Set-Cookie: ");
@@ -2311,6 +2326,9 @@
 	yd->auth = g_string_free(s, FALSE);
 	gaim_input_remove(gc->inpa);
 	close(source);
+	g_free(yd->rxqueue);
+	yd->rxqueue = NULL;
+	yd->rxlen = 0;
 	/* Now we have our cookies to login with.  I'll go get the milk. */
 	if (gaim_proxy_connect(account, "wcs2.msg.dcn.yahoo.com",
 			       gaim_account_get_int(account, "port", YAHOO_PAGER_PORT),
@@ -2324,12 +2342,41 @@
 {
 	GaimConnection *gc = data;
 	struct yahoo_data *yd = gc->proto_data;
+	int written, total_len;
+
 	if (source < 0) {
 		gaim_connection_error(gc, _("Unable to connect."));
 		return;
 	}
-	write(source, yd->auth, strlen(yd->auth));
+
+	total_len = strlen(yd->auth) - yd->auth_written;
+	written = write(source, yd->auth + yd->auth_written, total_len);
+
+	if (written < 0 && errno == EAGAIN)
+		written = 0;
+	else if (written <= 0) {
+		g_free(yd->auth);
+		yd->auth = NULL;
+		if (gc->inpa)
+			gaim_input_remove(gc->inpa);
+		gc->inpa = 0;
+		gaim_connection_error(gc, _("Unable to connect."));
+		return;
+	}
+
+	if (written < total_len) {
+		yd->auth_written += written;
+		if (!gc->inpa)
+			gc->inpa = gaim_input_add(source, GAIM_INPUT_WRITE,
+				yahoo_got_cookies, gc);
+		return;
+	}
+
 	g_free(yd->auth);
+	yd->auth = NULL;
+	yd->auth_written = 0;
+	if (gc->inpa)
+		gaim_input_remove(gc->inpa);
 	gc->inpa = gaim_input_add(source, GAIM_INPUT_READ, yahoo_web_pending, gc);
 }
 
@@ -2526,6 +2573,9 @@
 	gaim_connection_set_display_name(gc, gaim_account_get_username(account));
 
 	yd->fd = -1;
+	yd->txhandler = -1;
+	/* TODO: Is there a good grow size for the buffer? */
+	yd->txbuf = gaim_circ_buffer_new(0);
 	yd->friends = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, yahoo_friend_free);
 	yd->confs = NULL;
 	yd->conf_id = 2;
@@ -2579,22 +2629,23 @@
 		yahoo_c_leave(gc, 1); /* 1 = YAHOO_CHAT_ID */
 
 	g_hash_table_destroy(yd->friends);
-	if (yd->chat_name)
-		g_free(yd->chat_name);
-
-	if (yd->cookie_y)
-		g_free(yd->cookie_y);
-	if (yd->cookie_t)
-		g_free(yd->cookie_t);
+	g_free(yd->chat_name);
+
+	g_free(yd->cookie_y);
+	g_free(yd->cookie_t);
+
+	if (yd->txhandler)
+		gaim_input_remove(yd->txhandler);
+
+	gaim_circ_buffer_destroy(yd->txbuf);
 
 	if (yd->fd >= 0)
 		close(yd->fd);
 
-	if (yd->rxqueue)
-		g_free(yd->rxqueue);
+	g_free(yd->rxqueue);
 	yd->rxlen = 0;
-	if (yd->picture_url)
-		g_free(yd->picture_url);
+	g_free(yd->picture_url);
+
 	if (yd->picture_upload_todo)
 		yahoo_buddy_icon_upload_data_free(yd->picture_upload_todo);
 	if (yd->ycht)
--- a/src/protocols/yahoo/yahoo.h	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/yahoo/yahoo.h	Thu Feb 09 04:17:56 2006 +0000
@@ -26,6 +26,7 @@
 #define _YAHOO_H_
 
 #include "prpl.h"
+#include "gaim_buffer.h"
 
 #define YAHOO_PAGER_HOST "scs.msg.yahoo.com"
 #define YAHOO_PAGER_PORT 5050
@@ -97,9 +98,12 @@
 struct _YchtConn;
 
 struct yahoo_data {
+	GaimConnection *gc;
 	int fd;
 	guchar *rxqueue;
 	int rxlen;
+	GaimCircBuffer *txbuf;
+	guint txhandler;
 	GHashTable *friends;
 	int current_status;
 	gboolean logged_in;
@@ -110,6 +114,7 @@
 	gboolean in_chat;
 	char *chat_name;
 	char *auth;
+	gsize auth_written;
 	char *cookie_y;
 	char *cookie_t;
 	int session_id;
--- a/src/protocols/yahoo/yahoo_filexfer.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/yahoo/yahoo_filexfer.c	Thu Feb 09 04:17:56 2006 +0000
@@ -39,16 +39,21 @@
 	GaimConnection *gc;
 	long expires;
 	gboolean started;
+	guchar *txbuf;
+	gsize txbuflen;
+	gsize txbuf_written;
+	guint tx_handler;
 	gchar *rxqueue;
 	guint rxlen;
 };
 
 static void yahoo_xfer_data_free(struct yahoo_xfer_data *xd)
 {
-	if (xd->host)
-		g_free(xd->host);
-	if (xd->path)
-		g_free(xd->path);
+	g_free(xd->host);
+	g_free(xd->path);
+	g_free(xd->txbuf);
+	if (xd->tx_handler)
+		gaim_input_remove(xd->tx_handler);
 	g_free(xd);
 }
 
@@ -56,7 +61,7 @@
 {
 	GaimXfer *xfer;
 	struct yahoo_xfer_data *xd;
-	gchar *buf;
+	int total_len, written;
 
 	gaim_debug(GAIM_DEBUG_INFO, "yahoo",
 			   "AAA - in yahoo_receivefile_connected\n");
@@ -71,29 +76,53 @@
 		return;
 	}
 
+	/* The first time we get here, assemble the tx buffer */
+	if (xd->txbuflen == 0) {
+		xd->txbuf = g_strdup_printf("GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n",
+			      xd->path, xd->host);
+		xd->txbuflen = strlen(xd->txbuf);
+		xd->txbuf_written = 0;
+	}
+
+	total_len = xd->txbuflen - xd->txbuf_written;
+
 	xfer->fd = source;
+
+	written = write(xfer->fd, xd->txbuf + xd->txbuf_written, total_len);
+
+	if (written < 0 && errno == EAGAIN)
+		written = 0;
+	else if (written <= 0) {
+		gaim_debug_error("yahoo", "Unable to write in order to start ft errno = %d\n", errno);
+		gaim_xfer_cancel_remote(xfer);
+		return;
+	}
+
+	if (written < total_len) {
+		if (!xd->tx_handler)
+			xd->tx_handler = gaim_input_add(source, GAIM_INPUT_WRITE,
+				yahoo_receivefile_connected, xfer);
+		xd->txbuf_written += written;
+		return;
+	}
+
+	if (xd->tx_handler)
+		gaim_input_remove(xd->tx_handler);
+	xd->tx_handler = 0;
+	g_free(xd->txbuf);
+	xd->txbuf = NULL;
+	xd->txbuflen = 0;
+
 	gaim_xfer_start(xfer, source, NULL, 0);
 
-	buf = g_strdup_printf("GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n",
-			      xd->path, xd->host);
-	write(xfer->fd, buf, strlen(buf));
-	g_free(buf);
+}
 
-	return;
-}
 
 static void yahoo_sendfile_connected(gpointer data, gint source, GaimInputCondition condition)
 {
 	GaimXfer *xfer;
 	struct yahoo_xfer_data *xd;
-	struct yahoo_packet *pkt;
-	gchar *size, *post, *buf;
-	const char *host;
-	int content_length, port;
-	GaimConnection *gc;
-	GaimAccount *account;
-	struct yahoo_data *yd;
-	char *filename, *encoded_filename;
+	int written, total_len;
 
 	gaim_debug(GAIM_DEBUG_INFO, "yahoo",
 			   "AAA - in yahoo_sendfile_connected\n");
@@ -102,9 +131,6 @@
 	if (!(xd = xfer->data))
 		return;
 
-	gc = xd->gc;
-	account = gaim_connection_get_account(gc);
-	yd = gc->proto_data;
 
 	if (source < 0) {
 		gaim_xfer_error(GAIM_XFER_RECEIVE, gaim_xfer_get_account(xfer),
@@ -113,43 +139,96 @@
 		return;
 	}
 
-	xfer->fd = source;
-	gaim_xfer_start(xfer, source, NULL, 0);
+	/* The first time we get here, assemble the tx buffer */
+	if (xd->txbuflen == 0) {
+		struct yahoo_packet *pkt;
+		gchar *size, *filename, *encoded_filename, *header;
+		guchar *pkt_buf;
+		const char *host;
+		int port;
+		gsize content_length, header_len, pkt_buf_len;
+		GaimConnection *gc;
+		GaimAccount *account;
+		struct yahoo_data *yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_FILETRANSFER, YAHOO_STATUS_AVAILABLE, yd->session_id);
+		gc = xd->gc;
+		account = gaim_connection_get_account(gc);
+		yd = gc->proto_data;
+
+		pkt = yahoo_packet_new(YAHOO_SERVICE_FILETRANSFER,
+			YAHOO_STATUS_AVAILABLE, yd->session_id);
 
-	size = g_strdup_printf("%" G_GSIZE_FORMAT, gaim_xfer_get_size(xfer));
-	filename = g_path_get_basename(gaim_xfer_get_local_filename(xfer));
-	encoded_filename = yahoo_string_encode(gc, filename, NULL);
+		size = g_strdup_printf("%" G_GSIZE_FORMAT, gaim_xfer_get_size(xfer));
+		filename = g_path_get_basename(gaim_xfer_get_local_filename(xfer));
+		encoded_filename = yahoo_string_encode(gc, filename, NULL);
+
+		yahoo_packet_hash(pkt, "sssss", 0, gaim_connection_get_display_name(gc),
+			  5, xfer->who, 14, "", 27, encoded_filename, 28, size);
+		g_free(size);
+		g_free(encoded_filename);
+		g_free(filename);
+
+		content_length = YAHOO_PACKET_HDRLEN + yahoo_packet_length(pkt);
 
-	yahoo_packet_hash(pkt, "sssss", 0, gaim_connection_get_display_name(gc),
-	                  5, xfer->who, 14, "", 27, encoded_filename, 28, size);
+		pkt_buf_len = yahoo_packet_build(pkt, 8, FALSE, &pkt_buf);
+		yahoo_packet_free(pkt);
 
-	content_length = YAHOO_PACKET_HDRLEN + yahoo_packet_length(pkt);
-
-	buf = g_strdup_printf("Y=%s; T=%s", yd->cookie_y, yd->cookie_t);
+		host = gaim_account_get_string(account, "xfer_host", YAHOO_XFER_HOST);
+		port = gaim_account_get_int(account, "xfer_port", YAHOO_XFER_PORT);
+		header = g_strdup_printf(
+			"POST http://%s:%d/notifyft HTTP/1.0\r\n"
+			"Content-length: %" G_GSIZE_FORMAT "\r\n"
+			"Host: %s:%d\r\n"
+			"Cookie: Y=%s; T=%s\r\n"
+			"\r\n",
+			host, port, content_length + 4 + gaim_xfer_get_size(xfer),
+			host, port, yd->cookie_y, yd->cookie_t);
 
-	host = gaim_account_get_string(account, "xfer_host", YAHOO_XFER_HOST);
-	port = gaim_account_get_int(account, "xfer_port", YAHOO_XFER_PORT);
-	post = g_strdup_printf("POST http://%s:%d/notifyft HTTP/1.0\r\n"
-	                       "Content-length: %" G_GSIZE_FORMAT "\r\n"
-	                       "Host: %s:%d\r\n"
-	                       "Cookie: %s\r\n"
-	                       "\r\n",
-			       host, port, content_length + 4 + gaim_xfer_get_size(xfer),
-			       host, port, buf);
-	write(xfer->fd, post, strlen(post));
+
+		header_len = strlen(header);
+
+		xd->txbuflen = header_len + pkt_buf_len + 4;
+		xd->txbuf = g_malloc(xd->txbuflen);
+
+		memcpy(xd->txbuf, header, header_len);
+		g_free(header);
+		memcpy(xd->txbuf + header_len, pkt_buf, pkt_buf_len);
+		g_free(pkt_buf);
+		memcpy(xd->txbuf + header_len + pkt_buf_len, "29\xc0\x80", 4);
+
+		xd->txbuf_written = 0;
+	}
+
+	total_len = xd->txbuflen - xd->txbuf_written;
+
+	xfer->fd = source;
+
+	written = write(xfer->fd, xd->txbuf + xd->txbuf_written, total_len);
 
-	yahoo_packet_send_special(pkt, xfer->fd, 8);
-	yahoo_packet_free(pkt);
-
-	write(xfer->fd, "29\xc0\x80", 4);
+	if (written < 0 && errno == EAGAIN)
+		written = 0;
+	else if (written <= 0) {
+		gaim_debug_error("yahoo", "Unable to write in order to start ft errno = %d\n", errno);
+		gaim_xfer_cancel_remote(xfer);
+		return;
+	}
 
-	g_free(size);
-	g_free(post);
-	g_free(buf);
-	g_free(encoded_filename);
-	g_free(filename);
+	if (written < total_len) {
+		if (!xd->tx_handler)
+			xd->tx_handler = gaim_input_add(source, GAIM_INPUT_WRITE,
+				yahoo_sendfile_connected, xfer);
+		xd->txbuf_written += written;
+		return;
+	}
+
+	if (xd->tx_handler)
+		gaim_input_remove(xd->tx_handler);
+	xd->tx_handler = 0;
+	g_free(xd->txbuf);
+	xd->txbuf = NULL;
+	xd->txbuflen = 0;
+
+	gaim_xfer_start(xfer, source, NULL, 0);
 }
 
 static void yahoo_xfer_init(GaimXfer *xfer)
--- a/src/protocols/yahoo/yahoo_packet.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/yahoo/yahoo_packet.c	Thu Feb 09 04:17:56 2006 +0000
@@ -242,39 +242,97 @@
 #endif
 }
 
-int yahoo_packet_send(struct yahoo_packet *pkt, struct yahoo_data *yd)
+static void
+yahoo_packet_send_can_write(gpointer data, gint source, GaimInputCondition cond)
+{
+	struct yahoo_data *yd = data;
+	int ret, writelen;
+
+	writelen = gaim_circ_buffer_get_max_read(yd->txbuf);
+
+	if (writelen == 0) {
+		gaim_input_remove(yd->txhandler);
+		yd->txhandler = -1;
+		return;
+	}
+
+	ret = write(yd->fd, yd->txbuf->outptr, writelen);
+
+	if (ret < 0 && errno == EAGAIN)
+		return;
+	else if (ret < 0) {
+		/* TODO: what to do here - do we really have to disconnect? */
+		gaim_connection_error(yd->gc, _("Write Error"));
+		return;
+	}
+
+	gaim_circ_buffer_mark_read(yd->txbuf, ret);
+}
+
+
+gsize yahoo_packet_build(struct yahoo_packet *pkt, int pad, gboolean wm,
+			 guchar **buf)
 {
 	int pktlen = yahoo_packet_length(pkt);
 	int len = YAHOO_PACKET_HDRLEN + pktlen;
-	int ret;
-
 	guchar *data;
 	int pos = 0;
 
-	if (yd->fd < 0)
-		return -1;
-
 	data = g_malloc0(len + 1);
 
 	memcpy(data + pos, "YMSG", 4); pos += 4;
 
-	if (yd->wm)
+	if (wm)
 		pos += yahoo_put16(data + pos, YAHOO_WEBMESSENGER_PROTO_VER);
 	else
 		pos += yahoo_put16(data + pos, YAHOO_PROTO_VER);
-
 	pos += yahoo_put16(data + pos, 0x0000);
-	pos += yahoo_put16(data + pos, pktlen);
+	pos += yahoo_put16(data + pos, pktlen + pad);
 	pos += yahoo_put16(data + pos, pkt->service);
 	pos += yahoo_put32(data + pos, pkt->status);
 	pos += yahoo_put32(data + pos, pkt->id);
 
 	yahoo_packet_write(pkt, data + pos);
 
+	*buf = data;
+
+	return len;
+}
+
+int yahoo_packet_send(struct yahoo_packet *pkt, struct yahoo_data *yd)
+{
+	gsize len;
+	int ret;
+	guchar *data;
+
+	if (yd->fd < 0)
+		return -1;
+
+	len = yahoo_packet_build(pkt, 0, yd->wm, &data);
+
 	yahoo_packet_dump(data, len);
-	ret = write(yd->fd, data, len);
-	if (ret != len)
+	if (yd->txhandler == -1)
+		ret = write(yd->fd, data, len);
+	else {
+		ret = -1;
+		errno = EAGAIN;
+	}
+
+	if (ret < 0 && errno == EAGAIN)
+		ret = 0;
+	else if (ret <= 0) {
 		gaim_debug_warning("yahoo", "Only wrote %d of %d bytes!", ret, len);
+		g_free(data);
+		return ret;
+	}
+
+	if (ret < len) {
+		if (yd->txhandler == -1)
+			yd->txhandler = gaim_input_add(yd->fd, GAIM_INPUT_WRITE,
+				yahoo_packet_send_can_write, yd);
+		gaim_circ_buffer_append(yd->txbuf, data + ret, len - ret);
+	}
+
 	g_free(data);
 
 	return ret;
@@ -289,37 +347,6 @@
 	return ret;
 }
 
-int yahoo_packet_send_special(struct yahoo_packet *pkt, int fd, int pad)
-{
-	int pktlen = yahoo_packet_length(pkt);
-	int len = YAHOO_PACKET_HDRLEN + pktlen;
-	int ret;
-
-	guchar *data;
-	int pos = 0;
-
-	if (fd < 0)
-		return -1;
-
-	data = g_malloc0(len + 1);
-
-	memcpy(data + pos, "YMSG", 4); pos += 4;
-
-	pos += yahoo_put16(data + pos, YAHOO_PROTO_VER);
-	pos += yahoo_put16(data + pos, 0x0000);
-	pos += yahoo_put16(data + pos, pktlen + pad);
-	pos += yahoo_put16(data + pos, pkt->service);
-	pos += yahoo_put32(data + pos, pkt->status);
-	pos += yahoo_put32(data + pos, pkt->id);
-
-	yahoo_packet_write(pkt, data + pos);
-
-	ret = write(fd, data, len);
-	g_free(data);
-
-	return ret;
-}
-
 void yahoo_packet_free(struct yahoo_packet *pkt)
 {
 	while (pkt->hash) {
--- a/src/protocols/yahoo/yahoo_packet.h	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/yahoo/yahoo_packet.h	Thu Feb 09 04:17:56 2006 +0000
@@ -125,7 +125,8 @@
 void yahoo_packet_hash_int(struct yahoo_packet *pkt, int key, int value);
 int yahoo_packet_send(struct yahoo_packet *pkt, struct yahoo_data *yd);
 int yahoo_packet_send_and_free(struct yahoo_packet *pkt, struct yahoo_data *yd);
-int yahoo_packet_send_special(struct yahoo_packet *pkt, int fd, int pad);
+gsize yahoo_packet_build(struct yahoo_packet *pkt, int pad, gboolean wm,
+guchar **buf);
 void yahoo_packet_read(struct yahoo_packet *pkt, guchar *data, int len);
 void yahoo_packet_write(struct yahoo_packet *pkt, guchar *data);
 void yahoo_packet_dump(guchar *data, int len);
--- a/src/protocols/yahoo/yahoo_picture.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/yahoo/yahoo_picture.c	Thu Feb 09 04:17:56 2006 +0000
@@ -307,8 +307,7 @@
 
 	if (d->str)
 		g_string_free(d->str, TRUE);
-	if (d->filename)
-		g_free(d->filename);
+	g_free(d->filename);
 	if (d->watcher)
 		gaim_input_remove(d->watcher);
 	if (d->fd != -1)
@@ -316,19 +315,24 @@
 	g_free(d);
 }
 
-/* we could care less about the server's responce, but yahoo gets grumpy if we close before it sends it */
+/* we couldn't care less about the server's response, but yahoo gets grumpy if we close before it sends it */
 static void yahoo_buddy_icon_upload_reading(gpointer data, gint source, GaimInputCondition condition)
 {
 	struct yahoo_buddy_icon_upload_data *d = data;
 	GaimConnection *gc = d->gc;
 	char buf[1024];
+	int ret;
 
 	if (!GAIM_CONNECTION_IS_VALID(gc)) {
 		yahoo_buddy_icon_upload_data_free(d);
 		return;
 	}
 
-	if (read(d->fd, buf, sizeof(buf)) <= 0)
+	ret = read(d->fd, buf, sizeof(buf));
+
+	if (ret < 0 && errno == EAGAIN)
+		return;
+	else if (ret <= 0)
 		yahoo_buddy_icon_upload_data_free(d);
 }
 
@@ -344,6 +348,8 @@
 	}
 
 	wrote = write(d->fd, d->str->str + d->pos, d->str->len - d->pos);
+	if (wrote < 0 && errno == EAGAIN)
+		return;
 	if (wrote <= 0) {
 		yahoo_buddy_icon_upload_data_free(d);
 		return;
@@ -360,9 +366,10 @@
 {
 	struct yahoo_buddy_icon_upload_data *d = data;
 	struct yahoo_packet *pkt;
-	gchar *size, *post, *buf;
+	gchar *size, *header;
+	guchar *pkt_buf;
 	const char *host;
-	int content_length, port;
+	gsize content_length, port, pkt_buf_len;
 	GaimConnection *gc;
 	GaimAccount *account;
 	struct yahoo_data *yd;
@@ -380,8 +387,6 @@
 		return;
 	}
 
-	d->fd = source;
-	d->watcher = gaim_input_add(d->fd, GAIM_INPUT_WRITE, yahoo_buddy_icon_upload_pending, d);
 
 	pkt = yahoo_packet_new(0xc2, YAHOO_STATUS_AVAILABLE, yd->session_id);
 
@@ -392,30 +397,38 @@
 	gaim_account_set_int(account, YAHOO_PICEXPIRE_SETTING, time(NULL) + 604800);
 	yahoo_packet_hash_str(pkt, 0, gaim_connection_get_display_name(gc));
 	yahoo_packet_hash_str(pkt, 28, size);
+	g_free(size);
 	yahoo_packet_hash_str(pkt, 27, d->filename);
 	yahoo_packet_hash_str(pkt, 14, "");
 
 	content_length = YAHOO_PACKET_HDRLEN + yahoo_packet_length(pkt);
-	buf = g_strdup_printf("Y=%s; T=%s", yd->cookie_y, yd->cookie_t);
 
-	host = gaim_account_get_string(account, "xfer_host", YAHOO_XFER_HOST); 
+	host = gaim_account_get_string(account, "xfer_host", YAHOO_XFER_HOST);
 	port = gaim_account_get_int(account, "xfer_port", YAHOO_XFER_PORT);
-	post = g_strdup_printf("POST http://%s:%d/notifyft HTTP/1.0\r\n"
-	                       "Content-length: %" G_GSIZE_FORMAT "\r\n"
-	                       "Host: %s:%d\r\n"
-	                       "Cookie: %s\r\n"
-	                       "\r\n",
-	                       host, port, content_length + 4 + d->str->len, host, port, buf);
-	write(d->fd, post, strlen(post));
+	header = g_strdup_printf(
+		"POST http://%s:%d/notifyft HTTP/1.0\r\n"
+		"Content-length: %" G_GSIZE_FORMAT "\r\n"
+		"Host: %s:%d\r\n"
+		"Cookie: Y=%s; T=%s\r\n"
+		"\r\n",
+		host, port, content_length + 4 + d->str->len,
+		host, port, yd->cookie_y, yd->cookie_t);
+
+	/* There's no magic here, we just need to prepend in reverse order */
+	g_string_prepend(d->str, "29\xc0\x80");
 
-	yahoo_packet_send_special(pkt, d->fd, 8);
+	pkt_buf_len = yahoo_packet_build(pkt, 8, FALSE, &pkt_buf);
 	yahoo_packet_free(pkt);
+	g_string_prepend_len(d->str, pkt_buf, pkt_buf_len);
+	g_free(pkt_buf);
 
-	write(d->fd, "29\xc0\x80", 4);
+	g_string_prepend(d->str, header);
+	g_free(header);
 
-	g_free(size);
-	g_free(post);
-	g_free(buf);
+	d->fd = source;
+	d->watcher = gaim_input_add(d->fd, GAIM_INPUT_WRITE, yahoo_buddy_icon_upload_pending, d);
+
+	yahoo_buddy_icon_upload_pending(d, d->fd, GAIM_INPUT_WRITE);
 }
 
 void yahoo_buddy_icon_upload(GaimConnection *gc, struct yahoo_buddy_icon_upload_data *d)
--- a/src/protocols/yahoo/yahoochat.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/yahoo/yahoochat.c	Thu Feb 09 04:17:56 2006 +0000
@@ -1080,6 +1080,8 @@
 struct yahoo_roomlist {
 	int fd;
 	int inpa;
+	guchar *txbuf;
+	gsize tx_written;
 	guchar *rxqueue;
 	int rxlen;
 	gboolean started;
@@ -1095,12 +1097,10 @@
 {
 	if (yrl->inpa)
 		gaim_input_remove(yrl->inpa);
-	if (yrl->rxqueue)
-		g_free(yrl->rxqueue);
-	if (yrl->path)
-		g_free(yrl->path);
-	if (yrl->host)
-		g_free(yrl->host);
+	g_free(yrl->txbuf);
+	g_free(yrl->rxqueue);
+	g_free(yrl->path);
+	g_free(yrl->host);
 	if (yrl->parse)
 		g_markup_parse_context_free(yrl->parse);
 	g_free(yrl);
@@ -1143,12 +1143,9 @@
 static void yahoo_chatxml_state_destroy(struct yahoo_chatxml_state *s)
 {
 	g_queue_free(s->q);
-	if (s->room.name)
-		g_free(s->room.name);
-	if (s->room.topic)
-		g_free(s->room.topic);
-	if (s->room.id)
-		g_free(s->room.id);
+	g_free(s->room.name);
+	g_free(s->room.topic);
+	g_free(s->room.id);
 	g_free(s);
 }
 
@@ -1298,6 +1295,9 @@
 
 	len = read(yrl->fd, buf, sizeof(buf));
 
+	if (len < 0 && errno == EAGAIN)
+		return;
+
 	if (len <= 0) {
 		if (yrl->parse)
 			g_markup_parse_context_end_parse(yrl->parse, NULL);
@@ -1338,8 +1338,8 @@
 {
 	struct yahoo_roomlist *yrl = data;
 	GaimRoomlist *list = yrl->list;
-	char *buf, *cookie;
 	struct yahoo_data *yd = gaim_account_get_connection(list->account)->proto_data;
+	int written, total_len;
 
 	if (source < 0) {
 		gaim_notify_error(gaim_account_get_connection(list->account), NULL, _("Unable to connect"), _("Fetching the room list failed."));
@@ -1347,15 +1347,48 @@
 		return;
 	}
 
-	yrl->fd = source;
+	if (yrl->txbuf == NULL) {
+		yrl->fd = source;
+
+		yrl->txbuf = g_strdup_printf(
+			"GET http://%s/%s HTTP/1.0\r\n"
+			"Host: %s\r\n"
+			"Cookie: Y=%s; T=%s\r\n\r\n",
+			yrl->host, yrl->path, yrl->host, yd->cookie_y,
+			yd->cookie_t);
+	}
+
+	total_len = strlen(yrl->txbuf) - yrl->tx_written;
+	written = write(yrl->fd, yrl->txbuf + yrl->tx_written, total_len);
 
-	cookie = g_strdup_printf("Y=%s; T=%s", yd->cookie_y, yd->cookie_t);
-	buf = g_strdup_printf("GET http://%s/%s HTTP/1.0\r\nHost: %s\r\nCookie: %s\r\n\r\n",
-						  yrl->host, yrl->path, yrl->host, cookie);
-	write(yrl->fd, buf, strlen(buf));
-	g_free(cookie);
-	g_free(buf);
-	yrl->inpa = gaim_input_add(yrl->fd, GAIM_INPUT_READ, yahoo_roomlist_pending, yrl);
+	if (written < 0 && errno == EAGAIN)
+		written = 0;
+	else if (written <= 0) {
+		if (yrl->inpa)
+			gaim_input_remove(yrl->inpa);
+		yrl->inpa = 0;
+		g_free(yrl->txbuf);
+		yrl->txbuf = NULL;
+		gaim_notify_error(gaim_account_get_connection(list->account), NULL, _("Unable to connect"), _("Fetching the room list failed."));
+		yahoo_roomlist_cleanup(list, yrl);
+		return;
+	}
+
+	if (written < total_len) {
+		if (!yrl->inpa)
+			yrl->inpa = gaim_input_add(yrl->fd,
+				GAIM_INPUT_WRITE, yahoo_roomlist_got_connected,
+				yrl);
+		yrl->tx_written += written;
+		return;
+	}
+
+	g_free(yrl->txbuf);
+	yrl->txbuf = NULL;
+	if (yrl->inpa)
+		gaim_input_remove(yrl->inpa);
+	yrl->inpa = gaim_input_add(yrl->fd, GAIM_INPUT_READ,
+		yahoo_roomlist_pending, yrl);
 
 }
 
--- a/src/protocols/yahoo/ycht.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/yahoo/ycht.c	Thu Feb 09 04:17:56 2006 +0000
@@ -265,9 +265,39 @@
 	return ret;
 }
 
+static void ycht_packet_send_write_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	YchtConn *ycht = data;
+	int ret, writelen;
+
+	writelen = gaim_circ_buffer_get_max_read(ycht->txbuf);
+
+	if (writelen == 0) {
+		gaim_input_remove(ycht->tx_handler);
+		ycht->tx_handler = 0;
+		return;
+	}
+
+	ret = write(ycht->fd, ycht->txbuf->outptr, writelen);
+
+	if (ret < 0 && errno == EAGAIN)
+		return;
+	else if (ret <= 0) {
+		/* TODO: error handling */
+/*
+		gaim_connection_error(gaim_account_get_connection(irc->account),
+			      _("Server has disconnected"));
+*/
+		return;
+	}
+
+	gaim_circ_buffer_mark_read(ycht->txbuf, ret);
+
+}
+
 static void ycht_packet_send(YchtConn *ycht, YchtPkt *pkt)
 {
-	int len, pos;
+	int len, pos, written;
 	char *buf;
 	GList *l;
 
@@ -295,7 +325,29 @@
 		}
 	}
 
-	write(ycht->fd, buf, len);
+	if (!ycht->tx_handler)
+		written = write(ycht->fd, buf, len);
+	else {
+		written = -1;
+		errno = EAGAIN;
+	}
+
+	if (written < 0 && errno == EAGAIN)
+		written = 0;
+	else if (written <= 0) {
+		/* TODO: Error handling (was none before NBIO changes) */
+		written = 0;
+	}
+
+	if (written < len) {
+		if (!ycht->tx_handler)
+			ycht->tx_handler = gaim_input_add(ycht->fd,
+				GAIM_INPUT_WRITE, ycht_packet_send_write_cb,
+				ycht);
+		gaim_circ_buffer_append(ycht->txbuf, buf + written,
+			len - written);
+	}
+
 	g_free(buf);
 }
 
@@ -388,8 +440,12 @@
 	if (ycht->inpa)
 		gaim_input_remove(ycht->inpa);
 
-	if (ycht->rxqueue)
-		g_free(ycht->rxqueue);
+	if (ycht->tx_handler)
+		gaim_input_remove(ycht->tx_handler);
+
+	gaim_circ_buffer_destroy(ycht->txbuf);
+
+	g_free(ycht->rxqueue);
 
 	g_free(ycht);
 }
@@ -409,6 +465,9 @@
 
 	len = read(ycht->fd, buf, sizeof(buf));
 
+	if (len < 0 && errno == EAGAIN)
+		return;
+
 	if (len <= 0) {
 		ycht_connection_error(ycht, _("Unable to read"));
 		return;
@@ -529,8 +588,7 @@
 	char *tmp;
 
 	tmp = g_strdup(room);
-	if (ycht->room)
-		g_free(ycht->room);
+	g_free(ycht->room);
 	ycht->room = tmp;
 
 	if (!ycht->logged_in)
--- a/src/protocols/yahoo/ycht.h	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/protocols/yahoo/ycht.h	Thu Feb 09 04:17:56 2006 +0000
@@ -73,6 +73,8 @@
 	gboolean changing_rooms;
 	guchar *rxqueue;
 	guint rxlen;
+	GaimCircBuffer *txbuf;
+	guint tx_handler;
 } YchtConn;
 
 typedef struct {
--- a/src/proxy.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/proxy.c	Thu Feb 09 04:17:56 2006 +0000
@@ -49,6 +49,13 @@
 	GaimProxyInfo *gpi;
 	GaimAccount *account;
 	GSList *hosts;
+	guchar *write_buffer;
+	gsize write_buf_len;
+	gsize written_len;
+	GaimInputFunction read_cb;
+	guchar *read_buffer;
+	gsize read_buf_len;
+	gsize read_len;
 };
 
 static void try_connect(struct PHB *);
@@ -254,7 +261,7 @@
  * Proxy API
  **************************************************************************/
 
-#ifdef __unix__ 
+#ifdef __unix__
 
 /*
  * This structure represents both a pending DNS request and
@@ -956,7 +963,6 @@
 		return;
 	}
 
-	fcntl(source, F_SETFL, 0);
 	gaim_input_remove(phb->inpa);
 
 	if (phb->account == NULL ||
@@ -1027,7 +1033,6 @@
 			close(fd);
 			return -1;
 		}
-		fcntl(fd, F_SETFL, 0);
 		phb->port = fd;	/* bleh */
 		gaim_timeout_add(50, clean_connect, phb);	/* we do this because we never
 							   want to call our callback
@@ -1037,6 +1042,37 @@
 	return fd;
 }
 
+static void
+proxy_do_write(gpointer data, gint source, GaimInputCondition cond)
+{
+	struct PHB *phb = data;
+	const char * request = phb->write_buffer + phb->written_len;
+	gsize request_len = phb->write_buf_len - phb->written_len;
+
+	int ret = write(source, request, request_len);
+
+	if(ret < 0 && errno == EAGAIN)
+		return;
+	else if(ret < 0) {
+		gaim_input_remove(phb->inpa);
+		close(source);
+		g_free(phb->write_buffer);
+		phb->write_buffer = NULL;
+		try_connect(phb);
+		return;
+	} else if (ret < request_len) {
+		phb->written_len += ret;
+		return;
+	}
+
+	gaim_input_remove(phb->inpa);
+	g_free(phb->write_buffer);
+	phb->write_buffer = NULL;
+
+	/* register the response handler for the response */
+	phb->inpa = gaim_input_add(source, GAIM_INPUT_READ, phb->read_cb, phb);
+}
+
 #define HTTP_GOODSTRING "HTTP/1.0 200"
 #define HTTP_GOODSTRING2 "HTTP/1.1 200"
 
@@ -1044,13 +1080,11 @@
 http_complete(struct PHB *phb, gint source)
 {
 	gaim_debug_info("http proxy", "proxy connection established\n");
-	if(source < 0) {
-		try_connect(phb);
-	} else if(!phb->account || phb->account->gc) {
+	if(!phb->account || phb->account->gc) {
 		phb->func(phb->data, source, GAIM_INPUT_READ);
-		g_free(phb->host);
-		g_free(phb);
 	}
+	g_free(phb->host);
+	g_free(phb);
 }
 
 
@@ -1058,65 +1092,117 @@
 static void
 http_canread(gpointer data, gint source, GaimInputCondition cond)
 {
-	int nlc = 0;
-	int pos = 0;
-	int minor, major, status = 0, error=0;
+	int len, headers_len, status = 0;
+	gboolean error;
 	struct PHB *phb = data;
-	char inputline[8192], *p;
+	guchar *p;
+	gsize max_read;
 
+	if(phb->read_buffer == NULL) {
+		phb->read_buf_len = 8192;
+		phb->read_buffer = g_malloc(phb->read_buf_len);
+		phb->read_len = 0;
+	}
+
+	p = phb->read_buffer + phb->read_len - 1;
+	max_read = phb->read_buf_len - phb->read_len;
 
-	while ((pos < sizeof(inputline)-1) && (nlc != 2) && (read(source, &inputline[pos++], 1) == 1)) {
-		if (inputline[pos - 1] == '\n')
-			nlc++;
-		else if (inputline[pos - 1] != '\r')
-			nlc = 0;
+	len = read(source, p, max_read);
+	if(len < 0 && errno == EAGAIN)
+		return;
+	else if(len < 0) {
+		close(source);
+		source = -1;
+		g_free(phb->read_buffer);
+		phb->read_buffer = NULL;
+		gaim_input_remove(phb->inpa);
+		phb->inpa = 0;
+		http_complete(phb, source);
+		return;
+	} else {
+		phb->read_len += len;
 	}
-	inputline[pos] = '\0';
+	p[len] = '\0';
 
-	error = strncmp(inputline, "HTTP/", 5) != 0;
+	if((p = g_strstr_len(phb->read_buffer, phb->read_len, "\r\n\r\n"))) {
+		*p = '\0';
+		headers_len = (p - phb->read_buffer) + 4;
+	} else if(len == max_read)
+		headers_len = len;
+	else
+		return;
+
+	error = strncmp(phb->read_buffer, "HTTP/", 5) != 0;
 	if(!error) {
-		p = inputline + 5;
-		major = strtol(p, &p, 10);
-		error = (major==0) || (*p != '.');
+		char *c;
+		int major;
+		p = phb->read_buffer + 5;
+		c = p;
+		major = strtol(c, &c, 10);
+		p = c;
+		error = (major == 0) || (*p != '.');
 		if(!error) {
+			int minor;
 			p++;
-			minor = strtol(p, &p, 10);
-			error = (*p!=' ');
+			c = p;
+			minor = strtol(c, &c, 10);
+			p = c;
+			error = (*p != ' ');
 			if(!error) {
 				p++;
-				status = strtol(p, &p, 10);
-				error = (*p!=' ');
+				c = p;
+				status = strtol(c, &c, 10);
+				p = c;
+				error = (*p != ' ');
 			}
 		}
 	}
 
 	/* Read the contents */
-	p = g_strrstr(inputline, "Content-Length: ");
+	p = g_strrstr(phb->read_buffer, "Content-Length: ");
 	if(p != NULL) {
 		gchar *tmp;
 		int len = 0;
 		char tmpc;
 		p += strlen("Content-Length: ");
 		tmp = strchr(p, '\r');
-		*tmp = 0;
+		*tmp = '\0';
 		len = atoi(p);
 		*tmp = '\r';
-		while(len--) read(source, &tmpc, 1);
+
+		/* Compensate for what has already been read */
+		len -= phb->read_len - headers_len;
+		/* I'm assuming that we're doing this to prevent the server from
+		   complaining / breaking since we don't read the whole page */
+		while(len--) {
+			/* TODO: deal with EAGAIN (and other errors) better */
+			if (read(source, &tmpc, 1) < 0 && errno != EAGAIN)
+				break;
+		}
 	}
+
 	if(error) {
 		gaim_debug_error("proxy",
-				   "Unable to parse proxy's response: %s\n", inputline);
+				"Unable to parse proxy's response: %s\n",
+				phb->read_buffer);
 		close(source);
-		source=-1;
-	}
-	else if(status!=200) {
+		source = -1;
+		g_free(phb->read_buffer);
+		phb->read_buffer = NULL;
+		gaim_input_remove(phb->inpa);
+		phb->inpa = 0;
+		http_complete(phb, source);
+		return;
+	} else if(status != 200) {
 		gaim_debug_error("proxy",
-				   "Proxy server replied with:\n%s\n", inputline);
+				"Proxy server replied with:\n%s\n",
+				phb->read_buffer);
+
 
 		/* XXX: why in the hell are we calling gaim_connection_error() here? */
-		if ( status == 407 /* Proxy Auth */ ) {
+		if(status == 407 /* Proxy Auth */) {
 			gchar *ntlm;
-			if( (ntlm = g_strrstr(inputline, "Proxy-Authenticate: NTLM "))) { /* Check for Type-2 */
+			if((ntlm = g_strrstr(phb->read_buffer, "Proxy-Authenticate: NTLM "))) { /* Check for Type-2 */
 				gchar *nonce = ntlm;
 				gchar *domain = (gchar*)gaim_proxy_info_get_username(phb->gpi);
 				gchar *username;
@@ -1128,28 +1214,51 @@
 					source = -1;
 					gaim_connection_error(phb->account->gc, msg);
 					g_free(msg);
-				        gaim_input_remove(phb->inpa);
+					gaim_input_remove(phb->inpa);
+					g_free(phb->read_buffer);
+					g_free(phb->host);
+					g_free(phb);
 					return;
 				}
-				*username = 0;
-				username ++;
+				*username = '\0';
+				username++;
 				ntlm += strlen("Proxy-Authenticate: NTLM ");
 				while(*nonce != '\r' && *nonce != '\0') nonce ++;
-				*nonce = 0;
+				*nonce = '\0';
 				nonce = gaim_ntlm_parse_type2(ntlm, NULL);
-				response = gaim_ntlm_gen_type3(username, (gchar*)gaim_proxy_info_get_password(phb->gpi), (gchar*)gaim_proxy_info_get_host(phb->gpi), domain, nonce, NULL);
+				response = gaim_ntlm_gen_type3(username,
+					(gchar*) gaim_proxy_info_get_password(phb->gpi),
+					(gchar*) gaim_proxy_info_get_host(phb->gpi),
+					domain, nonce, NULL);
 				username--;
 				*username = '\\';
-				request = g_strdup_printf("CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\nProxy-Authorization: NTLM %s\r\nProxy-Connection: Keep-Alive\r\n\r\n",
-						                                 phb->host, phb->port, phb->host, phb->port,
-										                                  response);
-				write(source, request, strlen(request));
-				g_free(request);
+				request = g_strdup_printf(
+					"CONNECT %s:%d HTTP/1.1\r\n"
+					"Host: %s:%d\r\n"
+					"Proxy-Authorization: NTLM %s\r\n"
+					"Proxy-Connection: Keep-Alive\r\n\r\n",
+					phb->host, phb->port, phb->host,
+					phb->port, response);
 				g_free(response);
+
+				gaim_input_remove(phb->inpa);
+				g_free(phb->read_buffer);
+				phb->read_buffer = NULL;
+
+				phb->write_buffer = request;
+				phb->write_buf_len = strlen(request);
+				phb->written_len = 0;
+
+				phb->read_cb = http_canread;
+
+				phb->inpa = gaim_input_add(source,
+					GAIM_INPUT_WRITE, proxy_do_write, phb);
+
+				proxy_do_write(phb, source, cond);
 				return;
-			} else if((ntlm = g_strrstr(inputline, "Proxy-Authenticate: NTLM"))) { /* Empty message */
+			} else if((ntlm = g_strrstr(phb->read_buffer, "Proxy-Authenticate: NTLM"))) { /* Empty message */
 				gchar request[2048];
-				gchar *domain = (gchar*)gaim_proxy_info_get_username(phb->gpi);
+				gchar *domain = (gchar*) gaim_proxy_info_get_username(phb->gpi);
 				gchar *username;
 				int request_len;
 				if(!(username = strchr(domain, '\\'))) {
@@ -1158,21 +1267,45 @@
 					source = -1;
 					gaim_connection_error(phb->account->gc, msg);
 					g_free(msg);
-				        gaim_input_remove(phb->inpa);
+					gaim_input_remove(phb->inpa);
+					g_free(phb->read_buffer);
+					g_free(phb->host);
+					g_free(phb);
 					return;
 				}
-				*username = 0;
+				*username = '\0';
 
 				request_len = g_snprintf(request, sizeof(request),
-						"CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n",
-						phb->host, phb->port, phb->host, phb->port);
+						"CONNECT %s:%d HTTP/1.1\r\n"
+						"Host: %s:%d\r\n",
+						phb->host, phb->port,
+						phb->host, phb->port);
 
 				g_return_if_fail(request_len < sizeof(request));
 				request_len += g_snprintf(request + request_len,
-						sizeof(request) - request_len,
-						"Proxy-Authorization: NTLM %s\r\nProxy-Connection: Keep-Alive\r\n\r\n", gaim_ntlm_gen_type1((gchar*)gaim_proxy_info_get_host(phb->gpi),domain));
+					sizeof(request) - request_len,
+					"Proxy-Authorization: NTLM %s\r\n"
+					"Proxy-Connection: Keep-Alive\r\n\r\n",
+					gaim_ntlm_gen_type1(
+						(gchar*) gaim_proxy_info_get_host(phb->gpi),
+						domain));
 				*username = '\\';
-				write(source, request, request_len);
+
+				gaim_input_remove(phb->inpa);
+				g_free(phb->read_buffer);
+				phb->read_buffer = NULL;
+
+				phb->write_buffer = g_strndup(request,
+					request_len);
+				phb->write_buf_len = request_len;
+				phb->written_len = 0;
+
+				phb->read_cb = http_canread;
+
+				phb->inpa = gaim_input_add(source,
+					GAIM_INPUT_WRITE, proxy_do_write, phb);
+
+				proxy_do_write(phb, source, cond);
 				return;
 			} else {
 				char *msg = g_strdup_printf(_("Proxy connection error %d"), status);
@@ -1180,24 +1313,39 @@
 				source = -1;
 				gaim_connection_error(phb->account->gc, msg);
 				g_free(msg);
+				gaim_input_remove(phb->inpa);
+				g_free(phb->read_buffer);
+				g_free(phb->host);
+				g_free(phb);
 			}
 		}
-		if ( status == 403 /* Forbidden */ ) {
+		if(status == 403 /* Forbidden */ ) {
 			gchar *msg = g_strdup_printf(_("Access denied: proxy server forbids port %d tunnelling."), phb->port);
 			gaim_connection_error(phb->account->gc, msg);
 			g_free(msg);
+			gaim_input_remove(phb->inpa);
+			g_free(phb->read_buffer);
+			g_free(phb->host);
+			g_free(phb);
 		} else {
 			char *msg = g_strdup_printf(_("Proxy connection error %d"), status);
 			gaim_connection_error(phb->account->gc, msg);
 			g_free(msg);
+			gaim_input_remove(phb->inpa);
+			g_free(phb->read_buffer);
+			g_free(phb->host);
+			g_free(phb);
 		}
 	} else {
+		gaim_input_remove(phb->inpa);
+		g_free(phb->read_buffer);
+		phb->read_buffer = NULL;
 		http_complete(phb, source);
+		return;
 	}
+}
 
-	gaim_input_remove(phb->inpa);
-	return;
-}
+
 
 static void
 http_canwrite(gpointer data, gint source, GaimInputCondition cond)
@@ -1222,7 +1370,8 @@
 		return;
 	}
 
-	gaim_debug_info("proxy", "using CONNECT tunnelling for %s:%d\n", phb->host, phb->port);
+	gaim_debug_info("proxy", "using CONNECT tunnelling for %s:%d\n",
+		phb->host, phb->port);
 	request_len = g_snprintf(request, sizeof(request),
 				 "CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n",
 				 phb->host, phb->port, phb->host, phb->port);
@@ -1230,32 +1379,36 @@
 	if (gaim_proxy_info_get_username(phb->gpi) != NULL) {
 		char *t1, *t2;
 		t1 = g_strdup_printf("%s:%s",
-				     gaim_proxy_info_get_username(phb->gpi),
-				     gaim_proxy_info_get_password(phb->gpi) ?
-				     gaim_proxy_info_get_password(phb->gpi) : "");
+			gaim_proxy_info_get_username(phb->gpi),
+			gaim_proxy_info_get_password(phb->gpi) ?
+				gaim_proxy_info_get_password(phb->gpi) : "");
 		t2 = gaim_base64_encode((const guchar *)t1, strlen(t1));
 		g_free(t1);
 		g_return_if_fail(request_len < sizeof(request));
 		
 		request_len += g_snprintf(request + request_len,
-					  sizeof(request) - request_len,
-					  "Proxy-Authorization: Basic %s\r\nProxy-Authorization: NTLM %s\r\nProxy-Connection: Keep-Alive\r\n", t2, gaim_ntlm_gen_type1((gchar*)gaim_proxy_info_get_host(phb->gpi),""));
+			sizeof(request) - request_len,
+			"Proxy-Authorization: Basic %s\r\n"
+			"Proxy-Authorization: NTLM %s\r\n"
+			"Proxy-Connection: Keep-Alive\r\n", t2,
+			gaim_ntlm_gen_type1(
+				(gchar*)gaim_proxy_info_get_host(phb->gpi),""));
 		g_free(t2);
 	}
 
 	g_return_if_fail(request_len < sizeof(request));
 	strcpy(request + request_len, "\r\n");
 	request_len += 2;
-
-	if (write(source, request, request_len) < 0) {
-		close(source);
+	phb->write_buffer = g_strndup(request, request_len);
+	phb->write_buf_len = request_len;
+	phb->written_len = 0;
 
-		try_connect(phb);
-		return;
-	}
+	phb->read_cb = http_canread;
 
-	/* register the response handler for the CONNECT request */
-	phb->inpa = gaim_input_add(source, GAIM_INPUT_READ, http_canread, phb);
+	phb->inpa = gaim_input_add(source, GAIM_INPUT_WRITE, proxy_do_write,
+		phb);
+
+	proxy_do_write(phb, source, cond);
 }
 
 static int
@@ -1307,34 +1460,55 @@
 			close(fd);
 			return -1;
 		}
-		fcntl(fd, F_SETFL, 0);
 		http_canwrite(phb, fd, GAIM_INPUT_WRITE);
 	}
 
 	return fd;
 }
 
+
 static void
 s4_canread(gpointer data, gint source, GaimInputCondition cond)
 {
-	unsigned char packet[12];
 	struct PHB *phb = data;
+	char *buf;
+	int len, max_read;
+
+	/* This is really not going to block under normal circumstances, but to
+	 * be correct, we deal with the unlikely scenario */
+
+	if (phb->read_buffer == NULL) {
+		phb->read_buf_len = 12;
+		phb->read_buffer = g_malloc(phb->read_buf_len);
+		phb->read_len = 0;
+	}
+
+	buf = phb->read_buffer + phb->read_len - 1;
+	max_read = phb->read_buf_len - phb->read_len;
+
+	len = read(source, buf, max_read);
+
+	if ((len < 0 && errno == EAGAIN) || len + phb->read_len < 4)
+		return;
+	else if (len + phb->read_len >= 4) {
+		if (phb->read_buffer[1] == 90) {
+			if (phb->account == NULL ||
+				gaim_account_get_connection(phb->account) != NULL) {
+
+				phb->func(phb->data, source, GAIM_INPUT_READ);
+			}
+
+			gaim_input_remove(phb->inpa);
+			g_free(phb->read_buffer);
+			g_free(phb->host);
+			g_free(phb);
+			return;
+		}
+	}
 
 	gaim_input_remove(phb->inpa);
-
-	memset(packet, 0, sizeof(packet));
-
-	if (read(source, packet, 9) >= 4 && packet[1] == 90) {
-		if (phb->account == NULL ||
-			gaim_account_get_connection(phb->account) != NULL) {
-
-			phb->func(phb->data, source, GAIM_INPUT_READ);
-		}
-
-		g_free(phb->host);
-		g_free(phb);
-		return;
-	}
+	g_free(phb->read_buffer);
+	phb->read_buffer = NULL;
 
 	close(source);
 
@@ -1344,7 +1518,7 @@
 static void
 s4_canwrite(gpointer data, gint source, GaimInputCondition cond)
 {
-	unsigned char packet[12];
+	unsigned char packet[9];
 	struct hostent *hp;
 	struct PHB *phb = data;
 	socklen_t len;
@@ -1363,7 +1537,6 @@
 		try_connect(phb);
 		return;
 	}
-	fcntl(source, F_SETFL, 0);
 
 	/*
 	 * The socks4 spec doesn't include support for doing host name
@@ -1390,14 +1563,14 @@
 	packet[7] = (unsigned char)(hp->h_addr_list[0])[3];
 	packet[8] = 0;
 
-	if (write(source, packet, 9) != 9) {
-		close(source);
+	phb->write_buffer = g_strndup(packet, sizeof(packet));
+	phb->write_buf_len = sizeof(packet);
+	phb->written_len = 0;
+	phb->read_cb = s4_canread;
 
-		try_connect(phb);
-		return;
-	}
+	phb->inpa = gaim_input_add(source, GAIM_INPUT_WRITE, proxy_do_write, phb);
 
-	phb->inpa = gaim_input_add(source, GAIM_INPUT_READ, s4_canread, phb);
+	proxy_do_write(phb, source, cond);
 }
 
 static int
@@ -1443,7 +1616,6 @@
 			return -1;
 		}
 
-		fcntl(fd, F_SETFL, 0);
 		s4_canwrite(phb, fd, GAIM_INPUT_WRITE);
 	}
 
@@ -1453,26 +1625,47 @@
 static void
 s5_canread_again(gpointer data, gint source, GaimInputCondition cond)
 {
-	unsigned char buf[512];
+	guchar *dest, *buf;
 	struct PHB *phb = data;
+	int len;
 
-	gaim_input_remove(phb->inpa);
+	if (phb->read_buffer == NULL) {
+		phb->read_buf_len = 512;
+		phb->read_buffer = g_malloc(phb->read_buf_len);
+		phb->read_len = 0;
+	}
+
+	dest = phb->read_buffer + phb->read_len;
+	buf = phb->read_buffer;
+
 	gaim_debug_info("socks5 proxy", "Able to read again.\n");
 
-	if (read(source, buf, 4) < 4) {
+	len = read(source, dest, (phb->read_buf_len - phb->read_len));
+	if(len < 0 && errno == EAGAIN)
+		return;
+	else if(len < 0) {
 		gaim_debug_warning("socks5 proxy", "or not...\n");
 		close(source);
-
+		gaim_input_remove(phb->inpa);
+		g_free(phb->read_buffer);
+		phb->read_buffer = NULL;
 		try_connect(phb);
 		return;
 	}
+	phb->read_len += len;
+
+	if(phb->read_len < 4)
+		return;
+
 	if ((buf[0] != 0x05) || (buf[1] != 0x00)) {
 		if ((buf[0] == 0x05) && (buf[1] < 0x09))
 			gaim_debug_error("socks5 proxy", socks5errors[buf[1]]);
 		else
 			gaim_debug_error("socks5 proxy", "Bad data.\n");
 		close(source);
-
+		gaim_input_remove(phb->inpa);
+		g_free(phb->read_buffer);
+		phb->read_buffer = NULL;
 		try_connect(phb);
 		return;
 	}
@@ -1480,21 +1673,32 @@
 	/* Skip past BND.ADDR */
 	switch(buf[3]) {
 		case 0x01: /* the address is a version-4 IP address, with a length of 4 octets */
-			read(source, buf, 4);
+			if(phb->read_len < 4 + 4)
+				return;
+			buf += 4 + 4;
 			break;
 		case 0x03: /* the address field contains a fully-qualified domain name.  The first
 					  octet of the address field contains the number of octets of name that
 					  follow, there is no terminating NUL octet. */
-			read(source, buf, 1);
-			read(source, buf, buf[0]);
+			if(phb->read_len < 4 + 1)
+				return;
+			buf += 4 + 1;
+			if(phb->read_len < 4 + 1 + buf[0])
+				return;
+			buf += buf[0];
 			break;
 		case 0x04: /* the address is a version-6 IP address, with a length of 16 octets */
-			read(source, buf, 16);
+			if(phb->read_len < 4 + 16)
+				return;
+			buf += 4 + 16;
 			break;
 	}
 
+	if(phb->read_len < (buf - phb->read_buffer) + 2)
+		return;
+
 	/* Skip past BND.PORT */
-	read(source, buf, 2);
+	buf += 2;
 
 	if (phb->account == NULL ||
 		gaim_account_get_connection(phb->account) != NULL) {
@@ -1502,6 +1706,8 @@
 		phb->func(phb->data, source, GAIM_INPUT_READ);
 	}
 
+	gaim_input_remove(phb->inpa);
+	g_free(phb->read_buffer);
 	g_free(phb->host);
 	g_free(phb);
 }
@@ -1509,51 +1715,71 @@
 static void
 s5_sendconnect(gpointer data, gint source)
 {
-	unsigned char buf[512];
 	struct PHB *phb = data;
 	int hlen = strlen(phb->host);
+	phb->write_buf_len = 5 + hlen + 1;
+	phb->write_buffer = g_malloc(phb->write_buf_len);
+	phb->written_len = 0;
 
-	buf[0] = 0x05;
-	buf[1] = 0x01;		/* CONNECT */
-	buf[2] = 0x00;		/* reserved */
-	buf[3] = 0x03;		/* address type -- host name */
-	buf[4] = hlen;
-	memcpy(buf + 5, phb->host, hlen);
-	buf[5 + hlen] = phb->port >> 8;
-	buf[5 + hlen + 1] = phb->port & 0xff;
+	phb->write_buffer[0] = 0x05;
+	phb->write_buffer[1] = 0x01;		/* CONNECT */
+	phb->write_buffer[2] = 0x00;		/* reserved */
+	phb->write_buffer[3] = 0x03;		/* address type -- host name */
+	phb->write_buffer[4] = hlen;
+	memcpy(phb->write_buffer + 5, phb->host, hlen);
+	phb->write_buffer[5 + hlen] = phb->port >> 8;
+	phb->write_buffer[5 + hlen + 1] = phb->port & 0xff;
 
-	if (write(source, buf, (5 + hlen + 2)) < (5 + hlen + 2)) {
-		close(source);
+	phb->read_cb = s5_canread_again;
 
-		try_connect(phb);
-		return;
-	}
+	phb->inpa = gaim_input_add(source, GAIM_INPUT_WRITE, proxy_do_write, phb);
+	proxy_do_write(phb, source, GAIM_INPUT_WRITE);
 
-	phb->inpa = gaim_input_add(source, GAIM_INPUT_READ, s5_canread_again, phb);
 }
 
 static void
 s5_readauth(gpointer data, gint source, GaimInputCondition cond)
 {
-	unsigned char buf[512];
 	struct PHB *phb = data;
+	int len;
+
+	if (phb->read_buffer == NULL) {
+		phb->read_buf_len = 2;
+		phb->read_buffer = g_malloc(phb->read_buf_len);
+		phb->read_len = 0;
+	}
+
+	gaim_debug_info("socks5 proxy", "Got auth response.\n");
+
+	len = read(source, phb->read_buffer + phb->read_len,
+		phb->read_buf_len - phb->read_len);
+	if(len < 0 && errno == EAGAIN)
+		return;
+	else if(len < 0) {
+		close(source);
+		gaim_input_remove(phb->inpa);
+		g_free(phb->read_buffer);
+		phb->read_buffer = NULL;
+		try_connect(phb);
+		return;
+	}
+	phb->read_len += len;
+
+	if (phb->read_len < 2)
+		return;
 
 	gaim_input_remove(phb->inpa);
-	gaim_debug_info("socks5 proxy", "Got auth response.\n");
 
-	if (read(source, buf, 2) < 2) {
+	if ((phb->read_buffer[0] != 0x01) || (phb->read_buffer[1] != 0x00)) {
 		close(source);
-
+		g_free(phb->read_buffer);
+		phb->read_buffer = NULL;
 		try_connect(phb);
 		return;
 	}
 
-	if ((buf[0] != 0x01) || (buf[1] != 0x00)) {
-		close(source);
-
-		try_connect(phb);
-		return;
-	}
+	g_free(phb->read_buffer);
+	phb->read_buffer = NULL;
 
 	s5_sendconnect(phb, source);
 }
@@ -1604,125 +1830,175 @@
 static void
 s5_readchap(gpointer data, gint source, GaimInputCondition cond)
 {
-	unsigned char buf[260];
-	unsigned char cmdbuf[20];
+	guchar *cmdbuf, *buf;
 	struct PHB *phb = data;
+	int len, navas, currentav;
 
-	int navas, currentav;
-
-	gaim_input_remove(phb->inpa);
 	gaim_debug(GAIM_DEBUG_INFO, "socks5 proxy", "Got CHAP response.\n");
 
-	if (read(source, cmdbuf, 2) < 2) {
+	if (phb->read_buffer == NULL) {
+		phb->read_buf_len = 20;
+		phb->read_buffer = g_malloc(phb->read_buf_len);
+		phb->read_len = 0;
+	}
+
+	len = read(source, phb->read_buffer + phb->read_len,
+		phb->read_buf_len - phb->read_len);
+
+	if(len < 0 && errno == EAGAIN)
+		return;
+	else if(len < 0) {
 		close(source);
-
+		gaim_input_remove(phb->inpa);
+		g_free(phb->read_buffer);
+		phb->read_buffer = NULL;
 		try_connect(phb);
 		return;
 	}
+	phb->read_len += len;
 
-	if (cmdbuf[0] != 0x01) {
+	if (phb->read_len < 2)
+		return;
+
+	cmdbuf = phb->read_buffer;
+
+	if (*cmdbuf != 0x01) {
 		close(source);
-
+		gaim_input_remove(phb->inpa);
+		g_free(phb->read_buffer);
+		phb->read_buffer = NULL;
 		try_connect(phb);
 		return;
 	}
+	cmdbuf++;
 
-	navas = cmdbuf[1];
+	navas = *cmdbuf;
+	cmdbuf++;
 
 	for (currentav = 0; currentav < navas; currentav++) {
-		if (read(source, cmdbuf, 2) < 2) {
-			close(source);
-
-			try_connect(phb);
+		if (phb->read_len - (cmdbuf - phb->read_buffer) < 2)
 			return;
-		}
-		if (read(source, buf, cmdbuf[1]) < cmdbuf[1]) {
-			close(source);
-
-			try_connect(phb);
+		if (phb->read_len - (cmdbuf - phb->read_buffer) < cmdbuf[1])
 			return;
-		}
+		buf = cmdbuf + 2;
 		switch (cmdbuf[0]) {
 			case 0x00:
 				/* Did auth work? */
 				if (buf[0] == 0x00) {
+					gaim_input_remove(phb->inpa);
+					g_free(phb->read_buffer);
+					phb->read_buffer = NULL;
 					/* Success */
 					s5_sendconnect(phb, source);
 					return;
 				} else {
 					/* Failure */
-					gaim_debug_warning("proxy", "socks5 CHAP authentication "
-									   "failed.  Disconnecting...");
+					gaim_debug_warning("proxy",
+						"socks5 CHAP authentication "
+						"failed.  Disconnecting...");
 					close(source);
-
+					gaim_input_remove(phb->inpa);
+					g_free(phb->read_buffer);
+					phb->read_buffer = NULL;
 					try_connect(phb);
 					return;
 				}
 				break;
 			case 0x03:
 				/* Server wants our credentials */
+
+				phb->write_buf_len = 16 + 4;
+				phb->write_buffer = g_malloc(phb->write_buf_len);
+				phb->written_len = 0;
+
 				hmacmd5_chap(buf, cmdbuf[1],
 					gaim_proxy_info_get_password(phb->gpi),
-					buf + 4);
-				buf[0] = 0x01;
-				buf[1] = 0x01;
-				buf[2] = 0x04;
-				buf[3] = 0x10;
-				if (write(source, buf, 20) < 20) {
-					close(source);
+					phb->write_buffer + 4);
+				phb->write_buffer[0] = 0x01;
+				phb->write_buffer[1] = 0x01;
+				phb->write_buffer[2] = 0x04;
+				phb->write_buffer[3] = 0x10;
 
-					try_connect(phb);
-					return;
-				}
+				gaim_input_remove(phb->inpa);
+				g_free(phb->read_buffer);
+				phb->read_buffer = NULL;
+
+				phb->read_cb = s5_readchap;
+
+				phb->inpa = gaim_input_add(source,
+					GAIM_INPUT_WRITE, proxy_do_write, phb);
+
+				proxy_do_write(phb, source, GAIM_INPUT_WRITE);
 				break;
 			case 0x11:
 				/* Server wants to select an algorithm */
 				if (buf[0] != 0x85) {
 					/* Only currently support HMAC-MD5 */
-					gaim_debug_warning("proxy", "Server tried to select an "
-									   "algorithm that we did not advertise "
-									   "as supporting.  This is a violation "
-									   "of the socks5 CHAP specification.  "
-									   "Disconnecting...");
+					gaim_debug_warning("proxy",
+						"Server tried to select an "
+						"algorithm that we did not advertise "
+						"as supporting.  This is a violation "
+						"of the socks5 CHAP specification.  "
+						"Disconnecting...");
 					close(source);
-
+					gaim_input_remove(phb->inpa);
+					g_free(phb->read_buffer);
+					phb->read_buffer = NULL;
 					try_connect(phb);
 					return;
 				}
 				break;
 		}
+		cmdbuf = buf + cmdbuf[1];
 	}
 	/* Fell through.  We ran out of CHAP events to process, but haven't
 	 * succeeded or failed authentication - there may be more to come.
 	 * If this is the case, come straight back here. */
-	phb->inpa = gaim_input_add(source, GAIM_INPUT_READ, s5_readchap, phb);
 }
 
 static void
 s5_canread(gpointer data, gint source, GaimInputCondition cond)
 {
-	unsigned char buf[512];
 	struct PHB *phb = data;
+	int len;
+
+	if (phb->read_buffer == NULL) {
+		phb->read_buf_len = 2;
+		phb->read_buffer = g_malloc(phb->read_buf_len);
+		phb->read_len = 0;
+	}
+
+	gaim_debug_info("socks5 proxy", "Able to read.\n");
+
+	len = read(source, phb->read_buffer + phb->read_len,
+		phb->read_buf_len - phb->read_len);
+	if(len < 0 && errno == EAGAIN)
+		return;
+	else if(len < 0) {
+		close(source);
+		gaim_input_remove(phb->inpa);
+		g_free(phb->read_buffer);
+		phb->read_buffer = NULL;
+		try_connect(phb);
+		return;
+	}
+	phb->read_len += len;
+
+	if (phb->read_len < 2)
+		return;
 
 	gaim_input_remove(phb->inpa);
-	gaim_debug_info("socks5 proxy", "Able to read.\n");
 
-	if (read(source, buf, 2) < 2) {
+	if ((phb->read_buffer[0] != 0x05) || (phb->read_buffer[1] == 0xff)) {
 		close(source);
-
+		g_free(phb->read_buffer);
+		phb->read_buffer = NULL;
 		try_connect(phb);
 		return;
 	}
 
-	if ((buf[0] != 0x05) || (buf[1] == 0xff)) {
-		close(source);
-
-		try_connect(phb);
-		return;
-	}
-
-	if (buf[1] == 0x02) {
-		unsigned int i, j;
+	if (phb->read_buffer[1] == 0x02) {
+		gsize i, j;
 		const char *u, *p;
 
 		u = gaim_proxy_info_get_username(phb->gpi);
@@ -1731,43 +2007,62 @@
 		i = (u == NULL) ? 0 : strlen(u);
 		j = (p == NULL) ? 0 : strlen(p);
 
-		buf[0] = 0x01;	/* version 1 */
-		buf[1] = i;
+		phb->write_buf_len = 1 + 1 + i + 1 + j;
+		phb->write_buffer = g_malloc(phb->write_buf_len);
+		phb->written_len = 0;
+
+		phb->write_buffer[0] = 0x01;	/* version 1 */
+		phb->write_buffer[1] = i;
 		if (u != NULL)
-			memcpy(buf + 2, u, i);
-		buf[2 + i] = j;
+			memcpy(phb->write_buffer + 2, u, i);
+		phb->write_buffer[2 + i] = j;
 		if (p != NULL)
-			memcpy(buf + 2 + i + 1, p, j);
+			memcpy(phb->write_buffer + 2 + i + 1, p, j);
 
-		if (write(source, buf, 3 + i + j) < 3 + i + j) {
-			close(source);
+		g_free(phb->read_buffer);
+		phb->read_buffer = NULL;
+
+		phb->read_cb = s5_readauth;
 
-			try_connect(phb);
-			return;
-		}
+		phb->inpa = gaim_input_add(source, GAIM_INPUT_WRITE,
+			proxy_do_write, phb);
 
-		phb->inpa = gaim_input_add(source, GAIM_INPUT_READ, s5_readauth, phb);
-	} else if (buf[1] == 0x03) {
-		unsigned int userlen;
+		proxy_do_write(phb, source, GAIM_INPUT_WRITE);
+
+		return;
+	} else if (phb->read_buffer[1] == 0x03) {
+		gsize userlen;
 		userlen = strlen(gaim_proxy_info_get_username(phb->gpi));
-		buf[0] = 0x01;
-		buf[1] = 0x02;
-		buf[2] = 0x11;
-		buf[3] = 0x01;
-		buf[4] = 0x85;
-		buf[5] = 0x02;
-		buf[6] = userlen;
-		memcpy(buf + 7, gaim_proxy_info_get_username(phb->gpi), userlen);
-		if (write(source, buf, 7 + userlen) < 7 + userlen) {
-			close(source);
+
+		phb->write_buf_len = 7 + userlen;
+		phb->write_buffer = g_malloc(phb->write_buf_len);
+		phb->written_len = 0;
+
+		phb->write_buffer[0] = 0x01;
+		phb->write_buffer[1] = 0x02;
+		phb->write_buffer[2] = 0x11;
+		phb->write_buffer[3] = 0x01;
+		phb->write_buffer[4] = 0x85;
+		phb->write_buffer[5] = 0x02;
+		phb->write_buffer[6] = userlen;
+		memcpy(phb->write_buffer + 7,
+			gaim_proxy_info_get_username(phb->gpi), userlen);
 
-			try_connect(phb);
-			return;
-		}
+		g_free(phb->read_buffer);
+		phb->read_buffer = NULL;
+
+		phb->read_cb = s5_readchap;
+
+		phb->inpa = gaim_input_add(source, GAIM_INPUT_WRITE,
+			proxy_do_write, phb);
 
-		phb->inpa = gaim_input_add(source, GAIM_INPUT_READ, s5_readchap, phb);
-	}
-	else {
+		proxy_do_write(phb, source, GAIM_INPUT_WRITE);
+
+		return;
+	} else {
+		g_free(phb->read_buffer);
+		phb->read_buffer = NULL;
+
 		s5_sendconnect(phb, source);
 	}
 }
@@ -1775,7 +2070,7 @@
 static void
 s5_canwrite(gpointer data, gint source, GaimInputCondition cond)
 {
-	unsigned char buf[512];
+	unsigned char buf[5];
 	int i;
 	struct PHB *phb = data;
 	socklen_t len;
@@ -1793,7 +2088,6 @@
 		try_connect(phb);
 		return;
 	}
-	fcntl(source, F_SETFL, 0);
 
 	i = 0;
 	buf[0] = 0x05;		/* SOCKS version 5 */
@@ -1811,15 +2105,14 @@
 		i = 3;
 	}
 
-	if (write(source, buf, i) < i) {
-		gaim_debug_error("socks5 proxy", "Unable to write\n");
-		close(source);
+	phb->write_buf_len = i;
+	phb->write_buffer = g_malloc(phb->write_buf_len);
+	memcpy(phb->write_buffer, buf, i);
 
-		try_connect(phb);
-		return;
-	}
+	phb->read_cb = s5_canread;
 
-	phb->inpa = gaim_input_add(source, GAIM_INPUT_READ, s5_canread, phb);
+	phb->inpa = gaim_input_add(source, GAIM_INPUT_WRITE, proxy_do_write, phb);
+	proxy_do_write(phb, source, GAIM_INPUT_WRITE);
 }
 
 static int
@@ -1867,7 +2160,6 @@
 			return -1;
 		}
 
-		fcntl(fd, F_SETFL, 0);
 		s5_canwrite(phb, fd, GAIM_INPUT_WRITE);
 	}
 
@@ -2048,7 +2340,7 @@
 	}
 
 	return gaim_gethostbyname_async(connecthost, connectport,
-									connection_host_resolved, phb);
+		connection_host_resolved, phb);
 }
 
 int
@@ -2064,8 +2356,8 @@
 	phb->host = g_strdup(host);
 	phb->port = port;
 
-	return gaim_gethostbyname_async(gaim_proxy_info_get_host(gpi), gaim_proxy_info_get_port(gpi),
-									connection_host_resolved, phb);
+	return gaim_gethostbyname_async(gaim_proxy_info_get_host(gpi),
+		gaim_proxy_info_get_port(gpi), connection_host_resolved, phb);
 }
 
 
@@ -2121,16 +2413,16 @@
 
 	/* Setup callbacks for the preferences. */
 	handle = gaim_proxy_get_handle();
-	gaim_prefs_connect_callback(handle, "/core/proxy/type",
-								proxy_pref_cb, NULL);
-	gaim_prefs_connect_callback(handle, "/core/proxy/host",
-								proxy_pref_cb, NULL);
-	gaim_prefs_connect_callback(handle, "/core/proxy/port",
-								proxy_pref_cb, NULL);
+	gaim_prefs_connect_callback(handle, "/core/proxy/type", proxy_pref_cb,
+		NULL);
+	gaim_prefs_connect_callback(handle, "/core/proxy/host", proxy_pref_cb,
+		NULL);
+	gaim_prefs_connect_callback(handle, "/core/proxy/port", proxy_pref_cb,
+		NULL);
 	gaim_prefs_connect_callback(handle, "/core/proxy/username",
-								proxy_pref_cb, NULL);
+		proxy_pref_cb, NULL);
 	gaim_prefs_connect_callback(handle, "/core/proxy/password",
-								proxy_pref_cb, NULL);
+		proxy_pref_cb, NULL);
 #ifdef _WIN32
 	if(!g_thread_supported())
 		g_thread_init(NULL);
--- a/src/upnp.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/upnp.c	Thu Feb 09 04:17:56 2006 +0000
@@ -408,6 +408,7 @@
 
 	dd->full_url = g_strdup_printf("http://%s:%d",
 			descriptionAddress, port);
+	g_free(descriptionAddress);
 
 	/* Remove the timeout because everything it is waiting for has
 	 * successfully completed */
@@ -417,7 +418,6 @@
 	gaim_url_fetch_request(descriptionURL, TRUE, NULL, TRUE, httpRequest,
 			TRUE, upnp_parse_description_cb, dd);
 
-	g_free(descriptionAddress);
 	g_free(httpRequest);
 
 }
@@ -543,7 +543,7 @@
 	   we should retry the send NUM_UDP_ATTEMPTS times. Also,
 	   try different requests for WANIPConnection and WANPPPConnection*/
 	for(; dd->retry_count < NUM_UDP_ATTEMPTS; dd->retry_count++) {
-		sentSuccess = TRUE;
+		sentSuccess = FALSE;
 
 		if((dd->retry_count % 2) == 0) {
 			strncpy(dd->service_type, WAN_IP_CONN_SERVICE, sizeof(dd->service_type));
@@ -563,7 +563,7 @@
 				sentSuccess = TRUE;
 				break;
 			}
-		} while (errno == EINTR);
+		} while (errno == EINTR || errno == EAGAIN);
 
 		g_free(sendMessage);
 
--- a/src/util.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/util.c	Thu Feb 09 04:17:56 2006 +0000
@@ -49,12 +49,12 @@
 	char *user_agent;
 	gboolean http11;
 	char *request;
+	gsize request_written;
 	gboolean include_headers;
 
 	int inpa;
 
-	gboolean sentreq;
-	gboolean startsaving;
+	gboolean got_headers;
 	gboolean has_explicit_data_len;
 	char *webdata;
 	unsigned long len;
@@ -3218,6 +3218,7 @@
 	return content_len;
 }
 
+
 static void
 url_fetched_cb(gpointer url_data, gint sock, GaimInputCondition cond)
 {
@@ -3227,70 +3228,11 @@
 	char *data_cursor;
 	gboolean got_eof = FALSE;
 
-	if (sock == -1)
-	{
-		gfud->callback(gfud->user_data, NULL, 0);
-
-		destroy_fetch_url_data(gfud);
-
-		return;
-	}
-
-	if (!gfud->sentreq)
-	{
-		char *send;
-		char buf[1024];
-
-		if (gfud->request) {
-			send = gfud->request;
-		} else {
-			if (gfud->user_agent) {
-				/* Host header is not forbidden in HTTP/1.0 requests, and HTTP/1.1
-				 * clients must know how to handle the "chunked" transfer encoding.
-				 * Gaim doesn't know how to handle "chunked", so should always send
-				 * the Host header regardless, to get around some observed problems
-				 */
-				g_snprintf(buf, sizeof(buf),
-					"GET %s%s HTTP/%s\r\n"
-					"Connection: close\r\n"
-					"User-Agent: %s\r\n"
-					"Host: %s\r\n\r\n",
-					(gfud->full ? "" : "/"),
-					(gfud->full ? gfud->url : gfud->website.page),
-					(gfud->http11 ? "1.1" : "1.0"),
-					gfud->user_agent, gfud->website.address);
-			} else {
-				g_snprintf(buf, sizeof(buf),
-					"GET %s%s HTTP/%s\r\n"
-					"Connection: close\r\n"
-					"Host: %s\r\n\r\n",
-					(gfud->full ? "" : "/"),
-					(gfud->full ? gfud->url : gfud->website.page),
-					(gfud->http11 ? "1.1" : "1.0"),
-					gfud->website.address);
-			}
-			send = buf;
-		}
-
-		gaim_debug_misc("gaim_url_fetch", "Request: %s\n", send);
-
-		write(sock, send, strlen(send));
-		fcntl(sock, F_SETFL, O_NONBLOCK);
-		gfud->sentreq = TRUE;
-		gfud->inpa = gaim_input_add(sock, GAIM_INPUT_READ,
-			url_fetched_cb, url_data);
-		gfud->data_len = 4096;
-		gfud->webdata = g_malloc(gfud->data_len);
-
-		return;
-	}
-
-	while ((len = read(sock, buf, sizeof(buf))) > 0)
-	{
+	while((len = read(sock, buf, sizeof(buf))) > 0) {
 		/* If we've filled up our butfer, make it bigger */
-		if ((gfud->len + len) >= gfud->data_len)
-		{
-			gfud->data_len += MAX(((gfud->data_len) / 2), sizeof(buf));
+		if((gfud->len + len) >= gfud->data_len) {
+			while((gfud->len + len) >= gfud->data_len)
+				gfud->data_len += sizeof(buf);
 
 			gfud->webdata = g_realloc(gfud->webdata, gfud->data_len);
 		}
@@ -3303,12 +3245,11 @@
 
 		gfud->webdata[gfud->len] = '\0';
 
-		if (!gfud->startsaving)
-		{
+		if(!gfud->got_headers) {
 			char *tmp;
 
 			/** See if we've reached the end of the headers yet */
-			if ((tmp = strstr(gfud->webdata, "\r\n\r\n"))) {
+			if((tmp = strstr(gfud->webdata, "\r\n\r\n"))) {
 				char * new_data;
 				guint header_len = (tmp + 4 - gfud->webdata);
 				size_t content_len, body_len = 0;
@@ -3320,7 +3261,7 @@
 				if (parse_redirect(gfud->webdata, header_len, sock, gfud))
 					return;
 
-				gfud->startsaving = TRUE;
+				gfud->got_headers = TRUE;
 
 				/* No redirect. See if we can find a content length. */
 				content_len = parse_content_len(gfud->webdata, header_len);
@@ -3383,9 +3324,8 @@
 		}
 	}
 
-	if (len <= 0) {
-		if (errno == EWOULDBLOCK) {
-			errno = 0;
+	if(len <= 0) {
+		if(errno == EAGAIN) {
 			return;
 		} else if (errno != ETIMEDOUT) {
 			got_eof = TRUE;
@@ -3414,6 +3354,76 @@
 	}
 }
 
+static void
+url_fetch_connect_cb(gpointer url_data, gint sock, GaimInputCondition cond) {
+	GaimFetchUrlData *gfud = url_data;
+	int len, total_len;
+
+	if(sock == -1) {
+		gfud->callback(gfud->user_data, NULL, 0);
+		destroy_fetch_url_data(gfud);
+		return;
+	}
+
+	if (!gfud->request) {
+		if (gfud->user_agent) {
+			/* Host header is not forbidden in HTTP/1.0 requests, and HTTP/1.1
+			 * clients must know how to handle the "chunked" transfer encoding.
+			 * Gaim doesn't know how to handle "chunked", so should always send
+			 * the Host header regardless, to get around some observed problems
+			 */
+			gfud->request = g_strdup_printf(
+				"GET %s%s HTTP/%s\r\n"
+				"Connection: close\r\n"
+				"User-Agent: %s\r\n"
+				"Host: %s\r\n\r\n",
+				(gfud->full ? "" : "/"),
+				(gfud->full ? gfud->url : gfud->website.page),
+				(gfud->http11 ? "1.1" : "1.0"),
+				gfud->user_agent, gfud->website.address);
+		} else {
+			gfud->request = g_strdup_printf(
+				"GET %s%s HTTP/%s\r\n"
+				"Connection: close\r\n"
+				"Host: %s\r\n\r\n",
+				(gfud->full ? "" : "/"),
+				(gfud->full ? gfud->url : gfud->website.page),
+				(gfud->http11 ? "1.1" : "1.0"),
+				gfud->website.address);
+		}
+	}
+
+	gaim_debug_misc("gaim_url_fetch", "Request: '%s'\n", gfud->request);
+
+	if(!gfud->inpa)
+		gfud->inpa = gaim_input_add(sock, GAIM_INPUT_WRITE,
+			url_fetch_connect_cb, gfud);
+
+	total_len = strlen(gfud->request);
+
+	len = write(sock, gfud->request + gfud->request_written,
+			total_len - gfud->request_written);
+
+	if(len < 0 && errno == EAGAIN)
+		return;
+	else if(len < 0) {
+		gaim_input_remove(gfud->inpa);
+		close(sock);
+		gfud->callback(gfud->user_data, NULL, 0);
+		destroy_fetch_url_data(gfud);
+		return;
+	}
+	gfud->request_written += len;
+
+	if(gfud->request_written != total_len)
+		return;
+
+	gaim_input_remove(gfud->inpa);
+
+	gfud->inpa = gaim_input_add(sock, GAIM_INPUT_READ, url_fetched_cb,
+		gfud);
+}
+
 void
 gaim_url_fetch_request(const char *url, gboolean full,
 		const char *user_agent, gboolean http11,
@@ -3444,9 +3454,7 @@
 				   &gfud->website.page, &gfud->website.user, &gfud->website.passwd);
 
 	if (gaim_proxy_connect(NULL, gfud->website.address,
-								   gfud->website.port, url_fetched_cb,
-								   gfud) != 0)
-	{
+		gfud->website.port, url_fetch_connect_cb, gfud) != 0) {
 		destroy_fetch_url_data(gfud);
 
 		cb(user_data, g_strdup(_("g003: Error opening connection.\n")), 0);
@@ -3731,7 +3739,7 @@
 		gunichar wc = g_utf8_get_char(str);
 
 		/* super simple check. hopefully not too wrong. */
-		if(wc >= 0x80) { 
+		if(wc >= 0x80) {
 			g_string_append_printf(out, "&#%u;", (guint32) wc);
 		} else {
 			g_string_append_unichar(out, wc);
@@ -3757,12 +3765,12 @@
 	while( (b = strstr(buf, "&#")) ) {
 		gunichar wc;
 		int base = 0;
-    
+
 		/* append everything leading up to the &# */
 		g_string_append_len(out, buf, b-buf);
 
 		b += 2; /* skip past the &# */
-    
+
 		/* strtoul will handle 0x prefix as hex, but not x */
 		if(*b == 'x' || *b == 'X')
 			base = 16;
@@ -3815,7 +3823,7 @@
 }
 
 /* previously conversation::find_nick() */
-gboolean 
+gboolean
 gaim_utf8_has_word(const char *haystack, const char *needle)
 {
 	char *hay, *pin, *p;
--- a/src/win32/libc_interface.c	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/win32/libc_interface.c	Thu Feb 09 04:17:56 2006 +0000
@@ -276,9 +276,11 @@
 int wgaim_read(int fd, void *buf, unsigned int size) {
 	int ret;
 
-	if( wgaim_is_socket(fd) ) {
-		if( (ret = recv(fd, buf, size, 0)) == SOCKET_ERROR ) {
+	if(wgaim_is_socket(fd)) {
+		if((ret = recv(fd, buf, size, 0)) == SOCKET_ERROR) {
 			errno = WSAGetLastError();
+			if(errno == WSAEWOULDBLOCK)
+				errno = EAGAIN;
 			return -1;
 		}
 #if 0
@@ -292,8 +294,7 @@
 			/* success reading socket */
 			return ret;
 		}
-	}
-	else {
+	} else {
 		/* fd is not a socket handle.. pass it off to read */
 		return read(fd, buf, size);
 	}
@@ -302,25 +303,27 @@
 int wgaim_write(int fd, const void *buf, unsigned int size) {
 	int ret;
 
-	if( wgaim_is_socket(fd) ) {
-		if( (ret = send(fd, buf, size, 0)) == SOCKET_ERROR ) {
+	if(wgaim_is_socket(fd)) {
+		if((ret = send(fd, buf, size, 0)) == SOCKET_ERROR) {
 			errno = WSAGetLastError();
+			if(errno == WSAEWOULDBLOCK)
+				errno = EAGAIN;
 			return -1;
-		}
-		else {
+		} else {
 			/* success */
 			return ret;
 		}
-	}
-	else
+	} else
 		return write(fd, buf, size);
 }
 
 int wgaim_recv(int fd, void *buf, size_t len, int flags) {
 	int ret;
 
-	if ((ret = recv(fd, buf, len, flags)) == SOCKET_ERROR) {
+	if((ret = recv(fd, buf, len, flags)) == SOCKET_ERROR) {
 			errno = WSAGetLastError();
+			if(errno == WSAEWOULDBLOCK)
+				errno = EAGAIN;
 			return -1;
 	} else {
 		return ret;
--- a/src/win32/wgaimerror.h	Thu Feb 09 04:14:54 2006 +0000
+++ b/src/win32/wgaimerror.h	Thu Feb 09 04:17:56 2006 +0000
@@ -53,5 +53,6 @@
 #define EMSGSIZE WSAEMSGSIZE
 #define ECONNABORTED WSAECONNABORTED
 #define ECONNRESET WSAECONNRESET
+#define EHOSTUNREACH WSAEHOSTUNREACH
 
 #endif /* end _WGAIMERROR_H */