Index: head/lib/libfetch/common.h =================================================================== --- head/lib/libfetch/common.h +++ head/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); @@ -102,6 +137,7 @@ #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(...) \ Index: head/lib/libfetch/common.c =================================================================== --- head/lib/libfetch/common.c +++ head/lib/libfetch/common.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -74,6 +75,64 @@ { -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 @@ } - /* * Bind a socket to a specific local address */ @@ -337,6 +395,196 @@ /* + * 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 @@ 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 @@ 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 @@ return (conn); syserr: fetch_syserr(); +syserr1: goto fail; fail: if (sd >= 0) Index: head/lib/libfetch/fetch.3 =================================================================== --- head/lib/libfetch/fetch.3 +++ head/lib/libfetch/fetch.3 @@ -668,6 +668,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 @@ -724,6 +731,21 @@ 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: