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