diff options
author | joe <joe@61a7d7f5-40b7-0310-9c16-bb0ea8cb1845> | 2008-03-27 10:49:13 +0000 |
---|---|---|
committer | joe <joe@61a7d7f5-40b7-0310-9c16-bb0ea8cb1845> | 2008-03-27 10:49:13 +0000 |
commit | d33781a40d67bb73633bc16f2abce7da7652c3f5 (patch) | |
tree | ecb97fbe664f8096726ee7e3c2f112f8a29683d6 /src/ne_socks.c | |
parent | 54fec095bb71e78e31355dbdaad59ec9a6ad8042 (diff) | |
download | neon-d33781a40d67bb73633bc16f2abce7da7652c3f5.tar.gz |
Add support for initiating a proxy connection via SOCKS v4/v4a/v5
proxy, at socket layer:
* src/ne_socks.c: New file.
* src/ne_socket.h (ne_sock_proxy): New prototype.
* src/Makefile.in, neon.mak: Build ne_socks.c.
* test/socket.c (read_socks_string, read_socks_byte,
expect_socks_byte, read_socks_0string, socks_server, begin_socks,
socks_proxy, fail_socks): Add test cases.
git-svn-id: http://svn.webdav.org/repos/projects/neon/trunk@1421 61a7d7f5-40b7-0310-9c16-bb0ea8cb1845
Diffstat (limited to 'src/ne_socks.c')
-rw-r--r-- | src/ne_socks.c | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/src/ne_socks.c b/src/ne_socks.c new file mode 100644 index 0000000..73d9f04 --- /dev/null +++ b/src/ne_socks.c @@ -0,0 +1,354 @@ +/* + SOCKS proxy support for neon + Copyright (C) 2008, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA +*/ + +#include "config.h" + +#include "ne_internal.h" +#include "ne_string.h" +#include "ne_socket.h" +#include "ne_utils.h" + +#include <string.h> + +/* SOCKS protocol reference: + v4: http://www.ufasoft.com/doc/socks4_protocol.htm + v4a http://www.smartftp.com/Products/SmartFTP/RFC/socks4a.protocol + v5: http://tools.ietf.org/html/rfc1928 + ...v5 auth: http://tools.ietf.org/html/rfc1929 +*/ + +#define V5_REPLY_OK 0 +#define V5_REPLY_FAIL 1 +#define V5_REPLY_DISALLOW 2 +#define V5_REPLY_NET_UNREACH 3 +#define V5_REPLY_HOST_UNREACH 4 +#define V5_REPLY_CONN_REFUSED 5 +#define V5_REPLY_TTL_EXPIRED 6 +#define V5_REPLY_CMD_UNSUPPORTED 7 +#define V5_REPLY_TYPE_UNSUPPORTED 8 + +#define V5_VERSION 0x05 +#define V5_ADDR_IPV4 0x01 +#define V5_ADDR_FQDN 0x03 +#define V5_ADDR_IPV6 0x04 + +#define V5_CMD_CONNECT 0x01 + +#define V5_AUTH_NONE 0x00 +#define V5_AUTH_USER 0x02 +#define V5_AUTH_NOMETH 0xFF + +/* Fail with given V5 error code in given context. */ +static int v5fail(ne_socket *sock, unsigned int code, const char *context) +{ + const char *err; + + switch (code) { + case V5_REPLY_FAIL: + err = _("failure"); + break; + case V5_REPLY_DISALLOW: + err = _("connection not permitted"); + break; + case V5_REPLY_NET_UNREACH: + err = _("network unreachable"); + break; + case V5_REPLY_HOST_UNREACH: + err = _("host unreachable"); + break; + case V5_REPLY_TTL_EXPIRED: + err = _("TTL expired"); + break; + case V5_REPLY_CMD_UNSUPPORTED: + err = _("command not supported"); + break; + case V5_REPLY_TYPE_UNSUPPORTED: + err = _("address type not supported"); + break; + default: + ne_sock_set_error(sock, _("%s: unrecognized error (%u)"), context, code); + return NE_SOCK_ERROR; + } + + ne_sock_set_error(sock, "%s: %s", context, err); + return NE_SOCK_ERROR; +} + +/* Fail with given error string. */ +static int fail(ne_socket *sock, const char *error) +{ + ne_sock_set_error(sock, "%s", error); + return NE_SOCK_ERROR; +} + +/* Fail with given NE_SOCK_* error code and given context. */ +static int sofail(ne_socket *sock, ssize_t ret, const char *context) +{ + char *err = ne_strdup(ne_sock_error(sock)); + ne_sock_set_error(sock, "%s: %s", context, err); + ne_free(err); + return NE_SOCK_ERROR; +} + +/* SOCKSv5 proxy. */ +static int v5_proxy(ne_socket *sock, const ne_inet_addr *addr, + const char *hostname, unsigned int port, + const char *username, const char *password) +{ + unsigned char msg[1024], *p; + unsigned int len; + int ret; + ssize_t n; + + p = msg; + *p++ = V5_VERSION; + *p++ = 2; /* Two supported auth protocols; none and user. */ + *p++ = V5_AUTH_NONE; + *p++ = V5_AUTH_USER; + + ret = ne_sock_fullwrite(sock, (char *)msg, p - msg); + if (ret) { + return sofail(sock, ret, _("Could not send message to proxy")); + } + + n = ne_sock_fullread(sock, (char *)msg, 2); + if (n) { + return sofail(sock, ret, _("Could not read initial response from proxy")); + } + else if (msg[0] != V5_VERSION) { + return fail(sock, _("Invalid version in proxy response")); + } + + /* Authenticate, if necessary. */ + switch (msg[1]) { + case V5_AUTH_NONE: + break; + case V5_AUTH_USER: + p = msg; + *p++ = 0x01; + len = strlen(username) & 0xff; + *p++ = len; + memcpy(p, username, len); + p += len; + len = strlen(password) & 0xff; + *p++ = len; + memcpy(p, password, len); + p += len; + + ret = ne_sock_fullwrite(sock, (char *)msg, p - msg); + if (ret) { + return sofail(sock, ret, _("Could not send login message")); + } + + n = ne_sock_fullread(sock, (char *)msg, 2); + if (n) { + return sofail(sock, ret, _("Could not read login reply")); + } + else if (msg[0] != 1) { + return fail(sock, _("Invalid version in login reply")); + } + else if (msg[1] != 0) { + return fail(sock, _("Authentication failed")); + } + break; + case V5_AUTH_NOMETH: + return fail(sock, _("No acceptable authentication method")); + default: + return fail(sock, _("Unexpected authentication method chosen")); + } + + /* Send the CONNECT command. */ + p = msg; + *p++ = V5_VERSION; + *p++ = V5_CMD_CONNECT; + *p++ = 0; /* reserved */ + if (addr) { + unsigned char raw[16]; + + if (ne_iaddr_typeof(addr) == ne_iaddr_ipv4) { + len = 4; + *p++ = V5_ADDR_IPV4; + } + else { + len = 16; + *p++ = V5_ADDR_IPV6; + } + + memcpy(p, ne_iaddr_raw(addr, raw), len); + p += len; + } + else { + len = strlen(hostname) & 0xff; + *p++ = V5_ADDR_FQDN; + *p++ = len; + memcpy(p, hostname, len); + p += len; + } + + *p++ = (port >> 8) & 0xff; + *p++ = port & 0xff; + + ret = ne_sock_fullwrite(sock, (char *)msg, p - msg); + if (ret) { + return sofail(sock, ret, _("Could not send connect request")); + } + + n = ne_sock_fullread(sock, (char *)msg, 4); + if (n) { + return sofail(sock, n, _("Could not read connect reply")); + } + if (msg[0] != V5_VERSION) { + return fail(sock, _("Invalid version in connect reply")); + } + if (msg[1] != V5_REPLY_OK) { + return v5fail(sock, msg[1], _("Could not connect")); + } + + switch (msg[3]) { + case V5_ADDR_IPV4: + len = 4; + break; + case V5_ADDR_IPV6: + len = 16; + break; + case V5_ADDR_FQDN: + n = ne_sock_read(sock, (char *)msg, 1); + if (n != 1) { + return sofail(sock, n, + _("Could not read FQDN length in connect reply")); + } + len = msg[0]; + break; + default: + return fail(sock, _("Unknown address type in connect reply")); + } + + n = ne_sock_fullread(sock, (char *)msg, len + 2); + if (n) { + return sofail(sock, n, _("Could not read address in connect reply")); + } + + return 0; +} + +#define V4_VERSION 0x04 +#define V4_CMD_STREAM 0x01 + +#define V4_REP_OK 0x5a /* request granted */ +#define V4_REP_FAIL 0x5b /* request rejected or failed */ +#define V4_REP_NOIDENT 0x5c /* request failed, could connect to identd */ +#define V4_REP_IDFAIL 0x5d /* request failed, identd denial */ + +/* Fail for given SOCKSv4 error code. */ +static int v4fail(ne_socket *sock, unsigned int code, const char *context) +{ + const char *err; + + switch (code) { + case V4_REP_FAIL: + err = _("request rejected or failed"); + break; + case V4_REP_NOIDENT: + err = _("could not establish connection to identd"); + break; + case V4_REP_IDFAIL: + err = _("rejected due to identd user mismatch"); + break; + default: + ne_sock_set_error(sock, _("%s: unrecognized failure (%u)"), + context, code); + return NE_SOCK_ERROR; + } + + ne_sock_set_error(sock, "%s: %s", context, err); + return NE_SOCK_ERROR; +} + +/* SOCKS v4 or v4A proxy. */ +static int v4_proxy(ne_socket *sock, enum ne_sock_sversion vers, + const ne_inet_addr *addr, const char *hostname, + unsigned int port, const char *username) +{ + unsigned char msg[1024], raw[16], *p; + ssize_t n; + int ret; + + p = msg; + *p++ = V4_VERSION; + *p++ = V4_CMD_STREAM; + *p++ = (port >> 8) & 0xff; + *p++ = port & 0xff; + + if (vers == NE_SOCK_SOCKSV4A) { + /* A bogus address is used to signify use of the hostname, + * 0.0.0.X where X != 0. */ + memcpy(p, "\x00\x00\x00\xff", 4); + } + else { + /* API precondition that addr is IPv4; if it's not this will + * just copy out the first four bytes of the v6 address; + * garbage in => garbage out. */ + memcpy(p, ne_iaddr_raw(addr, raw), 4); + } + p += 4; + + if (username) { + unsigned int len = strlen(username) & 0xff; + memcpy(p, username, len); + p += len; + } + *p++ = '\0'; + + if (vers == NE_SOCK_SOCKSV4A) { + unsigned int len = strlen(hostname) & 0xff; + memcpy(p, hostname, len); + p += len; + *p++ = '\0'; + } + + ret = ne_sock_fullwrite(sock, (char *)msg, p - msg); + if (ret) { + return sofail(sock, ret, _("Could not send message to proxy")); + } + + n = ne_sock_fullread(sock, (char *)msg, 8); + if (n) { + return sofail(sock, ret, _("Could not read response from proxy")); + } + + if (msg[1] != V4_REP_OK) { + return v4fail(sock, ret, _("Could not connect")); + } + + return 0; +} + +int ne_sock_proxy(ne_socket *sock, enum ne_sock_sversion vers, + const ne_inet_addr *addr, const char *hostname, + unsigned int port, + const char *username, const char *password) +{ + if (vers == NE_SOCK_SOCKSV5) { + return v5_proxy(sock, addr, hostname, port, username, password); + } + else { + return v4_proxy(sock, vers, addr, hostname, port, username); + } +} |