/* SOCKS proxy support for neon Copyright (C) 2008, Joe Orton 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 /* 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); } }