mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 04:31:29 +00:00
3273 lines
99 KiB
Perl
Executable File
3273 lines
99 KiB
Perl
Executable File
#!/usr/bin/perl -w
|
|
|
|
# This file contains tests of the external behavior of Ncat.
|
|
|
|
require HTTP::Response;
|
|
require HTTP::Request;
|
|
|
|
use MIME::Base64;
|
|
use File::Temp qw/ tempfile /;
|
|
use URI::Escape;
|
|
use Data::Dumper;
|
|
use Socket;
|
|
use Socket6;
|
|
use Digest::MD5 qw/md5_hex/;
|
|
use POSIX ":sys_wait_h";
|
|
use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
|
|
|
|
use IPC::Open3;
|
|
use strict;
|
|
|
|
$| = 1;
|
|
|
|
my $HOST = "127.0.0.1";
|
|
my $IPV6_ADDR = "::1";
|
|
my $PORT = 40000;
|
|
my $PROXY_PORT = 40001;
|
|
my $UNIXSOCK = "ncat.unixsock";
|
|
my $UNIXSOCK_TMP = "ncat.unixsock_tmp";
|
|
|
|
my $WIN32 = $^O eq "MSWin32" || $^O eq "cygwin";
|
|
|
|
my $NCAT;
|
|
if ($WIN32) {
|
|
$NCAT = "../Debug/ncat.exe";
|
|
} else {
|
|
$NCAT = "../ncat";
|
|
}
|
|
|
|
my $CAT;
|
|
my $ECHO;
|
|
my $PERL;
|
|
my $BINSH;
|
|
if ($^O eq "cygwin") {
|
|
my $CYGPATH="C:/cygwin";
|
|
$CAT = "$CYGPATH/bin/cat";
|
|
$ECHO = "$CYGPATH/bin/echo";
|
|
$PERL = "$CYGPATH/bin/perl";
|
|
$BINSH = "$CYGPATH/bin/sh";
|
|
} else {
|
|
$CAT = "/bin/cat";
|
|
$ECHO = "/bin/echo";
|
|
$PERL = "/usr/bin/perl";
|
|
$BINSH = "/bin/sh";
|
|
}
|
|
|
|
my $HAVE_SCTP = !$WIN32;
|
|
my $HAVE_UNIXSOCK = !$WIN32;
|
|
|
|
my $BUFSIZ = 1024;
|
|
|
|
my $num_tests = 0;
|
|
my $num_failures = 0;
|
|
my $num_expected_failures = 0;
|
|
my $num_unexpected_passes = 0;
|
|
|
|
# If true during a test, failure is expected (XFAIL).
|
|
our $xfail = 0;
|
|
|
|
# Run $NCAT with the given arguments.
|
|
sub ncat {
|
|
my $pid;
|
|
local *IN;
|
|
local *OUT;
|
|
local *ERR;
|
|
# print join(" ", ($NCAT, @_)) . "\n";
|
|
$pid = open3(*IN, *OUT, *ERR, $NCAT, @_);
|
|
if (!defined $pid) {
|
|
die "open3 failed";
|
|
}
|
|
binmode *IN;
|
|
binmode *OUT;
|
|
binmode *ERR;
|
|
return ($pid, *OUT, *IN, *ERR);
|
|
}
|
|
|
|
sub wait_listen {
|
|
my $fh = shift;
|
|
my $timeout = shift || 0.3;
|
|
my $rd = "";
|
|
vec($rd, fileno($fh), 1) = 1;
|
|
my $partial = "";
|
|
for (;;) {
|
|
my ($n, $frag);
|
|
($n, $timeout) = select($rd, undef, undef, $timeout);
|
|
last if $n == 0;
|
|
$n = sysread($fh, $frag, $BUFSIZ);
|
|
last if (not defined($n)) || $n == 0;
|
|
$partial = $partial . $frag;
|
|
while ($partial =~ /^(.*?)\n(.*)$/s) {
|
|
my $line = $1;
|
|
$partial = $2;
|
|
if ($line =~ /^NCAT TEST: LISTEN/) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sub ncat_server {
|
|
my @ret = ncat($PORT, "--test", "-l", @_);
|
|
wait_listen($ret[3]);
|
|
return @ret;
|
|
}
|
|
|
|
sub ncat_server_noport {
|
|
my @ret = ncat("--test", "-l", @_);
|
|
wait_listen($ret[3]);
|
|
return @ret;
|
|
}
|
|
|
|
sub host_for_args {
|
|
if (grep(/^-[^-]*6/, @_)) {
|
|
return "::1";
|
|
} else {
|
|
return "127.0.0.1";
|
|
}
|
|
}
|
|
|
|
sub ncat_client {
|
|
my $host;
|
|
my @ret = ncat(host_for_args(@_), $PORT, @_);
|
|
# Give it a moment to connect.
|
|
select(undef, undef, undef, 0.1);
|
|
return @ret;
|
|
}
|
|
|
|
# Kill all child processes.
|
|
sub kill_children {
|
|
local $SIG{TERM} = "IGNORE";
|
|
kill "TERM", -$$;
|
|
while (waitpid(-1, 0) > 0) {
|
|
}
|
|
}
|
|
|
|
# Read until a timeout occurs. Return undef on EOF or "" on timeout.
|
|
sub timeout_read {
|
|
my $fh = shift;
|
|
my $timeout = 0.50;
|
|
if (scalar(@_) > 0) {
|
|
$timeout = shift;
|
|
}
|
|
my $result = "";
|
|
my $rd = "";
|
|
my $frag;
|
|
vec($rd, fileno($fh), 1) = 1;
|
|
# Here we rely on $timeout being decremented after select returns,
|
|
# which may not be supported on all systems.
|
|
while (select($rd, undef, undef, $timeout) != 0) {
|
|
return ($result or undef) if sysread($fh, $frag, $BUFSIZ) == 0;
|
|
$result .= $frag;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
$Data::Dumper::Terse = 1;
|
|
$Data::Dumper::Useqq = 1;
|
|
$Data::Dumper::Indent = 0;
|
|
sub d {
|
|
return Dumper(@_);
|
|
}
|
|
|
|
# Run the code reference received as an argument. Count it as a pass if the
|
|
# evaluation is successful, a failure otherwise.
|
|
sub test {
|
|
my $desc = shift;
|
|
my $code = shift;
|
|
$num_tests++;
|
|
if (eval { &$code() }) {
|
|
if ($xfail) {
|
|
print "UNEXPECTED PASS $desc\n";
|
|
$num_unexpected_passes++;
|
|
} else {
|
|
print "PASS $desc\n";
|
|
}
|
|
} else {
|
|
if ($xfail) {
|
|
print "XFAIL $desc\n";
|
|
$num_expected_failures++;
|
|
} else {
|
|
$num_failures++;
|
|
print "FAIL $desc\n";
|
|
print " $@";
|
|
}
|
|
}
|
|
}
|
|
|
|
my ($s_pid, $s_out, $s_in, $c_pid, $c_out, $c_in, $p_pid, $p_out, $p_in);
|
|
|
|
# Handle a common test situation. Start up a server and client with the given
|
|
# arguments and call test on a code block. Within the code block the server's
|
|
# PID, output filehandle, and input filehandle are accessible through
|
|
# $s_pid, $s_out, and $s_in
|
|
# and likewise for the client:
|
|
# $c_pid, $c_out, and $c_in.
|
|
sub server_client_test {
|
|
my $desc = shift;
|
|
my $server_args = shift;
|
|
my $client_args = shift;
|
|
my $code = shift;
|
|
($s_pid, $s_out, $s_in) = ncat_server(@$server_args);
|
|
($c_pid, $c_out, $c_in) = ncat_client(@$client_args);
|
|
test($desc, $code);
|
|
kill_children;
|
|
}
|
|
|
|
sub server_client_test_multi {
|
|
my $specs = shift;
|
|
my $desc = shift;
|
|
my $server_args_ref = shift;
|
|
my $client_args_ref = shift;
|
|
my $code = shift;
|
|
my $outer_xfail = $xfail;
|
|
|
|
for my $spec (@$specs) {
|
|
my @server_args = @$server_args_ref;
|
|
my @client_args = @$client_args_ref;
|
|
|
|
local $xfail = $outer_xfail;
|
|
for my $proto (split(/ /, $spec)) {
|
|
if ($proto eq "tcp") {
|
|
# Nothing needed.
|
|
} elsif ($proto eq "udp") {
|
|
push @server_args, ("--udp");
|
|
push @client_args, ("--udp");
|
|
} elsif ($proto eq "sctp") {
|
|
push @server_args, ("--sctp");
|
|
push @client_args, ("--sctp");
|
|
$xfail = 1 if !$HAVE_SCTP;
|
|
} elsif ($proto eq "ssl") {
|
|
push @server_args, ("--ssl", "--ssl-key", "test-cert.pem", "--ssl-cert", "test-cert.pem");
|
|
push @client_args, ("--ssl");
|
|
} elsif ($proto eq "xfail") {
|
|
$xfail = 1;
|
|
} else {
|
|
die "Unknown protocol $proto";
|
|
}
|
|
}
|
|
server_client_test("$desc ($spec)", [@server_args], [@client_args], $code);
|
|
}
|
|
}
|
|
|
|
# Like server_client_test, but run the test once each for each mix of TCP, UDP,
|
|
# SCTP, and SSL.
|
|
sub server_client_test_all {
|
|
server_client_test_multi(["tcp", "udp", "sctp", "tcp ssl", "sctp ssl"], @_);
|
|
}
|
|
|
|
sub server_client_test_tcp_sctp_ssl {
|
|
server_client_test_multi(["tcp", "sctp", "tcp ssl", "sctp ssl"], @_);
|
|
}
|
|
|
|
sub server_client_test_tcp_ssl {
|
|
server_client_test_multi(["tcp", "tcp ssl"], @_);
|
|
}
|
|
|
|
sub server_client_test_sctp_ssl {
|
|
server_client_test_multi(["sctp", "sctp ssl"], @_);
|
|
}
|
|
|
|
# Set up a proxy running on $PROXY_PORT. Start a server on $PORT and connect a
|
|
# client to the server through the proxy. The proxy is controlled through the
|
|
# variables
|
|
# $p_pid, $p_out, and $p_in.
|
|
sub proxy_test {
|
|
my $desc = shift;
|
|
my $proxy_args = shift;
|
|
my $server_args = shift;
|
|
my $client_args = shift;
|
|
my $code = shift;
|
|
($p_pid, $p_out, $p_in) = ncat(host_for_args(@$proxy_args), ($PROXY_PORT, "-l", "--proxy-type", "http"), @$proxy_args);
|
|
($s_pid, $s_out, $s_in) = ncat(host_for_args(@$server_args), ($PORT, "-l"), @$server_args);
|
|
($c_pid, $c_out, $c_in) = ncat(host_for_args(@$client_args), ($PORT, "--proxy", "$HOST:$PROXY_PORT"), @$client_args);
|
|
test($desc, $code);
|
|
kill_children;
|
|
}
|
|
|
|
# Like proxy_test, but connect the client directly to the proxy so you can
|
|
# control the proxy interaction.
|
|
sub proxy_test_raw {
|
|
my $desc = shift;
|
|
my $proxy_args = shift;
|
|
my $server_args = shift;
|
|
my $client_args = shift;
|
|
my $code = shift;
|
|
($p_pid, $p_out, $p_in) = ncat(host_for_args(@$proxy_args), ($PROXY_PORT, "-l", "--proxy-type", "http"), @$proxy_args);
|
|
($s_pid, $s_out, $s_in) = ncat(host_for_args(@$server_args), ($PORT, "-l"), @$server_args);
|
|
($c_pid, $c_out, $c_in) = ncat(host_for_args(@$client_args), ($PROXY_PORT), @$client_args);
|
|
test($desc, $code);
|
|
kill_children;
|
|
}
|
|
|
|
sub proxy_test_multi {
|
|
my $specs = shift;
|
|
my $desc = shift;
|
|
my $proxy_args_ref = shift;
|
|
my $server_args_ref = shift;
|
|
my $client_args_ref = shift;
|
|
my $code = shift;
|
|
my $outer_xfail = $xfail;
|
|
local $xfail;
|
|
|
|
for my $spec (@$specs) {
|
|
my @proxy_args = @$proxy_args_ref;
|
|
my @server_args = @$server_args_ref;
|
|
my @client_args = @$client_args_ref;
|
|
|
|
$xfail = $outer_xfail;
|
|
for my $proto (split(/ /, $spec)) {
|
|
if ($proto eq "tcp") {
|
|
# Nothing needed.
|
|
} elsif ($proto eq "udp") {
|
|
push @server_args, ("--udp");
|
|
push @client_args, ("--udp");
|
|
} elsif ($proto eq "sctp") {
|
|
push @server_args, ("--sctp");
|
|
push @client_args, ("--sctp");
|
|
$xfail = 1 if !$HAVE_SCTP;
|
|
} elsif ($proto eq "ssl") {
|
|
push @server_args, ("--ssl", "--ssl-key", "test-cert.pem", "--ssl-cert", "test-cert.pem");
|
|
push @client_args, ("--ssl");
|
|
} elsif ($proto eq "xfail") {
|
|
$xfail = 1;
|
|
} else {
|
|
die "Unknown protocol $proto";
|
|
}
|
|
}
|
|
proxy_test("$desc ($spec)", [@proxy_args], [@server_args], [@client_args], $code);
|
|
}
|
|
}
|
|
|
|
sub max_conns_test {
|
|
my $desc = shift;
|
|
my $server_args = shift;
|
|
my $client_args = shift;
|
|
my $count = shift;
|
|
my @client_pids;
|
|
my @client_outs;
|
|
my @client_ins;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server(@$server_args, ("--max-conns", $count));
|
|
test $desc, sub {
|
|
my ($i, $resp);
|
|
|
|
# Fill the connection limit exactly.
|
|
for ($i = 0; $i < $count; $i++) {
|
|
my @tmp;
|
|
($c_pid, $c_out, $c_in) = ncat_client(@$client_args);
|
|
push @client_pids, $c_pid;
|
|
push @client_outs, $c_out;
|
|
push @client_ins, $c_in;
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out, 2.0);
|
|
if (!$resp) {
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out);
|
|
}
|
|
$resp = "" if not defined($resp);
|
|
$resp eq "abc\n" or die "--max-conns $count server did not accept client #" . ($i + 1);
|
|
}
|
|
# Try a few more times. Should be rejected.
|
|
for (; $i < $count + 2; $i++) {
|
|
($c_pid, $c_out, $c_in) = ncat_client(@$client_args);
|
|
push @client_pids, $c_pid;
|
|
push @client_outs, $c_out;
|
|
push @client_ins, $c_in;
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out, 2.0);
|
|
if (!$resp) {
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out);
|
|
}
|
|
!$resp or die "--max-conns $count server accepted client #" . ($i + 1);
|
|
}
|
|
# Kill one of the connected clients, which should open up a
|
|
# space.
|
|
{
|
|
kill "TERM", $client_pids[0];
|
|
while (waitpid($client_pids[0], 0) > 0) {
|
|
}
|
|
shift @client_pids;
|
|
shift @client_outs;
|
|
sleep 2;
|
|
}
|
|
if ($count > 0) {
|
|
($c_pid, $c_out, $c_in) = ncat_client(@$client_args);
|
|
push @client_pids, $c_pid;
|
|
push @client_outs, $c_out;
|
|
push @client_ins, $c_in;
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out, 2.0);
|
|
if (!$resp) {
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out);
|
|
}
|
|
$resp = "" if not defined($resp);
|
|
$resp eq "abc\n" or die "--max-conns $count server did not accept client #$count after freeing one space";
|
|
}
|
|
return 1;
|
|
};
|
|
kill_children;
|
|
}
|
|
|
|
sub max_conns_test_multi {
|
|
my $specs = shift;
|
|
my $desc = shift;
|
|
my $server_args_ref = shift;
|
|
my $client_args_ref = shift;
|
|
my $count = shift;
|
|
my $outer_xfail = $xfail;
|
|
local $xfail;
|
|
|
|
for my $spec (@$specs) {
|
|
my @server_args = @$server_args_ref;
|
|
my @client_args = @$client_args_ref;
|
|
|
|
$xfail = $outer_xfail;
|
|
for my $proto (split(/ /, $spec)) {
|
|
if ($proto eq "tcp") {
|
|
# Nothing needed.
|
|
} elsif ($proto eq "udp") {
|
|
push @server_args, ("--udp");
|
|
push @client_args, ("--udp");
|
|
} elsif ($proto eq "sctp") {
|
|
push @server_args, ("--sctp");
|
|
push @client_args, ("--sctp");
|
|
$xfail = 1 if !$HAVE_SCTP;
|
|
} elsif ($proto eq "ssl") {
|
|
push @server_args, ("--ssl", "--ssl-key", "test-cert.pem", "--ssl-cert", "test-cert.pem");
|
|
push @client_args, ("--ssl");
|
|
} elsif ($proto eq "xfail") {
|
|
$xfail = 1;
|
|
} else {
|
|
die "Unknown protocol $proto";
|
|
}
|
|
}
|
|
max_conns_test("$desc ($spec)", [@server_args], [@client_args], $count);
|
|
}
|
|
}
|
|
|
|
sub max_conns_test_all {
|
|
max_conns_test_multi(["tcp", "udp", "sctp", "tcp ssl", "sctp ssl"], @_);
|
|
}
|
|
|
|
sub max_conns_test_tcp_sctp_ssl {
|
|
max_conns_test_multi(["tcp", "sctp", "tcp ssl", "sctp ssl"], @_);
|
|
}
|
|
|
|
sub max_conns_test_tcp_ssl {
|
|
max_conns_test_multi(["tcp", "tcp ssl"], @_);
|
|
}
|
|
|
|
sub match_ncat_environment {
|
|
$_ = shift;
|
|
return /NCAT_REMOTE_ADDR=.+\n
|
|
NCAT_REMOTE_PORT=.+\n
|
|
NCAT_LOCAL_ADDR=.+\n
|
|
NCAT_LOCAL_PORT=.+\n
|
|
NCAT_PROTO=.+
|
|
/x;
|
|
}
|
|
|
|
# Ignore broken pipe signals that result when trying to read from a terminated
|
|
# client.
|
|
$SIG{PIPE} = "IGNORE";
|
|
# Don't have to wait on children.
|
|
$SIG{CHLD} = "IGNORE";
|
|
|
|
# Individual tests begin here.
|
|
|
|
# Test server with no hostname or port.
|
|
($s_pid, $s_out, $s_in) = ncat_server_noport();
|
|
test "Server default listen address and port",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("127.0.0.1");
|
|
syswrite($c_in, "abc\n");
|
|
close $c_in;
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server_noport();
|
|
test "Server default listen address and port",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid2, $c_out2, $c_in2) = ncat("-6", "::1");
|
|
syswrite($c_in2, "abc\n");
|
|
close $c_in2;
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server_noport("-4");
|
|
test "Server -4 default listen address and port",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("127.0.0.1");
|
|
syswrite($c_in, "abc\n");
|
|
close $c_in;
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server_noport("-6");
|
|
test "Server -6 default listen address and port",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("-6", $IPV6_ADDR);
|
|
syswrite($c_in, "abc\n");
|
|
close $c_in;
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
kill_children;
|
|
|
|
# Test server with no port.
|
|
($s_pid, $s_out, $s_in) = ncat_server_noport("-l", $HOST);
|
|
test "Server default port",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat($HOST);
|
|
syswrite($c_in, "abc\n");
|
|
close $c_in;
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
kill_children;
|
|
|
|
# Test server with no address.
|
|
($s_pid, $s_out, $s_in) = ncat_server();
|
|
test "Server default listen address",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat($HOST, $PORT);
|
|
syswrite($c_in, "abc\n");
|
|
close $c_in;
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
kill_children;
|
|
|
|
{
|
|
# Expected to fail because we can't detect connection failure for IPv6 and retry IPv4
|
|
local $xfail=1;
|
|
# Test server with UDP.
|
|
($s_pid, $s_out, $s_in) = ncat_server_noport("--udp", "-4");
|
|
test "Server default listen address --udp IPV4",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("localhost", "--udp");
|
|
syswrite($c_in, "abc\n");
|
|
close $c_in;
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\" from localhost";
|
|
|
|
};
|
|
kill_children;
|
|
}
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server_noport("--udp", "-6");
|
|
test "Server default listen address --udp IPV6",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid1, $c_out1, $c_in1) = ncat("localhost", "--udp");
|
|
syswrite($c_in1, "abc\n");
|
|
close $c_in1;
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\" from ::1";
|
|
};
|
|
kill_children;
|
|
|
|
{
|
|
local $xfail = 1;
|
|
($s_pid, $s_out, $s_in) = ncat_server_noport("--udp");
|
|
test "Server default listen address --udp IPV4 + IPV6",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("localhost", "--udp");
|
|
syswrite($c_in, "abc\n");
|
|
close $c_in;
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\" from localhost";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server_noport("--udp");
|
|
test "Server default listen address --udp IPV4 + IPV6",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid1, $c_out1, $c_in1) = ncat("::1", "--udp");
|
|
syswrite($c_in1, "abc\n");
|
|
close $c_in1;
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\" from ::1";
|
|
};
|
|
kill_children;
|
|
};
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server_noport("-6", "--udp");
|
|
test "Server default listen address -6 --udp",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("127.0.0.1", "--udp");
|
|
syswrite($c_in, "abc\n");
|
|
close $c_in;
|
|
$resp = timeout_read($s_out);
|
|
!$resp or die "Server got \"$resp\", not \"\" from 127.0.0.1";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server_noport("-6", "--udp");
|
|
test "Server default listen address -6 --udp",
|
|
sub {
|
|
my $resp;
|
|
my ($c_pid1, $c_out1, $c_in1) = ncat("::1", "--udp");
|
|
syswrite($c_in1, "abc\n");
|
|
close $c_in1;
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\" from ::1";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server_noport("-4", "--udp");
|
|
test "Server default listen address -4 --udp",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("127.0.0.1", "--udp");
|
|
syswrite($c_in, "abc\n");
|
|
close $c_in;
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\" from 127.0.0.1";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server_noport("-4", "--udp");
|
|
test "Server default listen address -4 --udp",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid1, $c_out1, $c_in1) = ncat("::1", "--udp");
|
|
syswrite($c_in1, "abc\n");
|
|
close $c_in1;
|
|
$resp = timeout_read($s_out);
|
|
!$resp or die "Server got \"$resp\", not \"\" from ::1";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server_noport("-4");
|
|
test "Connect fallback with IPv4 server",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("localhost");
|
|
syswrite($c_in, "abc\n");
|
|
close $c_in;
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server_noport("-6");
|
|
test "Connect fallback with IPv6 server",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("localhost");
|
|
syswrite($c_in, "abc\n");
|
|
close $c_in;
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
|
|
kill_children;
|
|
# Test UNIX domain sockets listening
|
|
{
|
|
local $xfail = 1 if !$HAVE_UNIXSOCK;
|
|
($s_pid, $s_out, $s_in) = ncat_server_noport("-U", $UNIXSOCK);
|
|
test "Server UNIX socket listen on $UNIXSOCK (STREAM)",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("-U", $UNIXSOCK);
|
|
syswrite($c_in, "abc\n");
|
|
close $c_in;
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\" from client";
|
|
};
|
|
kill_children;
|
|
unlink($UNIXSOCK);
|
|
}
|
|
|
|
{
|
|
local $xfail = 1 if !$HAVE_UNIXSOCK;
|
|
($s_pid, $s_out, $s_in) = ncat_server_noport("-U", "--udp", $UNIXSOCK);
|
|
test "Server UNIX socket listen on $UNIXSOCK --udp (DGRAM)",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("-U", "--udp", $UNIXSOCK);
|
|
syswrite($c_in, "abc\n");
|
|
close $c_in;
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\" from client";
|
|
};
|
|
kill_children;
|
|
unlink($UNIXSOCK);
|
|
}
|
|
|
|
server_client_test "Connect success exit code",
|
|
[], ["--send-only"], sub {
|
|
my ($pid, $code);
|
|
local $SIG{CHLD} = sub { };
|
|
|
|
syswrite($c_in, "abc\n");
|
|
close($c_in);
|
|
do {
|
|
$pid = waitpid($c_pid, 0);
|
|
} while ($pid > 0 && $pid != $c_pid);
|
|
$pid == $c_pid or die;
|
|
$code = $? >> 8;
|
|
$code == 0 or die "Exit code was $code, not 0";
|
|
};
|
|
kill_children;
|
|
|
|
test "Connect connection refused exit code",
|
|
sub {
|
|
my ($pid, $code);
|
|
local $SIG{CHLD} = sub { };
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat($HOST, $PORT, "--send-only");
|
|
syswrite($c_in, "abc\n");
|
|
close($c_in);
|
|
do {
|
|
$pid = waitpid($c_pid, 0);
|
|
} while ($pid > 0 && $pid != $c_pid);
|
|
$pid == $c_pid or die;
|
|
$code = $? >> 8;
|
|
$code == 1 or die "Exit code was $code, not 1";
|
|
};
|
|
kill_children;
|
|
|
|
test "Connect connection interrupted exit code",
|
|
sub {
|
|
my ($pid, $code);
|
|
local $SIG{CHLD} = sub { };
|
|
local *SOCK;
|
|
local *S;
|
|
|
|
socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die;
|
|
setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die;
|
|
bind(SOCK, sockaddr_in($PORT, INADDR_ANY)) or die;
|
|
listen(SOCK, 1) or die;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat($HOST, $PORT);
|
|
|
|
accept(S, SOCK) or die;
|
|
# Shut down the socket with a RST.
|
|
setsockopt(S, SOL_SOCKET, SO_LINGER, pack("II", 1, 0)) or die;
|
|
close(S) or die;
|
|
|
|
do {
|
|
$pid = waitpid($c_pid, 0);
|
|
} while ($pid > 0 && $pid != $c_pid);
|
|
$pid == $c_pid or die;
|
|
$code = $? >> 8;
|
|
$code == 1 or die "Exit code was $code, not 1";
|
|
};
|
|
kill_children;
|
|
|
|
server_client_test "Listen success exit code",
|
|
[], ["--send-only"], sub {
|
|
my ($resp, $pid, $code);
|
|
local $SIG{CHLD} = sub { };
|
|
|
|
syswrite($c_in, "abc\n");
|
|
close($c_in);
|
|
do {
|
|
$pid = waitpid($s_pid, 0);
|
|
} while ($pid > 0 && $pid != $s_pid);
|
|
$pid == $s_pid or die "$pid != $s_pid";
|
|
$code = $? >> 8;
|
|
$code == 0 or die "Exit code was $code, not 0";
|
|
};
|
|
kill_children;
|
|
|
|
test "Listen connection interrupted exit code",
|
|
sub {
|
|
my ($pid, $code);
|
|
local $SIG{CHLD} = sub { };
|
|
local *SOCK;
|
|
|
|
my ($s_pid, $s_out, $s_in) = ncat_server("-4");
|
|
|
|
socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die;
|
|
my $addr = gethostbyname($HOST);
|
|
connect(SOCK, sockaddr_in($PORT, $addr)) or die $!;
|
|
# Shut down the socket with a RST.
|
|
setsockopt(SOCK, SOL_SOCKET, SO_LINGER, pack("II", 1, 0)) or die;
|
|
close(SOCK) or die;
|
|
|
|
do {
|
|
$pid = waitpid($s_pid, 0);
|
|
} while ($pid > 0 && $pid != $s_pid);
|
|
$pid == $s_pid or die;
|
|
$code = $? >> 8;
|
|
$code == 1 or die "Exit code was $code, not 1";
|
|
};
|
|
kill_children;
|
|
|
|
test "Program error exit code",
|
|
sub {
|
|
my ($pid, $code);
|
|
local $SIG{CHLD} = sub { };
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat($HOST, $PORT, "--baffle");
|
|
do {
|
|
$pid = waitpid($c_pid, 0);
|
|
} while ($pid > 0 && $pid != $c_pid);
|
|
$pid == $c_pid or die;
|
|
$code = $? >> 8;
|
|
$code == 2 or die "Exit code was $code, not 2";
|
|
|
|
my ($s_pid, $s_out, $s_in) = ncat("-l", "--baffle");
|
|
do {
|
|
$pid = waitpid($s_pid, 0);
|
|
} while ($pid > 0 && $pid != $s_pid);
|
|
$pid == $s_pid or die;
|
|
$code = $? >> 8;
|
|
$code == 2 or die "Exit code was $code, not 2";
|
|
};
|
|
kill_children;
|
|
|
|
server_client_test_all "Messages are logged to output file",
|
|
["--output", "server.log"], ["--output", "client.log"], sub {
|
|
|
|
syswrite($c_in, "abc\n");
|
|
sleep 1;
|
|
syswrite($s_in, "def\n");
|
|
sleep 1;
|
|
close($c_in);
|
|
open(FH, "server.log");
|
|
binmode FH;
|
|
my $contents = join("", <FH>);
|
|
close(FH);
|
|
$contents eq "abc\ndef\n" or die "Server logged " . d($contents);
|
|
open(FH, "client.log");
|
|
binmode FH;
|
|
$contents = join("", <FH>);
|
|
close(FH);
|
|
$contents eq "abc\ndef\n" or die "Client logged " . d($contents);
|
|
};
|
|
unlink "server.log";
|
|
unlink "client.log";
|
|
kill_children;
|
|
|
|
server_client_test_tcp_sctp_ssl "Debug messages go to stderr",
|
|
["-vvv"], ["-vvv"], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
kill_children;
|
|
|
|
{
|
|
local $xfail = 1;
|
|
server_client_test_tcp_ssl "Client closes socket write and keeps running after stdin EOF",
|
|
[], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
|
|
close($c_in);
|
|
|
|
$resp = timeout_read($s_out);
|
|
!defined($resp) or die "Server didn't get EOF (got \"$resp\")";
|
|
sleep 1;
|
|
waitpid($c_pid, WNOHANG) != -1 or die "Client stopped running";
|
|
};
|
|
kill_children;
|
|
}
|
|
|
|
server_client_test_tcp_ssl "--send-only client closes socket write and stops running after stdin EOF",
|
|
[], ["--send-only"], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
|
|
close($c_in);
|
|
|
|
$resp = timeout_read($s_out);
|
|
!defined($resp) or die "Server didn't get EOF (got \"$resp\")";
|
|
sleep 1;
|
|
waitpid($c_pid, WNOHANG) == -1 or die "Client still running";
|
|
};
|
|
kill_children;
|
|
|
|
server_client_test_tcp_ssl "Server closes socket write and keeps running after stdin EOF",
|
|
[], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp eq "abc\n" or die "Client got \"$resp\", not \"abc\\n\"";
|
|
|
|
close($s_in);
|
|
|
|
$resp = timeout_read($c_out);
|
|
!defined($resp) or die "Client didn't get EOF (got \"$resp\")";
|
|
sleep 1;
|
|
waitpid($s_pid, WNOHANG) != -1 or die "Server stopped running";
|
|
};
|
|
kill_children;
|
|
|
|
server_client_test_tcp_ssl "--send-only server closes socket write and stops running after stdin EOF",
|
|
["--send-only"], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp eq "abc\n" or die "Client got \"$resp\", not \"abc\\n\"";
|
|
|
|
close($s_in);
|
|
|
|
$resp = timeout_read($c_out);
|
|
!defined($resp) or die "Client didn't get EOF (got \"$resp\")";
|
|
sleep 1;
|
|
waitpid($s_pid, WNOHANG) == -1 or die "Server still running";
|
|
};
|
|
kill_children;
|
|
|
|
server_client_test_tcp_ssl "Client closes stdout and keeps running after socket EOF",
|
|
[], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp eq "abc\n" or die "Client got \"$resp\", not \"abc\\n\"";
|
|
|
|
close($s_in);
|
|
|
|
$resp = timeout_read($c_out);
|
|
!defined($resp) or die "Client didn't get EOF and didn't exit (got \"$resp\")";
|
|
sleep 1;
|
|
waitpid($c_pid, WNOHANG) != -1 or die "Client stopped running";
|
|
};
|
|
kill_children;
|
|
|
|
# SCTP doesn't have half-open sockets, so the program should exit.
|
|
# http://seclists.org/nmap-dev/2013/q1/203
|
|
server_client_test_sctp_ssl "Client closes stdout and stops running after socket EOF",
|
|
[], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp eq "abc\n" or die "Client got \"$resp\", not \"abc\\n\"";
|
|
|
|
close($s_in);
|
|
|
|
$resp = timeout_read($c_out);
|
|
!defined($resp) or die "Client didn't get EOF and didn't exit (got \"$resp\")";
|
|
sleep 1;
|
|
waitpid($c_pid, WNOHANG) == -1 or die "Client still running";
|
|
};
|
|
kill_children;
|
|
|
|
server_client_test_tcp_sctp_ssl "--recv-only client closes stdout and stops running after socket EOF",
|
|
[], ["--recv-only"], sub {
|
|
my $resp;
|
|
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp eq "abc\n" or die "Client got \"$resp\", not \"abc\\n\"";
|
|
|
|
close($s_in);
|
|
|
|
$resp = timeout_read($c_out);
|
|
!defined($resp) or die "Client didn't get EOF and didn't exit (got \"$resp\")";
|
|
sleep 1;
|
|
waitpid($c_pid, WNOHANG) == -1 or die "Client still running";
|
|
};
|
|
kill_children;
|
|
|
|
# Test that the server closes its output stream after a client disconnects.
|
|
# This is for uses like
|
|
# ncat -l | tar xzvf -
|
|
# tar czf - <files> | ncat localhost --send-only
|
|
# where tar on the listening side could be any program that potentially buffers
|
|
# its input. The listener must close its standard output so the program knows
|
|
# to stop reading and process what remains in its buffer.
|
|
{
|
|
# XFAIL because of http://seclists.org/nmap-dev/2013/q1/227. The "close stdout"
|
|
# part works, but not the "server keeps running" part.
|
|
local $xfail = 1;
|
|
server_client_test_tcp_ssl "Server closes stdout and keeps running after socket EOF",
|
|
[], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
|
|
close($c_in);
|
|
|
|
$resp = timeout_read($s_out);
|
|
!defined($resp) or die "Server didn't send EOF";
|
|
sleep 1;
|
|
waitpid($s_pid, WNOHANG) != -1 or die "Server stopped running";
|
|
};
|
|
kill_children;
|
|
}
|
|
|
|
server_client_test_sctp_ssl "Server closes stdout and stops running after socket EOF",
|
|
[], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
|
|
close($c_in);
|
|
|
|
$resp = timeout_read($s_out);
|
|
!defined($resp) or die "Server didn't send EOF";
|
|
sleep 1;
|
|
waitpid($s_pid, WNOHANG) == -1 or die "Server still running";
|
|
};
|
|
kill_children;
|
|
|
|
server_client_test_tcp_sctp_ssl "--recv-only server closes stdout and stops running after socket EOF",
|
|
["--recv-only"], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
|
|
close($c_in);
|
|
|
|
$resp = timeout_read($s_out);
|
|
!defined($resp) or die "Server didn't send EOF";
|
|
sleep 1;
|
|
waitpid($s_pid, WNOHANG) == -1 or die "Server still running";
|
|
};
|
|
kill_children;
|
|
|
|
# Tests to check that server defaults to non-persistent without --keep-open.
|
|
|
|
# Server immediately quits after the first connection closed without --keep-open
|
|
($s_pid, $s_out, $s_in) = ncat_server();
|
|
test "Server quits without --keep-open",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat_client();
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
kill "TERM", $c_pid;
|
|
while (waitpid($c_pid, 0) > 0) {
|
|
}
|
|
sleep 1;
|
|
# -1 because children are automatically reaped; 0 means it's still running.
|
|
waitpid($s_pid, WNOHANG) == -1 or die "Server still running";
|
|
};
|
|
kill_children;
|
|
|
|
# Server with --exec immediately quits after the first connection closed without --keep-open
|
|
($s_pid, $s_out, $s_in) = ncat_server("--exec", "$CAT");
|
|
test "Server with --exec quits without --keep-open",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat_client();
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($c_out);
|
|
$resp eq "abc\n" or die "Client got back \"$resp\", not \"abc\\n\"";
|
|
kill "TERM", $c_pid;
|
|
while (waitpid($c_pid, 0) > 0) {
|
|
}
|
|
sleep 1;
|
|
waitpid($s_pid, WNOHANG) == -1 or die "Server still running";
|
|
};
|
|
kill_children;
|
|
|
|
# Server immediately quits after the first connection ssl negotiation fails without --keep-open
|
|
{
|
|
($s_pid, $s_out, $s_in) = ncat_server("--ssl");
|
|
test "Server quits after a failed ssl negotiation without --keep-open",
|
|
sub {
|
|
my $resp;
|
|
|
|
# Let's sleep for one second here, since in some cases the server might not
|
|
# get the chance to start listening before the client tries to connect.
|
|
sleep 1;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat_client();
|
|
syswrite($c_in, "abc\n");
|
|
|
|
kill "TERM", $c_pid;
|
|
while (waitpid($c_pid, 0) > 0) {
|
|
}
|
|
sleep 1;
|
|
# -1 because children are automatically reaped; 0 means it's still running.
|
|
waitpid($s_pid, WNOHANG) == -1 or die "Server still running";
|
|
};
|
|
kill_children;
|
|
}
|
|
|
|
# Server does not accept multiple connections without --keep-open
|
|
($s_pid, $s_out, $s_in) = ncat_server();
|
|
test "Server does not accept multiple conns. without --keep-open",
|
|
sub {
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client();
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client();
|
|
|
|
sleep 1;
|
|
|
|
waitpid($c2_pid, WNOHANG) == -1 or die "A second client could connect to the server";
|
|
|
|
};
|
|
kill_children;
|
|
|
|
# Test server persistence with --keep-open.
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--keep-open");
|
|
test "--keep-open",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client();
|
|
syswrite($c1_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client();
|
|
syswrite($c2_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--keep-open", "--exec", "$CAT");
|
|
test "--keep-open --exec",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client();
|
|
syswrite($c1_in, "abc\n");
|
|
$resp = timeout_read($c1_out);
|
|
$resp eq "abc\n" or die "Client 1 got back \"$resp\", not \"abc\\n\"";
|
|
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client();
|
|
syswrite($c2_in, "abc\n");
|
|
$resp = timeout_read($c2_out);
|
|
$resp eq "abc\n" or die "Client 2 got back \"$resp\", not \"abc\\n\"";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--keep-open", "--udp", "--exec", "$CAT");
|
|
test "--keep-open --exec (udp)",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client("--udp");
|
|
syswrite($c1_in, "abc\n");
|
|
$resp = timeout_read($c1_out);
|
|
$resp eq "abc\n" or die "Client 1 got back \"$resp\", not \"abc\\n\"";
|
|
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client("--udp");
|
|
syswrite($c2_in, "abc\n");
|
|
$resp = timeout_read($c2_out);
|
|
$resp eq "abc\n" or die "Client 2 got back \"$resp\", not \"abc\\n\"";
|
|
};
|
|
kill_children;
|
|
|
|
# Test --exec, --sh-exec and --lua-exec.
|
|
|
|
server_client_test_all "--exec",
|
|
["--exec", "$PERL -e \$|=1;while(<>)\{tr/a-z/A-Z/;print\}"], [], sub {
|
|
syswrite($c_in, "abc\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp eq "ABC\n" or die "Client received " . d($resp) . ", not " . d("ABC\n");
|
|
};
|
|
|
|
server_client_test_all "--sh-exec",
|
|
["--sh-exec", "perl -e '\$|=1;while(<>)\{tr/a-z/A-Z/;print\}'"], [], sub {
|
|
syswrite($c_in, "abc\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp eq "ABC\n" or die "Client received " . d($resp) . ", not " . d("ABC\n");
|
|
};
|
|
|
|
server_client_test_all "--exec, quits instantly",
|
|
["--exec", "$ECHO abc"], [], sub {
|
|
syswrite($c_in, "test\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp eq "abc\n" or die "Client received " . d($resp) . ", not " . d("abc\n");
|
|
};
|
|
|
|
server_client_test_all "--sh-exec with -C",
|
|
["--sh-exec", "$PERL -e '\$|=1;while(<>){tr/a-z/A-Z/;print}'", "-C"], [], sub {
|
|
syswrite($c_in, "abc\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp eq "ABC\r\n" or die "Client received " . d($resp) . ", not " . d("ABC\r\n");
|
|
};
|
|
|
|
proxy_test "--exec through proxy",
|
|
[], [], ["--exec", "$ECHO abc"], sub {
|
|
my $resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp eq "abc\n" or die "Server received " . d($resp) . ", not " . d("abc\n");
|
|
};
|
|
|
|
server_client_test_all "--lua-exec",
|
|
["--lua-exec", "toupper.lua"], [], sub {
|
|
syswrite($c_in, "abc\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp eq "ABC\n" or die "Client received " . d($resp) . ", not " . d("ABC\n");
|
|
};
|
|
|
|
# Test environment variables being set for --exec, --sh-exec and --lua-exec.
|
|
|
|
server_client_test_all "--exec, environment variables",
|
|
["--exec", "$BINSH test-environment.sh"], [], sub {
|
|
syswrite($c_in, "abc\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
match_ncat_environment($resp) or die "Client received " . d($resp) . ".";
|
|
};
|
|
|
|
server_client_test_all "--sh-exec, environment variables",
|
|
["--sh-exec", "sh test-environment.sh"], [], sub {
|
|
syswrite($c_in, "abc\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
match_ncat_environment($resp) or die "Client received " . d($resp) . ".";
|
|
};
|
|
|
|
proxy_test "--exec through proxy, environment variables",
|
|
[], [], ["--exec", "$BINSH test-environment.sh"], sub {
|
|
my $resp = timeout_read($s_out) or die "Read timeout";
|
|
match_ncat_environment($resp) or die "Client received " . d($resp) . ".";
|
|
};
|
|
|
|
server_client_test_all "--lua-exec, environment variables",
|
|
["--lua-exec", "test-environment.lua"], [], sub {
|
|
syswrite($c_in, "abc\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
match_ncat_environment($resp) or die "Client received " . d($resp) . ".";
|
|
};
|
|
|
|
# Do a syswrite and then a delay to force separate reads in the subprocess.
|
|
sub delaywrite {
|
|
my ($handle, $data) = @_;
|
|
my $delay = 0.1;
|
|
syswrite($handle, $data);
|
|
select(undef, undef, undef, $delay);
|
|
}
|
|
|
|
server_client_test_all "-C translation on input",
|
|
["-C"], ["-C"], sub {
|
|
my $resp;
|
|
my $expected = "\r\na\r\nb\r\n---\r\nc\r\nd\r\n---e\r\n\r\nf\r\n---\r\n";
|
|
|
|
delaywrite($c_in, "\na\nb\n");
|
|
delaywrite($c_in, "---");
|
|
delaywrite($c_in, "\r\nc\r\nd\r\n");
|
|
delaywrite($c_in, "---");
|
|
delaywrite($c_in, "e\n\nf\n");
|
|
delaywrite($c_in, "---\r");
|
|
delaywrite($c_in, "\n");
|
|
$resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp eq $expected or die "Server received " . d($resp) . ", not " . d($expected);
|
|
|
|
delaywrite($s_in, "\na\nb\n");
|
|
delaywrite($s_in, "---");
|
|
delaywrite($s_in, "\r\nc\r\nd\r\n");
|
|
delaywrite($s_in, "---");
|
|
delaywrite($s_in, "e\n\nf\n");
|
|
delaywrite($s_in, "---\r");
|
|
delaywrite($s_in, "\n");
|
|
$resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp eq $expected or die "Client received " . d($resp) . ", not " . d($expected);
|
|
};
|
|
kill_children;
|
|
|
|
server_client_test_all "-C server no translation on output",
|
|
["-C"], [], sub {
|
|
my $resp;
|
|
my $expected = "\na\nb\n---\r\nc\r\nd\r\n";
|
|
|
|
delaywrite($c_in, "\na\nb\n");
|
|
delaywrite($c_in, "---");
|
|
delaywrite($c_in, "\r\nc\r\nd\r\n");
|
|
$resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp eq $expected or die "Server received " . d($resp) . ", not " . d($expected);
|
|
};
|
|
kill_children;
|
|
|
|
server_client_test_tcp_sctp_ssl "-C client no translation on output",
|
|
[], ["-C"], sub {
|
|
my $resp;
|
|
my $expected = "\na\nb\n---\r\nc\r\nd\r\n";
|
|
|
|
delaywrite($s_in, "\na\nb\n");
|
|
delaywrite($s_in, "---");
|
|
delaywrite($s_in, "\r\nc\r\nd\r\n");
|
|
$resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp eq $expected or die "Client received " . d($resp) . ", not " . d($expected);
|
|
};
|
|
kill_children;
|
|
|
|
# Test that both reads and writes reset the idle counter, and that the client
|
|
# properly exits after the timeout expires.
|
|
server_client_test_all "idle timeout (connect mode)",
|
|
[], ["-i", "3000ms"], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out) or die "Read timeout";
|
|
sleep 2;
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out) or die "Read timeout";
|
|
sleep 2;
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out) or die "Read timeout";
|
|
sleep 4;
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out);
|
|
!$resp or die "Client received \"$resp\" after delay of 4000 ms with idle timeout of 3000 ms."
|
|
};
|
|
|
|
# Test that both reads and writes reset the idle counter, and that the server
|
|
# properly exits after the timeout expires.
|
|
server_client_test_tcp_sctp_ssl "idle timeout (listen mode)",
|
|
["-i", "3000ms"], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out) or die "Read timeout";
|
|
sleep 2;
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out) or die "Read timeout";
|
|
sleep 2;
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out) or die "Read timeout";
|
|
sleep 4;
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
!$resp or die "Server received \"$resp\" after delay of 4000 ms with idle timeout of 3000 ms."
|
|
};
|
|
|
|
server_client_test_multi ["udp"], "idle timeout (listen mode)",
|
|
["-i", "3000ms"], [], sub {
|
|
my $resp;
|
|
|
|
# when using UDP client must at least write something to the server
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out) or die "Server didn't receive the message";
|
|
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out) or die "Read timeout";
|
|
sleep 2;
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out) or die "Read timeout";
|
|
sleep 2;
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out) or die "Read timeout";
|
|
sleep 4;
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
!$resp or die "Server received \"$resp\" after delay of 4000 ms with idle timeout of 3000 ms."
|
|
};
|
|
|
|
# --send-only tests.
|
|
|
|
server_client_test_all "--send-only client",
|
|
[], ["--send-only"], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
$resp or die "Read timeout";
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out);
|
|
!$resp or die "Client received \"$resp\" in --send-only mode";
|
|
};
|
|
|
|
server_client_test_all "--send-only server",
|
|
["--send-only"], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
!$resp or die "Server received \"$resp\" in --send-only mode";
|
|
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out);
|
|
$resp or die "Read timeout";
|
|
$resp eq "abc\n" or die "Client got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--broker", "--send-only");
|
|
test "--send-only broker",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client();
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client();
|
|
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c1_out);
|
|
$resp or die "Read timeout";
|
|
$resp eq "abc\n" or die "Client got \"$resp\", not \"abc\\n\"";
|
|
$resp = timeout_read($c2_out);
|
|
$resp or die "Read timeout";
|
|
$resp eq "abc\n" or die "Client got \"$resp\", not \"abc\\n\"";
|
|
|
|
syswrite($c1_in, "abc\n");
|
|
$resp = timeout_read($c2_out);
|
|
!$resp or die "--send-only broker relayed \"$resp\"";
|
|
};
|
|
kill_children;
|
|
|
|
# --recv-only tests.
|
|
|
|
# Note this test excludes UDP. The --recv-only UDP client never sends anything
|
|
# to the server, so the server never knows to start sending its data.
|
|
server_client_test_tcp_sctp_ssl "--recv-only client",
|
|
[], ["--recv-only"], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
!$resp or die "Server received \"$resp\" from --recv-only client";
|
|
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out);
|
|
$resp or die "Read timeout";
|
|
$resp eq "abc\n" or die "Client got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
|
|
server_client_test_all "--recv-only server",
|
|
["--recv-only"], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
$resp or die "Read timeout";
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out);
|
|
!$resp or die "Client received \"$resp\" from --recv-only server";
|
|
};
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--broker", "--recv-only");
|
|
test "--recv-only broker",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client();
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client();
|
|
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c1_out);
|
|
!$resp or die "Client received \"$resp\" from --recv-only broker";
|
|
$resp = timeout_read($c2_out);
|
|
!$resp or die "Client received \"$resp\" from --recv-only broker";
|
|
|
|
syswrite($c1_in, "abc\n");
|
|
$resp = timeout_read($c2_out);
|
|
!$resp or die "Client received \"$resp\" from --recv-only broker";
|
|
};
|
|
kill_children;
|
|
|
|
#Broker Tests
|
|
($s_pid, $s_out, $s_in) = ncat_server("--broker");
|
|
test "--broker mode (tcp)",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client();
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client();
|
|
|
|
syswrite($c2_in, "abc\n");
|
|
$resp = timeout_read($c1_out);
|
|
$resp eq "abc\n" or die "Client 1 received \"$resp\", not abc";
|
|
|
|
syswrite($c1_in, "abc\n");
|
|
$resp = timeout_read($c2_out);
|
|
$resp eq "abc\n" or die "Client 2 received \"$resp\", not abc";
|
|
};
|
|
kill_children;
|
|
|
|
{
|
|
local $xfail=1 if !$HAVE_SCTP;
|
|
($s_pid, $s_out, $s_in) = ncat_server("--broker", "--sctp");
|
|
test "--broker mode (sctp)",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client("--sctp");
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client("--sctp");
|
|
|
|
syswrite($c2_in, "abc\n");
|
|
$resp = timeout_read($c1_out);
|
|
$resp eq "abc\n" or die "Client 1 received \"$resp\", not abc";
|
|
|
|
syswrite($c1_in, "abc\n");
|
|
$resp = timeout_read($c2_out);
|
|
$resp eq "abc\n" or die "Client 2 received \"$resp\", not abc";
|
|
};
|
|
kill_children;
|
|
}
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--broker", "--ssl");
|
|
test "--broker mode (tcp ssl)",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client("--ssl");
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client("--ssl");
|
|
|
|
syswrite($c2_in, "abc\n");
|
|
$resp = timeout_read($c1_out);
|
|
$resp eq "abc\n" or die "Client 1 received \"$resp\", not abc";
|
|
|
|
syswrite($c1_in, "abc\n");
|
|
$resp = timeout_read($c2_out);
|
|
$resp eq "abc\n" or die "Client 2 received \"$resp\", not abc";
|
|
};
|
|
kill_children;
|
|
|
|
{
|
|
local $xfail=1 if !$HAVE_SCTP;
|
|
($s_pid, $s_out, $s_in) = ncat_server("--broker", "--sctp", "--ssl");
|
|
test "--broker mode (sctp ssl)",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client("--sctp", "--ssl");
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client("--sctp", "--ssl");
|
|
|
|
syswrite($c2_in, "abc\n");
|
|
$resp = timeout_read($c1_out);
|
|
$resp eq "abc\n" or die "Client 1 received \"$resp\", not abc";
|
|
|
|
syswrite($c1_in, "abc\n");
|
|
$resp = timeout_read($c2_out);
|
|
$resp eq "abc\n" or die "Client 2 received \"$resp\", not abc";
|
|
};
|
|
kill_children;
|
|
}
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--broker");
|
|
test "IPV4 and IPV6 clients can talk to each other in broker mode",
|
|
sub {
|
|
my $resp;
|
|
sleep 1;
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client("-6");
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client("-4");
|
|
|
|
syswrite($c2_in, "abc\n");
|
|
$resp = timeout_read($c1_out, 2);
|
|
$resp eq "abc\n" or die "IPV6 Client received \"$resp\", not abc";
|
|
|
|
syswrite($c1_in, "abc\n");
|
|
$resp = timeout_read($c2_out, 2);
|
|
$resp eq "abc\n" or die "IPV4 Client received \"$resp\", not abc";
|
|
};
|
|
kill_children;
|
|
|
|
|
|
# Source address tests.
|
|
|
|
test "Connect with -p",
|
|
sub {
|
|
my ($pid, $code);
|
|
local $SIG{CHLD} = sub { };
|
|
local *SOCK;
|
|
local *S;
|
|
|
|
socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die;
|
|
setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die;
|
|
bind(SOCK, sockaddr_in($PORT, INADDR_ANY)) or die;
|
|
listen(SOCK, 1) or die;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("-p", "1234", $HOST, $PORT);
|
|
|
|
accept(S, SOCK) or die;
|
|
my ($port, $addr) = sockaddr_in(getpeername(S));
|
|
$port == 1234 or die "Client connected to proxy with source port $port, not 1234";
|
|
close(S);
|
|
};
|
|
kill_children;
|
|
|
|
test "Connect through HTTP proxy with -p",
|
|
sub {
|
|
my ($pid, $code);
|
|
local $SIG{CHLD} = sub { };
|
|
local *SOCK;
|
|
local *S;
|
|
|
|
socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die;
|
|
setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die;
|
|
bind(SOCK, sockaddr_in($PROXY_PORT, INADDR_ANY)) or die;
|
|
listen(SOCK, 1) or die;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("--proxy-type", "http", "--proxy", "$HOST:$PROXY_PORT", "-p", "1234", $HOST, $PORT);
|
|
|
|
accept(S, SOCK) or die;
|
|
my ($port, $addr) = sockaddr_in(getpeername(S));
|
|
$port == 1234 or die "Client connected to proxy with source port $port, not 1234";
|
|
close(S);
|
|
};
|
|
kill_children;
|
|
|
|
test "Connect through SOCKS4 proxy with -p",
|
|
sub {
|
|
my ($pid, $code);
|
|
local $SIG{CHLD} = sub { };
|
|
local *SOCK;
|
|
local *S;
|
|
|
|
socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die;
|
|
setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die;
|
|
bind(SOCK, sockaddr_in($PROXY_PORT, INADDR_ANY)) or die;
|
|
listen(SOCK, 1) or die;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("--proxy-type", "socks4", "--proxy", "$HOST:$PROXY_PORT", "-p", "1234", $HOST, $PORT);
|
|
|
|
accept(S, SOCK) or die;
|
|
my ($port, $addr) = sockaddr_in(getpeername(S));
|
|
$port == 1234 or die "Client connected to proxy with source port $port, not 1234";
|
|
close(S);
|
|
};
|
|
kill_children;
|
|
|
|
{
|
|
local $xfail=1 if !$HAVE_UNIXSOCK;
|
|
# Test connecting to UNIX datagram socket with -s
|
|
test "Connect to UNIX datagram socket with -s",
|
|
sub {
|
|
my ($pid, $code);
|
|
local $SIG{CHLD} = sub { };
|
|
local *SOCK;
|
|
my $buff;
|
|
|
|
unlink($UNIXSOCK);
|
|
unlink($UNIXSOCK_TMP);
|
|
|
|
socket(SOCK, AF_UNIX, SOCK_DGRAM, 0) or die;
|
|
bind(SOCK, sockaddr_un($UNIXSOCK)) or die;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("-U", "--udp", "-s", $UNIXSOCK_TMP, $UNIXSOCK);
|
|
syswrite($c_in, "abc\n");
|
|
close($c_in);
|
|
|
|
my $peeraddr = recv(SOCK, $buff, 4, 0) or die;
|
|
my ($path) = sockaddr_un($peeraddr);
|
|
$path eq $UNIXSOCK_TMP or die "Client connected to proxy with source socket path $path, not $UNIXSOCK_TMP";
|
|
};
|
|
kill_children;
|
|
unlink($UNIXSOCK);
|
|
unlink($UNIXSOCK_TMP);
|
|
}
|
|
|
|
|
|
# HTTP proxy tests.
|
|
|
|
sub http_request {
|
|
my ($method, $uri) = @_;
|
|
return "$method $uri HTTP/1.0\r\n\r\n";
|
|
};
|
|
|
|
server_client_test "HTTP proxy bad request",
|
|
["--proxy-type", "http"], [], sub {
|
|
syswrite($c_in, "bad\r\n\r\n");
|
|
close($c_in);
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 400 or die "Expected response code 400, got $code";
|
|
};
|
|
|
|
server_client_test "HTTP CONNECT no port number",
|
|
["--proxy-type", "http"], [], sub {
|
|
# Supposed to have a port number.
|
|
my $req = http_request("CONNECT", "$HOST");
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 400 or die "Expected response code 400, got $code";
|
|
};
|
|
|
|
server_client_test "HTTP CONNECT no port number",
|
|
["--proxy-type", "http"], [], sub {
|
|
# Supposed to have a port number.
|
|
my $req = http_request("CONNECT", "$HOST:");
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 400 or die "Expected response code 400, got $code";
|
|
};
|
|
|
|
server_client_test "HTTP CONNECT good request",
|
|
["--proxy-type", "http"], [], sub {
|
|
my $req = http_request("CONNECT", "$HOST:$PORT");
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 200 or die "Expected response code 200, got $code";
|
|
};
|
|
|
|
server_client_test "HTTP CONNECT IPv6 address, no port number",
|
|
["--proxy-type", "http", "-6"], ["-6"], sub {
|
|
# Supposed to have a port number.
|
|
my $req = http_request("CONNECT", "[$IPV6_ADDR]");
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 400 or die "Expected response code 400, got $code";
|
|
};
|
|
|
|
server_client_test "HTTP CONNECT IPv6 address, no port number",
|
|
["--proxy-type", "http", "-6"], ["-6"], sub {
|
|
# Supposed to have a port number.
|
|
my $req = http_request("CONNECT", "[$IPV6_ADDR]:");
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 400 or die "Expected response code 400, got $code";
|
|
};
|
|
|
|
server_client_test "HTTP CONNECT IPv6 address, good request",
|
|
["--proxy-type", "http", "-6"], ["-6"], sub {
|
|
my $req = http_request("CONNECT", "[$IPV6_ADDR]:$PORT");
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 200 or die "Expected response code 200, got $code";
|
|
};
|
|
|
|
# Try accessing an IPv6 server with a proxy that uses -4, should fail.
|
|
proxy_test_raw "HTTP CONNECT IPv4-only proxy",
|
|
["-4"], ["-6"], ["-4"], sub {
|
|
my $req = http_request("CONNECT", "[$IPV6_ADDR]:$PORT");
|
|
syswrite($c_in, $req);
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 504 or die "Expected response code 504, got $code";
|
|
};
|
|
|
|
# Try accessing an IPv4 server with a proxy that uses -6, should fail.
|
|
proxy_test_raw "HTTP CONNECT IPv6-only proxy",
|
|
["-6"], ["-4"], ["-6"], sub {
|
|
my $req = http_request("CONNECT", "$HOST:$PORT");
|
|
syswrite($c_in, $req);
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 504 or die "Expected response code 504, got $code";
|
|
};
|
|
|
|
{
|
|
local $xfail = 1;
|
|
proxy_test_raw "HTTP CONNECT IPv4 client, IPv6 server",
|
|
[], ["-6"], ["-4"], sub {
|
|
my $req = http_request("CONNECT", "[$IPV6_ADDR]:$PORT");
|
|
syswrite($c_in, $req);
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 200 or die "Expected response code 200, got $code";
|
|
};
|
|
}
|
|
|
|
# HTTP Digest functions.
|
|
sub H {
|
|
return md5_hex(shift);
|
|
}
|
|
sub KD {
|
|
my ($s, $d) = @_;
|
|
return H("$s:$d");
|
|
}
|
|
sub digest_response {
|
|
# Assume MD5 algorithm.
|
|
my ($user, $pass, $realm, $method, $uri, $nonce, $qop, $nc, $cnonce) = @_;
|
|
my $A1 = "$user:$realm:$pass";
|
|
my $A2 = "$method:$uri";
|
|
if ($qop) {
|
|
return KD(H($A1), "$nonce:$nc:$cnonce:$qop:" . H($A2));
|
|
} else {
|
|
return KD(H($A1), "$nonce:" . H($A2));
|
|
}
|
|
}
|
|
# Parse Proxy-Authenticate or Proxy-Authorization. Return ($scheme, %attrs).
|
|
sub parse_proxy_header {
|
|
my $s = shift;
|
|
my $scheme;
|
|
my %attrs;
|
|
|
|
if ($s =~ m/^\s*(\w+)/) {
|
|
$scheme = $1;
|
|
}
|
|
while ($s =~ m/(\w+)\s*=\s*(?:"([^"]*)"|(\w+))/g) {
|
|
$attrs{$1} = $2 || $3;
|
|
}
|
|
|
|
return ($scheme, %attrs);
|
|
}
|
|
|
|
server_client_test "HTTP proxy client prefers Digest auth",
|
|
["-k"], ["--proxy", "$HOST:$PORT", "--proxy-auth", "user:pass", "--proxy-type", "http"],
|
|
sub {
|
|
my $nonce = "0123456789abcdef";
|
|
my $realm = "realm";
|
|
my $req = timeout_read($s_out);
|
|
$req or die "No initial request from client";
|
|
syswrite($s_in, "HTTP/1.0 407 Authentication Required\r\
|
|
Proxy-Authenticate: Basic realm=\"$realm\"\r\
|
|
Proxy-Authenticate: Digest realm=\"$realm\", nonce=\"$nonce\", qop=\"auth\"\r\n\r\n");
|
|
$req = timeout_read($s_out);
|
|
$req or die "No followup request from client";
|
|
$req = HTTP::Request->parse($req);
|
|
foreach my $hdr ($req->header("Proxy-Authorization")) {
|
|
my ($scheme, %attrs) = parse_proxy_header($hdr);
|
|
if ($scheme eq "Basic") {
|
|
die "Client used Basic auth when Digest was available";
|
|
}
|
|
}
|
|
return 1;
|
|
};
|
|
|
|
server_client_test "HTTP proxy client prefers Digest auth, comma-separated",
|
|
["-k"], ["--proxy", "$HOST:$PORT", "--proxy-auth", "user:pass", "--proxy-type", "http"],
|
|
sub {
|
|
my $nonce = "0123456789abcdef";
|
|
my $realm = "realm";
|
|
my $req = timeout_read($s_out);
|
|
$req or die "No initial request from client";
|
|
syswrite($s_in, "HTTP/1.0 407 Authentication Required\r\
|
|
Proxy-Authenticate: Basic realm=\"$realm\", Digest realm=\"$realm\", nonce=\"$nonce\", qop=\"auth\"\r\n\r\n");
|
|
$req = timeout_read($s_out);
|
|
$req or die "No followup request from client";
|
|
$req = HTTP::Request->parse($req);
|
|
foreach my $hdr ($req->header("Proxy-Authorization")) {
|
|
my ($scheme, %attrs) = parse_proxy_header($hdr);
|
|
if ($scheme eq "Basic") {
|
|
die "Client used Basic auth when Digest was available";
|
|
}
|
|
}
|
|
return 1;
|
|
};
|
|
|
|
server_client_test "HTTP proxy Digest client auth",
|
|
["-k"], ["--proxy", "$HOST:$PORT", "--proxy-auth", "user:pass", "--proxy-type", "http"],
|
|
sub {
|
|
my $nonce = "0123456789abcdef";
|
|
my $realm = "realm";
|
|
my $req = timeout_read($s_out);
|
|
$req or die "No initial request from client";
|
|
syswrite($s_in, "HTTP/1.0 407 Authentication Required\r\
|
|
Proxy-Authenticate: Digest realm=\"$realm\", nonce=\"$nonce\", qop=\"auth\", opaque=\"abcd\"\r\n\r\n");
|
|
$req = timeout_read($s_out);
|
|
$req or die "No followup request from client";
|
|
$req = HTTP::Request->parse($req);
|
|
foreach my $hdr ($req->header("Proxy-Authorization")) {
|
|
my ($scheme, %attrs) = parse_proxy_header($hdr);
|
|
next if $scheme ne "Digest";
|
|
die "no qop" if not $attrs{"qop"};
|
|
die "no nonce" if not $attrs{"nonce"};
|
|
die "no uri" if not $attrs{"uri"};
|
|
die "no nc" if not $attrs{"nc"};
|
|
die "no cnonce" if not $attrs{"cnonce"};
|
|
die "no response" if not $attrs{"response"};
|
|
die "no opaque" if not $attrs{"opaque"};
|
|
die "qop mismatch" if $attrs{"qop"} ne "auth";
|
|
die "nonce mismatch" if $attrs{"nonce"} ne $nonce;
|
|
die "opaque mismatch" if $attrs{"opaque"} ne "abcd";
|
|
my $expected = digest_response("user", "pass", $realm, "CONNECT", $attrs{"uri"}, $nonce, "auth", $attrs{"nc"}, $attrs{"cnonce"});
|
|
die "auth mismatch: $attrs{response} but expected $expected" if $attrs{"response"} ne $expected;
|
|
return 1;
|
|
}
|
|
die "No Proxy-Authorization: Digest in client request";
|
|
};
|
|
|
|
server_client_test "HTTP proxy Digest client auth, no qop",
|
|
["-k"], ["--proxy", "$HOST:$PORT", "--proxy-auth", "user:pass", "--proxy-type", "http"],
|
|
sub {
|
|
my $nonce = "0123456789abcdef";
|
|
my $realm = "realm";
|
|
my $req = timeout_read($s_out);
|
|
$req or die "No initial request from client";
|
|
syswrite($s_in, "HTTP/1.0 407 Authentication Required\r\
|
|
Proxy-Authenticate: Digest realm=\"$realm\", nonce=\"$nonce\", opaque=\"abcd\"\r\n\r\n");
|
|
$req = timeout_read($s_out);
|
|
$req or die "No followup request from client";
|
|
$req = HTTP::Request->parse($req);
|
|
foreach my $hdr ($req->header("Proxy-Authorization")) {
|
|
my ($scheme, %attrs) = parse_proxy_header($hdr);
|
|
next if $scheme ne "Digest";
|
|
die "no nonce" if not $attrs{"nonce"};
|
|
die "no uri" if not $attrs{"uri"};
|
|
die "no response" if not $attrs{"response"};
|
|
die "no opaque" if not $attrs{"opaque"};
|
|
die "nonce mismatch" if $attrs{"nonce"} ne $nonce;
|
|
die "opaque mismatch" if $attrs{"opaque"} ne "abcd";
|
|
die "nc present" if $attrs{"nc"};
|
|
die "cnonce present" if $attrs{"cnonce"};
|
|
my $expected = digest_response("user", "pass", $realm, "CONNECT", $attrs{"uri"}, $nonce, undef, undef, undef);
|
|
die "auth mismatch: $attrs{response} but expected $expected" if $attrs{"response"} ne $expected;
|
|
return 1;
|
|
}
|
|
die "No Proxy-Authorization: Digest in client request";
|
|
};
|
|
|
|
# This violates RFC 2617 section 1.2, which requires at least one auth-param.
|
|
# But NTLM and Negotiate don't use any.
|
|
server_client_test "HTTP proxy client handles scheme without auth-params",
|
|
["-k"], ["--proxy", "$HOST:$PORT", "--proxy-auth", "user:pass", "--proxy-type", "http"],
|
|
sub {
|
|
my $nonce = "0123456789abcdef";
|
|
my $realm = "realm";
|
|
my $req = timeout_read($s_out);
|
|
$req or die "No initial request from client";
|
|
syswrite($s_in, "HTTP/1.0 407 Authentication Required\r\
|
|
Proxy-Authenticate: Basic realm=\"$realm\"\r\
|
|
Proxy-Authenticate: NTLM\r\
|
|
Proxy-Authenticate: Digest realm=\"$realm\", nonce=\"$nonce\", qop=\"auth\"\r\n\r\n");
|
|
$req = timeout_read($s_out);
|
|
$req or die "No followup request from client";
|
|
$req = HTTP::Request->parse($req);
|
|
$req->header("Proxy-Authorization") or die "Client didn't sent Proxy-Authorization";
|
|
};
|
|
|
|
server_client_test "HTTP proxy client handles scheme without auth-params, comma-separated",
|
|
["-k"], ["--proxy", "$HOST:$PORT", "--proxy-auth", "user:pass", "--proxy-type", "http"],
|
|
sub {
|
|
my $nonce = "0123456789abcdef";
|
|
my $realm = "realm";
|
|
my $req = timeout_read($s_out);
|
|
$req or die "No initial request from client";
|
|
syswrite($s_in, "HTTP/1.0 407 Authentication Required\r\
|
|
Proxy-Authenticate: Basic realm=\"$realm\", NTLM, Digest realm=\"$realm\", nonce=\"$nonce\", qop=\"auth\"\r\n\r\n");
|
|
$req = timeout_read($s_out);
|
|
$req or die "No followup request from client";
|
|
$req = HTTP::Request->parse($req);
|
|
$req->header("Proxy-Authorization") or die "Client didn't sent Proxy-Authorization";
|
|
};
|
|
|
|
# Check that the proxy relays in both directions.
|
|
proxy_test "HTTP CONNECT proxy relays",
|
|
[], [], [], sub {
|
|
syswrite($c_in, "abc\n");
|
|
my $resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp eq "abc\n" or die "Proxy relayed \"$resp\", not \"abc\\n\"";
|
|
syswrite($s_in, "def\n");
|
|
$resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp eq "def\n" or die "Proxy relayed \"$resp\", not \"def\\n\"";
|
|
};
|
|
|
|
# Proxy client shouldn't see the status line returned by the proxy server.
|
|
server_client_test "HTTP CONNECT client hides proxy server response",
|
|
["--proxy-type", "http"], ["--proxy", "$HOST:$PORT", "--proxy-type", "http"], sub {
|
|
my $resp = timeout_read($c_out);
|
|
!$resp or die "Proxy client sent " . d($resp) . " to the user stream";
|
|
};
|
|
|
|
server_client_test "HTTP CONNECT client, different Status-Line",
|
|
[], ["--proxy", "$HOST:$PORT", "--proxy-type", "http"], sub {
|
|
my $resp;
|
|
syswrite($s_in, "HTTP/1.1 200 Go ahead\r\n\r\nabc\n");
|
|
$resp = timeout_read($c_out);
|
|
if (!defined($resp)) {
|
|
die "Client didn't recognize connection";
|
|
} elsif ($resp ne "abc\n") {
|
|
die "Proxy client sent " . d($resp) . " to the user stream";
|
|
}
|
|
return 1;
|
|
};
|
|
|
|
server_client_test "HTTP CONNECT client, server sends header",
|
|
[], ["--proxy", "$HOST:$PORT", "--proxy-type", "http"], sub {
|
|
my $resp;
|
|
syswrite($s_in, "HTTP/1.0 200 OK\r\nServer: ncat-test 1.2.3\r\n\r\nabc\n");
|
|
$resp = timeout_read($c_out);
|
|
if (!defined($resp)) {
|
|
die "Client didn't recognize connection";
|
|
} elsif ($resp ne "abc\n") {
|
|
die "Proxy client sent " . d($resp) . " to the user stream";
|
|
}
|
|
return 1;
|
|
};
|
|
|
|
# Check that the proxy doesn't consume anything following the request when
|
|
# request and body are combined in one send. Section 3.3 of the CONNECT spec
|
|
# explicitly allows the client to send data before the connection is
|
|
# established.
|
|
proxy_test_raw "HTTP CONNECT server doesn't consume anything after request",
|
|
[], [], [], sub {
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\nUser-Agent: ncat-test\r\n\r\nabc\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 200 or die "Expected response code 200, got $code";
|
|
|
|
$resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp eq "abc\n" or die "Proxy relayed \"$resp\", not \"abc\\n\"";
|
|
};
|
|
|
|
server_client_test "HTTP CONNECT overlong Request-Line",
|
|
["--proxy-type", "http"], [], sub {
|
|
syswrite($c_in, "CONNECT " . ("A" x 24000) . ":$PORT HTTP/1.0\r\n\r\n");
|
|
close($c_in);
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 413 or $code == 414 or die "Expected response code 413 or 414, got $code";
|
|
};
|
|
|
|
server_client_test "HTTP CONNECT overlong header",
|
|
["--proxy-type", "http"], [], sub {
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\n");
|
|
for (my $i = 0; $i < 10000; $i++) {
|
|
syswrite($c_in, "Header: Value\r\n");
|
|
}
|
|
syswrite($c_in, "\r\n");
|
|
close($c_in);
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 413 or die "Expected response code 413, got $code";
|
|
};
|
|
|
|
server_client_test "HTTP GET hostname only",
|
|
["--proxy-type", "http"], [], sub {
|
|
my $req = http_request("GET", "$HOST");
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 400 or die "Expected response code 400, got $code";
|
|
};
|
|
|
|
server_client_test "HTTP GET path only",
|
|
["--proxy-type", "http"], [], sub {
|
|
my $req = http_request("GET", "/");
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 400 or die "Expected response code 400, got $code";
|
|
};
|
|
|
|
proxy_test_raw "HTTP GET absolute URI",
|
|
[], [], [], sub {
|
|
my $req = http_request("GET", "http://$HOST:$PORT/");
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp =~ /^GET \/ HTTP\/1\./ or die "Proxy sent \"$resp\"";
|
|
};
|
|
|
|
proxy_test_raw "HTTP GET absolute URI, no path",
|
|
[], [], [], sub {
|
|
my $req = http_request("GET", "http://$HOST:$PORT");
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp =~ /^GET \/ HTTP\/1\./ or die "Proxy sent \"$resp\"";
|
|
};
|
|
|
|
proxy_test_raw "HTTP GET percent escape",
|
|
[], [], [], sub {
|
|
my $req = http_request("GET", "http://$HOST:$PORT/%41");
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($s_out) or die "Read timeout";
|
|
uri_unescape($resp) =~ /^GET \/A HTTP\/1\./ or die "Proxy sent \"$resp\"";
|
|
};
|
|
|
|
proxy_test_raw "HTTP GET remove Connection header fields",
|
|
[], [], [], sub {
|
|
my $req = "GET http://$HOST:$PORT/ HTTP/1.0\r\nKeep-Alive: 300\r\nOne: 1\r\nConnection: keep-alive, two, close\r\nTwo: 2\r\nThree: 3\r\n\r\n";
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp = HTTP::Request->parse($resp);
|
|
!defined($resp->header("Keep-Alive")) or die "Proxy did not remove Keep-Alive header field";
|
|
!defined($resp->header("Two")) or die "Proxy did not remove Two header field";
|
|
$resp->header("One") eq "1" or die "Proxy modified One header field";
|
|
$resp->header("Three") eq "3" or die "Proxy modified Three header field";
|
|
};
|
|
|
|
proxy_test_raw "HTTP GET combine multiple headers with the same name",
|
|
[], [], [], sub {
|
|
my $req = "GET http://$HOST:$PORT/ HTTP/1.0\r\nConnection: keep-alive\r\nKeep-Alive: 300\r\nConnection: two\r\nOne: 1\r\nConnection: close\r\nTwo: 2\r\nThree: 3\r\n\r\n";
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp = HTTP::Request->parse($resp);
|
|
!defined($resp->header("Keep-Alive")) or die "Proxy did not remove Keep-Alive header field";
|
|
!defined($resp->header("Two")) or die "Proxy did not remove Keep-Alive header field";
|
|
$resp->header("One") eq "1" or die "Proxy modified One header field";
|
|
$resp->header("Three") eq "3" or die "Proxy modified Three header field";
|
|
};
|
|
|
|
# RFC 2616 section 5.1.2: "In order to avoid request loops, a proxy MUST be able
|
|
# to recognize all of its server names, including any aliases, local variations,
|
|
# and the numeric IP address."
|
|
server_client_test "HTTP GET request loop",
|
|
["--proxy-type", "http"], [], sub {
|
|
my $req = http_request("GET", "http://$HOST:$PORT/");
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 403 or die "Expected response code 403, got $code";
|
|
};
|
|
|
|
server_client_test "HTTP GET IPv6 request loop",
|
|
["-6", "--proxy-type", "http"], ["-6"], sub {
|
|
my $req = http_request("GET", "http://[$IPV6_ADDR]:$PORT/");
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 403 or die "Expected response code 403, got $code";
|
|
};
|
|
|
|
proxy_test_raw "HTTP HEAD absolute URI",
|
|
[], [], [], sub {
|
|
my $req = http_request("HEAD", "http://$HOST:$PORT/");
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp = HTTP::Request->parse($resp);
|
|
$resp->method eq "HEAD" or die "Proxy sent \"" . $resp->method . "\"";
|
|
};
|
|
|
|
proxy_test_raw "HTTP POST",
|
|
[], [], [], sub {
|
|
my $req = "POST http://$HOST:$PORT/ HTTP/1.0\r\nContent-Length: 4\r\n\r\nabc\n";
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp = HTTP::Request->parse($resp);
|
|
$resp->method eq "POST" or die "Proxy sent \"" . $resp->method . "\"";
|
|
$resp->content eq "abc\n" or die "Proxy sent \"" . $resp->content . "\"";
|
|
};
|
|
|
|
proxy_test_raw "HTTP POST Content-Length: 0",
|
|
[], [], [], sub {
|
|
my $req = "POST http://$HOST:$PORT/ HTTP/1.0\r\nContent-Length: 0\r\n\r\n";
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp = HTTP::Request->parse($resp);
|
|
$resp->method eq "POST" or die "Proxy sent \"" . $resp->method . "\"";
|
|
$resp->content eq "" or die "Proxy sent \"" . $resp->content . "\"";
|
|
};
|
|
|
|
proxy_test_raw "HTTP POST short Content-Length",
|
|
[], [], [], sub {
|
|
my $req = "POST http://$HOST:$PORT/ HTTP/1.0\r\nContent-Length: 2\r\n\r\nabc\n";
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp = HTTP::Request->parse($resp);
|
|
$resp->method eq "POST" or die "Proxy sent \"" . $resp->method . "\"";
|
|
$resp->content eq "ab" or die "Proxy sent \"" . $resp->content . "\"";
|
|
};
|
|
|
|
proxy_test_raw "HTTP POST long Content-Length",
|
|
[], [], [], sub {
|
|
my $req = "POST http://$HOST:$PORT/ HTTP/1.0\r\nContent-Length: 10\r\n\r\nabc\n";
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($s_out) or die "Read timeout";
|
|
$resp = HTTP::Request->parse($resp);
|
|
$resp->method eq "POST" or die "Proxy sent \"" . $resp->method . "\"";
|
|
$resp->content eq "abc\n" or die "Proxy sent \"" . $resp->content . "\"";
|
|
};
|
|
|
|
proxy_test_raw "HTTP POST chunked transfer encoding",
|
|
[], [], [], sub {
|
|
my $req = "POST http://$HOST:$PORT/ HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nabc\n0\r\n";
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($s_out);
|
|
# We expect the proxy to relay the request or else die with an error
|
|
# saying it can't do it.
|
|
if ($resp) {
|
|
$resp = HTTP::Request->parse($resp);
|
|
$resp->method eq "POST" or die "Proxy sent \"" . $resp->method . "\"";
|
|
$resp->content eq "abc\n" or die "Proxy sent \"" . $resp->content . "\"";
|
|
} else {
|
|
$resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp = HTTP::Response->parse($resp);
|
|
$resp->code == 400 or $resp->code == 411 or die "Proxy returned code " . $resp->code;
|
|
}
|
|
};
|
|
|
|
proxy_test_raw "HTTP POST chunked transfer encoding, no data",
|
|
[], [], [], sub {
|
|
my $req = "POST http://$HOST:$PORT/ HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n";
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($s_out);
|
|
if ($resp) {
|
|
$resp = HTTP::Request->parse($resp);
|
|
$resp->method eq "POST" or die "Proxy sent \"" . $resp->method . "\"";
|
|
$resp->content eq "" or die "Proxy sent \"" . $resp->content . "\"";
|
|
} else {
|
|
$resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp = HTTP::Response->parse($resp);
|
|
$resp->code == 400 or $resp->code == 411 or die "Proxy returned code " . $resp->code;
|
|
}
|
|
};
|
|
|
|
server_client_test "HTTP proxy unknown method",
|
|
["--proxy-type", "http"], [], sub {
|
|
# Supposed to have a port number.
|
|
my $req = http_request("NOTHING", "http://$HOST:$PORT/");
|
|
syswrite($c_in, $req);
|
|
close($c_in);
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
my $code = HTTP::Response->parse($resp)->code;
|
|
$code == 405 or die "Expected response code 405, got $code";
|
|
};
|
|
|
|
# Check that proxy auth is base64 encoded properly. 's' and '~' are 0x77 and
|
|
# 0x7E respectively, printing characters with many bits set.
|
|
for my $auth ("", "a", "a:", ":a", "user:sss", "user:ssss", "user:sssss", "user:~~~", "user:~~~~", "user:~~~~~") {
|
|
server_client_test "HTTP proxy auth base64 encoding: \"$auth\"",
|
|
["-k"], ["--proxy", "$HOST:$PORT", "--proxy-type", "http", "--proxy-auth", $auth], sub {
|
|
my $resp = timeout_read($s_out) or die "Read timeout";
|
|
syswrite($s_in, "HTTP/1.0 407 Auth\r\nProxy-Authenticate: Basic realm=\"Ncat\"\r\n\r\n");
|
|
$resp = timeout_read($s_out) or die "Read timeout";
|
|
my $auth_header = HTTP::Response->parse($resp)->header("Proxy-Authorization") or die "Proxy client didn't send Proxy-Authorization header field";
|
|
my ($b64_auth) = ($auth_header =~ /^Basic (.*)/) or die "No auth data in \"$auth_header\"";
|
|
my $dec_auth = decode_base64($b64_auth);
|
|
$auth eq $dec_auth or die "Proxy client sent \"$b64_auth\" for \"$auth\", decodes to \"$dec_auth\"";
|
|
};
|
|
}
|
|
|
|
server_client_test_multi ["tcp", "tcp ssl"], "HTTP proxy server auth challenge",
|
|
["--proxy-type", "http", "--proxy-auth", "user:pass"],
|
|
[],
|
|
sub {
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\n\r\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp = HTTP::Response->parse($resp);
|
|
my $code = $resp->code;
|
|
$code == 407 or die "Expected response code 407, got $code";
|
|
my $auth = $resp->header("Proxy-Authenticate");
|
|
$auth or die "Proxy server didn't send Proxy-Authenticate header field";
|
|
};
|
|
|
|
server_client_test_multi ["tcp", "tcp ssl"], "HTTP proxy server correct auth",
|
|
["--proxy-type", "http", "--proxy-auth", "user:pass"],
|
|
[],
|
|
sub {
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\n");
|
|
syswrite($c_in, "Proxy-Authorization: Basic " . encode_base64("user:pass") . "\r\n");
|
|
syswrite($c_in, "\r\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp = HTTP::Response->parse($resp);
|
|
my $code = $resp->code;
|
|
$code == 200 or die "Expected response code 200, got $code";
|
|
};
|
|
|
|
server_client_test_multi ["tcp", "tcp ssl"], "HTTP proxy Basic wrong user",
|
|
["--proxy-type", "http", "--proxy-auth", "user:pass"],
|
|
[],
|
|
sub {
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\n");
|
|
syswrite($c_in, "Proxy-Authorization: Basic " . encode_base64("nobody:pass") . "\r\n");
|
|
syswrite($c_in, "\r\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp = HTTP::Response->parse($resp);
|
|
my $code = $resp->code;
|
|
$code == 407 or die "Expected response code 407, got $code";
|
|
};
|
|
|
|
server_client_test_multi ["tcp", "tcp ssl"], "HTTP proxy Basic wrong pass",
|
|
["--proxy-type", "http", "--proxy-auth", "user:pass"],
|
|
[],
|
|
sub {
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\n");
|
|
syswrite($c_in, "Proxy-Authorization: Basic " . encode_base64("user:word") . "\r\n");
|
|
syswrite($c_in, "\r\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp = HTTP::Response->parse($resp);
|
|
my $code = $resp->code;
|
|
$code == 407 or die "Expected response code 407, got $code";
|
|
};
|
|
|
|
server_client_test_multi ["tcp", "tcp ssl"], "HTTP proxy Basic correct auth, different case",
|
|
["--proxy-type", "http", "--proxy-auth", "user:pass"],
|
|
[],
|
|
sub {
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\n");
|
|
syswrite($c_in, "pROXY-aUTHORIZATION: BASIC " . encode_base64("user:pass") . "\r\n");
|
|
syswrite($c_in, "\r\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp = HTTP::Response->parse($resp);
|
|
my $code = $resp->code;
|
|
$code == 200 or die "Expected response code 200, got $code";
|
|
};
|
|
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--proxy-type", "http", "--proxy-auth", "user:pass");
|
|
test "HTTP proxy Digest wrong user",
|
|
sub {
|
|
my ($c_pid, $c_out, $c_in) = ncat_client();
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\n\r\n");
|
|
my $resp = timeout_read($c_out);
|
|
$resp or die "No response from server";
|
|
$resp = HTTP::Response->parse($resp);
|
|
foreach my $hdr ($resp->header("Proxy-Authenticate")) {
|
|
my ($scheme, %attrs) = parse_proxy_header($hdr);
|
|
next if $scheme ne "Digest";
|
|
die "no nonce" if not $attrs{"nonce"};
|
|
die "no realm" if not $attrs{"realm"};
|
|
my ($c_pid, $c_out, $c_in) = ncat_client();
|
|
my $response = digest_response("xxx", "pass", $attrs{"realm"}, "CONNECT", "$HOST:$PORT", $attrs{"nonce"}, undef, undef, undef);
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\
|
|
Proxy-Authorization: Digest username=\"xxx\", realm=\"$attrs{realm}\", nonce=\"$attrs{nonce}\", uri=\"$HOST:$PORT\", response=\"$response\"\r\n\r\n");
|
|
$resp = timeout_read($c_out);
|
|
$resp or die "No response from server";
|
|
$resp = HTTP::Response->parse($resp);
|
|
my $code = $resp->code;
|
|
$resp->code == 407 or die "Expected response code 407, got $code";
|
|
return 1;
|
|
}
|
|
die "No Proxy-Authenticate: Digest in server response";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--proxy-type", "http", "--proxy-auth", "user:pass");
|
|
test "HTTP proxy Digest wrong pass",
|
|
sub {
|
|
my ($c_pid, $c_out, $c_in) = ncat_client();
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\n\r\n");
|
|
my $resp = timeout_read($c_out);
|
|
$resp or die "No response from server";
|
|
$resp = HTTP::Response->parse($resp);
|
|
foreach my $hdr ($resp->header("Proxy-Authenticate")) {
|
|
my ($scheme, %attrs) = parse_proxy_header($hdr);
|
|
next if $scheme ne "Digest";
|
|
die "no nonce" if not $attrs{"nonce"};
|
|
die "no realm" if not $attrs{"realm"};
|
|
my ($c_pid, $c_out, $c_in) = ncat_client();
|
|
my $response = digest_response("user", "xxx", $attrs{"realm"}, "CONNECT", "$HOST:$PORT", $attrs{"nonce"}, undef, undef, undef);
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\
|
|
Proxy-Authorization: Digest username=\"user\", realm=\"$attrs{realm}\", nonce=\"$attrs{nonce}\", uri=\"$HOST:$PORT\", response=\"$response\"\r\n\r\n");
|
|
$resp = timeout_read($c_out);
|
|
$resp or die "No response from server";
|
|
$resp = HTTP::Response->parse($resp);
|
|
my $code = $resp->code;
|
|
$resp->code == 407 or die "Expected response code 407, got $code";
|
|
return 1;
|
|
}
|
|
die "No Proxy-Authenticate: Digest in server response";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--proxy-type", "http", "--proxy-auth", "user:pass");
|
|
test "HTTP proxy Digest correct auth",
|
|
sub {
|
|
my ($c_pid, $c_out, $c_in) = ncat_client();
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\n\r\n");
|
|
my $resp = timeout_read($c_out);
|
|
$resp or die "No response from server";
|
|
$resp = HTTP::Response->parse($resp);
|
|
foreach my $hdr ($resp->header("Proxy-Authenticate")) {
|
|
my ($scheme, %attrs) = parse_proxy_header($hdr);
|
|
next if $scheme ne "Digest";
|
|
die "no nonce" if not $attrs{"nonce"};
|
|
die "no realm" if not $attrs{"realm"};
|
|
my ($c_pid, $c_out, $c_in) = ncat_client();
|
|
my $response = digest_response("user", "pass", $attrs{"realm"}, "CONNECT", "$HOST:$PORT", $attrs{"nonce"}, "auth", "00000001", "abcdefg");
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\
|
|
Proxy-Authorization: Digest username=\"user\", realm=\"$attrs{realm}\", nonce=\"$attrs{nonce}\", uri=\"$HOST:$PORT\", qop=\"auth\", nc=\"00000001\", cnonce=\"abcdefg\", response=\"$response\"\r\n\r\n");
|
|
$resp = timeout_read($c_out);
|
|
$resp or die "No response from server";
|
|
$resp = HTTP::Response->parse($resp);
|
|
my $code = $resp->code;
|
|
$resp->code == 200 or die "Expected response code 200, got $code";
|
|
return 1;
|
|
}
|
|
die "No Proxy-Authenticate: Digest in server response";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--proxy-type", "http", "--proxy-auth", "user:pass");
|
|
test "HTTP proxy Digest correct auth, no qop",
|
|
sub {
|
|
my ($c_pid, $c_out, $c_in) = ncat_client();
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\n\r\n");
|
|
my $resp = timeout_read($c_out);
|
|
$resp or die "No response from server";
|
|
$resp = HTTP::Response->parse($resp);
|
|
foreach my $hdr ($resp->header("Proxy-Authenticate")) {
|
|
my ($scheme, %attrs) = parse_proxy_header($hdr);
|
|
next if $scheme ne "Digest";
|
|
die "no nonce" if not $attrs{"nonce"};
|
|
die "no realm" if not $attrs{"realm"};
|
|
my ($c_pid, $c_out, $c_in) = ncat_client();
|
|
my $response = digest_response("user", "pass", $attrs{"realm"}, "CONNECT", "$HOST:$PORT", $attrs{"nonce"}, undef, undef, undef);
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\
|
|
Proxy-Authorization: Digest username=\"user\", realm=\"$attrs{realm}\", nonce=\"$attrs{nonce}\", uri=\"$HOST:$PORT\", response=\"$response\"\r\n\r\n");
|
|
$resp = timeout_read($c_out);
|
|
$resp or die "No response from server";
|
|
$resp = HTTP::Response->parse($resp);
|
|
my $code = $resp->code;
|
|
$resp->code == 200 or die "Expected response code 200, got $code";
|
|
return 1;
|
|
}
|
|
die "No Proxy-Authenticate: Digest in server response";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--proxy-type", "http", "--proxy-auth", "user:pass");
|
|
test "HTTP proxy Digest missing fields",
|
|
sub {
|
|
my ($c_pid, $c_out, $c_in) = ncat_client();
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\n\r\n");
|
|
my $resp = timeout_read($c_out);
|
|
$resp or die "No response from server";
|
|
$resp = HTTP::Response->parse($resp);
|
|
foreach my $hdr ($resp->header("Proxy-Authenticate")) {
|
|
my ($scheme, %attrs) = parse_proxy_header($hdr);
|
|
next if $scheme ne "Digest";
|
|
my ($c_pid, $c_out, $c_in) = ncat_client();
|
|
my $response = digest_response("user", "pass", $attrs{"realm"}, "CONNECT", "$HOST:$PORT", $attrs{"nonce"}, undef, undef, undef);
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\
|
|
Proxy-Authorization: Digest username=\"user\", nonce=\"$attrs{nonce}\", response=\"$response\"\r\n\r\n");
|
|
$resp = timeout_read($c_out);
|
|
$resp or die "No response from server";
|
|
$resp = HTTP::Response->parse($resp);
|
|
my $code = $resp->code;
|
|
$resp->code == 407 or die "Expected response code 407, got $code";
|
|
return 1;
|
|
}
|
|
die "No Proxy-Authenticate: Digest in server response";
|
|
};
|
|
kill_children;
|
|
|
|
{
|
|
local $xfail = 1;
|
|
($s_pid, $s_out, $s_in) = ncat_server("--proxy-type", "http", "--proxy-auth", "user:pass");
|
|
test "HTTP proxy Digest prevents replay",
|
|
sub {
|
|
my ($c_pid, $c_out, $c_in) = ncat_client();
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\n\r\n");
|
|
my $resp = timeout_read($c_out);
|
|
$resp or die "No response from server";
|
|
$resp = HTTP::Response->parse($resp);
|
|
foreach my $hdr ($resp->header("Proxy-Authenticate")) {
|
|
my ($scheme, %attrs) = parse_proxy_header($hdr);
|
|
next if $scheme ne "Digest";
|
|
die "no nonce" if not $attrs{"nonce"};
|
|
die "no realm" if not $attrs{"realm"};
|
|
my ($c_pid, $c_out, $c_in) = ncat_client();
|
|
my $response = digest_response("user", "pass", $attrs{"realm"}, "CONNECT", "$HOST:$PORT", $attrs{"nonce"}, "auth", "00000001", "abcdefg");
|
|
my $req = "CONNECT $HOST:$PORT HTTP/1.0\r\
|
|
Proxy-Authorization: Digest username=\"user\", realm=\"$attrs{realm}\", nonce=\"$attrs{nonce}\", uri=\"$HOST:$PORT\", qop=\"auth\", nc=\"00000001\", cnonce=\"abcdefg\", response=\"$response\"\r\n\r\n";
|
|
syswrite($c_in, $req);
|
|
$resp = timeout_read($c_out);
|
|
$resp or die "No response from server";
|
|
$resp = HTTP::Response->parse($resp);
|
|
my $code = $resp->code;
|
|
$resp->code == 200 or die "Expected response code 200, got $code";
|
|
syswrite($c_in, $req);
|
|
$resp = timeout_read($c_out);
|
|
if ($resp) {
|
|
$resp = HTTP::Response->parse($resp);
|
|
$code = $resp->code;
|
|
$resp->code == 407 or die "Expected response code 407, got $code";
|
|
}
|
|
return 1;
|
|
}
|
|
die "No Proxy-Authenticate: Digest in server response";
|
|
};
|
|
kill_children;
|
|
}
|
|
|
|
# Test that header field values can be split across lines with LWS.
|
|
server_client_test_multi ["tcp", "tcp ssl"], "HTTP proxy server LWS",
|
|
["--proxy-type", "http", "--proxy-auth", "user:pass"],
|
|
[],
|
|
sub {
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\n");
|
|
syswrite($c_in, "Proxy-Authorization:\t Basic \r\n\t \n dXNlcjpwYXNz\r\n");
|
|
syswrite($c_in, "\r\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp = HTTP::Response->parse($resp);
|
|
my $code = $resp->code;
|
|
$code == 200 or die "Expected response code 200, got $code";
|
|
};
|
|
|
|
server_client_test_multi ["tcp", "tcp ssl"], "HTTP proxy server LWS",
|
|
["--proxy-type", "http", "--proxy-auth", "user:pass"],
|
|
[],
|
|
sub {
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\n");
|
|
syswrite($c_in, "Proxy-Authorization: Basic\r\n dXNlcjpwYXNz\r\n");
|
|
syswrite($c_in, "\r\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp = HTTP::Response->parse($resp);
|
|
my $code = $resp->code;
|
|
$code == 200 or die "Expected response code 200, got $code";
|
|
};
|
|
|
|
server_client_test_multi ["tcp", "tcp ssl"], "HTTP proxy server no auth",
|
|
["--proxy-type", "http", "--proxy-auth", "user:pass"],
|
|
[],
|
|
sub {
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\n");
|
|
syswrite($c_in, "Proxy-Authorization: \r\n");
|
|
syswrite($c_in, "\r\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp = HTTP::Response->parse($resp);
|
|
my $code = $resp->code;
|
|
$code != 200 or die "Got unexpected 200 response";
|
|
};
|
|
|
|
server_client_test_multi ["tcp", "tcp ssl"], "HTTP proxy server broken auth",
|
|
["--proxy-type", "http", "--proxy-auth", "user:pass"],
|
|
[],
|
|
sub {
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\n");
|
|
syswrite($c_in, "Proxy-Authorization: French fries\r\n");
|
|
syswrite($c_in, "\r\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp = HTTP::Response->parse($resp);
|
|
my $code = $resp->code;
|
|
$code != 200 or die "Got unexpected 200 response";
|
|
};
|
|
|
|
server_client_test_multi ["tcp", "tcp ssl"], "HTTP proxy server extra auth",
|
|
["--proxy-type", "http", "--proxy-auth", "user:pass"],
|
|
[],
|
|
sub {
|
|
syswrite($c_in, "CONNECT $HOST:$PORT HTTP/1.0\r\n");
|
|
syswrite($c_in, "Proxy-Authorization: Basic " . encode_base64("user:pass") . " extra\r\n");
|
|
syswrite($c_in, "\r\n");
|
|
my $resp = timeout_read($c_out) or die "Read timeout";
|
|
$resp = HTTP::Response->parse($resp);
|
|
my $code = $resp->code;
|
|
$code != 200 or die "Got unexpected 200 response";
|
|
};
|
|
|
|
# Allow and deny list tests.
|
|
|
|
server_client_test_all "Allow localhost (IPv4 address)",
|
|
["--allow", "127.0.0.1"], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
$resp or die "Read timeout";
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
|
|
server_client_test_all "Allow localhost (host name)",
|
|
["--allow", "localhost"], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
$resp or die "Read timeout";
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
|
|
# Anyone not allowed is denied.
|
|
server_client_test_all "Allow non-localhost",
|
|
["--allow", "1.2.3.4"], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
!$resp or die "Server did not reject host not in allow list";
|
|
};
|
|
|
|
# --allow options should accumulate.
|
|
server_client_test_all "--allow options accumulate",
|
|
["--allow", "127.0.0.1", "--allow", "1.2.3.4"], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
$resp or die "Read timeout";
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
|
|
server_client_test_all "Deny localhost (IPv4 address)",
|
|
["--deny", "127.0.0.1"], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
!$resp or die "Server did not reject host in deny list";
|
|
};
|
|
|
|
server_client_test_all "Deny localhost (host name)",
|
|
["--deny", "localhost"], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
!$resp or die "Server did not reject host in deny list";
|
|
};
|
|
|
|
# Anyone not denied is allowed.
|
|
server_client_test_all "Deny non-localhost",
|
|
["--deny", "1.2.3.4"], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
$resp or die "Read timeout";
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
|
|
# --deny options should accumulate.
|
|
server_client_test_all "--deny options accumulate",
|
|
["--deny", "127.0.0.1", "--deny", "1.2.3.4"], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
!$resp or die "Server did not reject host in deny list";
|
|
};
|
|
|
|
# If a host is both allowed and denied, denial takes precedence.
|
|
server_client_test_all "Allow and deny",
|
|
["--allow", "127.0.0.1", "--deny", "127.0.0.1"], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
!$resp or die "Server did not reject host in deny list";
|
|
};
|
|
|
|
# Test that --allowfile and --denyfile handle blank lines and more than one
|
|
# specification per line.
|
|
for my $contents (
|
|
"1.2.3.4
|
|
|
|
localhost",
|
|
"1.2.3.4 localhost"
|
|
) {
|
|
my ($fh, $filename) = tempfile("ncat-test-XXXXX", SUFFIX => ".txt");
|
|
print $fh $contents;
|
|
server_client_test_all "--allowfile",
|
|
["--allowfile", $filename], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
$resp or die "Read timeout";
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
server_client_test_all "--denyfile",
|
|
["--denyfile", $filename], [], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
!$resp or die "Server did not reject host in --denyfile list";
|
|
};
|
|
unlink $filename;
|
|
}
|
|
|
|
# Test --ssl sending.
|
|
server_client_test "SSL server relays",
|
|
["--ssl", "--ssl-key", "test-cert.pem", "--ssl-cert", "test-cert.pem"], ["--ssl"], sub {
|
|
my $resp;
|
|
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
$resp or die "Read timeout";
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c_out);
|
|
$resp or die "Read timeout";
|
|
$resp eq "abc\n" or die "Client got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
|
|
# Test that an SSL server gracefully handles non-SSL connections.
|
|
($s_pid, $s_out, $s_in) = ncat_server("--ssl", "--ssl-key", "test-cert.pem", "--ssl-cert", "test-cert.pem", "--keep-open");
|
|
test "SSL server handles non-SSL connections",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client();
|
|
syswrite($c1_in, "abc\n");
|
|
kill "TERM", $c1_pid;
|
|
waitpid $c1_pid, 0;
|
|
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client("--ssl");
|
|
syswrite($c2_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
kill "TERM", $c2_pid;
|
|
waitpid $c2_pid, 0;
|
|
};
|
|
kill_children;
|
|
|
|
{
|
|
($s_pid, $s_out, $s_in) = ncat_server("--ssl", "--ssl-key", "test-cert.pem", "--ssl-cert", "test-cert.pem");
|
|
test "SSL server doesn't block during handshake",
|
|
sub {
|
|
my $resp;
|
|
|
|
# Connect without SSL so the handshake isn't completed.
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client();
|
|
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client("--ssl");
|
|
syswrite($c2_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
!$resp or die "Server is still accepting connections.";
|
|
};
|
|
kill_children;
|
|
}
|
|
|
|
{
|
|
($s_pid, $s_out, $s_in) = ncat_server("--ssl", "--ssl-key", "test-cert.pem", "--ssl-cert", "test-cert.pem", "--keep-open");
|
|
test "SSL server doesn't block during handshake(--keep-open)",
|
|
sub {
|
|
my $resp;
|
|
|
|
# Connect without SSL so the handshake isn't completed.
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client();
|
|
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client("--ssl");
|
|
syswrite($c2_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
kill_children;
|
|
}
|
|
{
|
|
($s_pid, $s_out, $s_in) = ncat_server("--ssl", "--exec","$PERL -e \$|=1;while(<>)\{tr/a-z/A-Z/;print\}", "--ssl-key", "test-cert.pem", "--ssl-cert", "test-cert.pem", "--keep-open");
|
|
test "SSL --exec server doesn't block during handshake",
|
|
sub {
|
|
my $resp;
|
|
|
|
# Connect without SSL so the handshake isn't completed.
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client();
|
|
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client("--ssl");
|
|
syswrite($c2_in, "abc\n");
|
|
|
|
$resp = timeout_read($c2_out);
|
|
$resp eq "ABC\n" or die "Client2 got \"$resp\", not \"ABC\\n\"";
|
|
};
|
|
kill_children;
|
|
}
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--ssl", "--ssl-key", "test-cert.pem", "--ssl-cert", "test-cert.pem");
|
|
test "SSL verification, correct domain name",
|
|
sub {
|
|
my $resp;
|
|
|
|
($c_pid, $c_out, $c_in) = ncat("localhost", $PORT, "--ssl-verify", "--ssl-trustfile", "test-cert.pem");
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
$resp or die "Read timeout";
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--ssl", "--ssl-key", "test-cert.pem", "--ssl-cert", "test-cert.pem");
|
|
test "SSL verification, wrong domain name",
|
|
sub {
|
|
my $resp;
|
|
|
|
# Use the IPv6 address as an alternate name that doesn't match the one
|
|
# on the certificate.
|
|
($c_pid, $c_out, $c_in) = ncat($IPV6_ADDR, $PORT, "-6", "--ssl-verify", "--ssl-trustfile", "test-cert.pem");
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
!$resp or die "Server got \"$resp\" when verification should have failed";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--ssl");
|
|
test "SSL verification, no server cert",
|
|
sub {
|
|
my $resp;
|
|
|
|
($c_pid, $c_out, $c_in) = ncat("localhost", $PORT, "--ssl-verify", "--ssl-trustfile", "test-cert.pem");
|
|
syswrite($c_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
!$resp or die "Server got \"$resp\" when verification should have failed";
|
|
};
|
|
kill_children;
|
|
|
|
# Test --max-conns.
|
|
($s_pid, $s_out, $s_in) = ncat_server("--keep-open", "--max-conns", "1");
|
|
test "--keep-open server keeps connection count properly.",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client();
|
|
kill "TERM", $c1_pid;
|
|
waitpid $c1_pid, 0;
|
|
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client();
|
|
syswrite($c2_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--broker", "--max-conns", "1");
|
|
test "--broker server keeps connection count properly.",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client();
|
|
kill "TERM", $c1_pid;
|
|
waitpid $c1_pid, 0;
|
|
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client();
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c2_out);
|
|
$resp eq "abc\n" or die "Second client got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--ssl", "--ssl-key", "test-cert.pem", "--ssl-cert", "test-cert.pem", "--keep-open", "--max-conns", "1");
|
|
test "SSL --keep-open server keeps connection count properly.",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client();
|
|
kill "TERM", $c1_pid;
|
|
waitpid $c1_pid, 0;
|
|
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client("--ssl");
|
|
syswrite($c2_in, "abc\n");
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--ssl", "--ssl-key", "test-cert.pem", "--ssl-cert", "test-cert.pem", "--broker", "--max-conns", "1");
|
|
test "SSL --broker server keeps connection count properly.",
|
|
sub {
|
|
my $resp;
|
|
|
|
my ($c1_pid, $c1_out, $c1_in) = ncat_client();
|
|
syswrite($c1_in, "abc\n");
|
|
kill "TERM", $c1_pid;
|
|
waitpid $c1_pid, 0;
|
|
|
|
my ($c2_pid, $c2_out, $c2_in) = ncat_client("--ssl");
|
|
syswrite($s_in, "abc\n");
|
|
$resp = timeout_read($c2_out);
|
|
$resp eq "abc\n" or die "Second client got \"$resp\", not \"abc\\n\"";
|
|
};
|
|
kill_children;
|
|
|
|
# expand IPv6
|
|
sub ipv6_expand {
|
|
local($_) = shift;
|
|
s/^:/0:/;
|
|
s/:$/:0/;
|
|
s/(^|:)([^:]{1,3})(?=:|$)/$1.substr("0000$2", -4)/ge;
|
|
my $c = tr/:/:/;
|
|
s/::/":".("0000:" x (8-$c))/e;
|
|
return $_;
|
|
}
|
|
sub socks5_auth {
|
|
my ($pid,$code);
|
|
my $buf="";
|
|
my @Barray;
|
|
my $auth_data = shift;
|
|
my $ipvx = shift;
|
|
my $dest_addr = shift;
|
|
my $passed = 0;
|
|
|
|
my $username= "";
|
|
my $passwd= "";
|
|
my $recv_addr = "";
|
|
my $recv_port;
|
|
|
|
my ($pf,$s_addr);
|
|
|
|
local $SIG{CHLD} = sub { };
|
|
local *SOCK;
|
|
local *S;
|
|
|
|
if ($ipvx eq -4) {
|
|
$pf = PF_INET;
|
|
$s_addr = sockaddr_in($PROXY_PORT, INADDR_ANY);
|
|
} else {
|
|
$pf = PF_INET6;
|
|
$s_addr = sockaddr_in6($PROXY_PORT, inet_pton(PF_INET6, "::1"));
|
|
}
|
|
|
|
|
|
socket(SOCK, $pf, SOCK_STREAM, getprotobyname("tcp")) or die;
|
|
setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die;
|
|
bind(SOCK, $s_addr) or die;
|
|
listen(SOCK, 1) or die;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("--proxy-type", "socks5", "--proxy", "localhost:$PROXY_PORT", @$auth_data, $ipvx, $dest_addr, $PORT);
|
|
|
|
accept(S, SOCK) or die "Client not connected";
|
|
binmode(S);
|
|
sysread(S, $buf, 10) or die "Connection closed";
|
|
|
|
@Barray = map hex($_), unpack("H*", $buf) =~ /(..)/g;
|
|
die "wrong request format" if scalar(@Barray) < 3;
|
|
die "wrong protocol version" if $Barray[0] != 5;
|
|
|
|
if(scalar(@$auth_data) > 0) {
|
|
# subnegotiation for authentication
|
|
for(my $i=2; $i < scalar(@Barray); $i++) {
|
|
if($Barray[$i] == 2) {
|
|
$passed = 1;
|
|
}
|
|
}
|
|
|
|
die "Client did not sent required authentication method x02" if $passed == 0;
|
|
|
|
|
|
send(S, "\x05\x02",0) or die "Send: Connection closed";
|
|
sysread(S, $buf, $BUFSIZ) or die "Read: Connection closed";
|
|
|
|
@Barray = map hex($_), unpack("H*", $buf) =~ /(..)/g;
|
|
die "wrong request format - small length" if scalar(@Barray) < 5;
|
|
die "wrong request format - wrong version" if $Barray[0] != 1;
|
|
die "wrong request format - username legth longer then packet size"
|
|
if $Barray[1] >= scalar(@Barray);
|
|
|
|
# get username
|
|
for (my $i=2; $i < $Barray[1]+2; $i++) {
|
|
$username .= chr($Barray[$i]);
|
|
}
|
|
|
|
#get password
|
|
for (my $i=3+$Barray[1]; $i < scalar(@Barray); $i++) {
|
|
$passwd .= chr($Barray[$i]);
|
|
}
|
|
|
|
if ($username ne "vasek" or $passwd ne "admin") {
|
|
send(S, "\x01\x11", 0);
|
|
# do not close connection - we can check if client try continue
|
|
} else {
|
|
send(S, "\x01\x00",0);
|
|
}
|
|
} else {
|
|
# no authentication
|
|
send(S, "\x05\x00",0) or die "Send: Connection closed";
|
|
|
|
}
|
|
|
|
sysread(S, $buf, $BUFSIZ) or die "Read: connection closed";
|
|
|
|
@Barray = map hex($_), unpack("H*", $buf) =~ /(..)/g;
|
|
die "wrong request length format" if scalar(@Barray) < 10;
|
|
die "wrong protocol version after success authentication" if $Barray[0] != 5;
|
|
die "expected connect cmd" if $Barray[1] != 1;
|
|
|
|
if($Barray[3] == 1) {
|
|
# IPv4
|
|
|
|
$recv_addr = $Barray[4] .".". $Barray[5] .".". $Barray[6] .".". $Barray[7];
|
|
die "received wrong destination IPv4" if $recv_addr ne $dest_addr;
|
|
} elsif ($Barray[3] == 4) {
|
|
#IPv6
|
|
|
|
for(my $i=4; $i<20;$i++) {
|
|
if($i > 4 and $i % 2 == 0) {
|
|
$recv_addr .= ":";
|
|
}
|
|
$recv_addr .= sprintf("%02X",$Barray[$i]);
|
|
}
|
|
|
|
die "received wrong destination IPv6" if $recv_addr ne ipv6_expand($dest_addr);
|
|
} elsif ($Barray[3] == 3) {
|
|
# domaint name
|
|
|
|
for my $i (@Barray[5..(scalar(@Barray)-3)]) {
|
|
$recv_addr .= chr($i);
|
|
}
|
|
die "received wrong destination domain name" if $recv_addr ne $dest_addr;
|
|
die "received wrong length of domain name" if length($recv_addr) != $Barray[4];
|
|
} else {
|
|
die "unknown ATYP: $Barray[3]";
|
|
}
|
|
|
|
$recv_port = $Barray[-2]*256 + $Barray[-1];
|
|
die "received wrong destination port" if $recv_port ne $PORT;
|
|
|
|
send(S, "\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00", 0);
|
|
|
|
# check if connection is still open
|
|
syswrite($c_in, "abc\n");
|
|
sysread(S, $buf, 10) or die "Connection closed";
|
|
|
|
|
|
close(S);
|
|
close(SOCK);
|
|
};
|
|
|
|
|
|
test "SOCKS5 client, server require auth username/password (access allowed), IPv4",
|
|
sub { socks5_auth(["--proxy-auth","vasek:admin"], "-4", "127.0.0.1"); };
|
|
kill_children;
|
|
|
|
test "SOCKS5 client, server require auth username/password (access allowed), IPv6",
|
|
sub { socks5_auth(["--proxy-auth","vasek:admin"], "-6", "::1"); };
|
|
kill_children;
|
|
|
|
test "SOCKS5 client, server require auth username/password (access allowed), domain",
|
|
sub { socks5_auth(["--proxy-auth","vasek:admin"], "-4", "www.seznam.cz"); };
|
|
kill_children;
|
|
|
|
test "SOCKS5 client, server allows connection - no auth",
|
|
sub { socks5_auth([], "-4", "127.0.0.1")};
|
|
kill_children;
|
|
{
|
|
local $xfail = 1;
|
|
test "SOCKS5 client, server require auth username/password (access denied)",
|
|
sub { socks5_auth(["--proxy-auth","klara:admin"], "-4", "127.0.0.1"); };
|
|
kill_children;
|
|
|
|
test "SOCKS5 client, server require auth username/password (too long login)",
|
|
sub { socks5_auth(["--proxy-auth",'monika' x 100 . ':admindd'], "-4", "127.0.0.1");};
|
|
kill_children;
|
|
}
|
|
|
|
{
|
|
local $xfail = 1;
|
|
test "SOCKS5 client, server sends short response",
|
|
sub {
|
|
my ($pid,$code);
|
|
my $buf="";
|
|
local $SIG{CHLD} = sub { };
|
|
local *SOCK;
|
|
local *S;
|
|
|
|
socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die;
|
|
setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die;
|
|
bind(SOCK, sockaddr_in($PROXY_PORT, INADDR_ANY)) or die;
|
|
listen(SOCK, 1) or die;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("-4","--proxy-type", "socks5", "--proxy", "$HOST:$PROXY_PORT", "127.0.0.1", $PORT);
|
|
|
|
accept(S, SOCK) or die "Client not connected";
|
|
binmode(S);
|
|
sysread(S, $buf, 10) or die "Connection closed";
|
|
# not important received data now,
|
|
# when we know that's ok from test above
|
|
|
|
# we need O_NONBLOCK for read/write actions else
|
|
# client block us until we kill process manually
|
|
fcntl(S, F_SETFL, O_NONBLOCK) or
|
|
die "Can't set flags for the socket: $!\n";
|
|
send(S, "\x05", 0) or die "Send: Connection closed";
|
|
|
|
sysread(S, $buf, $BUFSIZ) or die "Connection closed";
|
|
|
|
close(S);
|
|
close(SOCK);
|
|
};
|
|
kill_children;
|
|
}
|
|
|
|
{
|
|
local $xfail = 1;
|
|
test "SOCKS5 client, server sends no acceptable auth method",
|
|
sub {
|
|
my ($pid,$code);
|
|
my $buf="";
|
|
my ($my_addr,$recv_addr,$recv_port);
|
|
|
|
local $SIG{CHLD} = sub { };
|
|
local *SOCK;
|
|
local *S;
|
|
|
|
socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die;
|
|
setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die;
|
|
bind(SOCK, sockaddr_in($PROXY_PORT, INADDR_ANY)) or die;
|
|
listen(SOCK, 1) or die;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("-4","--proxy-type", "socks5", "--proxy", "$HOST:$PROXY_PORT", "127.0.0.1", $PORT);
|
|
|
|
accept(S, SOCK) or die "Client not connected";
|
|
binmode(S);
|
|
sysread(S, $buf, 10) or die "Connection closed";
|
|
|
|
send(S, "\x05\xFF",0) or die "Send: Connection closed";
|
|
sysread(S, $buf, $BUFSIZ) or die "Connection closed";
|
|
|
|
close(S);
|
|
close(SOCK);
|
|
};
|
|
kill_children;
|
|
}
|
|
|
|
{
|
|
local $xfail = 1;
|
|
test "SOCKS5 client, server sends unknown code",
|
|
sub {
|
|
my ($pid,$code);
|
|
my $buf="";
|
|
my ($my_addr,$recv_addr,$recv_port);
|
|
|
|
local $SIG{CHLD} = sub { };
|
|
local *SOCK;
|
|
local *S;
|
|
|
|
socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die;
|
|
setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die;
|
|
bind(SOCK, sockaddr_in($PROXY_PORT, INADDR_ANY)) or die;
|
|
listen(SOCK, 1) or die;
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat("-4","--proxy-type", "socks5", "--proxy", "$HOST:$PROXY_PORT", "127.0.0.1", $PORT);
|
|
|
|
accept(S, SOCK) or die "Client not connected";
|
|
binmode(S);
|
|
sysread(S, $buf, 10) or die "Connection closed";
|
|
|
|
send(S, "\x05\xAA",0) or die "Send: Connection closed";
|
|
sysread(S, $buf, $BUFSIZ) or die "Connection closed";
|
|
|
|
close(S);
|
|
close(SOCK);
|
|
};
|
|
kill_children;
|
|
}
|
|
|
|
for my $count (0, 1, 10) {
|
|
max_conns_test_tcp_sctp_ssl("--max-conns $count --keep-open", ["--keep-open"], [], $count);
|
|
}
|
|
|
|
for my $count (0, 1, 10) {
|
|
max_conns_test_tcp_ssl("--max-conns $count --broker", ["--broker"], [], $count);
|
|
}
|
|
|
|
max_conns_test_all("--max-conns 0 --keep-open with exec", ["--keep-open", "--exec", "$CAT"], [], 0);
|
|
for my $count (1, 10) {
|
|
max_conns_test_multi(["tcp", "sctp", "udp xfail", "tcp ssl", "sctp ssl"],
|
|
"--max-conns $count --keep-open with exec", ["--keep-open", "--exec", "$CAT"], [], $count);
|
|
}
|
|
|
|
# Tests for zero byte option.
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server();
|
|
test "-z client with Connect success exit code (tcp)",
|
|
sub {
|
|
my ($pid, $code);
|
|
local $SIG{CHLD} = sub { };
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat_client("-z");
|
|
|
|
do {
|
|
$pid = waitpid($c_pid, 0);
|
|
} while ($pid > 0 && $pid != $c_pid);
|
|
$pid == $c_pid or die "$pid != $c_pid";
|
|
$code = $? >> 8;
|
|
$code == 0 or die "Exit code was $code, not 0";
|
|
};
|
|
kill_children;
|
|
|
|
($s_pid, $s_out, $s_in) = ncat_server("--udp");
|
|
test "-z client sends \"\\0\" to server and exits with success exit code (udp)",
|
|
sub {
|
|
my ($resp, $pid, $code);
|
|
local $SIG{CHLD} = sub { };
|
|
|
|
my ($c_pid, $c_out, $c_in) = ncat_client("-z", "--udp");
|
|
$resp = timeout_read($s_out);
|
|
$resp eq "\0" or die "Server got \"$resp\", not \"\\0\" from client";
|
|
|
|
do {
|
|
$pid = waitpid($c_pid, 0);
|
|
} while ($pid > 0 && $pid != $c_pid);
|
|
$pid == $c_pid or die "$pid != $c_pid";
|
|
$code = $? >> 8;
|
|
$code == 0 or die "Exit code was $code, not 0";
|
|
};
|
|
kill_children;
|
|
|
|
test "-z client with connection refused exit code (tcp)",
|
|
sub {
|
|
my ($pid, $code);
|
|
local $SIG{CHLD} = sub { };
|
|
($c_pid, $c_out, $c_in) = ncat_client("-z");
|
|
|
|
do {
|
|
$pid = waitpid($c_pid, 0);
|
|
} while ($pid > 0 && $pid != $c_pid);
|
|
$pid == $c_pid or die "$pid != $c_pid";
|
|
$code = $? >> 8;
|
|
$code == 1 or die "Exit code was $code, not 1";
|
|
};
|
|
kill_children;
|
|
|
|
test "-z client with connection refused exit code (udp)",
|
|
sub {
|
|
my ($pid, $code);
|
|
local $SIG{CHLD} = sub { };
|
|
($c_pid, $c_out, $c_in) = ncat_client("-z", "--udp");
|
|
|
|
do {
|
|
$pid = waitpid($c_pid, 0);
|
|
} while ($pid > 0 && $pid != $c_pid);
|
|
$pid == $c_pid or die "$pid != $c_pid";
|
|
$code = $? >> 8;
|
|
$code == 1 or die "Exit code was $code, not 1";
|
|
};
|
|
kill_children;
|
|
|
|
# Without --keep-open, just make sure that --max-conns 0 disallows any connection.
|
|
max_conns_test_all("--max-conns 0", [], [], 0);
|
|
max_conns_test_all("--max-conns 0 with exec", ["--exec", "$CAT"], [], 0);
|
|
|
|
print "$num_expected_failures expected failures.\n" if $num_expected_failures > 0;
|
|
print "$num_unexpected_passes unexpected passes.\n" if $num_unexpected_passes > 0;
|
|
print "$num_failures unexpected failures.\n";
|
|
print "$num_tests tests total.\n";
|
|
|
|
if ($num_failures + $num_unexpected_passes == 0) {
|
|
exit 0;
|
|
} else {
|
|
exit 1;
|
|
}
|