#!/usr/bin/perl

use IO::Socket;
use Getopt::Long;
use Crypt::CBC;
use Carp;

use strict;

my (
    $listen_port,
    $listen_ip,
    $listen_key, $timeout, $local_key,
    $usage, $debug,
    $bufmax, $msg, $algo, $version, $v,
    $mode_server, $mode_client,
    %client, %server_code, %client_code, @chars
    );

$debug       = 0;
$listen_port = 33434;
$listen_ip   = "127.0.0.1";
$listen_key  = "X8sDD6DD0xxx9aQw3";
$bufmax      = 65500;
@chars       = ( "A" .. "Z", 0 - 9 );
$algo        = "Twofish";
$timeout     = 5;
$version     = "1.0.1";

%server_code = (
		ack       => "00000001",
		auth_ack  => "00000010",
		exec_ack  => "00000100",
		quit_ack  => "00001000",
		auth_rej  => "10000010",
		bit_rej   => "10000000",
		error_rej => "",
		);

%client_code = (
		syn  => "00000001",
		auth => "00000010",
		exec => "00000100",
		quit => "00001000",
		);

GetOptions (
	    "port|p=i"    => \$listen_port,
	    "ip|i=s"      => \$listen_ip,
	    "key|k=s"     => \$listen_key,
	    "server|s"    => \$mode_server,
	    "client|c"    => \$mode_client,
	    "timeout|t=i" => \$timeout,
	    "algo|a=s"    => \$algo,
	    "debug|d"     => \$debug,
	    "version|v"   => \$v,
	    "help|h"      => \$usage,
	   ) || exit;

if ($usage) {
  &usage;
  exit;
}

if ($v) {
  print STDERR "$0 version $version\n";
  exit;
}

my $waitedpid = 0;
$SIG{CHLD} = \&REAPER;
$SIG{INT}  = sub { exit; };
$SIG{TERM} = 'IGNORE';


my $cbc = new Crypt::CBC($listen_key, $algo);
$listen_key = undef;


if ($mode_server && !$mode_client) {
  ### SERVER ###
  while (1) {
    my $sock = new IO::Socket::INET(
				    LocalAddr => $listen_ip,
				    LocalPort => $listen_port,
				    Proto     => 'udp')
      or die "Unable to bind to $listen_ip:$listen_port : $! !\n";

    while ($sock->recv($msg, $bufmax)) {
      my($port, $remote_ip) = sockaddr_in($sock->peername);
      my $ip = inet_ntoa($remote_ip);
      $debug && print " <= " . &bit($msg)     . " $ip:$port ";
      my $response = &decode($ip, $port, $msg);
      $debug && print " => " . &bit($response) . " $ip:$port\n";
      $sock->send($response . "\n");
    }
  }
}
elsif ($mode_client && !$mode_server) {
  ### CLIENT ###
  my $sock = new IO::Socket::INET(
				  PeerAddr => $listen_ip,
				  PeerPort => $listen_port,
				  Proto    => 'udp')
    or die "Unable to connect to $listen_ip:$listen_port : $! !\n";
  local $| = 1;
  &establish($sock);
  $SIG{INT} = sub { $sock->send($client_code{quit} . &encode($local_key)); exit };
  while (1) {
    print "\n# ";
    my $cmd = <STDIN>;
    chomp $cmd;
    $sock->send($client_code{exec} . &encode($local_key) . " " . &encode($cmd));
    print &c_decode($sock);
  }
}
else {
  &usage;
  exit;
}







1;

sub REAPER {
  $waitedpid = wait;
  $SIG{CHLD} = \&REAPER;
}


sub decode {
  my($ip, $port, $msg) = @_;
  chomp $msg;
  if ($msg =~ /^$client_code{syn}$/) {
    ##### new conn
    # mark him as known but unauthenticated
    my $k = &gen_remote_key;
    $client{$k} = "$ip:$port";
    $debug && print "new connection request from $ip:$port, assigned key: $k\n";
    return $server_code{ack} . &encode($k);
  }
  elsif ($msg =~ /^$client_code{auth}(.*)$/) {
    #### authenticate new conn
    my $remote = &decrypt($1);
    $debug && print "authenticated $ip:$port $remote\n";
    if (&auth($ip, $port, $remote)) {
      # authenticated, inform him about our bufmax value
      return $server_code{auth_ack} . &encode($bufmax);
    }
    else {
      # unknown remote party, reject
      return $server_code{auth_rej};
    }
  }
  elsif ($msg =~ /^$client_code{exec}(.+?) (.*)$/s) {
    #### execute request
    my $remote = &decrypt($1);
    my $cmd    = &decrypt($2);
    $debug && print "$remote $cmd\n";
    if (&auth($ip, $port, $remote)) {
      return $server_code{exec_ack} . &encode(&exec($cmd));
    }
    else {
      # unknown remote party, reject
      return $server_code{auth_rej};
    }
  }
  elsif ($msg =~ /^$client_code{quit}(.*)$/) {
    my $remote = &decrypt($1);
    $debug && print "$remote quit\n";
    #### quit request
    if (&auth($ip, $port, $remote)) {
      delete $client{$remote};
      return $server_code{quit_ack};
    }
    else {
      # unknown remote party, reject
      return $server_code{auth_rej};
    }
  }
  elsif ($msg =~ /^[0-1]{8}.*/s) {
    #### format ok, but unknown bit mask
    return $server_code{bit_rej};
  }
  else {
    #### completely invalid
    return $server_code{error_rej};
  }
}


sub gen_remote_key {
  return join( "", @chars[ map  { rand @chars } ( 1 .. 10 ) ] );
}


sub encode {
  my $msg = shift;
  return pack("u", $cbc->encrypt($msg));
}

sub decrypt {
  my $msg = shift;
  return $cbc->decrypt(unpack"u", $msg);
}

sub exec {
  my $cmd = shift;
  my $output;
  eval {
    $output = `$cmd`;
  };
  if ($@) {
    return $@;
  }
  else {
    return $output;
  }
}


sub auth {
  my($ip, $port, $remote) = @_;
  if (exists $client{$remote}) {
    if ($client{$remote} eq "$ip:$port") {
      return 1;
    }
    else {
      return 0;
    }
  }
  else {
    return 0;
  }
}

sub establish {
  my $sock = shift;
  $sock->send($client_code{syn});
  my $response = &get($sock);
  $debug && print " => " . &bit($response) . "\n";
  if ($response =~ /^$server_code{ack}(.*)$/s) {
    my $key = &decrypt($1);
    $local_key = $key;
  }
  else {
    die "Connection error.\n";
  }

  $sock->send($client_code{auth} . &encode($local_key));
  my $response = &get($sock);
  $debug && print " => " . &bit($response) . "\n";
  if ($response =~ /^$server_code{auth_ack}(.*)$/s) {
    $bufmax = &decrypt($1);
  }
  else {
    die "Access denied.\n";
  }
}

sub get {
  my $sock = shift;
  my $msg;
  eval {
    local $SIG{ALRM} = sub { die "Connection timed out.\n"; };
    alarm $timeout;
    $sock->recv($msg, $bufmax) or die "recv: $!\n";
    alarm 0;
    1;
  } or die $@;
  return $msg;
}

sub c_decode {
  my $sock = shift;
  my $msg = &get($sock);
  $debug && print " => " . &bit($msg) . "\n";
  if ($msg =~ /^$server_code{exec_ack}(.*)/s) {
    my $response = &decrypt($1);
  }
  else {
    print STDERR "Access denied.\n";
    exit;
  }
}

sub bit {
  my $msg = shift;
  if ($msg =~ /^([0-1]{8})/) {
    return $1;
  }
}

sub usage {
  print STDERR qq(
usage: $0 -s | -c [options]
  -s --server             server mode
  -c --client             client mode
Options:
  -k --key <key>          encryption key
                          (must be the same on server and client!)
  -p --port <port>        udp port to bind/connect to
  -i --ip <address>       ip address to listen/connect to
  -t --timeout <timeout>  timeout in seconds for packet receiving
  -a --algo <algorithmus> CBC algorithmus to be used
                          (must be the same on server and client!)
  -d --debug              turn on some debugging output
  -v --version            print out the version number
  -h --help               prints out the usage message

udpsh $version. Copyright (c) 2000-2001 Thomas Linden.

This is free software with ABSOLUTELY NO WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE..
You can redistribute it and/or modify it under the terms of the GNU
General Public License.

);
}
