Index: lib/libfetch/common.h =================================================================== --- lib/libfetch/common.h +++ lib/libfetch/common.h @@ -70,12 +70,47 @@ 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); Index: lib/libfetch/common.c =================================================================== --- lib/libfetch/common.c +++ lib/libfetch/common.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -314,7 +315,6 @@ } - /* * Bind a socket to a specific local address */ @@ -336,6 +336,191 @@ } +/* + * 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; + + 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) { + fprintf(stderr, "SOCKS5: Failed to send selection method.\n"); + goto fail; + } + + /* Verify response from SOCKS5 server */ + if (fetch_read(conn, buf, 2) != 2) { + fprintf(stderr, "SOCKS5: Failed to read method.\n"); + goto fail; + } + + ptr = buf; + if (ptr[0] != SOCKS_VERSION_5) { + fprintf(stderr, "SOCKS5: Currently only version 5 is implemented.\n"); + goto fail; + } + if (ptr[1] == SOCKS_NOMETHODS) { + fprintf(stderr, "SOCKS5: No acceptable methods. Disconnecting.\n"); + goto fail; + } + else if (ptr[1] != SOCKS5_NOTIMPLEMENTED) { + fprintf(stderr, "SOCKS5: Method currently not implemented. Disconnecting.\n"); + 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) { + fprintf(stderr, "Hostname above 256 bytes, exiting.\n"); + 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) { + fprintf(stderr, "SOCKS5: Failed to request.\n"); + goto fail; + } + + /* BND.ADDR is variable length, read the largest on non-blocking socket */ + if (!fetch_read(conn, buf, BUFF_SIZE)) { + fprintf(stderr, "SOCKS5: Failed to receive reply.\n"); + goto fail; + } + + ptr = buf; + if (*ptr++ != SOCKS_VERSION_5) { + fprintf(stderr, "SOCKS5: Server responded with a non-version 5 response.\n"); + goto fail; + } + + switch(*ptr++) { + case SOCKS_SUCCESS: + break; + case SOCKS_GENERAL_FAILURE: + fprintf(stderr, "SOCKS5: General server failure\n"); + goto fail; + case SOCKS_CONNECTION_NOT_ALLOWED: + fprintf(stderr, "SOCKS5: Connection not allowed by ruleset.\n"); + goto fail; + case SOCKS_NETWORK_UNREACHABLE: + fprintf(stderr, "SOCKS5: Network unreachable.\n"); + goto fail; + case SOCKS_HOST_UNREACHABLE: + fprintf(stderr, "SOCKS5: Host unreachable.\n"); + goto fail; + case SOCKS_CONNECTION_REFUSED: + fprintf(stderr, "SOCKS5: Connection refused.\n"); + goto fail; + case SOCKS_TTL_EXPIRED: + fprintf(stderr, "SOCKS5: TTL expired.\n"); + goto fail; + case SOCKS_COMMAND_NOT_SUPPORTED: + fprintf(stderr, "SOCKS5: Command not supported.\n"); + goto fail; + case SOCKS_ADDRESS_NOT_SUPPORTED: + fprintf(stderr, "SOCKS5: Address type not supported.\n"); + goto fail; + default: + fprintf(stderr, "SOCKS5: Unspecified failure.\n"); + goto fail; + } + + return (1); + +fail: + 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) { + fprintf(stderr, "Bad SOCKS5_PROXY format, missing closing ']': %s\n", + socks5env); + return (0); + } + ext=ext+1; + *host = strndup(socks5env, ext - socks5env); + if (*host == NULL) + goto fail; + *port = strtoimax(ext + 1, (char **)&endptr, 10); + if (*port == 0 && errno == EINVAL) { + fprintf(stderr, "Bad SOCKS5_PROXY port: %s\n", socks5env); + 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; + *port = strtoimax(ext + 1, (char **)&endptr, 10); + if (*port == 0 && errno == EINVAL) { + fprintf(stderr, "Bad SOCKS5_PROXY port: %s\n", socks5env); + 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. */ @@ -346,21 +531,36 @@ 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) goto fail; } @@ -389,13 +589,19 @@ sd = -1; } if (err != 0) { - if (verbose) + if (verbose && sockshost == NULL) fetch_info("failed to connect to %s:%d", host, port); + else if (verbose && sockshost) + fetch_info("failed to connect to SOCKS5 server %s:%d", sockshost, socksport); 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) Index: lib/libfetch/fetch.3 =================================================================== --- lib/libfetch/fetch.3 +++ lib/libfetch/fetch.3 @@ -26,7 +26,7 @@ .\" .\" $FreeBSD$ .\" -.Dd March 18, 2016 +.Dd June 19, 2019 .Dt FETCH 3 .Os .Sh NAME @@ -652,6 +652,13 @@ 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 @@ -710,6 +717,21 @@ 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