summaryrefslogtreecommitdiffstats
path: root/ncat/test
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--ncat/test/addrset.c104
-rwxr-xr-xncat/test/ncat-test.pl3283
-rwxr-xr-xncat/test/test-addrset.sh333
-rw-r--r--ncat/test/test-cert.pem47
-rw-r--r--ncat/test/test-cmdline-split.c100
-rw-r--r--ncat/test/test-environment.lua13
-rwxr-xr-xncat/test/test-environment.sh11
-rw-r--r--ncat/test/test-uri.c129
-rw-r--r--ncat/test/test-wildcard.c626
-rw-r--r--ncat/test/toupper.lua14
10 files changed, 4660 insertions, 0 deletions
diff --git a/ncat/test/addrset.c b/ncat/test/addrset.c
new file mode 100644
index 0000000..61a0f0d
--- /dev/null
+++ b/ncat/test/addrset.c
@@ -0,0 +1,104 @@
+/*
+ Usage: ./addrset [<specification> ...]
+
+ This program tests the addrset functions in nbase/nbase_addrset.c,
+ the ones that maintain the lists of addresses for --allow and
+ --deny. It takes as arguments specifications that are added to an
+ addrset. It then reads whitespace-separated host names or IP
+ addresses from standard input and echoes only those that are in the
+ addrset.
+
+ David Fifield
+
+ Example:
+ $ echo "1.2.3.4 1.0.0.5 1.2.3.8" | ./addrset "1.2.3.10/24"
+ 1.2.3.4
+ 1.2.3.8
+*/
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "ncat_core.h"
+
+#ifdef WIN32
+#include "../nsock/src/error.h"
+#endif
+
+
+#ifdef WIN32
+static void win_init(void)
+{
+ WSADATA data;
+ int rc;
+
+ rc = WSAStartup(MAKEWORD(2,2), &data);
+ if (rc)
+ fatal("failed to start winsock: %s\n", socket_strerror(rc));
+}
+#endif
+
+static int resolve_name(const char *name, struct addrinfo **result)
+{
+ struct addrinfo hints = { 0 };
+
+ hints.ai_protocol = IPPROTO_TCP;
+ *result = NULL;
+
+ return getaddrinfo(name, NULL, &hints, result);
+}
+
+int main(int argc, char *argv[])
+{
+ struct addrset *set;
+ char line[1024];
+ int i;
+
+#ifdef WIN32
+ win_init();
+#endif
+
+ set = addrset_new();
+
+ options_init();
+
+ for (i = 1; i < argc; i++) {
+ if (!addrset_add_spec(set, argv[i], o.af, !o.nodns)) {
+ fprintf(stderr, "Error adding spec \"%s\".\n", argv[i]);
+ exit(1);
+ }
+ }
+
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ char *s, *hostname;
+ struct addrinfo *addrs;
+
+ s = line;
+ while ((hostname = strtok(s, " \t\n")) != NULL) {
+ int rc;
+
+ s = NULL;
+
+ rc = resolve_name(hostname, &addrs);
+ if (rc != 0) {
+ fprintf(stderr, "Error resolving \"%s\": %s.\n", hostname, gai_strerror(rc));
+ continue;
+ }
+ if (addrs == NULL) {
+ fprintf(stderr, "No addresses found for \"%s\".\n", hostname);
+ continue;
+ }
+
+ /* Check just the first address returned. */
+ if (addrset_contains(set, addrs->ai_addr))
+ printf("%s\n", hostname);
+
+ freeaddrinfo(addrs);
+ }
+ }
+
+ addrset_free(set);
+
+ return 0;
+}
diff --git a/ncat/test/ncat-test.pl b/ncat/test/ncat-test.pl
new file mode 100755
index 0000000..5a1c98e
--- /dev/null
+++ b/ncat/test/ncat-test.pl
@@ -0,0 +1,3283 @@
+#!/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;
+
+{ # If the cert has expired, generate a new one.
+ my $verify = `openssl verify -trusted test-cert.pem test-cert.pem`;
+ if ($verify =~ /error 10 at/) {
+ system("openssl req -new -x509 -nodes -subj /O=ncat-test/CN=localhost/ -keyout test-cert.pem -out test-cert.pem");
+ }
+}
+$| = 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 STDERR "RUN: " . 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;
+ }
+ #print STDERR "READ: $result\n";
+ 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($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;
+
+($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 not IPv4",
+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 not 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 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) = @_;
+ #print STDERR "$method $uri HTTP/1.0\r\n\r\n";
+ 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";
+};
+
+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";
+};
+
+proxy_test_raw "HTTP CONNECT IPv6 client, IPv4 server",
+[], ["-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 == 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;
+}
diff --git a/ncat/test/test-addrset.sh b/ncat/test/test-addrset.sh
new file mode 100755
index 0000000..819afe2
--- /dev/null
+++ b/ncat/test/test-addrset.sh
@@ -0,0 +1,333 @@
+#!/bin/sh
+
+# Automated tests for the addrset functions in ncat_hostmatch.c. This
+# program runs various addresses against different host specifications
+# and checks that the output is what is expected.
+
+ADDRSET=./addrset
+TESTS=0
+TEST_PASS=0
+TEST_FAIL=0
+
+# Takes as arguments a whitespace-separated list of host specifications
+# and a space-separated list of expected matching addresses. Tests hosts
+# are passed in stdin.
+test_addrset() {
+ specs=$1
+ expected=$2
+ result=$($ADDRSET $specs)
+ ret=$?
+ # Change newlines to spaces.
+ result=$(echo $result)
+ TESTS=$(expr $TESTS + 1);
+ if [ "$ret" != "0" ]; then
+ echo "FAIL $specs: $ADDRSET returned $ret."
+ TEST_FAIL=$(expr $TEST_FAIL + 1)
+ elif [ "$result" != "$expected" ]; then
+ echo "FAIL $specs: \"$result\" !="
+ echo " \"$expected\"."
+ TEST_FAIL=$(expr $TEST_FAIL + 1)
+ else
+ echo "PASS $specs"
+ TEST_PASS=$(expr $TEST_PASS + 1)
+ fi
+}
+
+# Takes as an argument a host specification with invalid syntax. The
+# test passes if addrset returns with a non-zero exit code.
+expect_fail() {
+ specs=$1
+ $ADDRSET $specs < /dev/null 2> /dev/null
+ ret=$?
+ TESTS=$(expr $TESTS + 1)
+ if [ "$ret" = "0" ]; then
+ echo "FAIL $ADDRSET $specs was expected to fail, but didn't."
+ TEST_FAIL=$(expr $TEST_FAIL + 1)
+ else
+ echo "PASS $specs"
+ TEST_PASS=$(expr $TEST_PASS + 1)
+ fi
+}
+
+# seq replacement for systems without seq.
+seq() {
+ low=$1
+ high=$2
+ while [ $low -le $high ]; do
+ echo $low
+ low=$(expr $low + 1)
+ done
+}
+
+# No specifications.
+test_addrset "" "" <<EOF
+1.1.1.1
+2.2.2.2
+EOF
+
+# IPv4 address equality.
+(for a in `seq 0 255`; do echo 192.168.0.$a; done) \
+ | test_addrset "192.168.0.0" "192.168.0.0"
+
+# IPv6 address equality.
+(for a in `seq 0 255`; do printf "FE80:0000:0000:0000:0202:E3%02X:FE14:1102\n" $a; done) \
+ | test_addrset "fe80::202:e3ff:fe14:1102" "FE80:0000:0000:0000:0202:E3FF:FE14:1102"
+
+# IPv4 and IPv6 at once.
+test_addrset "1.2.3.4 1:2:3::4" "1.2.3.4 1:2:3::4 1:2:3:0::4" <<EOF
+0.0.0.0
+1.2.3.4
+::
+1:2:3::4
+1:2:3:0::4
+f:e:d:c:b::a
+EOF
+
+# Simple IPv4 range.
+(for a in `seq 0 255`; do echo 192.168.0.$a; done) \
+ | test_addrset "192.168.0.1-5" "192.168.0.1 192.168.0.2 192.168.0.3 192.168.0.4 192.168.0.5"
+
+# Addresses outside IPv4 range.
+(for a in `seq 0 255`; do echo 192.168.0.$a; done) \
+ | test_addrset "192.168.1.1-5" ""
+
+# One-element range.
+(for a in `seq 0 255`; do echo 192.168.0.$a; done) \
+ | test_addrset "192.168-168.0.1" "192.168.0.1"
+
+# Double IPv4 ranges.
+(for a in `seq 0 255`; do echo 192.168.$a.$a; done) \
+ | test_addrset "192.168.3-8.1-5" "192.168.3.3 192.168.4.4 192.168.5.5"
+
+# Half-open range.
+(for a in `seq 0 255`; do echo 192.168.$a.0; done) \
+ | test_addrset "192.168.-3.0" "192.168.0.0 192.168.1.0 192.168.2.0 192.168.3.0"
+
+# Half-open range.
+(for a in `seq 0 255`; do echo 192.168.$a.0; done) \
+ | test_addrset "192.168.252-.0" "192.168.252.0 192.168.253.0 192.168.254.0 192.168.255.0"
+
+# Full-open range.
+test_addrset "192.168.-.0" "192.168.0.0 192.168.10.0 192.168.100.0 192.168.255.0" <<EOF
+192.168.0.0
+192.168.10.0
+192.168.100.0
+192.168.255.0
+192.168.0.1
+1.2.3.4
+EOF
+
+# Comma ranges.
+(for a in `seq 0 255`; do echo 192.168.0.$a; done) \
+ | test_addrset "192.168.0.2,3,5,7,11" "192.168.0.2 192.168.0.3 192.168.0.5 192.168.0.7 192.168.0.11"
+
+# Comma ranges combined with dash ranges.
+test_addrset "192-200,202.0.0.1,3-5" "202.0.0.1 202.0.0.5 192.0.0.3" <<EOF
+201.0.0.1
+202.0.0.1
+202.0.0.5
+202.0.0.6
+192.0.0.3
+EOF
+
+# Wildcard octet.
+test_addrset "192.168.0.*" "192.168.0.3 192.168.0.200 192.168.0.255" <<EOF
+1.2.3.4
+192.168.0.3
+192.168.0.200
+192.161.0.0
+192.168.0.255
+EOF
+
+# Two wildcards.
+test_addrset "192.*.0.*" "192.168.0.3 192.168.0.200 192.161.0.0 192.168.0.255" <<EOF
+1.2.3.4
+192.168.0.3
+192.168.0.200
+192.161.0.0
+192.168.0.255
+EOF
+
+# Many range types.
+test_addrset "*.1-10,12.*.4-5,6,7" "1.2.3.4 4.5.6.7 70.10.4.4" <<EOF
+1.2.3.4
+4.5.6.7
+70.11.4.4
+70.10.4.4
+255.255.255.255
+EOF
+
+# IPv4 CIDR netmask.
+test_addrset "192.168.0.0/24" "192.168.0.5 192.168.0.90" <<EOF
+192.168.0.5
+192.168.0.90
+192.168.1.5
+1.2.3.4
+EOF
+
+# /32 netmask.
+test_addrset "1.2.3.4/32" "1.2.3.4" <<EOF
+192.168.0.10
+192.168.0.90
+192.168.1.5
+1.2.3.4
+EOF
+
+# /0 netmask.
+test_addrset "5.5.5.5/0" "0.0.0.0 123.123.123.123 255.255.255.255" <<EOF
+0.0.0.0
+123.123.123.123
+255.255.255.255
+EOF
+
+# IPv4 range combined with CIDR netmask.
+test_addrset "1-5.1-5.1-5.1-5/28" "1.2.3.4 1.2.3.5 1.2.3.7 1.2.3.0" <<EOF
+1.2.3.4
+1.2.3.5
+6.1.2.3
+1.2.3.7
+1.2.3.0
+EOF
+
+# Exhaustive listing of a range with netmask.
+(for a in `seq 0 255`; do echo 192.168.0.$a; done) \
+ | test_addrset "192.168.0.5,30,191/30" \
+"192.168.0.4 192.168.0.5 192.168.0.6 192.168.0.7 192.168.0.28 192.168.0.29 192.168.0.30 192.168.0.31 192.168.0.188 192.168.0.189 192.168.0.190 192.168.0.191"
+
+# Exhaustive listing of a range with netmask, different octet.
+(for a in `seq 0 255`; do echo 192.168.$a.0; done) \
+ | test_addrset "192.168.5,30,191.0/22" \
+"192.168.4.0 192.168.5.0 192.168.6.0 192.168.7.0 192.168.28.0 192.168.29.0 192.168.30.0 192.168.31.0 192.168.188.0 192.168.189.0 192.168.190.0 192.168.191.0"
+
+# IPv6 CIDR netmask.
+test_addrset "1:2::0003/120" "1:2::3 1:2::0 1:2::ff" <<EOF
+1:2::3
+1:2::0
+1:2::ff
+1:2::1ff
+1:3::3
+EOF
+
+# IPv6 CIDR netmask.
+test_addrset "1:2::3:4:5/95" "1:2::3:4:5 1:2::2:0:0 1:2::3:ffff:ffff" <<EOF
+1:2::3:4:5
+1:2::1:ffff:ffff
+1:2::2:0:0
+1:2::3:ffff:ffff
+1:2::4:0:0
+1:3::3
+EOF
+
+# IPv6 CIDR netmask.
+test_addrset "11::2/15" "11::2:3:4:5 10::1 11:ffff:ffff:ffff:ffff:ffff:ffff:ffff" <<EOF
+11::2:3:4:5
+9:ffff:ffff:ffff:ffff:ffff:ffff:ffff
+10::1
+11:ffff:ffff:ffff:ffff:ffff:ffff:ffff
+12::0
+EOF
+
+# /128 netmask.
+test_addrset "1:2::0003/128" "1:2::3" <<EOF
+1:2::3
+1:2::0
+1:2::ff
+1:2::1ff
+1:3::3
+EOF
+
+# /0 netmask.
+test_addrset "1:2::0003/0" "1:2::3 1:2::0 1:2::ff 1:2::1ff 1:3::3 ff::00" <<EOF
+1:2::3
+1:2::0
+1:2::ff
+1:2::1ff
+1:3::3
+ff::00
+EOF
+
+# Name lookup.
+test_addrset "scanme.nmap.org" "scanme.nmap.org" <<EOF
+1:2::3:4
+1.2.3.4
+scanme.nmap.org
+EOF
+
+# Name lookup combined with CIDR netmask.
+test_addrset "scanme.nmap.org/30" "scanme.nmap.org" <<EOF
+1:2::3:4
+1.2.3.4
+scanme.nmap.org
+EOF
+
+# Name lookup combined with /0 CIDR netmask.
+test_addrset "scanme.nmap.org/0" "1.2.3.4 scanme.nmap.org" <<EOF
+1.2.3.4
+scanme.nmap.org
+EOF
+
+expect_fail "."
+expect_fail "-"
+expect_fail ","
+expect_fail "1.2.3.4,"
+expect_fail ",1.2.3.4"
+expect_fail "1.2.3.4.5"
+expect_fail "1:2:3:4:5:6:7:8:9"
+expect_fail "11::22::33"
+
+expect_fail "256.256.256.256"
+expect_fail "FFFFF::FFFFF"
+
+# Backwards range.
+expect_fail "10-5.2.3.4"
+
+expect_fail "*10.10.10.10"
+expect_fail "5-10-15.10.10.10"
+expect_fail "-10-15.10.10.10"
+expect_fail "10-15-.10.10.10"
+expect_fail ",.6.7.8"
+expect_fail "5,.5.5.5"
+expect_fail ",5.5.5.5"
+expect_fail ",5.5.5.5"
+expect_fail "+1.2.3.4"
+expect_fail "+1.+2.+3.+4"
+
+expect_fail "1.2.3.4/"
+expect_fail "1.2.3.4/33"
+expect_fail "1.2.3.4/+24"
+expect_fail "1.2.3.4/24abc"
+expect_fail "1.2.3.4//24"
+expect_fail "1.2.3.4/-0"
+expect_fail "FF::FF/129"
+
+# Specifications whose behavior is unspecified but not important; that
+# is, if the behavior of these changed it wouldn't matter much to users.
+
+# test_addrset "01.02.03.04" "1.2.3.4" <<EOF
+# 1.2.3.4
+# 5.6.7.8
+# EOF
+#
+# test_addrset "1" "0.0.0.1" <<EOF
+# 1.0.0.0
+# 0.0.0.1
+# 1.2.3.4
+# EOF
+#
+# test_addrset "1.2" "1.0.0.2" <<EOF
+# 1.0.0.2
+# 1.2.0.0
+# 1.2.3.4
+# EOF
+#
+# test_addrset "1.2.3" "1.2.0.3" <<EOF
+# 1.0.2.3
+# 1.2.0.3
+# 1.2.3.4
+# EOF
+
+if [ "$TEST_FAIL" -gt 0 ]; then
+ echo "$TEST_PASS / $TESTS passed, $TEST_FAIL failed"
+ exit 1
+fi
+echo "$TEST_PASS / $TESTS passed"
diff --git a/ncat/test/test-cert.pem b/ncat/test/test-cert.pem
new file mode 100644
index 0000000..a64fc2f
--- /dev/null
+++ b/ncat/test/test-cert.pem
@@ -0,0 +1,47 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQChGGUWUm7QUE2Z
+xBNaKuz7eSuR6LkGhhc5kzwRp4BqbUzHkdKr8glFeyP8Vmqszp+pKcdVahFE9WH3
+/mWk1Vuu8dGf6omhDwdzkqzASnSK8XDYYs8wWZycgewRkzJ2ZWqkN0MjiVnek4XV
+jcom6ZxQb000V0/rpkzcmHfzatnkSo6cIjiF/Yk886bulMFwbtQ69KPtg/7jNgPE
+N2me8nzSRW6cdBKcFR9tSJ196W5CxZzL7dUDitk+mUwghsCVhAygCUEMsAxlzZsv
+Rio6Hx5Jjaoeb3ZrWJSrQP11MNQ6aXYWHYPKFQrJlWqvmUdPNUNZA5UJg3jWZj89
+/UkTYrmLAgMBAAECggEBAIeyKNRYs4c9XWB8zWBScDSTqKPLSK+6G64f26VzVrRP
+RTGGH9M+mZixVWqk0E6n7c1sp7/HDKztlYxz+eKS5hvDDjSKN0w3gKtVjbacAPdY
+0Rk0ghnUIhujRNq5BvS8EMwq0J8Kf20BDucH/JQyi70lQ/8m3Oc+udfHhqLACrJE
+1R/8hRKasqRChA7YytC8W9KUEJHiThCVeMO5ljg+nfV3ElCnVm9WZI3YY6LjHJ5b
+xJt0XxHFK3fpwrHc+9FT8n1A2eKQhojZrfmZqMvpwqQ5tGdRfiXWYWKahnyCqQZU
+EOF4pvjtq4JCRJtdTbdO+SO6/gYIGBnSKFX6SRmCHsECgYEAzRVUpQkvyRKVLpp6
+vy4d3D1bmeQJETorpteQWH6u8mJT1SiTxr4DTjcK1FSwNhT1L5YWuaIbcy5N9cRc
+CVCOx9ynVpvlmVGXbGWCpO8thC8kHS4C0o6meN7Hdc4XlvhnsP6lXVx0+IiYkO97
+DAkcNfYq9VCCfwxShSint0u6gkMCgYEAyRdGypoOoW8bj5oHYKZWn6nUTvUeQYI4
+AbrpKvmyHziy7A3nDXR20bFsZLzwIx5cWXC2IgxnV67PxJBKx6zC8eqGVQvtOXoL
++EZclDx7X/qPuOV9cGQC0g90+vEMtho7Y0+OOMjnX4Mp5Mxe0MKsHBVPJXV+J9au
+HzbEQgS+axkCgYAfY8dr50cQIK8CzhRBlN5vKlkN6fBVWbD6vX3TfbhrUw9/zdEo
+l0bMPvKCY2ABNx38JZ3e1egbrheyqVZo+pKQFr7qK7xtxiZGKhdgBVfTJqABc1xt
+O/ZRA9BFwMCtk+HTHnbTHmas78NiU7VitzZBMCeNGsCOj6VFyl45WPb4HwKBgFlK
+0a9zOx3qBVX9slrzYbGiKtkotNen6BC0m4buu25rsd9pyPhLh9tTKmuNDiC6AY3h
+o654zYa85BtHaz1A9IuRdTbchekozZngPInCVZJn41vZ45p9flcib/JiCR/vhAF0
+ORXz9/GMf5TOPyf52Yza1XsvseYkyMqpefpKuvc5AoGBALdY8Eq/GQzHCWQtGnrC
+J1FF9FdmnRFx3eoCktFQHrYTXg0NDAUykTENoHTHiXpm8AGZIe2amMQdFxIO6xQl
+mb9QDUWe9pw0CkDIjrcqq5qSoZP7fkdKxPDrM4nK78Mxl7TiZiEZ66LNV4L3DL3y
+Icb8eZxcfAFT0vH4cAjIgjG3
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDIzCCAgugAwIBAgIJANerJEEww4jYMA0GCSqGSIb3DQEBCwUAMCgxEjAQBgNV
+BAoMCW5jYXQtdGVzdDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIyMDkyMjAxMzEz
+MFoXDTIyMTAyMjAxMzEzMFowKDESMBAGA1UECgwJbmNhdC10ZXN0MRIwEAYDVQQD
+DAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChGGUW
+Um7QUE2ZxBNaKuz7eSuR6LkGhhc5kzwRp4BqbUzHkdKr8glFeyP8Vmqszp+pKcdV
+ahFE9WH3/mWk1Vuu8dGf6omhDwdzkqzASnSK8XDYYs8wWZycgewRkzJ2ZWqkN0Mj
+iVnek4XVjcom6ZxQb000V0/rpkzcmHfzatnkSo6cIjiF/Yk886bulMFwbtQ69KPt
+g/7jNgPEN2me8nzSRW6cdBKcFR9tSJ196W5CxZzL7dUDitk+mUwghsCVhAygCUEM
+sAxlzZsvRio6Hx5Jjaoeb3ZrWJSrQP11MNQ6aXYWHYPKFQrJlWqvmUdPNUNZA5UJ
+g3jWZj89/UkTYrmLAgMBAAGjUDBOMB0GA1UdDgQWBBSnNuK9hPnalhF9DrnQ3B23
+1h8slzAfBgNVHSMEGDAWgBSnNuK9hPnalhF9DrnQ3B231h8slzAMBgNVHRMEBTAD
+AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAQm68Dk8jc3jGkvJSLbXpC6M3FJbCS2wD5
+rmf51uEoeqzoCnlWWLlX5PvZw9cpS93SfqhNOIulP0sg/sXaCZrfQ5WbrezjR+vT
+MrY6ZyaIMCEX2D+9mpkNdrP/s5k2Rkz2ZZa6YComXWAnEY2N1eIHr2BLiP/gL41G
+GGuS+0SopoWSgINoYLzE+X0dMVsrS01u41056BAnFTv5IQjygQVd5673ras82a3N
+41G+2CKxAtbZ3Bdgz4LJ0pzSVWRwLm2mCqn9eabMtJFjg6v5AlOBgbOHdsLgkMkf
+GnjLBSbr9t37MFFJWK7JFNXfH9MYOrsiaEK1/+N0NQXF733a6fbP
+-----END CERTIFICATE-----
diff --git a/ncat/test/test-cmdline-split.c b/ncat/test/test-cmdline-split.c
new file mode 100644
index 0000000..faaa555
--- /dev/null
+++ b/ncat/test/test-cmdline-split.c
@@ -0,0 +1,100 @@
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static long test_count = 0;
+static long success_count = 0;
+char **cmdline_split(const char *cmdexec);
+
+int test_cmdline(const char *line, const char **target_args)
+{
+ char **cmd_args;
+ int args_match = 1;
+
+ test_count++;
+
+ cmd_args = cmdline_split(line);
+
+ /*
+ * Make sure that all of the target arguments are have been extracted
+ * by cmdline_split.
+ */
+ while (*cmd_args && *target_args) {
+ if (strcmp(*cmd_args, *target_args)) {
+ args_match = 0;
+ break;
+ }
+ cmd_args++;
+ target_args++;
+ }
+ if ((*cmd_args != NULL) || (*target_args != NULL)) {
+ /*
+ * One of the argument list had more arguments than the other.
+ * Therefore, they do not match
+ */
+ args_match = 0;
+ }
+
+ if (args_match) {
+ success_count++;
+ printf("PASS '%s'\n", line);
+ return 1;
+ } else {
+ printf("FAIL '%s'\n", line);
+ return 0;
+ }
+}
+
+int test_cmdline_fail(const char *line)
+{
+ char **cmd_args;
+
+ test_count++;
+
+ cmd_args = cmdline_split(line);
+
+ if (*cmd_args == NULL) {
+ success_count++;
+ printf("PASS '%s'\n", line);
+ return 1;
+ } else {
+ printf("PASS '%s'\n", line);
+ return 0;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int i;
+
+ struct {
+ const char *cmdexec;
+ const char *args[10];
+ } TEST_CASES[] = {
+ {"ncat -l -k", {"ncat", "-l", "-k", NULL}},
+ {"ncat localhost 793", {"ncat", "localhost", "793", NULL}},
+ {"./ncat scanme.nmap.org 80", {"./ncat", "scanme.nmap.org", "80",
+ NULL}},
+ {"t\\ p\\ s hello world how are you?", {"t p s", "hello", "world", "how", "are",
+ "you?", NULL}},
+ {"t\\ p\\ s hello world how\\ are you?", {"t p s", "hello", "world", "how are",
+ "you?", NULL}},
+ {"ncat\\", {"ncat", NULL}},
+ {"a\\nb", {"anb", NULL}},
+ {" ncat a ", {"ncat", "a", NULL}},
+ {"\\ncat \\a", {"ncat", "a", NULL}},
+ {"ncat\\\\ a", {"ncat\\", "a", NULL}},
+ {"ncat\\", {"ncat", NULL}},
+ {"ncat\\ \\", {"ncat ", NULL}},
+ };
+
+ for (i = 0; i < sizeof(TEST_CASES)/sizeof(TEST_CASES[0]); i++) {
+ test_cmdline(TEST_CASES[i].cmdexec,
+ TEST_CASES[i].args);
+ }
+
+ test_cmdline_fail("");
+ printf("%ld / %ld tests passed.\n", success_count, test_count);
+ return success_count == test_count ? 0 : 1;
+}
diff --git a/ncat/test/test-environment.lua b/ncat/test/test-environment.lua
new file mode 100644
index 0000000..bae265b
--- /dev/null
+++ b/ncat/test/test-environment.lua
@@ -0,0 +1,13 @@
+#!/usr/bin/lua
+
+function print_env(v)
+ print(("%s=%s"):format(v, os.getenv(v)))
+end
+
+print_env("NCAT_REMOTE_ADDR")
+print_env("NCAT_REMOTE_PORT")
+
+print_env("NCAT_LOCAL_ADDR")
+print_env("NCAT_LOCAL_PORT")
+
+print_env("NCAT_PROTO")
diff --git a/ncat/test/test-environment.sh b/ncat/test/test-environment.sh
new file mode 100755
index 0000000..e78a220
--- /dev/null
+++ b/ncat/test/test-environment.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+# Print the contents of all environment variables set by Ncat.
+
+echo "NCAT_REMOTE_ADDR=$NCAT_REMOTE_ADDR"
+echo "NCAT_REMOTE_PORT=$NCAT_REMOTE_PORT"
+
+echo "NCAT_LOCAL_ADDR=$NCAT_LOCAL_ADDR"
+echo "NCAT_LOCAL_PORT=$NCAT_LOCAL_PORT"
+
+echo "NCAT_PROTO=$NCAT_PROTO"
diff --git a/ncat/test/test-uri.c b/ncat/test/test-uri.c
new file mode 100644
index 0000000..eb6f5df
--- /dev/null
+++ b/ncat/test/test-uri.c
@@ -0,0 +1,129 @@
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "ncat_core.h"
+#include "http.h"
+
+static long test_count = 0;
+static long success_count = 0;
+
+/* Check strings or null pointers for equality. */
+int nullstreq(const char *s, const char *t)
+{
+ if (s == NULL) {
+ if (t == NULL)
+ return 1;
+ else
+ return 0;
+ } else {
+ if (t == NULL)
+ return 0;
+ else
+ return strcmp(s, t) == 0;
+ }
+}
+
+int test_uri(const char *uri_s, const char *scheme, const char *host, int port, const char *path)
+{
+ struct uri uri;
+ int scheme_match, host_match, port_match, path_match;
+
+ test_count++;
+
+ if (uri_parse(&uri, uri_s) == NULL) {
+ printf("FAIL %s: couldn't parse.\n", uri_s);
+ return 0;
+ }
+
+ scheme_match = nullstreq(uri.scheme, scheme);
+ host_match = nullstreq(uri.host, host);
+ port_match = uri.port == port;
+ path_match = nullstreq(uri.path, path);
+
+ if (scheme_match && host_match && port_match && path_match) {
+ printf("PASS %s\n", uri_s);
+ uri_free(&uri);
+ success_count++;
+ return 1;
+ } else {
+ printf("FAIL %s:", uri_s);
+ if (!scheme_match)
+ printf(" \"%s\" != \"%s\".", uri.scheme, scheme);
+ if (!host_match)
+ printf(" \"%s\" != \"%s\".", uri.host, host);
+ if (!port_match)
+ printf(" %d != %d.", uri.port, port);
+ if (!path_match)
+ printf(" \"%s\" != \"%s\".", uri.path, path);
+ printf("\n");
+ uri_free(&uri);
+ return 0;
+ }
+}
+
+int test_fail(const char *uri_s)
+{
+ struct uri uri;
+
+ test_count++;
+
+ if (uri_parse(&uri, uri_s) != NULL) {
+ uri_free(&uri);
+ printf("FAIL %s: not expected to parse.\n", uri_s);
+ return 0;
+ } else {
+ printf("PASS %s\n", uri_s);
+ success_count++;
+ return 0;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ test_uri("http://www.example.com", "http", "www.example.com", 80, "");
+
+ test_uri("HTTP://www.example.com", "http", "www.example.com", 80, "");
+ test_uri("http://WWW.EXAMPLE.COM", "http", "WWW.EXAMPLE.COM", 80, "");
+
+ test_uri("http://www.example.com:100", "http", "www.example.com", 100, "");
+ test_uri("http://www.example.com:1", "http", "www.example.com", 1, "");
+ test_uri("http://www.example.com:65535", "http", "www.example.com", 65535, "");
+ test_uri("http://www.example.com:", "http", "www.example.com", 80, "");
+ test_uri("http://www.example.com:/", "http", "www.example.com", 80, "/");
+
+ test_uri("http://www.example.com/", "http", "www.example.com", 80, "/");
+ test_uri("http://www.example.com:100/", "http", "www.example.com", 100, "/");
+
+ test_uri("http://1.2.3.4", "http", "1.2.3.4", 80, "");
+ test_uri("http://1.2.3.4:100", "http", "1.2.3.4", 100, "");
+ test_uri("http://[::ffff]", "http", "::ffff", 80, "");
+ test_uri("http://[::ffff]:100", "http", "::ffff", 100, "");
+
+ test_uri("http://www.example.com/path?query#frag", "http", "www.example.com", 80, "/path?query#frag");
+
+ test_uri("http://www.exampl%65.com", "http", "www.example.com", 80, "");
+ test_uri("http://www.exampl%6a.com", "http", "www.examplj.com", 80, "");
+ test_uri("http://www.exampl%6A.com", "http", "www.examplj.com", 80, "");
+ test_uri("http://www.exampl%2523.com", "http", "www.exampl%23.com", 80, "");
+ test_fail("http://www.example.com:%380");
+ test_uri("http://www.example.com/a%23b", "http", "www.example.com", 80, "/a%23b");
+
+ test_uri("unknown://www.example.com", "unknown", "www.example.com", -1, "");
+
+ test_uri("unknown:", "unknown", NULL, -1, "");
+
+ test_fail("");
+ test_fail("/dir/file");
+ test_fail("http://www.example.com:-1");
+ test_fail("http://www.example.com:0");
+ test_fail("http://www.example.com:65536");
+
+ /* We explicitly don't support userinfo in the authority. */
+ test_fail("http://user@www.example.com");
+ test_fail("http://user:pass@www.example.com");
+
+ printf("%ld / %ld tests passed.\n", success_count, test_count);
+
+ return success_count == test_count ? 0 : 1;
+}
diff --git a/ncat/test/test-wildcard.c b/ncat/test/test-wildcard.c
new file mode 100644
index 0000000..fe55e19
--- /dev/null
+++ b/ncat/test/test-wildcard.c
@@ -0,0 +1,626 @@
+/*
+Usage: ./test-wildcard
+
+This is a test program for the ssl_post_connect_check function. It generates
+certificates with a variety of different combinations of commonNames and
+dNSNames, then checks that matching names are accepted and non-matching names
+are rejected. The SSL transactions happen over OpenSSL BIO pairs.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <openssl/bio.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/rsa.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#include "ncat_core.h"
+#include "ncat_ssl.h"
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+#include <openssl/bn.h>
+#endif
+
+#define KEY_BITS 2048
+
+static int tests_run = 0, tests_passed = 0;
+
+/* A length-delimited string. */
+struct lstr {
+ size_t len;
+ const char *s;
+};
+
+/* Make an anonymous struct lstr. */
+#define LSTR(s) { sizeof(s) - 1, (s) }
+
+/* Variable-length arrays of struct lstr are terminated with a special sentinel
+ value. */
+#define LSTR_SENTINEL { -1, NULL }
+const struct lstr lstr_sentinel = LSTR_SENTINEL;
+
+int is_sentinel(const struct lstr *name) {
+ return name->len == -1;
+}
+
+int ssl_post_connect_check(SSL *ssl, const char *hostname);
+
+static struct lstr *check(SSL *ssl, const struct lstr names[]);
+static int ssl_ctx_trust_cert(SSL_CTX *ctx, X509 *cert);
+static int gen_cert(X509 **cert, EVP_PKEY **key,
+ const struct lstr commonNames[], const struct lstr dNSNames[]);
+static void print_escaped(const char *s, size_t len);
+static void print_array(const struct lstr array[]);
+static int arrays_equal(const struct lstr a[], const struct lstr b[]);
+
+/* Returns positive on success, 0 on failure. The various arrays must be
+ NULL-terminated. */
+static int test(const struct lstr commonNames[], const struct lstr dNSNames[],
+ const struct lstr test_names[], const struct lstr expected[])
+{
+ SSL_CTX *server_ctx, *client_ctx;
+ SSL *server_ssl, *client_ssl;
+ BIO *server_bio, *client_bio;
+ X509 *cert;
+ EVP_PKEY *key;
+ struct lstr *results;
+ int need_accept, need_connect;
+ int passed;
+
+ tests_run++;
+
+ ncat_assert(gen_cert(&cert, &key, commonNames, dNSNames) == 1);
+
+ ncat_assert(BIO_new_bio_pair(&server_bio, 0, &client_bio, 0) == 1);
+
+ server_ctx = SSL_CTX_new(SSLv23_server_method());
+ ncat_assert(server_ctx != NULL);
+
+ client_ctx = SSL_CTX_new(SSLv23_client_method());
+ ncat_assert(client_ctx != NULL);
+ SSL_CTX_set_verify(client_ctx, SSL_VERIFY_PEER, NULL);
+ SSL_CTX_set_verify_depth(client_ctx, 1);
+ ssl_ctx_trust_cert(client_ctx, cert);
+
+ server_ssl = SSL_new(server_ctx);
+ ncat_assert(server_ssl != NULL);
+ SSL_set_accept_state(server_ssl);
+ SSL_set_bio(server_ssl, server_bio, server_bio);
+ ncat_assert(SSL_use_certificate(server_ssl, cert) == 1);
+ ncat_assert(SSL_use_PrivateKey(server_ssl, key) == 1);
+
+ client_ssl = SSL_new(client_ctx);
+ ncat_assert(client_ssl != NULL);
+ SSL_set_connect_state(client_ssl);
+ SSL_set_bio(client_ssl, client_bio, client_bio);
+
+ passed = 0;
+
+ need_accept = 1;
+ need_connect = 1;
+ do {
+ int rc, err;
+
+ if (need_accept) {
+ rc = SSL_accept(server_ssl);
+ err = SSL_get_error(server_ssl, rc);
+ if (rc == 1) {
+ need_accept = 0;
+ } else {
+ if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
+ printf("SSL_accept: %s \n",
+ ERR_error_string(ERR_get_error(), NULL));
+ goto end;
+ }
+ }
+ }
+ if (need_connect) {
+ rc = SSL_connect(client_ssl);
+ err = SSL_get_error(client_ssl, rc);
+ if (rc == 1) {
+ need_connect = 0;
+ } else {
+ if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
+ printf("SSL_connect: %s \n",
+ ERR_error_string(ERR_get_error(), NULL));
+ goto end;
+ }
+ }
+ }
+ } while (need_accept || need_connect);
+
+ results = check(client_ssl, test_names);
+ if (arrays_equal(results, expected)) {
+ tests_passed++;
+ passed = 1;
+ printf("PASS CN");
+ print_array(commonNames);
+ printf(" DNS");
+ print_array(dNSNames);
+ printf("\n");
+ } else {
+ printf("FAIL CN");
+ print_array(commonNames);
+ printf(" DNS");
+ print_array(dNSNames);
+ printf("\n");
+ printf(" got ");
+ print_array(results);
+ printf("\n");
+ printf("expected ");
+ print_array(expected);
+ printf("\n");
+ }
+ free(results);
+
+end:
+ X509_free(cert);
+ EVP_PKEY_free(key);
+
+ (void) BIO_destroy_bio_pair(server_bio);
+
+ SSL_CTX_free(server_ctx);
+ SSL_CTX_free(client_ctx);
+
+ SSL_free(server_ssl);
+ SSL_free(client_ssl);
+
+ return passed;
+}
+
+/* Returns a sentinel-terminated malloc-allocated array of names that match ssl
+ with ssl_post_connect_check. */
+static struct lstr *check(SSL *ssl, const struct lstr names[])
+{
+ const struct lstr *name;
+ struct lstr *results = NULL;
+ size_t size = 0, capacity = 0;
+
+ if (names == NULL)
+ return NULL;
+
+ for (name = names; !is_sentinel(name); name++) {
+ if (ssl_post_connect_check(ssl, name->s)) {
+ if (size >= capacity) {
+ capacity = (size + 1) * 2;
+ results = safe_realloc(results, (capacity + 1) * sizeof(results[0]));
+ }
+ results[size++] = *name;
+ }
+ }
+ results = safe_realloc(results, (size + 1) * sizeof(results[0]));
+ results[size] = lstr_sentinel;
+
+ return results;
+}
+
+/* Make a certificate object trusted by an SSL_CTX. I couldn't find a way to do
+ this directly, so the certificate is written in PEM format to a temporary
+ file and then loaded with SSL_CTX_load_verify_locations. Returns 1 on success
+ and 0 on failure. */
+static int ssl_ctx_trust_cert(SSL_CTX *ctx, X509 *cert)
+{
+ char name[] = "ncat-test-XXXXXX";
+ int fd;
+ FILE *fp;
+ int rc;
+
+ fd = mkstemp(name);
+ if (fd == -1)
+ return 0;
+ fp = fdopen(fd, "w");
+ if (fp == NULL) {
+ close(fd);
+ return 0;
+ }
+ if (PEM_write_X509(fp, cert) == 0) {
+ fclose(fp);
+ return 0;
+ }
+ fclose(fp);
+
+ rc = SSL_CTX_load_verify_locations(ctx, name, NULL);
+ if (rc == 0) {
+ fprintf(stderr, "SSL_CTX_load_verify_locations: %s \n",
+ ERR_error_string(ERR_get_error(), NULL));
+ }
+ if (unlink(name) == -1)
+ fprintf(stderr, "unlink(\"%s\"): %s\n", name, strerror(errno));
+
+ return rc;
+}
+
+static int set_dNSNames(X509 *cert, const struct lstr dNSNames[])
+{
+ STACK_OF(GENERAL_NAME) *gen_names;
+ GENERAL_NAME *gen_name;
+ X509_EXTENSION *ext;
+ const struct lstr *name;
+
+ if (dNSNames == NULL || is_sentinel(&dNSNames[0]))
+ return 1;
+
+ /* We break the abstraction here a bit because the normal way of setting
+ a list of values, using an i2v method, uses a stack of CONF_VALUE that
+ doesn't contain the length of each value. We rely on the fact that
+ the internal representation (the "i" in "i2d") for
+ NID_subject_alt_name is STACK_OF(GENERAL_NAME). */
+
+ gen_names = sk_GENERAL_NAME_new_null();
+ if (gen_names == NULL)
+ return 0;
+
+ for (name = dNSNames; !is_sentinel(name); name++) {
+ gen_name = GENERAL_NAME_new();
+ if (gen_name == NULL)
+ goto stack_err;
+ gen_name->type = GEN_DNS;
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined LIBRESSL_VERSION_NUMBER
+ gen_name->d.dNSName = ASN1_IA5STRING_new();
+#else
+ gen_name->d.dNSName = M_ASN1_IA5STRING_new();
+#endif
+ if (gen_name->d.dNSName == NULL)
+ goto name_err;
+ if (ASN1_STRING_set(gen_name->d.dNSName, name->s, name->len) == 0)
+ goto name_err;
+ if (sk_GENERAL_NAME_push(gen_names, gen_name) == 0)
+ goto name_err;
+ }
+ ext = X509V3_EXT_i2d(NID_subject_alt_name, 0, gen_names);
+ if (ext == NULL)
+ goto stack_err;
+ if (X509_add_ext(cert, ext, -1) == 0) {
+ X509_EXTENSION_free(ext);
+ goto stack_err;
+ }
+ X509_EXTENSION_free(ext);
+ sk_GENERAL_NAME_pop_free(gen_names, GENERAL_NAME_free);
+
+ return 1;
+
+name_err:
+ GENERAL_NAME_free(gen_name);
+
+stack_err:
+ sk_GENERAL_NAME_pop_free(gen_names, GENERAL_NAME_free);
+
+ return 0;
+}
+
+static int gen_cert(X509 **cert, EVP_PKEY **key,
+ const struct lstr commonNames[], const struct lstr dNSNames[])
+{
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ int rc, ret=0;
+ RSA *rsa = NULL;
+ BIGNUM *bne = NULL;
+
+ *cert = NULL;
+ *key = NULL;
+
+ /* Generate a private key. */
+ *key = EVP_PKEY_new();
+ if (*key == NULL)
+ goto err;
+ do {
+ /* Generate RSA key. */
+ bne = BN_new();
+ ret = BN_set_word(bne, RSA_F4);
+ if (ret != 1)
+ goto err;
+
+ rsa = RSA_new();
+ ret = RSA_generate_key_ex(rsa, KEY_BITS, bne, NULL);
+ if (ret != 1)
+ goto err;
+ /* Check RSA key. */
+ rc = RSA_check_key(rsa);
+ } while (rc == 0);
+ if (rc == -1)
+ goto err;
+ if (EVP_PKEY_assign_RSA(*key, rsa) == 0) {
+ RSA_free(rsa);
+ goto err;
+ }
+#else
+ *cert = NULL;
+ *key = EVP_RSA_gen(KEY_BITS);
+ if (*key == NULL)
+ goto err;
+#endif
+
+ /* Generate a certificate. */
+ *cert = X509_new();
+ if (*cert == NULL)
+ goto err;
+ if (X509_set_version(*cert, 2) == 0) /* Version 3. */
+ goto err;
+ ASN1_INTEGER_set(X509_get_serialNumber(*cert), get_random_u32() & 0x7FFFFFFF);
+
+ /* Set the commonNames. */
+ if (commonNames != NULL) {
+ X509_NAME *subj;
+ const struct lstr *name;
+
+ subj = X509_get_subject_name(*cert);
+ for (name = commonNames; !is_sentinel(name); name++) {
+ if (X509_NAME_add_entry_by_txt(subj, "commonName", MBSTRING_ASC,
+ (unsigned char *) name->s, name->len, -1, 0) == 0) {
+ goto err;
+ }
+ }
+ }
+
+ /* Set the dNSNames. */
+ if (set_dNSNames(*cert, dNSNames) == 0)
+ goto err;
+
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined LIBRESSL_VERSION_NUMBER
+ {
+ ASN1_TIME *tb, *ta;
+ tb = NULL;
+ ta = NULL;
+
+ if (X509_set_issuer_name(*cert, X509_get_subject_name(*cert)) == 0
+ || (tb = ASN1_STRING_dup(X509_get0_notBefore(*cert))) == 0
+ || X509_gmtime_adj(tb, 0) == 0
+ || X509_set1_notBefore(*cert, tb) == 0
+ || (ta = ASN1_STRING_dup(X509_get0_notAfter(*cert))) == 0
+ || X509_gmtime_adj(ta, 60) == 0
+ || X509_set1_notAfter(*cert, ta) == 0
+ || X509_set_pubkey(*cert, *key) == 0) {
+ ASN1_STRING_free(tb);
+ ASN1_STRING_free(ta);
+ goto err;
+ }
+ ASN1_STRING_free(tb);
+ ASN1_STRING_free(ta);
+ }
+#else
+ if (X509_set_issuer_name(*cert, X509_get_subject_name(*cert)) == 0
+ || X509_gmtime_adj(X509_get_notBefore(*cert), 0) == 0
+ || X509_gmtime_adj(X509_get_notAfter(*cert), 60) == 0
+ || X509_set_pubkey(*cert, *key) == 0) {
+ goto err;
+ }
+#endif
+
+ /* Sign it. */
+ if (X509_sign(*cert, *key, EVP_sha1()) == 0)
+ goto err;
+
+ return 1;
+
+err:
+ if (*cert != NULL)
+ X509_free(*cert);
+ if (*key != NULL)
+ EVP_PKEY_free(*key);
+
+ return 0;
+}
+
+static void print_escaped(const char *s, size_t len)
+{
+ int c;
+ for ( ; len > 0; len--) {
+ c = (unsigned char) *s++;
+ if (isprint(c) && !isspace(c))
+ putchar(c);
+ else
+ printf("\\%03o", c);
+ }
+}
+
+static void print_array(const struct lstr array[])
+{
+ const struct lstr *p;
+
+ if (array == NULL) {
+ printf("[]");
+ return;
+ }
+ printf("[");
+ for (p = array; !is_sentinel(p); p++) {
+ if (p != array)
+ printf(" ");
+ print_escaped(p->s, p->len);
+ }
+ printf("]");
+}
+
+static int lstr_equal(const struct lstr *a, const struct lstr *b)
+{
+ return a->len == b->len && memcmp(a->s, b->s, a->len) == 0;
+}
+
+static int arrays_equal(const struct lstr a[], const struct lstr b[])
+{
+ if (a == NULL)
+ return b == NULL;
+ if (b == NULL)
+ return a == NULL;
+ while (!is_sentinel(a) && !is_sentinel(b)) {
+ if (!lstr_equal(a, b))
+ return 0;
+ a++;
+ b++;
+ }
+
+ return is_sentinel(a) && is_sentinel(b);
+}
+
+/* This is just a constant used to give a fixed length to the arrays that are
+ conceptually variable-length in the test cases. Increase it if some array
+ grows too big. */
+#define ARR_LEN 10
+
+const struct lstr test_names[] = {
+ LSTR("a.com"), LSTR("www.a.com"), LSTR("sub.www.a.com"),
+ LSTR("www.example.com"), LSTR("example.co.uk"), LSTR("*.*.com"),
+ LSTR_SENTINEL
+};
+
+/* These tests just check that matching a single string works properly. */
+struct {
+ const struct lstr name[ARR_LEN];
+ const struct lstr expected[ARR_LEN];
+} single_tests[] = {
+ { { LSTR_SENTINEL },
+ { LSTR_SENTINEL } },
+ { { LSTR("a.com"), LSTR_SENTINEL },
+ { LSTR("a.com"), LSTR_SENTINEL } },
+ { { LSTR("www.a.com"), LSTR_SENTINEL },
+ { LSTR("www.a.com"), LSTR_SENTINEL } },
+ { { LSTR("*.a.com"), LSTR_SENTINEL },
+ { LSTR("www.a.com"), LSTR_SENTINEL } },
+ { { LSTR("w*.a.com"), LSTR_SENTINEL },
+ { LSTR_SENTINEL } },
+ { { LSTR("*w.a.com"), LSTR_SENTINEL },
+ { LSTR_SENTINEL } },
+ { { LSTR("www.*.com"), LSTR_SENTINEL },
+ { LSTR_SENTINEL } },
+ { { LSTR("*.com"), LSTR_SENTINEL },
+ { LSTR_SENTINEL } },
+ { { LSTR("*.com."), LSTR_SENTINEL },
+ { LSTR_SENTINEL } },
+ { { LSTR("*.*.com"), LSTR_SENTINEL },
+ { LSTR_SENTINEL } },
+ { { LSTR("a.com\0evil.com"), LSTR_SENTINEL },
+ { LSTR_SENTINEL } },
+};
+
+/* These test different combinations of commonName and dNSName. */
+struct {
+ const struct lstr common[ARR_LEN];
+ const struct lstr dns[ARR_LEN];
+ const struct lstr expected[ARR_LEN];
+} double_tests[] = {
+ /* Should not match any commonName if any dNSNames exist. */
+ { { LSTR("a.com"), LSTR_SENTINEL },
+ { LSTR("example.co.uk"), LSTR_SENTINEL },
+ { LSTR("example.co.uk"), LSTR_SENTINEL } },
+ { { LSTR("a.com"), LSTR_SENTINEL },
+ { LSTR("b.com"), LSTR_SENTINEL },
+ { LSTR_SENTINEL } },
+ /* Should check against all of the dNSNames. */
+ { { LSTR_SENTINEL },
+ { LSTR("a.com"), LSTR("example.co.uk"), LSTR("b.com"), LSTR_SENTINEL },
+ { LSTR("a.com"), LSTR("example.co.uk"), LSTR_SENTINEL } },
+};
+
+const struct lstr specificity_test_names[] = {
+ LSTR("a.com"),
+ LSTR("sub.b.com"), LSTR("sub.c.com"), LSTR("sub.d.com"),
+ LSTR("sub.sub.e.com"), LSTR("sub.sub.f.com"), LSTR("sub.sub.g.com"),
+ LSTR_SENTINEL
+};
+
+/* Validation should check only the "most specific" commonName if multiple
+ exist. This "most specific" term is used in RFCs 2818, 4261, and 5018 at
+ least, but is not defined anywhere that I can find. Let's interpret it as the
+ greatest number of name elements, with wildcard names considered less
+ specific than all non-wildcard names. For ties, the name that comes later is
+ considered more specific. */
+struct {
+ const struct lstr patterns[ARR_LEN];
+ const struct lstr expected_forward;
+ const struct lstr expected_backward;
+} specificity_tests[] = {
+ { { LSTR("a.com"), LSTR("*.b.com"), LSTR("sub.c.com"), LSTR("sub.d.com"), LSTR("*.sub.e.com"), LSTR("*.sub.f.com"), LSTR("sub.sub.g.com"), LSTR_SENTINEL },
+ LSTR("sub.sub.g.com"), LSTR("sub.sub.g.com") },
+ { { LSTR("a.com"), LSTR("*.b.com"), LSTR("sub.c.com"), LSTR("sub.d.com"), LSTR("*.sub.e.com"), LSTR("*.sub.f.com"), LSTR_SENTINEL },
+ LSTR("sub.d.com"), LSTR("sub.c.com") },
+ { { LSTR("a.com"), LSTR("*.b.com"), LSTR("sub.c.com"), LSTR("*.sub.e.com"), LSTR("*.sub.f.com"), LSTR_SENTINEL },
+ LSTR("sub.c.com"), LSTR("sub.c.com") },
+ { { LSTR("a.com"), LSTR("*.b.com"), LSTR("*.sub.e.com"), LSTR("*.sub.f.com"), LSTR_SENTINEL },
+ LSTR("a.com"), LSTR("a.com") },
+ { { LSTR("*.b.com"), LSTR("*.sub.e.com"), LSTR("*.sub.f.com"), LSTR_SENTINEL },
+ LSTR("sub.sub.f.com"), LSTR("sub.sub.e.com") },
+ { { LSTR("*.b.com"), LSTR("*.sub.e.com"), LSTR_SENTINEL },
+ LSTR("sub.sub.e.com"), LSTR("sub.sub.e.com") },
+};
+
+#define NELEMS(a) (sizeof(a) / sizeof(a[0]))
+
+void reverse(struct lstr a[])
+{
+ struct lstr tmp;
+ unsigned int i, j;
+
+ i = 0;
+ for (j = 0; !is_sentinel(&a[j]); j++)
+ ;
+ if (j == 0)
+ return;
+ j--;
+ while (i < j) {
+ tmp = a[i];
+ a[i] = a[j];
+ a[j] = tmp;
+ i++;
+ j--;
+ }
+}
+
+void test_specificity(const struct lstr patterns[],
+ const struct lstr test_names[],
+ const struct lstr expected_forward[],
+ const struct lstr expected_backward[])
+{
+ struct lstr scratch[ARR_LEN];
+ unsigned int i;
+
+ for (i = 0; i < ARR_LEN && !is_sentinel(&patterns[i]); i++)
+ scratch[i] = patterns[i];
+ ncat_assert(i < ARR_LEN);
+ scratch[i] = lstr_sentinel;
+
+ test(scratch, NULL, test_names, expected_forward);
+ reverse(scratch);
+ test(scratch, NULL, test_names, expected_backward);
+
+ return;
+}
+
+int main(void)
+{
+ unsigned int i;
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined LIBRESSL_VERSION_NUMBER
+ SSL_library_init();
+ ERR_load_crypto_strings();
+ SSL_load_error_strings();
+#endif
+
+ /* Test single pattens in both the commonName and dNSName positions. */
+ for (i = 0; i < NELEMS(single_tests); i++)
+ test(single_tests[i].name, NULL, test_names, single_tests[i].expected);
+ for (i = 0; i < NELEMS(single_tests); i++)
+ test(NULL, single_tests[i].name, test_names, single_tests[i].expected);
+
+ for (i = 0; i < NELEMS(double_tests); i++) {
+ test(double_tests[i].common, double_tests[i].dns,
+ test_names, double_tests[i].expected);
+ }
+
+ for (i = 0; i < NELEMS(specificity_tests); i++) {
+ struct lstr expected_forward[2], expected_backward[2];
+
+ /* Put the expected names in arrays for the test. */
+ expected_forward[0] = specificity_tests[i].expected_forward;
+ expected_forward[1] = lstr_sentinel;
+ expected_backward[0] = specificity_tests[i].expected_backward;
+ expected_backward[1] = lstr_sentinel;
+ test_specificity(specificity_tests[i].patterns,
+ specificity_test_names, expected_forward, expected_backward);
+ }
+
+ printf("%d / %d tests passed.\n", tests_passed, tests_run);
+
+ return tests_passed == tests_run ? 0 : 1;
+}
diff --git a/ncat/test/toupper.lua b/ncat/test/toupper.lua
new file mode 100644
index 0000000..7b9d79a
--- /dev/null
+++ b/ncat/test/toupper.lua
@@ -0,0 +1,14 @@
+--Emulates the RFC 862 echo service, behaving like Unix's "cat" tool.
+
+while true do
+
+ data = io.stdin:read(1)
+
+ if data == nil then
+ break
+ end
+
+ io.write(data:upper())
+ io.flush()
+
+end