Skip to content

Commit

Permalink
http: do not ignore proxy path
Browse files Browse the repository at this point in the history
The documentation for `http.proxy` describes that option, and the
environment variables it overrides, as supporting "the syntax understood
by curl". curl allows SOCKS proxies to use a path to a Unix domain
socket, like `socks5h://localhost/path/to/socket.sock`. Git should
therefore include, if present, the path part of the proxy URL in what it
passes to libcurl.

Signed-off-by: Ryan Hendrickson <ryan.hendrickson@alum.mit.edu>
  • Loading branch information
rhendric committed Jul 27, 2024
1 parent ad57f14 commit 9ebc260
Show file tree
Hide file tree
Showing 5 changed files with 345 additions and 1 deletion.
20 changes: 19 additions & 1 deletion http.c
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,7 @@ static CURL *get_curl_handle(void)
*/
curl_easy_setopt(result, CURLOPT_PROXY, "");
} else if (curl_http_proxy) {
struct strbuf proxy = STRBUF_INIT;
if (starts_with(curl_http_proxy, "socks5h"))
curl_easy_setopt(result,
CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
Expand Down Expand Up @@ -1265,7 +1266,24 @@ static CURL *get_curl_handle(void)
if (!proxy_auth.host)
die("Invalid proxy URL '%s'", curl_http_proxy);

curl_easy_setopt(result, CURLOPT_PROXY, proxy_auth.host);
strbuf_addstr(&proxy, proxy_auth.host);
if (proxy_auth.path) {
int path_is_supported = 0;
/* curl_version_info was added in curl 7.10 */
#if LIBCURL_VERSION_NUM >= 0x070a00
curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW);
path_is_supported = ver->version_num >= 0x075400;
#endif
if (path_is_supported) {
strbuf_addch(&proxy, '/');
strbuf_add_percentencode(&proxy, proxy_auth.path, 0);
} else {
die("libcurl 7.84 or later is required to support paths in proxy URLs");
}
}
curl_easy_setopt(result, CURLOPT_PROXY, proxy.buf);
strbuf_release(&proxy);

var_override(&curl_no_proxy, getenv("NO_PROXY"));
var_override(&curl_no_proxy, getenv("no_proxy"));
curl_easy_setopt(result, CURLOPT_NOPROXY, curl_no_proxy);
Expand Down
21 changes: 21 additions & 0 deletions t/socks5-proxy-server/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2014 kaimi.io

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
27 changes: 27 additions & 0 deletions t/socks5-proxy-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
socks5-proxy-server
===================

This Perl code implements a SOCKS5 proxy server that listens for incoming connections and processes them in separate threads. The server takes three input parameters: `path`, `login`, and `password`.

When a client attempts to connect to the server, the server checks if the client supports any of the available authentication methods (no authentication or login/password authentication). If a suitable method is found, the server establishes a connection with the target server and begins forwarding data between the client and the target server.

The code uses the `IO::Select` module for working with sockets and the `threads` module for creating and managing threads. It includes several functions, including:

- `main`: the main function that creates threads for processing incoming connections.
- `replenish`: a function that creates additional threads if the number of free threads is less than the specified value.
- `new_client`: a function that handles incoming client connections and checks if the available authentication methods are supported by the client.
- `socks_auth`: a function that performs login and password authentication.
- `handle_client`: a function that establishes a connection with the target server and forwards data between the client and the target server.

This code includes the use of the following Perl modules: `IO::Select`, `Socket`, `threads`, `threads::shared`.

To run this code, enter the following command:
`perl socks5.pl path login password`

Note that this code is designed for educational purposes and should not be used in production environments without proper modifications and security measures. 😊

This code is distributed under the [MIT License](LICENSE), which allows for free use, modification, and distribution of the code as long as the original copyright notice and license are included.

---

Originally from: https://github.com/kaimi-io/socks5-proxy-server
260 changes: 260 additions & 0 deletions t/socks5-proxy-server/src/socks5.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
#!/usr/bin/perl
#В коде использованы части rdss (.Slip) и sss (hm2k)
use strict;
use IO::Select;
use Socket;
use threads;
use threads::shared;

if(scalar @ARGV < 1)
{
die "Usage: serv.pl path login pass\n";
}

my ($path, $login, $passw) = @ARGV;

my %config = (thr_init => 10, thr_min => 2, thr_max => 20, conn_limit => SOMAXCONN);

$| = 1;

my %status : shared;
my $accept : shared;

my $sock_addr = sockaddr_un $path or die $!;
socket my $socket, PF_UNIX, SOCK_STREAM, 0;
bind $socket, $sock_addr or die $!;
listen $socket, $config{'conn_limit'} or die $!;


my $sel = IO::Select->new($socket);

print "Starting...\n";

replenish($config{'thr_init'});

while(1)
{
lock %status;
cond_wait %status;

my @free = sort {$a <=> $b} grep {$status{$_} eq 'free'} keys %status;

if(scalar @free < $config{'thr_min'})
{
replenish($#free - $config{'thr_min'});
}
elsif(scalar @free > $config{'thr_max'})
{
my @kill = @free[0..$#free - $config{'thr_max'}];
status($_ => 'kill') for @kill;
}
}

status($_ => 'kill') for keys %status;

sub main
{
my $sock = shift;
my $loop = 50;

my $tid = threads->tid;
my $conn;

threads->self->detach;
status($tid, 'free');

while(status($tid) ne 'kill' && $loop > 0)
{
next unless $sel->can_read(.1);
{
lock $accept;
next unless accept $conn, $sock;
}
$loop--;
status($tid => 'work');
new_client($conn);
close $conn;
status($tid => 'free');
}

status($tid, 'kill');
}

sub status
{
my ($tid, $state) = @_;
lock %status;

return $status{$tid} unless $state;
if($state)
{
$status{$tid} = $state unless defined $status{$tid} and $status{$tid} eq 'kill';
}
else
{
delete $status{$tid};
}

cond_broadcast %status;
}

sub replenish
{
threads->create(\&main, $socket) for 1..$_[0];
}

sub new_client
{
my $sock = shift;

sysread $sock, my $buf, 2;

my ($ver, $auth_num) = unpack('H2H2', $buf);
#Версия протокола
return unless $ver eq '05';
sysread $sock, $buf, $auth_num;

my $ok = 0;
#Перечисление методов авторизации
for(my ($mode, $i) = (0, 0); $i < $auth_num; $mode = ord substr $buf, ++$i, 1)
{
#0 - Без авторизации; 2 - Username/Password
if($mode == 0 && !$login)
{
syswrite $sock, "\x05\x00";
$ok++;
last;
}
elsif($mode == 2 && $login)
{
return unless socks_auth($sock);
$ok++;
last;
}
}
#Подходящие методы есть
if($ok)
{
sysread $sock, $buf, 3;
my ($ver, $cmd, $r) = unpack 'H2H2H2', $buf;

if($ver eq '05' && $r eq '00')
{
my ($client_host, $client_host_raw, $client_port, $client_port_raw) = get_conn_info($sock);
return unless ($client_host || $client_port);

syswrite $sock, "\x05\x00\x00".$client_host_raw.$client_port_raw;
handle_client($sock, $client_host, $client_port, $cmd);
}
}
else
{
syswrite $sock, "\x05\xFF";
}
}

sub socks_auth
{
my $sock = shift;

syswrite $sock, "\x05\x02";
sysread $sock, my $buf, 1;

if(ord $buf == 1)
{
#username length : username : password length : password
sysread $sock, $buf, 1;
sysread $sock, my $s_login, ord($buf);
sysread $sock, $buf, 1;
sysread $sock, my $s_passw, ord($buf);

#0x00 = success; any other value = failure
if($login eq $s_login && $passw eq $s_passw)
{
syswrite $sock, "\x05\x00";
return 1;
}
else
{
syswrite $sock, "\x05\x01";
}
}

return 0;
}

sub handle_client
{
my ($sock, $host, $port, $cmd) = @_;

#0x01 = establish a TCP/IP stream connection
if($cmd == 1)
{
my $sock_addr = sockaddr_in $port, inet_aton($host) or return;
socket my $target, PF_INET, SOCK_STREAM, getprotobyname('tcp') or return;
connect $target, $sock_addr or return;

while($sock || $target)
{
my ($rin, $cbuf, $tbuf, $rout, $eout) = ('', '', '', '', '');
vec($rin, fileno($sock), 1) = 1 if $sock;
vec($rin, fileno($target), 1) = 1 if $target;
select($rout = $rin, undef, $eout = $rin, 120);
return if(!$rout && !$eout);

if($sock && (vec($eout, fileno($sock), 1) || vec($rout, fileno($sock), 1)))
{
my $res = sysread $sock, $tbuf, 1024;
return if(!defined $res || !$res);
}
if($target && (vec($eout, fileno($target), 1) || vec($rout, fileno($target), 1)))
{
my $res = sysread $target, $cbuf, 1024;
return if(!defined $res || !$res);
}
while(my $len = length($tbuf))
{
my $r = syswrite $target, $tbuf, $len;
return if(!defined $r || $r <= 0);
$tbuf = substr($tbuf, $r);
}
while(my $len = length($cbuf))
{
my $r = syswrite $sock, $cbuf, $len;
return if(!defined $r || $r <= 0);
$cbuf = substr($cbuf, $r);
}
}
}
}

sub get_conn_info
{
my $sock = shift;

my ($host, $raw_host) = ('', '');
sysread $sock, my $buf, 1;
($raw_host, $buf) = ($buf, ord $buf);
#0x01 = IPv4 address; 0x03 = Domain name
if($buf == 1)
{
#4 bytes for IPv4 address
sysread $sock, $buf, 4;
$raw_host .= $buf;
$host = join '.', map(ord, split //, $buf);
}
elsif($buf == 3)
{
#1 byte of name length followed by the name for Domain name
sysread $sock, $buf, 1;
sysread $sock, $host, ord($buf);
$raw_host .= $host;
}

#port number in a network byte order, 2 bytes
my ($port, $raw_port) = ('', '');
sysread $sock, $raw_port, 2;
$port = ord(substr($raw_port, 0, 1)) << 8 | ord(substr($raw_port, 1, 1));

return $host, $raw_host, $port, $raw_port;
}
18 changes: 18 additions & 0 deletions t/t5564-http-proxy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,22 @@ test_expect_success 'clone can prompt for proxy password' '
expect_askpass pass proxuser
'

start_socks() {
# The %30 tests that the correct amount of percent-encoding is applied
# to the proxy string passed to curl.
"$PERL_PATH" "$TEST_DIRECTORY/socks5-proxy-server/src/socks5.pl" \
"$TRASH_DIRECTORY/%30.sock" proxuser proxpass &
socks_pid=$!
}

test_lazy_prereq LIBCURL_7_84 'expr x$(curl-config --vernum) \>= x075400'

test_expect_success PERL,LIBCURL_7_84 'clone via Unix socket' '
start_socks &&
test_when_finished "rm -rf clone && kill $socks_pid" &&
test_config_global http.proxy "socks5://proxuser:proxpass@localhost$PWD/%2530.sock" &&
GIT_TRACE_CURL=$PWD/trace git clone "$HTTPD_URL/smart/repo.git" clone &&
grep -i "SOCKS5 request granted." trace
'

test_done

0 comments on commit 9ebc260

Please sign in to comment.