summaryrefslogtreecommitdiff
path: root/src/ne_socks.c
diff options
context:
space:
mode:
authorjoe <joe@61a7d7f5-40b7-0310-9c16-bb0ea8cb1845>2008-03-27 10:49:13 +0000
committerjoe <joe@61a7d7f5-40b7-0310-9c16-bb0ea8cb1845>2008-03-27 10:49:13 +0000
commitd33781a40d67bb73633bc16f2abce7da7652c3f5 (patch)
treeecb97fbe664f8096726ee7e3c2f112f8a29683d6 /src/ne_socks.c
parent54fec095bb71e78e31355dbdaad59ec9a6ad8042 (diff)
downloadneon-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.c354
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);
+ }
+}