From 30ace44394173bc2b43599c304acdadb40cc0ed4 Mon Sep 17 00:00:00 2001 From: Duncaen Date: Wed, 31 Aug 2016 23:40:31 +0200 Subject: [PATCH 1/2] lib/fetch: add socks5 support --- lib/fetch/common.c | 124 +++++++++++++++++++++++++++++++++++++++++++-- lib/fetch/common.h | 23 +++++++++ lib/fetch/fetch.c | 11 ++++ lib/fetch/fetch.h | 1 + 4 files changed, 154 insertions(+), 5 deletions(-) diff --git a/lib/fetch/common.c b/lib/fetch/common.c index dd98a88a..f8e84154 100644 --- a/lib/fetch/common.c +++ b/lib/fetch/common.c @@ -269,6 +269,106 @@ fetch_bind(int sd, int af, const char *addr) return rv; } +int +fetch_socks5(conn_t *conn, struct url *url, int verbose) +{ + char buf[16]; + uint8_t auth; + size_t alen; + + alen = strlen(url->host); + /* + auth = (*url->user != '\0' && *url->pwd != '\0') + ? SOCKS5_USER_PASS : SOCKS5_NO_AUTH; + */ + auth = SOCKS5_NO_AUTH; + + buf[0] = SOCKS5_VERSION; + buf[1] = 0x01; /* number of auth methods */ + buf[2] = auth; + // XXX: support user/pass auth + if (fetch_write(conn, buf, 3) != 3) + return -1; + + if (fetch_read(conn, buf, 2) != 2) + return -1; + + if (buf[0] != SOCKS5_VERSION || buf[1] != auth) { + if (verbose) + fetch_info("socks version or auth method not recognized"); + errno = EINVAL; + return -1; + } + + if (verbose) + fetch_info("connecting socks5 to %s:%d", url->host, url->port); + + /* write request */ + buf[0] = SOCKS5_VERSION; + buf[1] = SOCKS5_TCP_STREAM; + buf[2] = 0x00; + buf[3] = SOCKS5_ATYPE_DOMAIN; + // XXX: support other address types + buf[4] = alen; + if (fetch_write(conn, buf, 5) != 5) + return -1; + + if (fetch_write(conn, url->host, alen) == -1) + return -1; + + buf[0] = (url->port >> 0x08); + buf[1] = (url->port & 0xFF); + if (fetch_write(conn, buf, 2) != 2) + return -1; + + /* read answer */ + if (fetch_read(conn, buf, 4) != 4) + return -1; + + if (buf[0] != SOCKS5_VERSION) { + if (verbose) + fetch_info("socks version not recognized"); + return -1; + } + + /* answer status */ + if (buf[1] != SOCKS5_REPLY_SUCCESS) { + switch (buf[1]) { + case SOCKS5_REPLY_DENY: errno = EACCES; break; + case SOCKS5_REPLY_NO_NET: errno = ENETUNREACH; break; + case SOCKS5_REPLY_NO_HOST: errno = EHOSTUNREACH; break; + case SOCKS5_REPLY_REFUSED: errno = ECONNREFUSED; break; + case SOCKS5_REPLY_TIMEOUT: errno = ETIMEDOUT; break; + case SOCKS5_REPLY_CMD_NOTSUP: errno = ENOTSUP; break; + case SOCKS5_REPLY_ADR_NOTSUP: errno = ENOTSUP; break; + } + return -1; + } + + switch (buf[3]) { + case SOCKS5_ATYPE_IPV4: + if (fetch_read(conn, buf, 4) != 4) + return -1; + break; + case SOCKS5_ATYPE_DOMAIN: + if (fetch_read(conn, buf, 1) != 1 && + fetch_read(conn, buf, buf[0]) != buf[0]) + return -1; + break; + case SOCKS5_ATYPE_IPV6: + if (fetch_read(conn, buf, 16) != 16) + return -1; + break; + default: + return -1; + } + + // port + if (fetch_read(conn, buf, 2) != 2) + return -1; + + return 0; +} /* * Establish a TCP connection to the specified port on the specified host. @@ -278,27 +378,36 @@ fetch_connect(struct url *url, int af, int verbose) { conn_t *conn; char pbuf[10]; - const char *bindaddr; + struct url *socks_url, *connurl; + const char *bindaddr, *socks_proxy; struct addrinfo hints, *res, *res0; int sd, error; + socks_url = NULL; + socks_proxy = getenv("SOCKS_PROXY"); + if (socks_proxy != NULL && *socks_proxy != '\0' && + (socks_url = fetchParseURL(socks_proxy)) != NULL) + connurl = socks_url; + else + connurl = url; + if (verbose) - fetch_info("looking up %s", url->host); + fetch_info("looking up %s", connurl->host); /* look up host name and set up socket address structure */ - snprintf(pbuf, sizeof(pbuf), "%d", url->port); + snprintf(pbuf, sizeof(pbuf), "%d", connurl->port); memset(&hints, 0, sizeof(hints)); hints.ai_family = af; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; - if ((error = getaddrinfo(url->host, pbuf, &hints, &res0)) != 0) { + if ((error = getaddrinfo(connurl->host, pbuf, &hints, &res0)) != 0) { netdb_seterr(error); return (NULL); } bindaddr = getenv("FETCH_BIND_ADDRESS"); if (verbose) - fetch_info("connecting to %s:%d", url->host, url->port); + fetch_info("connecting to %s:%d", connurl->host, connurl->port); /* try to connect */ for (sd = -1, res = res0; res; sd = -1, res = res->ai_next) { @@ -326,6 +435,11 @@ fetch_connect(struct url *url, int af, int verbose) close(sd); return NULL; } + if (socks_url && fetch_socks5(conn, url, verbose) != 0) { + fetch_syserr(); + close(sd); + return NULL; + } conn->cache_url = fetchCopyURL(url); conn->cache_af = af; return (conn); diff --git a/lib/fetch/common.h b/lib/fetch/common.h index 09a5d6b1..c577eec4 100644 --- a/lib/fetch/common.h +++ b/lib/fetch/common.h @@ -37,6 +37,28 @@ #define FTP_DEFAULT_PROXY_PORT 21 #define HTTP_DEFAULT_PROXY_PORT 3128 +#define SOCKS5_VERSION 0x05 + +#define SOCKS5_NO_AUTH 0x00 +#define SOCKS5_USER_PASS 0x02 +#define SOCKS5_NO_ACCEPT_METHOD 0xFF + +#define SOCKS5_TCP_STREAM 0x01 + +#define SOCKS5_ATYPE_IPV4 0x01 +#define SOCKS5_ATYPE_DOMAIN 0x03 +#define SOCKS5_ATYPE_IPV6 0x04 + +#define SOCKS5_REPLY_SUCCESS 0x00 +#define SOCKS5_REPLY_FAILURE 0x01 +#define SOCKS5_REPLY_DENY 0x02 +#define SOCKS5_REPLY_NO_NET 0x03 +#define SOCKS5_REPLY_NO_HOST 0x04 +#define SOCKS5_REPLY_REFUSED 0x05 +#define SOCKS5_REPLY_TIMEOUT 0x06 +#define SOCKS5_REPLY_CMD_NOTSUP 0x07 +#define SOCKS5_REPLY_ADR_NOTSUP 0x08 + #ifdef WITH_SSL #include #include @@ -98,6 +120,7 @@ int fetch_default_proxy_port(const char *); int fetch_bind(int, int, const char *); conn_t *fetch_cache_get(const struct url *, int); void fetch_cache_put(conn_t *, int (*)(conn_t *)); +int fetch_socks5(conn_t *, struct url *, int); conn_t *fetch_connect(struct url *, int, int); conn_t *fetch_reopen(int); #ifdef WITH_SSL diff --git a/lib/fetch/fetch.c b/lib/fetch/fetch.c index acdb716b..d1041a92 100644 --- a/lib/fetch/fetch.c +++ b/lib/fetch/fetch.c @@ -459,6 +459,17 @@ fetchParseURL(const char *URL) URL += 2; goto find_user; } + if (strncmp(URL, "socks5:", 7) == 0) { + pre_quoted = 1; + strcpy(u->scheme, SCHEME_SOCKS5); + URL += 7; + if (URL[0] != '/' || URL[1] != '/') { + url_seterr(URL_MALFORMED); + goto ouch; + } + URL += 2; + goto find_user; + } url_seterr(URL_BAD_SCHEME); goto ouch; diff --git a/lib/fetch/fetch.h b/lib/fetch/fetch.h index c5728581..df1f3220 100644 --- a/lib/fetch/fetch.h +++ b/lib/fetch/fetch.h @@ -72,6 +72,7 @@ struct url_list { #define SCHEME_HTTP "http" #define SCHEME_HTTPS "https" #define SCHEME_FILE "file" +#define SCHEME_SOCKS5 "socks5" /* Error codes */ #define FETCH_ABORT 1 From 08b9ed878f09e631cd032a0b470ec397226c9e6c Mon Sep 17 00:00:00 2001 From: Duncaen Date: Fri, 2 Sep 2016 17:43:17 +0200 Subject: [PATCH 2/2] lib/fetch: default port, error checks and authentication support --- lib/fetch/common.c | 95 +++++++++++++++++++++++++++++++++++++--------- lib/fetch/common.h | 35 +++++++++-------- 2 files changed, 97 insertions(+), 33 deletions(-) diff --git a/lib/fetch/common.c b/lib/fetch/common.c index f8e84154..94fb2651 100644 --- a/lib/fetch/common.c +++ b/lib/fetch/common.c @@ -205,6 +205,8 @@ fetch_default_port(const char *scheme) return (HTTP_DEFAULT_PORT); if (strcasecmp(scheme, SCHEME_HTTPS) == 0) return (HTTPS_DEFAULT_PORT); + if (strcasecmp(scheme, SCHEME_SOCKS5) == 0) + return (SOCKS5_DEFAULT_PORT); if ((se = getservbyname(scheme, "tcp")) != NULL) return (ntohs(se->s_port)); return (0); @@ -270,36 +272,79 @@ fetch_bind(int sd, int af, const char *addr) } int -fetch_socks5(conn_t *conn, struct url *url, int verbose) +fetch_socks5(conn_t *conn, struct url *url, struct url *socks, int verbose) { char buf[16]; uint8_t auth; size_t alen; alen = strlen(url->host); - /* - auth = (*url->user != '\0' && *url->pwd != '\0') + auth = (*socks->user != '\0' && *socks->pwd != '\0') ? SOCKS5_USER_PASS : SOCKS5_NO_AUTH; - */ - auth = SOCKS5_NO_AUTH; buf[0] = SOCKS5_VERSION; buf[1] = 0x01; /* number of auth methods */ buf[2] = auth; - // XXX: support user/pass auth if (fetch_write(conn, buf, 3) != 3) return -1; if (fetch_read(conn, buf, 2) != 2) return -1; - if (buf[0] != SOCKS5_VERSION || buf[1] != auth) { + if (buf[0] != SOCKS5_VERSION) { if (verbose) - fetch_info("socks version or auth method not recognized"); + fetch_info("socks5 version not recognized"); errno = EINVAL; return -1; } + if ((uint8_t)buf[1] == SOCKS5_NO_METHOD) { + if (verbose) + fetch_info("no acceptable socks5 authentication method"); + errno = EPERM; + return -1; + } + + switch (buf[1]) { + case SOCKS5_USER_PASS: + if (verbose) + fetch_info("authenticate socks5 user '%s'", socks->user); + buf[0] = SOCKS5_PASS_VERSION; + buf[1] = strlen(socks->user); + if (fetch_write(conn, buf, 2) != 2) + return -1; + if (fetch_write(conn, socks->user, buf[1]) == -1) + return -1; + + buf[0] = strlen(socks->pwd); + if (fetch_write(conn, buf, 1) != 1) + return -1; + if (fetch_write(conn, socks->pwd, buf[0]) == -1) + return -1; + + if (fetch_read(conn, buf, 2) != 2) + return -1; + + if (buf[0] != SOCKS5_PASS_VERSION) { + if (verbose) + fetch_info("socks5 password version not recognized"); + errno = EINVAL; + return -1; + } + + if (verbose) + fetch_info("socks5 authentication response %d", buf[1]); + + if (buf[1] != SOCKS5_AUTH_SUCCESS) { + if (verbose) + fetch_info("socks5 authentication failed"); + errno = EPERM; + return -1; + } + + break; + } + if (verbose) fetch_info("connecting socks5 to %s:%d", url->host, url->port); @@ -308,7 +353,6 @@ fetch_socks5(conn_t *conn, struct url *url, int verbose) buf[1] = SOCKS5_TCP_STREAM; buf[2] = 0x00; buf[3] = SOCKS5_ATYPE_DOMAIN; - // XXX: support other address types buf[4] = alen; if (fetch_write(conn, buf, 5) != 5) return -1; @@ -327,12 +371,15 @@ fetch_socks5(conn_t *conn, struct url *url, int verbose) if (buf[0] != SOCKS5_VERSION) { if (verbose) - fetch_info("socks version not recognized"); + fetch_info("socks5 version not recognized"); + errno = EINVAL; return -1; } /* answer status */ if (buf[1] != SOCKS5_REPLY_SUCCESS) { + if (verbose) + fetch_info("socks5 response status %d", buf[1]); switch (buf[1]) { case SOCKS5_REPLY_DENY: errno = EACCES; break; case SOCKS5_REPLY_NO_NET: errno = ENETUNREACH; break; @@ -385,11 +432,20 @@ fetch_connect(struct url *url, int af, int verbose) socks_url = NULL; socks_proxy = getenv("SOCKS_PROXY"); - if (socks_proxy != NULL && *socks_proxy != '\0' && - (socks_url = fetchParseURL(socks_proxy)) != NULL) + if (socks_proxy != NULL && *socks_proxy != '\0') { + if (!(socks_url = fetchParseURL(socks_proxy))) + return NULL; + if (strcasecmp(socks_url->scheme, SCHEME_SOCKS5) != 0) { + if (verbose) + fetch_info("SOCKS_PROXY scheme '%s' not supported", socks_url->scheme); + return NULL; + } + if (!socks_url->port) + socks_url->port = fetch_default_port(socks_url->scheme); connurl = socks_url; - else + } else { connurl = url; + } if (verbose) fetch_info("looking up %s", connurl->host); @@ -435,10 +491,15 @@ fetch_connect(struct url *url, int af, int verbose) close(sd); return NULL; } - if (socks_url && fetch_socks5(conn, url, verbose) != 0) { - fetch_syserr(); - close(sd); - return NULL; + if (socks_url) { + if (strcasecmp(socks_url->scheme, SCHEME_SOCKS5) == 0) { + if (fetch_socks5(conn, url, socks_url, verbose) != 0) { + fetch_syserr(); + close(sd); + free(conn); + return NULL; + } + } } conn->cache_url = fetchCopyURL(url); conn->cache_af = af; diff --git a/lib/fetch/common.h b/lib/fetch/common.h index c577eec4..3fde522c 100644 --- a/lib/fetch/common.h +++ b/lib/fetch/common.h @@ -36,26 +36,29 @@ #define HTTPS_DEFAULT_PORT 443 #define FTP_DEFAULT_PROXY_PORT 21 #define HTTP_DEFAULT_PROXY_PORT 3128 +#define SOCKS5_DEFAULT_PORT 1080 -#define SOCKS5_VERSION 0x05 +#define SOCKS5_VERSION 0x05 +#define SOCKS5_PASS_VERSION 0x01 -#define SOCKS5_NO_AUTH 0x00 -#define SOCKS5_USER_PASS 0x02 -#define SOCKS5_NO_ACCEPT_METHOD 0xFF +#define SOCKS5_NO_AUTH 0x00 +#define SOCKS5_USER_PASS 0x02 +#define SOCKS5_AUTH_SUCCESS 0x00 +#define SOCKS5_NO_METHOD 0xFF -#define SOCKS5_TCP_STREAM 0x01 +#define SOCKS5_TCP_STREAM 0x01 -#define SOCKS5_ATYPE_IPV4 0x01 -#define SOCKS5_ATYPE_DOMAIN 0x03 -#define SOCKS5_ATYPE_IPV6 0x04 +#define SOCKS5_ATYPE_IPV4 0x01 +#define SOCKS5_ATYPE_DOMAIN 0x03 +#define SOCKS5_ATYPE_IPV6 0x04 -#define SOCKS5_REPLY_SUCCESS 0x00 -#define SOCKS5_REPLY_FAILURE 0x01 -#define SOCKS5_REPLY_DENY 0x02 -#define SOCKS5_REPLY_NO_NET 0x03 -#define SOCKS5_REPLY_NO_HOST 0x04 -#define SOCKS5_REPLY_REFUSED 0x05 -#define SOCKS5_REPLY_TIMEOUT 0x06 +#define SOCKS5_REPLY_SUCCESS 0x00 +#define SOCKS5_REPLY_FAILURE 0x01 +#define SOCKS5_REPLY_DENY 0x02 +#define SOCKS5_REPLY_NO_NET 0x03 +#define SOCKS5_REPLY_NO_HOST 0x04 +#define SOCKS5_REPLY_REFUSED 0x05 +#define SOCKS5_REPLY_TIMEOUT 0x06 #define SOCKS5_REPLY_CMD_NOTSUP 0x07 #define SOCKS5_REPLY_ADR_NOTSUP 0x08 @@ -120,7 +123,7 @@ int fetch_default_proxy_port(const char *); int fetch_bind(int, int, const char *); conn_t *fetch_cache_get(const struct url *, int); void fetch_cache_put(conn_t *, int (*)(conn_t *)); -int fetch_socks5(conn_t *, struct url *, int); +int fetch_socks5(conn_t *, struct url *, struct url *, int); conn_t *fetch_connect(struct url *, int, int); conn_t *fetch_reopen(int); #ifdef WITH_SSL