#!/usr/bin/perl

use v5.012;
use strict;
use warnings;

use File::Temp;
use Getopt::Std;
use POSIX qw(:sys_wait_h);

sub usage(;$)
{
	my ($err) = @_;
	$err //= 1;
	my $s = <<EOUSAGE ;
Usage:	simple [-c spipe] [-d plainport] [-e encport] [-s spiped] [-t string]
	-c	specify the path to spipe (default: spipe)
	-d	specify the port for the unencrypted nc listener
		(default: 8086)
	-e	specify the port for the encrypted spiped listener
		(default: 6502)
	-s	specify the path to spiped (default: spiped)
	-t	specify the text string to pass through the pipe
		(default: "mellon")
EOUSAGE
	if ($err) {
		die $s;
	} else {
		print $s;
	}
}

sub killclose($ @)
{
	my ($pids, @pipes) = @_;

	my @p = grep defined, values %{$pids};
	say "Sending SIGTERM to @p";
	kill 15, @p;
	say 'Closing '.scalar(@pipes).' file handles';
	close $_ for @pipes;
}

MAIN:
{
	my %opts;
	getopts 'c:s:C:S:t:', \%opts or usage;
	my $spipecmd = $opts{c} // 'spipe';
	my $spipedcmd = $opts{d} // 'spiped';
	my $decport = $opts{d} // 8086;
	my $encport = $opts{e} // 6502;
	my $token = $opts{t} // 'mellon';

	usage unless @ARGV == 0;

	my $key;
	{
		sysopen my $rand, '/dev/urandom', 0 or
		    die "Could not open /dev/urandom: $!\n";
		my $n = sysread $rand, $key, 32;
		if (!defined $n) {
			die "Could not read from /dev/urandom: $!\n";
		} elsif ($n < 32) {
			die "Could not read 32 bytes from /dev/urandom, only got $n\n";
		}
		close $rand;
	}

	my $keyfile = File::Temp->new(TEMPLATE => 'spiped-test.XXXXXX',
	    SUFFIX => '.key', TMPDIR => 1, CLEANUP => 1);
	print $keyfile $key or
	    die "Could not write the 32-byte random key to $keyfile: $!\n";
	close $keyfile or
	    die "Could not close the key file $keyfile: $!\n";

	say "Starting nc...";
	my %pid;
	$pid{nc} = open my $nc, '|-', 'nc', '-l', '-p', $decport or
	    die "Could not execute 'nc': $!\n";
	if (!defined $pid{nc}) {
		die "Could not start nc: $!\n";
	}
	say "nc started with pid $pid{nc}";
	sleep 1;

	$pid{spiped} = fork;
	if (!defined $pid{spiped}) {
		my $msg = $!;
		killclose \%pid, $nc;
		die "Could not fork for spiped: $msg\n";
	} elsif ($pid{spiped} == 0) {
		exec { $spipedcmd } 'spiped', '-d', '-F', '-k', "$keyfile",
		    '-s', "127.0.0.1:$encport", '-t', "127.0.0.1:$decport";
		die "Could not run spiped: $!\n";
	}
	say "spiped started with pid $pid{spiped}";
	sleep 1;

	$pid{spipe} = open my $spipe, '-|', $spipecmd, '-k', "$keyfile",
	    '-t', "127.0.0.1:$encport";
	if (!defined $pid{spipe}) {
		my $msg = $!;
		killclose \%pid, $nc;
		die "Could not start spipe: $msg\n";
	}
	say "spipe started with pid $pid{spipe}";

	if (!say $nc $token) {
		my $msg = $!;
		killclose \%pid, $nc, $spipe;
		die "Could not write to the nc child: $msg\n";
	}
	say "Wrote '$token'";
	close $nc;
	my $ncstatus = $?;
	delete $pid{nc};
	if ($ncstatus != 0) {
		killclose \%pid, $spipe;
		die "The nc child did something weird, wait status $ncstatus\n";
	}

	my $line = <$spipe>;
	if (!defined $line) {
		my $msg = $!;
		killclose \%pid, $spipe;
		die "Could not read from the spipe child: $msg\n";
	}
	chomp $line;
	say "Read back '$line'";

	killclose \%pid, $spipe;
	if ($line ne $token) {
		die "Wrote '$token' yet read '$line'\n";
	}
	say 'Everything seems fine';
}
