From: Balakrishnan Balasubramanian <3070606-balki@users.noreply.gitlab.com> Date: Thu, 19 May 2022 13:33:22 +0000 (+0200) Subject: socks: support unix sockets for socks proxy X-Git-Tag: curl-7_84_0~169 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=dfa84a0450ca4bc08ccaebb8dfffb23735b281ef;p=thirdparty%2Fcurl.git socks: support unix sockets for socks proxy Usage: curl -x "socks5h://localhost/run/tor/socks" "https://example.com" Updated runtests.pl to run a socksd server listening on unix socket Added tests test1467 test1468 Added documentation for proxy command line option and socks proxy options Closes #8668 --- diff --git a/docs/cmdline-opts/proxy.d b/docs/cmdline-opts/proxy.d index 60674b5f89..49068b93e5 100644 --- a/docs/cmdline-opts/proxy.d +++ b/docs/cmdline-opts/proxy.d @@ -14,6 +14,9 @@ specified or http:// will be treated as HTTP proxy. Use socks4://, socks4a://, socks5:// or socks5h:// to request a specific SOCKS version to be used. (Added in 7.21.7) +Unix domain sockets are supported for socks proxy. Set localhost for the host +part. e.g. socks5h://localhost/path/to/socket.sock + HTTPS proxy support via https:// protocol prefix was added in 7.52.0 for OpenSSL, GnuTLS and NSS. diff --git a/docs/cmdline-opts/socks4.d b/docs/cmdline-opts/socks4.d index a1fb1b3445..c460d494bd 100644 --- a/docs/cmdline-opts/socks4.d +++ b/docs/cmdline-opts/socks4.d @@ -10,6 +10,9 @@ Use the specified SOCKS4 proxy. If the port number is not specified, it is assumed at port 1080. Using this socket type make curl resolve the host name and passing the address on to the proxy. +To specify proxy on a unix domain socket, use localhost for host, e.g. +socks4://localhost/path/to/socket.sock + This option overrides any previous use of --proxy, as they are mutually exclusive. diff --git a/docs/cmdline-opts/socks4a.d b/docs/cmdline-opts/socks4a.d index e39b968aab..ba56594b0b 100644 --- a/docs/cmdline-opts/socks4a.d +++ b/docs/cmdline-opts/socks4a.d @@ -9,6 +9,9 @@ See-also: socks4 socks5 socks5-hostname Use the specified SOCKS4a proxy. If the port number is not specified, it is assumed at port 1080. This asks the proxy to resolve the host name. +To specify proxy on a unix domain socket, use localhost for host, e.g. +socks4a://localhost/path/to/socket.sock + This option overrides any previous use of --proxy, as they are mutually exclusive. diff --git a/docs/cmdline-opts/socks5-hostname.d b/docs/cmdline-opts/socks5-hostname.d index 6530429a9e..628a2db241 100644 --- a/docs/cmdline-opts/socks5-hostname.d +++ b/docs/cmdline-opts/socks5-hostname.d @@ -9,6 +9,9 @@ See-also: socks5 socks4a Use the specified SOCKS5 proxy (and let the proxy resolve the host name). If the port number is not specified, it is assumed at port 1080. +To specify proxy on a unix domain socket, use localhost for host, e.g. +socks5h://localhost/path/to/socket.sock + This option overrides any previous use of --proxy, as they are mutually exclusive. diff --git a/docs/cmdline-opts/socks5.d b/docs/cmdline-opts/socks5.d index af1c057712..bf43652b3e 100644 --- a/docs/cmdline-opts/socks5.d +++ b/docs/cmdline-opts/socks5.d @@ -9,6 +9,9 @@ See-also: socks5-hostname socks4a Use the specified SOCKS5 proxy - but resolve the host name locally. If the port number is not specified, it is assumed at port 1080. +To specify proxy on a unix domain socket, use localhost for host, e.g. +socks5://localhost/path/to/socket.sock + This option overrides any previous use of --proxy, as they are mutually exclusive. diff --git a/docs/libcurl/opts/CURLOPT_PROXY.3 b/docs/libcurl/opts/CURLOPT_PROXY.3 index 704a65d6a3..5cf67b13c5 100644 --- a/docs/libcurl/opts/CURLOPT_PROXY.3 +++ b/docs/libcurl/opts/CURLOPT_PROXY.3 @@ -5,7 +5,7 @@ .\" * | (__| |_| | _ <| |___ .\" * \___|\___/|_| \_\_____| .\" * -.\" * Copyright (C) 1998 - 2021, Daniel Stenberg, , et al. +.\" * Copyright (C) 1998 - 2022, Daniel Stenberg, , et al. .\" * .\" * This software is licensed as described in the file COPYING, which .\" * you should have received as part of this distribution. The terms @@ -73,6 +73,9 @@ use of a proxy, even if there is an environment variable set for it. A proxy host string can also include protocol scheme (http://) and embedded user + password. +Unix domain sockets are supported for socks proxies since 7.84.0. Set +localhost for the host part. e.g. socks5h://localhost/path/to/socket.sock + The application does not have to keep the string around after setting this option. .SH "Environment variables" diff --git a/lib/url.c b/lib/url.c index faf4b4278d..6907664b71 100644 --- a/lib/url.c +++ b/lib/url.c @@ -147,6 +147,10 @@ static void conn_free(struct connectdata *conn); # error READBUFFER_SIZE is too small #endif +#ifdef USE_UNIX_SOCKETS +#define UNIX_SOCKET_PREFIX "localhost" +#endif + /* * get_protocol_family() * @@ -2407,13 +2411,18 @@ static CURLcode parse_proxy(struct Curl_easy *data, int port = -1; char *proxyuser = NULL; char *proxypasswd = NULL; - char *host; + char *host = NULL; bool sockstype; CURLUcode uc; struct proxy_info *proxyinfo; CURLU *uhp = curl_url(); CURLcode result = CURLE_OK; char *scheme = NULL; +#ifdef USE_UNIX_SOCKETS + char *path = NULL; + bool is_unix_proxy = FALSE; +#endif + if(!uhp) { result = CURLE_OUT_OF_MEMORY; @@ -2538,21 +2547,54 @@ static CURLcode parse_proxy(struct Curl_easy *data, result = CURLE_OUT_OF_MEMORY; goto error; } - Curl_safefree(proxyinfo->host.rawalloc); - proxyinfo->host.rawalloc = host; - if(host[0] == '[') { - /* this is a numerical IPv6, strip off the brackets */ - size_t len = strlen(host); - host[len-1] = 0; /* clear the trailing bracket */ - host++; - zonefrom_url(uhp, data, conn); +#ifdef USE_UNIX_SOCKETS + if(sockstype && strcasecompare(UNIX_SOCKET_PREFIX, host)) { + uc = curl_url_get(uhp, CURLUPART_PATH, &path, CURLU_URLDECODE); + if(uc) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + /* path will be "/", if no path was was found */ + if(strcmp("/", path)) { + is_unix_proxy = TRUE; + free(host); + host = aprintf(UNIX_SOCKET_PREFIX"%s", path); + if(!host) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + Curl_safefree(proxyinfo->host.rawalloc); + proxyinfo->host.rawalloc = host; + proxyinfo->host.name = host; + host = NULL; + } + } + + if(!is_unix_proxy) { +#endif + Curl_safefree(proxyinfo->host.rawalloc); + proxyinfo->host.rawalloc = host; + if(host[0] == '[') { + /* this is a numerical IPv6, strip off the brackets */ + size_t len = strlen(host); + host[len-1] = 0; /* clear the trailing bracket */ + host++; + zonefrom_url(uhp, data, conn); + } + proxyinfo->host.name = host; + host = NULL; +#ifdef USE_UNIX_SOCKETS } - proxyinfo->host.name = host; +#endif error: free(proxyuser); free(proxypasswd); + free(host); free(scheme); +#ifdef USE_UNIX_SOCKETS + free(path); +#endif curl_url_cleanup(uhp); return result; } @@ -3384,25 +3426,35 @@ static CURLcode resolve_server(struct Curl_easy *data, struct Curl_dns_entry *hostaddr = NULL; #ifdef USE_UNIX_SOCKETS - if(conn->unix_domain_socket) { + char *unix_path = NULL; + + if(conn->unix_domain_socket) + unix_path = conn->unix_domain_socket; +#ifndef CURL_DISABLE_PROXY + else if(conn->socks_proxy.host.name + && !strncmp(UNIX_SOCKET_PREFIX"/", + conn->socks_proxy.host.name, sizeof(UNIX_SOCKET_PREFIX))) + unix_path = conn->socks_proxy.host.name + sizeof(UNIX_SOCKET_PREFIX) - 1; +#endif + + if(unix_path) { /* Unix domain sockets are local. The host gets ignored, just use the * specified domain socket address. Do not cache "DNS entries". There is * no DNS involved and we already have the filesystem path available */ - const char *path = conn->unix_domain_socket; hostaddr = calloc(1, sizeof(struct Curl_dns_entry)); if(!hostaddr) result = CURLE_OUT_OF_MEMORY; else { bool longpath = FALSE; - hostaddr->addr = Curl_unix2addr(path, &longpath, + hostaddr->addr = Curl_unix2addr(unix_path, &longpath, conn->bits.abstract_unix_socket); if(hostaddr->addr) hostaddr->inuse++; else { /* Long paths are not supported for now */ if(longpath) { - failf(data, "Unix socket path too long: '%s'", path); + failf(data, "Unix socket path too long: '%s'", unix_path); result = CURLE_COULDNT_RESOLVE_HOST; } else diff --git a/tests/FILEFORMAT.md b/tests/FILEFORMAT.md index f8ef6b32cb..b7b107db9e 100644 --- a/tests/FILEFORMAT.md +++ b/tests/FILEFORMAT.md @@ -128,6 +128,7 @@ Available substitute variables include: - `%HTTPTLS6PORT` - IPv6 port number of the HTTP TLS server - `%HTTPTLSPORT` - Port number of the HTTP TLS server - `%HTTPUNIXPATH` - Path to the Unix socket of the HTTP server +- `%SOCKSUNIXPATH` - Absolute Path to the Unix socket of the SOCKS server - `%IMAP6PORT` - IPv6 port number of the IMAP server - `%IMAPPORT` - Port number of the IMAP server - `%MQTTPORT` - Port number of the MQTT server diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc index bd444e5f76..cc486a5c9e 100644 --- a/tests/data/Makefile.inc +++ b/tests/data/Makefile.inc @@ -185,7 +185,7 @@ test1432 test1433 test1434 test1435 test1436 test1437 test1438 test1439 \ test1440 test1441 test1442 test1443 test1444 test1445 test1446 test1447 \ test1448 test1449 test1450 test1451 test1452 test1453 test1454 test1455 \ test1456 test1457 test1458 test1459 test1460 test1461 test1462 test1463 \ -test1464 test1465 test1466 \ +test1464 test1465 test1466 test1467 test1468 \ \ test1500 test1501 test1502 test1503 test1504 test1505 test1506 test1507 \ test1508 test1509 test1510 test1511 test1512 test1513 test1514 test1515 \ diff --git a/tests/data/test1467 b/tests/data/test1467 new file mode 100644 index 0000000000..1be4c2c769 --- /dev/null +++ b/tests/data/test1467 @@ -0,0 +1,59 @@ + + + +HTTP +HTTP GET +SOCKS5 + + + +# +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +ETag: "21025-dc7-39462498" +Accept-Ranges: bytes +Content-Length: 6 +Connection: close +Content-Type: text/html +Funny-head: yesyes + +-foo- + + + +# +# Client-side + + +proxy +unix-sockets + + +http +socks5unix + + +HTTP GET via SOCKS5 proxy via unix sockets + + +--socks5 localhost%SOCKSUNIXPATH http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test1468 b/tests/data/test1468 new file mode 100644 index 0000000000..1ff6ada4db --- /dev/null +++ b/tests/data/test1468 @@ -0,0 +1,63 @@ + + + +HTTP +HTTP GET +SOCKS5 +SOCKS5h + + + +# +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +ETag: "21025-dc7-39462498" +Accept-Ranges: bytes +Content-Length: 6 +Connection: close +Content-Type: text/html +Funny-head: yesyes + +-foo- + + + +# +# Client-side + + +proxy +unix-sockets + + +http +socks5unix + + +HTTP GET with host name using SOCKS5h via unix sockets + + +http://this.is.a.host.name:%HTTPPORT/%TESTNUMBER --proxy socks5h://localhost%SOCKSUNIXPATH + + + +# +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: this.is.a.host.name:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +atyp 3 => this.is.a.host.name + + + diff --git a/tests/runtests.pl b/tests/runtests.pl index 29ee6fa2ae..c4fc453d89 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -162,6 +162,7 @@ my $SMBPORT=$noport; # SMB server port my $SMBSPORT=$noport; # SMBS server port my $TELNETPORT=$noport; # TELNET server port with negotiation my $HTTPUNIXPATH; # HTTP server Unix domain socket path +my $SOCKSUNIXPATH; # socks server Unix domain socket path my $use_external_proxy = 0; my $proxy_address; @@ -1435,6 +1436,7 @@ my %protofunc = ('http' => \&verifyhttp, 'tftp' => \&verifyftp, 'ssh' => \&verifyssh, 'socks' => \&verifysocks, + 'socks5unix' => \&verifysocks, 'gopher' => \&verifyhttp, 'httptls' => \&verifyhttptls, 'dict' => \&verifyftp, @@ -2379,7 +2381,7 @@ sub runmqttserver { # Start the socks server # sub runsocksserver { - my ($id, $verbose, $ipv6) = @_; + my ($id, $verbose, $ipv6, $is_unix) = @_; my $ip=$HOSTIP; my $proto = 'socks'; my $ipvnum = 4; @@ -2411,12 +2413,21 @@ sub runsocksserver { $logfile = server_logfilename($LOGDIR, $proto, $ipvnum, $idnum); # start our socks server, get commands from the FTP cmd file - my $cmd="server/socksd".exe_ext('SRV'). - " --port 0 ". - " --pidfile $pidfile". - " --portfile $portfile". - " --backend $HOSTIP". - " --config $FTPDCMD"; + my $cmd=""; + if($is_unix) { + $cmd="server/socksd".exe_ext('SRV'). + " --pidfile $pidfile". + " --unix-socket $SOCKSUNIXPATH". + " --backend $HOSTIP". + " --config $FTPDCMD"; + } else { + $cmd="server/socksd".exe_ext('SRV'). + " --port 0 ". + " --pidfile $pidfile". + " --portfile $portfile". + " --backend $HOSTIP". + " --config $FTPDCMD"; + } my ($sockspid, $pid2) = startnew($cmd, $pidfile, 30, 0); if($sockspid <= 0 || !pidexists($sockspid)) { @@ -3337,6 +3348,7 @@ sub checksystem { logmsg "* Unix socket paths:\n"; if($http_unix) { logmsg sprintf("* HTTP-Unix:%s\n", $HTTPUNIXPATH); + logmsg sprintf("* Socks-Unix:%s\n", $SOCKSUNIXPATH); } } } @@ -3397,6 +3409,7 @@ sub subVariables { # server Unix domain socket paths $$thing =~ s/${prefix}HTTPUNIXPATH/$HTTPUNIXPATH/g; + $$thing =~ s/${prefix}SOCKSUNIXPATH/$SOCKSUNIXPATH/g; # client IP addresses $$thing =~ s/${prefix}CLIENT6IP/$CLIENT6IP/g; @@ -5273,6 +5286,16 @@ sub startservers { $run{'socks'}="$pid $pid2"; } } + elsif($what eq "socks5unix") { + if(!$run{'socks5unix'}) { + ($pid, $pid2) = runsocksserver("2", $verbose, "", "unix"); + if($pid <= 0) { + return "failed starting socks5unix server"; + } + printf ("* pid socks5unix => %d %d\n", $pid, $pid2) if($verbose); + $run{'socks5unix'}="$pid $pid2"; + } + } elsif($what eq "mqtt" ) { if(!$run{'mqtt'}) { ($pid, $pid2) = runmqttserver("", $verbose); @@ -5867,6 +5890,7 @@ if ($gdbthis) { } $HTTPUNIXPATH = "http$$.sock"; # HTTP server Unix domain socket path +$SOCKSUNIXPATH = $pwd."/socks$$.sock"; # HTTP server Unix domain socket path, absolute path ####################################################################### # clear and create logging directory: