diff src/proxy.c @ 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 31a3a9af1494
children de4f1fb08088
line wrap: on
line diff
--- 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);