]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
socks: support unix sockets for socks proxy
authorBalakrishnan Balasubramanian <3070606-balki@users.noreply.gitlab.com>
Thu, 19 May 2022 13:33:22 +0000 (15:33 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Thu, 19 May 2022 13:35:03 +0000 (15:35 +0200)
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

12 files changed:
docs/cmdline-opts/proxy.d
docs/cmdline-opts/socks4.d
docs/cmdline-opts/socks4a.d
docs/cmdline-opts/socks5-hostname.d
docs/cmdline-opts/socks5.d
docs/libcurl/opts/CURLOPT_PROXY.3
lib/url.c
tests/FILEFORMAT.md
tests/data/Makefile.inc
tests/data/test1467 [new file with mode: 0644]
tests/data/test1468 [new file with mode: 0644]
tests/runtests.pl

index 60674b5f89d20e08ec9f36d7f6acb6bf24a742ca..49068b93e569c4f550088bc96fb8f51277f32094 100644 (file)
@@ -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.
 
index a1fb1b34453afa9dc8b525693119955c3e1d20be..c460d494bd9fb2eee502fd19f9b6af3fc1301915 100644 (file)
@@ -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.
 
index e39b968aab964ccf5b89096f801778b2753e8db7..ba56594b0b19ef6a8373e4ab4e0b535f3c9b1c64 100644 (file)
@@ -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.
 
index 6530429a9ee9c69c4fdbfbfa71f291c3646f6859..628a2db24180fe0868307ff6b5a281c8e599773c 100644 (file)
@@ -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.
 
index af1c05771230f1b9991e374500e70b668e04dd62..bf43652b3e56b4ddca9f28ba83d88b2faeb7f8b3 100644 (file)
@@ -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.
 
index 704a65d6a3e68cb7a47e00613cf8ffca09cb82ac..5cf67b13c5fc1951931e6171ef1d49722367521d 100644 (file)
@@ -5,7 +5,7 @@
 .\" *                            | (__| |_| |  _ <| |___
 .\" *                             \___|\___/|_| \_\_____|
 .\" *
-.\" * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, 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"
index faf4b4278db536ffc27645e4db20e5150fafe130..6907664b718828c756846c415ac762ddd93efc66 100644 (file)
--- 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
index f8ef6b32cbafc8579e3c26e791befde6aa212ce7..b7b107db9eb7ccbc84b23daae737c566be95c2d0 100644 (file)
@@ -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
index bd444e5f7600b4968d59c3f12cb987ea6c580694..cc486a5c9e2e0f4885402f0a7c7b19c599356eee 100644 (file)
@@ -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 (file)
index 0000000..1be4c2c
--- /dev/null
@@ -0,0 +1,59 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+SOCKS5
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data>
+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-
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+proxy
+unix-sockets
+</features>
+<server>
+http
+socks5unix
+</server>
+ <name>
+HTTP GET via SOCKS5 proxy via unix sockets
+ </name>
+ <command>
+--socks5 localhost%SOCKSUNIXPATH http://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+GET /%TESTNUMBER HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+\r
+</protocol>
+</verify>
+</testcase>
diff --git a/tests/data/test1468 b/tests/data/test1468
new file mode 100644 (file)
index 0000000..1ff6ada
--- /dev/null
@@ -0,0 +1,63 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+SOCKS5
+SOCKS5h
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data>
+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-
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+proxy
+unix-sockets
+</features>
+<server>
+http
+socks5unix
+</server>
+ <name>
+HTTP GET with host name using SOCKS5h via unix sockets
+ </name>
+ <command>
+http://this.is.a.host.name:%HTTPPORT/%TESTNUMBER --proxy socks5h://localhost%SOCKSUNIXPATH
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+GET /%TESTNUMBER HTTP/1.1\r
+Host: this.is.a.host.name:%HTTPPORT\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+\r
+</protocol>
+<socks>
+atyp 3 => this.is.a.host.name
+</socks>
+</verify>
+</testcase>
index 29ee6fa2ae301e105888fdb54d703fb6cc208581..c4fc453d89a2d2d9566b7fb8d14745b9ef5205c4 100755 (executable)
@@ -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: