svn commit: r357968 - head/lib/libfetch

Kyle Evans kevans at FreeBSD.org
Sat Feb 15 18:03:17 UTC 2020


Author: kevans
Date: Sat Feb 15 18:03:16 2020
New Revision: 357968
URL: https://svnweb.freebsd.org/changeset/base/357968

Log:
  fetch(3): Add SOCKS5 support
  
  This change adds SOCKS5 support to the library fetch(3) and updates the man
  page.
  
  Details: Within the fetch_connect() function, fetch(3) checks if the
  SOCKS5_PROXY environment variable is set. If so, it connects to this host
  rather than the end-host. It then initializes the SOCKS5 connection in
  accordance with RFC 1928 and returns the resulting conn_t (file descriptor)
  for usage by the regular FTP/HTTP handlers.
  
  Design Decision: This change defaults all DNS resolutions through the proxy
  by sending all IPs as hostnames. Going forward, another feature might be to
  create another environmental variable to toggle resolutions through the
  proxy or not..
  
  One may set the SOCKS5_PROXY environment variable in any of the formats:
  
  SOCKS5_PROXY=proxy.example.com
  SOCKS5_PROXY=proxy.example.com:1080
  SOCKS5_PROXY=192.0.2.0
  SOCKS5_PROXY=198.51.100.0:1080
  SOCKS5_PROXY=[2001:db8::1]
  SOCKS5_PROXY=[2001:db8::2]:1080
  
  Then perform a request with fetch(1).
  
  (note by kevans)
  I've since been informed that Void Linux/xbps has a fork of libfetch that
  also implements SOCKS5. I may compare/contrast the two in the mid-to-near
  future.
  
  Submitted by:	Farhan Khan <farhan farhan codes>
  Differential Revision:	https://reviews.freebsd.org/D18908

Modified:
  head/lib/libfetch/common.c
  head/lib/libfetch/common.h
  head/lib/libfetch/fetch.3

Modified: head/lib/libfetch/common.c
==============================================================================
--- head/lib/libfetch/common.c	Sat Feb 15 15:39:53 2020	(r357967)
+++ head/lib/libfetch/common.c	Sat Feb 15 18:03:16 2020	(r357968)
@@ -42,6 +42,7 @@ __FBSDID("$FreeBSD$");
 #include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <inttypes.h>
 #include <netdb.h>
 #include <poll.h>
 #include <pwd.h>
@@ -74,6 +75,64 @@ static struct fetcherr netdb_errlist[] = {
 	{ -1,		FETCH_UNKNOWN,	"Unknown resolver error" }
 };
 
+/*
+ * SOCKS5 error enumerations
+ */
+enum SOCKS5_ERR {
+/* Protocol errors */
+	SOCKS5_ERR_SELECTION,
+	SOCKS5_ERR_READ_METHOD,
+	SOCKS5_ERR_VER5_ONLY,
+	SOCKS5_ERR_NOMETHODS,
+	SOCKS5_ERR_NOTIMPLEMENTED,
+	SOCKS5_ERR_HOSTNAME_SIZE,
+	SOCKS5_ERR_REQUEST,
+	SOCKS5_ERR_REPLY,
+	SOCKS5_ERR_NON_VER5_RESP,
+	SOCKS5_ERR_GENERAL,
+	SOCKS5_ERR_NOT_ALLOWED,
+	SOCKS5_ERR_NET_UNREACHABLE,
+	SOCKS5_ERR_HOST_UNREACHABLE,
+	SOCKS5_ERR_CONN_REFUSED,
+	SOCKS5_ERR_TTL_EXPIRED,
+	SOCKS5_ERR_COM_UNSUPPORTED,
+	SOCKS5_ERR_ADDR_UNSUPPORTED,
+	SOCKS5_ERR_UNSPECIFIED,
+/* Configuration errors */
+	SOCKS5_ERR_BAD_HOST,
+	SOCKS5_ERR_BAD_PROXY_FORMAT,
+	SOCKS5_ERR_BAD_PORT
+};
+
+/*
+ * Error messages for SOCKS5 errors
+ */
+static struct fetcherr socks5_errlist[] = {
+/* SOCKS5 protocol errors */
+	{ SOCKS5_ERR_SELECTION,		FETCH_ABORT,	"SOCKS5: Failed to send selection method" },
+	{ SOCKS5_ERR_READ_METHOD,	FETCH_ABORT,	"SOCKS5: Failed to read method" },
+	{ SOCKS5_ERR_VER5_ONLY,		FETCH_PROTO,	"SOCKS5: Only version 5 is implemented" },
+	{ SOCKS5_ERR_NOMETHODS,		FETCH_PROTO,	"SOCKS5: No acceptable methods" },
+	{ SOCKS5_ERR_NOTIMPLEMENTED,	FETCH_PROTO,	"SOCKS5: Method currently not implemented" },
+	{ SOCKS5_ERR_HOSTNAME_SIZE,	FETCH_PROTO,	"SOCKS5: Hostname size is above 256 bytes" },
+	{ SOCKS5_ERR_REQUEST,		FETCH_PROTO,	"SOCKS5: Failed to request" },
+	{ SOCKS5_ERR_REPLY,		FETCH_PROTO,	"SOCKS5: Failed to receive reply" },
+	{ SOCKS5_ERR_NON_VER5_RESP,	FETCH_PROTO,	"SOCKS5: Server responded with a non-version 5 response" },
+	{ SOCKS5_ERR_GENERAL,		FETCH_ABORT,	"SOCKS5: General server failure" },
+	{ SOCKS5_ERR_NOT_ALLOWED,	FETCH_AUTH,	"SOCKS5: Connection not allowed by ruleset" },
+	{ SOCKS5_ERR_NET_UNREACHABLE,	FETCH_NETWORK,	"SOCKS5: Network unreachable" },
+	{ SOCKS5_ERR_HOST_UNREACHABLE,	FETCH_ABORT,	"SOCKS5: Host unreachable" },
+	{ SOCKS5_ERR_CONN_REFUSED,	FETCH_ABORT,	"SOCKS5: Connection refused" },
+	{ SOCKS5_ERR_TTL_EXPIRED,	FETCH_TIMEOUT,	"SOCKS5: TTL expired" },
+	{ SOCKS5_ERR_COM_UNSUPPORTED,	FETCH_PROTO,	"SOCKS5: Command not supported" },
+	{ SOCKS5_ERR_ADDR_UNSUPPORTED,	FETCH_ABORT,	"SOCKS5: Address type not supported" },
+	{ SOCKS5_ERR_UNSPECIFIED,	FETCH_UNKNOWN,	"SOCKS5: Unspecified error" },
+/* Configuration error */
+	{ SOCKS5_ERR_BAD_HOST,		FETCH_ABORT,	"SOCKS5: Bad proxy host" },
+	{ SOCKS5_ERR_BAD_PROXY_FORMAT,	FETCH_ABORT,	"SOCKS5: Bad proxy format" },
+	{ SOCKS5_ERR_BAD_PORT,		FETCH_ABORT,	"SOCKS5: Bad port" }
+};
+
 /* End-of-Line */
 static const char ENDL[2] = "\r\n";
 
@@ -314,7 +373,6 @@ syserr:
 }
 
 
-
 /*
  * Bind a socket to a specific local address
  */
@@ -337,6 +395,196 @@ fetch_bind(int sd, int af, const char *addr)
 
 
 /*
+ * SOCKS5 connection initiation, based on RFC 1928
+ * Default DNS resolution over SOCKS5
+ */
+int
+fetch_socks5_init(conn_t *conn, const char *host, int port, int verbose)
+{
+	/*
+	 * Size is based on largest packet prefix (4 bytes) +
+	 * Largest FQDN (256) + one byte size (1) +
+	 * Port (2)
+	 */
+	unsigned char buf[BUFF_SIZE];
+	unsigned char *ptr;
+	int ret = 1;
+
+	if (verbose)
+		fetch_info("Initializing SOCKS5 connection: %s:%d", host, port);
+
+	/* Connection initialization */
+	ptr = buf;
+	*ptr++ = SOCKS_VERSION_5;
+	*ptr++ = SOCKS_CONNECTION;
+	*ptr++ = SOCKS_RSV;
+
+	if (fetch_write(conn, buf, 3) != 3) {
+		ret = SOCKS5_ERR_SELECTION;
+		goto fail;
+	}
+
+	/* Verify response from SOCKS5 server */
+	if (fetch_read(conn, buf, 2) != 2) {
+		ret = SOCKS5_ERR_READ_METHOD;
+		goto fail;
+	}
+
+	ptr = buf;
+	if (ptr[0] != SOCKS_VERSION_5) {
+		ret = SOCKS5_ERR_VER5_ONLY;
+		goto fail;
+	}
+	if (ptr[1] == SOCKS_NOMETHODS) {
+		ret = SOCKS5_ERR_NOMETHODS;
+		goto fail;
+	}
+	else if (ptr[1] != SOCKS5_NOTIMPLEMENTED) {
+		ret = SOCKS5_ERR_NOTIMPLEMENTED;
+		goto fail;
+	}
+
+	/* Send Request */
+	*ptr++ = SOCKS_VERSION_5;
+	*ptr++ = SOCKS_CONNECTION;
+	*ptr++ = SOCKS_RSV;
+	/* Encode all targets as a hostname to avoid DNS leaks */
+	*ptr++ = SOCKS_ATYP_DOMAINNAME;
+	if (strlen(host) > FQDN_SIZE) {
+		ret = SOCKS5_ERR_HOSTNAME_SIZE;
+		goto fail;
+	}
+	*ptr++ = strlen(host);
+	strncpy(ptr, host, strlen(host));
+	ptr = ptr + strlen(host);
+
+	port = htons(port);
+	*ptr++ = port & 0x00ff;
+	*ptr++ = (port & 0xff00) >> 8;
+
+	if (fetch_write(conn, buf, ptr - buf) != ptr - buf) {
+		ret = SOCKS5_ERR_REQUEST;
+		goto fail;
+	}
+
+	/* BND.ADDR is variable length, read the largest on non-blocking socket */
+	if (!fetch_read(conn, buf, BUFF_SIZE)) {
+		ret = SOCKS5_ERR_REPLY;
+		goto fail;
+	}
+
+	ptr = buf;
+	if (*ptr++ != SOCKS_VERSION_5) {
+		ret = SOCKS5_ERR_NON_VER5_RESP;
+		goto fail;
+	}
+
+	switch(*ptr++) {
+	case SOCKS_SUCCESS:
+		break;
+	case SOCKS_GENERAL_FAILURE:
+		ret = SOCKS5_ERR_GENERAL;
+		goto fail;
+	case SOCKS_CONNECTION_NOT_ALLOWED:
+		ret = SOCKS5_ERR_NOT_ALLOWED;
+		goto fail;
+	case SOCKS_NETWORK_UNREACHABLE:
+		ret = SOCKS5_ERR_NET_UNREACHABLE;
+		goto fail;
+	case SOCKS_HOST_UNREACHABLE:
+		ret = SOCKS5_ERR_HOST_UNREACHABLE;
+		goto fail;
+	case SOCKS_CONNECTION_REFUSED:
+		ret = SOCKS5_ERR_CONN_REFUSED;
+		goto fail;
+	case SOCKS_TTL_EXPIRED:
+		ret = SOCKS5_ERR_TTL_EXPIRED;
+		goto fail;
+	case SOCKS_COMMAND_NOT_SUPPORTED:
+		ret = SOCKS5_ERR_COM_UNSUPPORTED;
+		goto fail;
+	case SOCKS_ADDRESS_NOT_SUPPORTED:
+		ret = SOCKS5_ERR_ADDR_UNSUPPORTED;
+		goto fail;
+	default:
+		ret = SOCKS5_ERR_UNSPECIFIED;
+		goto fail;
+	}
+
+	return (ret);
+
+fail:
+	socks5_seterr(ret);
+	return (0);
+}
+
+/*
+ * Perform SOCKS5 initialization
+ */
+int
+fetch_socks5_getenv(char **host, int *port)
+{
+	char *socks5env, *endptr, *ext;
+
+	if ((socks5env = getenv("SOCKS5_PROXY")) == NULL || *socks5env == '\0') {
+		*host = NULL;
+		*port = -1;
+		return (-1);
+	}
+
+	/* IPv6 addresses begin and end in brackets */
+	if (socks5env[0] == '[') {
+		if (socks5env[strlen(socks5env) - 1] == ']') {
+			*host = strndup(socks5env, strlen(socks5env));
+			if (*host == NULL)
+				goto fail;
+			*port = 1080; /* Default port as defined in RFC1928 */
+		} else {
+			ext = strstr(socks5env, "]:");
+			if (ext == NULL) {
+				socks5_seterr(SOCKS5_ERR_BAD_PROXY_FORMAT);
+				return (0);
+			}
+			ext=ext+1;
+			*host = strndup(socks5env, ext - socks5env);
+			if (*host == NULL)
+				goto fail;
+			errno = 0;
+			*port = strtoimax(ext + 1, (char **)&endptr, 10);
+			if (*endptr != '\0' || errno != 0 || *port < 0 ||
+			    *port > 65535) {
+				socks5_seterr(SOCKS5_ERR_BAD_PORT);
+				return (0);
+			}
+		}
+	} else {
+		ext = strrchr(socks5env, ':');
+		if (ext == NULL) {
+			*host = strdup(socks5env);
+			*port = 1080;
+		} else {
+			*host = strndup(socks5env, ext-socks5env);
+			if (*host == NULL)
+				goto fail;
+			errno = 0;
+			*port = strtoimax(ext + 1, (char **)&endptr, 10);
+			if (*endptr != '\0' || errno != 0 || *port < 0 ||
+			    *port > 65535) {
+				socks5_seterr(SOCKS5_ERR_BAD_PORT);
+				return (0);
+			}
+		}
+	}
+
+	return (2);
+
+fail:
+	fprintf(stderr, "Failure to allocate memory, exiting.\n");
+	return (-1);
+}
+
+
+/*
  * Establish a TCP connection to the specified port on the specified host.
  */
 conn_t *
@@ -346,22 +594,42 @@ fetch_connect(const char *host, int port, int af, int 
 	const char *bindaddr;
 	conn_t *conn = NULL;
 	int err = 0, sd = -1;
+	char *sockshost;
+	int socksport;
 
 	DEBUGF("---> %s:%d\n", host, port);
 
-	/* resolve server address */
-	if (verbose)
-		fetch_info("resolving server address: %s:%d", host, port);
-	if ((sais = fetch_resolve(host, port, af)) == NULL)
+	/* Check if SOCKS5_PROXY env variable is set */
+	if (!fetch_socks5_getenv(&sockshost, &socksport))
 		goto fail;
 
-	/* resolve client address */
-	bindaddr = getenv("FETCH_BIND_ADDRESS");
-	if (bindaddr != NULL && *bindaddr != '\0') {
+	/* Not using SOCKS5 proxy */
+	if (sockshost == NULL) {
+		/* resolve server address */
 		if (verbose)
-			fetch_info("resolving client address: %s", bindaddr);
-		if ((cais = fetch_resolve(bindaddr, 0, af)) == NULL)
+			fetch_info("resolving server address: %s:%d", host,
+			    port);
+		if ((sais = fetch_resolve(host, port, af)) == NULL)
 			goto fail;
+
+		/* resolve client address */
+		bindaddr = getenv("FETCH_BIND_ADDRESS");
+		if (bindaddr != NULL && *bindaddr != '\0') {
+			if (verbose)
+				fetch_info("resolving client address: %s",
+				    bindaddr);
+			if ((cais = fetch_resolve(bindaddr, 0, af)) == NULL)
+				goto fail;
+		}
+	} else {
+		/* resolve socks5 proxy address */
+		if (verbose)
+			fetch_info("resolving SOCKS5 server address: %s:%d",
+			    sockshost, socksport);
+		if ((sais = fetch_resolve(sockshost, socksport, af)) == NULL) {
+			socks5_seterr(SOCKS5_ERR_BAD_HOST);
+			goto fail;
+		}
 	}
 
 	/* try each server address in turn */
@@ -389,13 +657,26 @@ fetch_connect(const char *host, int port, int af, int 
 		sd = -1;
 	}
 	if (err != 0) {
-		if (verbose)
+		if (verbose && sockshost == NULL) {
 			fetch_info("failed to connect to %s:%d", host, port);
+			goto syserr;
+		} else if (sockshost != NULL) {
+			if (verbose)
+				fetch_info(
+				    "failed to connect to SOCKS5 server %s:%d",
+				    sockshost, socksport);
+			socks5_seterr(SOCKS5_ERR_CONN_REFUSED);
+			goto syserr1;
+		}
 		goto syserr;
 	}
 
 	if ((conn = fetch_reopen(sd)) == NULL)
 		goto syserr;
+
+	if (sockshost)
+		if (!fetch_socks5_init(conn, host, port, verbose))
+			goto fail;
 	if (cais != NULL)
 		freeaddrinfo(cais);
 	if (sais != NULL)
@@ -403,6 +684,7 @@ fetch_connect(const char *host, int port, int af, int 
 	return (conn);
 syserr:
 	fetch_syserr();
+syserr1:
 	goto fail;
 fail:
 	if (sd >= 0)

Modified: head/lib/libfetch/common.h
==============================================================================
--- head/lib/libfetch/common.h	Sat Feb 15 15:39:53 2020	(r357967)
+++ head/lib/libfetch/common.h	Sat Feb 15 18:03:16 2020	(r357968)
@@ -70,12 +70,47 @@ struct fetcherr {
 	const char	*string;
 };
 
+/* For SOCKS header size */
+#define HEAD_SIZE	4
+#define FQDN_SIZE	256
+#define PACK_SIZE	1
+#define PORT_SIZE	2
+#define BUFF_SIZE	HEAD_SIZE + FQDN_SIZE + PACK_SIZE + PORT_SIZE
+
+/* SOCKS5 Request Header */
+#define SOCKS_VERSION_5		0x05
+/* SOCKS5 CMD */
+#define SOCKS_CONNECTION	0x01
+#define SOCKS_BIND		0x02
+#define SOCKS_UDP		0x03
+#define SOCKS_NOMETHODS		0xFF
+#define SOCKS5_NOTIMPLEMENTED	0x00
+/* SOCKS5 Reserved */
+#define SOCKS_RSV		0x00
+/* SOCKS5 Address Type */
+#define SOCKS_ATYP_IPV4		0x01
+#define SOCKS_ATYP_DOMAINNAME	0x03
+#define SOCKS_ATYP_IPV6		0x04
+/* SOCKS5 Reply Field */
+#define SOCKS_SUCCESS			0x00
+#define SOCKS_GENERAL_FAILURE		0x01
+#define SOCKS_CONNECTION_NOT_ALLOWED	0x02
+#define SOCKS_NETWORK_UNREACHABLE	0x03
+#define SOCKS_HOST_UNREACHABLE		0x04
+#define SOCKS_CONNECTION_REFUSED	0x05
+#define SOCKS_TTL_EXPIRED		0x06
+#define SOCKS_COMMAND_NOT_SUPPORTED	0x07
+#define SOCKS_ADDRESS_NOT_SUPPORTED	0x08
+
 /* for fetch_writev */
 struct iovec;
 
 void		 fetch_seterr(struct fetcherr *, int);
 void		 fetch_syserr(void);
 void		 fetch_info(const char *, ...) __printflike(1, 2);
+int		 fetch_socks5_getenv(char **host, int *port);
+int		 fetch_socks5_init(conn_t *conn, const char *host,
+		     int port, int verbose);
 int		 fetch_default_port(const char *);
 int		 fetch_default_proxy_port(const char *);
 struct addrinfo *fetch_resolve(const char *, int, int);
@@ -102,6 +137,7 @@ int		 fetch_no_proxy_match(const char *);
 #define http_seterr(n)	 fetch_seterr(http_errlist, n)
 #define netdb_seterr(n)	 fetch_seterr(netdb_errlist, n)
 #define url_seterr(n)	 fetch_seterr(url_errlist, n)
+#define socks5_seterr(n) fetch_seterr(socks5_errlist, n)
 
 #ifndef NDEBUG
 #define DEBUGF(...)							\

Modified: head/lib/libfetch/fetch.3
==============================================================================
--- head/lib/libfetch/fetch.3	Sat Feb 15 15:39:53 2020	(r357967)
+++ head/lib/libfetch/fetch.3	Sat Feb 15 18:03:16 2020	(r357968)
@@ -668,6 +668,13 @@ which proxies should not be used.
 Same as
 .Ev NO_PROXY ,
 for compatibility.
+.It Ev SOCKS5_PROXY
+Uses SOCKS version 5 to make connection.
+The format must be the IP or hostname followed by a colon for the port.
+IPv6 addresses must enclose the address in brackets.
+If no port is specified, the default is 1080.
+This setting will supercede a connection to an
+.Ev HTTP_PROXY .
 .It Ev SSL_ALLOW_SSL3
 Allow SSL version 3 when negotiating the connection (not recommended).
 .It Ev SSL_CA_CERT_FILE
@@ -724,6 +731,21 @@ host, define
 as follows:
 .Bd -literal -offset indent
 NO_PROXY=localhost,127.0.0.1
+.Ed
+.Pp
+To use a SOCKS5 proxy, set the
+.Ev SOCKS5_PROXY
+environment variable to a
+valid host or IP followed by an optional colon and the port.
+IPv6 addresses must be enclosed in brackets.
+The following are examples of valid settings:
+.Bd -literal -offset indent
+SOCKS5_PROXY=proxy.example.com
+SOCKS5_PROXY=proxy.example.com:1080
+SOCKS5_PROXY=192.0.2.0
+SOCKS5_PROXY=198.51.100.0:1080
+SOCKS5_PROXY=[2001:db8::1]
+SOCKS5_PROXY=[2001:db8::2]:1080
 .Ed
 .Pp
 Access HTTPS website without any certificate verification whatsoever:


More information about the svn-src-all mailing list