summaryrefslogtreecommitdiff
path: root/lib/libfetch
diff options
context:
space:
mode:
authorKyle Evans <kevans@FreeBSD.org>2020-02-15 18:03:16 +0000
committerKyle Evans <kevans@FreeBSD.org>2020-02-15 18:03:16 +0000
commitc44be5aa0a9e889c2ebde73986acabdaab952c65 (patch)
tree6eb1814e91a94f2cc998d66d964c7e993042cc4a /lib/libfetch
parentaf141236018894868608675854fdb8ca4b9f50fa (diff)
downloadsrc-test-c44be5aa0a9e889c2ebde73986acabdaab952c65.tar.gz
src-test-c44be5aa0a9e889c2ebde73986acabdaab952c65.zip
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
Notes
Notes: svn path=/head/; revision=357968
Diffstat (limited to 'lib/libfetch')
-rw-r--r--lib/libfetch/common.c304
-rw-r--r--lib/libfetch/common.h36
-rw-r--r--lib/libfetch/fetch.322
3 files changed, 351 insertions, 11 deletions
diff --git a/lib/libfetch/common.c b/lib/libfetch/common.c
index 873df38eaecd3..e480c311f84c3 100644
--- a/lib/libfetch/common.c
+++ b/lib/libfetch/common.c
@@ -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 verbose)
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 verbose)
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 verbose)
return (conn);
syserr:
fetch_syserr();
+syserr1:
goto fail;
fail:
if (sd >= 0)
diff --git a/lib/libfetch/common.h b/lib/libfetch/common.h
index 1c80e089d90e2..1b45f1b05b723 100644
--- a/lib/libfetch/common.h
+++ b/lib/libfetch/common.h
@@ -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(...) \
diff --git a/lib/libfetch/fetch.3 b/lib/libfetch/fetch.3
index e28cd55c6ef08..fe99e56da71cc 100644
--- a/lib/libfetch/fetch.3
+++ b/lib/libfetch/fetch.3
@@ -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
@@ -726,6 +733,21 @@ as follows:
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:
.Bd -literal -offset indent
SSL_NO_VERIFY_PEER=1