Mercurial > pidgin.yaz
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);