‘SSL Capable NetCat’

Summary

Credit:

‘The information has been provided by GomoR.
The original article can be found at: http://www.gomor.org/bin/view/GomorOrg/SslNetcat#Download


Details

‘You all know what is netcat (written by Hobbit in 1996), how to use it and that it should have been integrated in all UNIX systems a long time ago. netcat lacked some features, and I tried to add them in this Perl version. For example, SSL support, TCP and UDP proxying and IPv4/IPv6 proxying features.

Tool source:
#!/usr/bin/perl
#
# $Id: scnc 13 2008-04-27 10:53:59Z gomor $
#

our $SupportSsl = 0;
our $SupportTelnet = 0;
our $SupportIpv6 = 0;

package Scnc;
use warnings; use strict;

use Carp;
use IO::Socket::INET;
use IO::Select;

eval(‘use IO::Socket::SSL;’);
$@ ? warn(‘*** IO::Socket::SSL module not found, SSL support disabledn’)
   : $SupportSsl++;

eval(‘use Net::Telnet;’);
$@ ? warn(‘*** Net::Telnet module not found, telnet negociation disabledn’)
   : $SupportTelnet++;

eval(‘use IO::Socket::INET6;’);
$@ ? warn(‘*** IO::Socket::INET6 module not found, IPv6 support disabledn’)
   : $SupportIpv6++;

sub new {
   my $self = shift;
   bless({
      _s => undef,
      @_,
   }, $self);
}

sub resolv {
   my ($self) = @_;
   if ($SupportIpv6 && $self->{6}) {
      my @res = Socket6::getaddrinfo($self->{s}, ‘ftp’, AF_INET6, SOCK_STREAM);
      if (@res >= 5) {
         my $saddr = $res[3];
         my ($ip) = Socket6::getnameinfo(
            $res[3], Socket6::NI_NUMERICHOST()|Socket6::NI_NUMERICSERV());
         return $ip;
      }
      die(‘resolv: unable to resolv host: ‘.$self->{s}.’ [$!]n’);
   }
   else {
      my $saddr = gethostbyname($self->{s})
         or die(‘resolv: unable to resolv host: ‘.$self->{s}.’ [$!]n’);
      return inet_ntoa($saddr);
   }
}

sub init {
   my ($self) = @_;

   $SIG{INT} = sub { $self->exit };
   $SIG{CHLD} = ‘IGNORE’;

   STDOUT->blocking(0);
   STDOUT->autoflush(1);
   STDIN->blocking(0);
   STDIN->autoflush(1);

   # Set default values
   if (! defined($self->{e})) {
      $self->{e} = ”;
   }
   if (! defined($self->{s})) {
      ($SupportIpv6 && $self->{6}) ? do { $self->{s} = ‘::’; }
                                   : do { $self->{s} = ‘0.0.0.0’; };
   }
   if (! defined($self->{p})) {
      $self->{p} = 0;
   }
   $self->{s} = $self->resolv;

   # Sanity checks
   if ($self->{l} && $self->{r}) {
      delete($self->{l});
   }
   if ($self->{u} && $self->{c}) {
      die(‘init: SSL does not work over UDPn’);
   }
}

sub client {
   my ($self) = @_;

   my $inet;
   my %args = (
      PeerHost => $self->{s},
      PeerPort => $self->{p},
      Proto => $self->{u} ? ‘udp’ : ‘tcp’,
   );
   if ($SupportIpv6 && $self->{6}) {
      $inet = ‘IO::Socket::INET6’;
      $args{Domain} = AF_INET6;
   }
   elsif ($SupportSsl && $self->{c}) {
      $inet = ‘IO::Socket::SSL’;
      $args{Domain} = ($SupportIpv6 && $self->{6}) ? AF_INET6 : AF_INET;
      if ($self->{a} && $self->{f} && $self->{k}) {
         $args{SSL_server} = 0;
         $args{SSL_use_cert} = 1;
         $args{SSL_ca_file} = $self->{a};
         $args{SSL_cert_file} = $self->{f};
         $args{SSL_key_file} = $self->{k};
      }
   }
   else {
      $inet = ‘IO::Socket::INET’;
      $args{Domain} = AF_INET;
   }

   my $s = $inet->new(%args);
   if (! defined($s)) {
      if ($SupportSsl && $self->{c}) {
         die(‘client: SSL error connecting to: ‘.$self->{s}.’:’.$self->{p}.
             ‘ [‘.IO::Socket::SSL::errstr().’]’);
      }
      else {
         die(‘client: error connecting to: ‘.$self->{s}.’:’.$self->{p}.
             ‘ [$!]’);
      }
      ($SupportIpv6 && $self->{6}) ? print ‘ (IPv6)n’ : print ‘ (IPv4)n’;
   }

   $s->blocking(0);
   $s->autoflush(1);
   $self->{_s} = $s;

   # We MUST use connected UDP, otherwise syswrite() and sysread() do not work
   if ($self->{u}) {
      if ($SupportIpv6 && $self->{6}) {
         connect($s, Socket6::sockaddr_in6($self->{p},
                 Socket6::inet_pton(AF_INET6, $self->{s})))
            or die(‘client: error doing connect() [$!]n’);
      }
      else {
         connect($s, sockaddr_in($self->{p}, inet_aton($self->{s})))
            or die(‘client: error doing connect() [$!]n’);
      }
      $s->syswrite(‘’); # So the server will know we are here,
                           # and will connect() the socket on its side.
   }

   if ($self->{v}) {
      print ‘client: connected to: ‘.$self->{s}.’:’.$self->{p};
      ($SupportIpv6 && $self->{6}) ? print ‘ (IPv6)n’ : print ‘ (IPv4)n’;
      if ($self->{c}) {
         print ‘client: using cipher: ‘.$s->get_cipher.’n’;
      }
   }
}

sub _rwLoop {
   my ($self, $in, $out, $err) = @_;

   my $s = IO::Select->new;
   $s->add($self->{_s});
   $s->add($in);

   while (my @read = $s->can_read) {
      my $toRead = 1024; # We read by chunk of 1024 bytes
      for my $this (@read) {
         if ($this == $self->{_s}) {
            while (defined(my $r = $self->{_s}->sysread(my $buf, $toRead))) {
               if ($r > 0) { print $out $buf; last if $r <= $toRead; }
               else { return; }
            }
         }
         elsif ($this == $in) {
            my $toWrite = ”;
            while (defined(my $r = $in->sysread(my $buf, $toRead))) {
               if ($r > 0) { $toWrite .= $buf; }
            }
            if (length($toWrite)) { $self->{_s}->syswrite($toWrite); }
         }
      }
   }
}

sub clientLoop {
   my ($self, $in, $out, $err) = @_;

   if (length($self->{e})) {
      socketpair(CHILD, PARENT, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
         or die(‘clientLoop: socketpair [$!]n’);
      CHILD->blocking(0);
      CHILD->autoflush(1);
      PARENT->blocking(0);
      PARENT->autoflush(1);

      defined(my $pid = fork()) or die(‘clientLoop: fork [$!]n’);
      if ($pid == 0) {
         my $parent = fileno(PARENT);
         open(STDIN, ‘<&=$parent’);
         open(STDOUT, ‘>&=$parent’);
         open(STDERR, ‘>&=$parent’);
         system($self->{e});
         exit(0);
      }
      else {
         my $child = fileno(CHILD);
         open(STDIN, ‘<&=$child’);
         open(STDOUT, ‘>&=$child’);
         open(STDERR, ‘>&=$child’);
         $self->_rwLoop(*STDIN, *STDOUT, *STDERR);
      }
   }
   else {
      $self->_rwLoop($in, $out, $err);
   }
}

sub server {
   my ($self) = @_;

   my $inet;
   my %args = (
      LocalAddr => $self->{s},
      LocalPort => $self->{p},
      Proto => $self->{u} ? ‘udp’ : ‘tcp’,
      Type => $self->{u} ? SOCK_DGRAM : SOCK_STREAM,
      ReuseAddr => 1,
   );
   $args{Listen} = 10 if ! $self->{u};
   if ($SupportIpv6 && $self->{6}) {
      $inet = ‘IO::Socket::INET6’;
      $args{Domain} = AF_INET6;
   }
   elsif ($SupportSsl && $self->{c}) {
      $inet = ‘IO::Socket::SSL’;
      $args{Domain} = ($SupportIpv6 && $self->{6}) ? AF_INET6 : AF_INET;
      if ($self->{a} && $self->{f} && $self->{k}) {
         $args{SSL_server} = 1;
         $args{SSL_use_cert} = 1;
         $args{SSL_ca_file} = $self->{a};
         $args{SSL_cert_file} = $self->{f};
         $args{SSL_key_file} = $self->{k};
      }
   }
   else {
      $inet = ‘IO::Socket::INET’;
      $args{Domain} = AF_INET;
   }

   my $s = $inet->new(%args);
   if (! defined($s)) {
      if ($SupportSsl && $self->{c}) {
         die(‘server: SSL error listening on: ‘.$self->{s}.’:’.$self->{p}.
             ‘ [‘.IO::Socket::SSL::errstr().’]’);
      }
      else {
         die(‘server: error listening on: ‘.$self->{s}.’:’.$self->{p}.
             ‘ [$!]’);
      }
      ($SupportIpv6 && $self->{6}) ? print ‘ (IPv6)n’ : print ‘ (IPv4)n’;
   }

   $s->blocking(0);
   $s->autoflush(1);
   $self->{_s} = $s;

   if ($self->{v}) {
      if ($SupportSsl && $self->{c}) {
         print(‘server: SSL listening on: ‘.$self->{s}.’:’.$self->{p});
      }
      else {
         print(‘server: listening on: ‘.$self->{s}.’:’.$self->{p});
      }
      ($SupportIpv6 && $self->{6}) ? print ‘ (IPv6)n’ : print ‘ (IPv4)n’;
   }
}

sub serverLoop {
   my ($self, $in, $out, $err) = @_;

   my $s = IO::Select->new;
   $s->add($self->{_s});

   open(my $oldout, ‘>&STDOUT’);

   while (my @ready = $s->can_read) {
      if ($self->{u}) {
         if (my $new = $self->{_s}->recv(my $tmp, 0)) {
            my ($port, $saddr, $ipaddr);
            if ($SupportIpv6 && $self->{6}) {
               ($port, $saddr) = Socket6::sockaddr_in6($new);
               $ipaddr = Socket6::inet_ntop(AF_INET6, $saddr);
            }
            else {
               ($port, $saddr) = sockaddr_in($new);
               $ipaddr = inet_ntoa($saddr);
            }

            if ($self->{v}) {
               print $oldout ‘serverLoop: connection from: $ipaddr:$portn’;
               if ($self->{c}) {
                  print ‘serverLoop: using cipher: ‘.$new->get_cipher.’n’;
               }
            }

            # We MUST use connected UDP, otherwise syswrite() and sysread()
            # do not work
            if ($SupportIpv6 && $self->{6}) {
               connect($self->{_s}, Socket6::sockaddr_in6($port, $saddr))
                  or die(‘serverLoop: error doing connect() [$!]n’);
            }
            else {
               connect($self->{_s}, sockaddr_in($port, $saddr))
                  or die(‘serverLoop: error doing connect() [$!]n’);
            }
            $self->clientLoop($in, $out, $err);
         }
      }
      elsif (my $new = $self->{_s}->accept) {
         if ($self->{v}) {
            print $oldout ‘serverLoop: connection from: ‘.$new->peerhost.’:’.
                          $new->peerport.’n’;
            if ($self->{c}) {
               print ‘serverLoop: using cipher: ‘.$new->get_cipher.’n’;
            }
         }

         $new->blocking(0);
         $new->autoflush(1);

         defined(my $pid = fork()) or die(‘serverLoop: fork [$!]n’);
         if ($pid == 0) { # Son process
            $self->{_s} = $new;
            $self->clientLoop($in, $out, $err);
            if ($self->{v}) {
               print $oldout ‘serverLoop: client ‘.$new->peerhost.’:’.
                             $new->peerport.’ disconnectedn’;
            }
            exit(0);
         }
      }
   }
}

sub proxy {
   my ($self) = @_;
   $self->server;
}

sub proxyLoop {
   my ($self, $in, $out, $err) = @_;

   my $s = IO::Select->new;
   $s->add($self->{_s});

   open(my $oldout, ‘>&STDOUT’);

   while (my @ready = $s->can_read) {
      if ($self->{u}) {
         if (my $new = $self->{_s}->recv(my $tmp, 0)) {
            my ($port, $saddr, $ipaddr);
            if ($SupportIpv6 && $self->{6}) {
               ($port, $saddr) = Socket6::sockaddr_in6($new);
               $ipaddr = Socket6::inet_ntop(AF_INET6, $saddr);
            }
            else {
               ($port, $saddr) = sockaddr_in($new);
               $ipaddr = inet_ntoa($saddr);
            }

            if ($self->{v}) {
               print $oldout ‘proxyLoop: connection from: $ipaddr:$portn’;
               if ($self->{c}) {
                  print ‘proxyLoop: using cipher: ‘.$new->get_cipher.’n’;
               }
            }

            # We MUST use connected UDP, otherwise syswrite() and sysread()
            # do not work
            if ($SupportIpv6 && $self->{6}) {
               connect($self->{_s}, Socket6::sockaddr_in6($port, $saddr))
                  or die(‘proxyLoop: error doing connect() [$!]n’);
            }
            else {
               connect($self->{_s}, sockaddr_in($port, $saddr))
                  or die(‘proxyLoop: error doing connect() [$!]n’);
            }
            my $new = $self->{_s};

            my ($host, $dport, $v6, $ssl) = split(‘:’, $self->{r});
            $self->{6} = (defined($v6) && $v6 =~ /ipv6/i) ? 1 : 0;
            $self->{c} = (defined($ssl)) ? 1 : 0;
            $self->{s} = $host; $self->resolv;
            $self->{p} = $dport;
            $self->client;
            $self->clientLoop($new, $new, $new);
         }
      }
      elsif (my $new = $self->{_s}->accept) {
         print $oldout ‘proxyLoop: connection from: ‘.$new->peerhost.’:’.
                       $new->peerport.’n’
            if $self->{v};

         $new->blocking(0);
         $new->autoflush(1);

         defined(my $pid = fork()) or die(‘proxyLoop: fork [$!]n’);
         if ($pid == 0) { # Son process
            my ($host, $port, $v6, $ssl) = split(‘:’, $self->{r});
            $self->{6} = (defined($v6) && $v6 =~ /ipv6/i) ? 1 : 0;
            $self->{c} = (defined($ssl)) ? 1 : 0;
            $self->{s} = $host; $self->resolv;
            $self->{p} = $port;
            $self->client;
            $self->clientLoop($new, $new, $new);
            print $oldout ‘proxyLoop: client ‘.$new->peerhost.’:’.
                          $new->peerport.’ disconnectedn’
               if $self->{v};
            exit(0);
         }
      }
   }
}

sub run {
   my ($self) = @_;

   $self->init;
   if ($self->{l}) {
      $self->server;
      $self->serverLoop(*STDIN, *STDOUT, *STDERR);
   }
   elsif ($self->{r}) {
      $self->proxy;
      $self->proxyLoop(*STDIN, *STDOUT, *STDERR);
   }
   else {
      $self->client;
      $self->clientLoop(*STDIN, *STDOUT, *STDERR);
   }
   $self->exit;
}

sub exit {
   my ($self) = @_;
   $self->{_s}->close if defined($self->{_s});
   exit;
}

1;

package main;
use warnings; use strict;

my $prog = ‘scnc’;
my $progname = ‘SSL Capable NetCat 1.00’;

use Getopt::Std;

my $opts = ”;
my $usage = ‘$prognamennUsage: $prog [-options] target portnn’;

if ($SupportSsl) {
   $usage .= ‘ -c use SSL (default to not)n’;
   $usage .= ‘ -a use SSL certificate authority filen’;
   $usage .= ‘ -f use SSL certificate file (PEM format)n’;
   $usage .= ‘ -k use SSL private key file (PEM format)n’;
   $opts .= ‘ca:f:k:’;
}
if ($SupportTelnet) {
   $usage .= ‘ -t do telnet negociation (default to not)n’;
   $opts .= ‘t’;
}
if ($SupportIpv6) {
   $usage .= ‘ -6 use IPv6 (default to not)n’;
   $opts .= ‘6’;
}
$usage .= ‘ -e cmd command to executen’;
$usage .= ‘ -l listen for connections (default to not)n’;
$usage .= ‘ -p port use local port number (default to random high)n’;
$usage .= ‘ -s address use address for bindings (default to all addresses)n’;
$usage .= ‘ -u use UDP socket (default to TCP)n’;
$usage .= ‘ -v be verbose (default to not)n’;
$usage .= ‘ -r host:port proxy connection to host:portn’;
$usage .= ‘ -r host:port:ipv6 proxy connection to host:port using IPv6n’;
$usage .= ‘ -r host:port::ssl proxy connection to host:port using SSLn’;
$usage .= ‘ -r host:port:ipv6:ssl proxy connection to host:port using IPv6 and SSLn’;
$opts .= ‘e:lp:s:uvr:’;

my %opts;
getopts($opts, %opts);

$opts{p} = pop unless defined($opts{p});
$opts{s} = pop unless defined($opts{s});

if ((! $opts{l})
&& (! $opts{r})
&& (! defined($opts{s}) || ! defined($opts{p}))) {
   print $usage and exit(0);
}

Scnc->new(%opts)->run;’

Categories: Tools