#!/usr/bin/perl
# Filename:	tcp_forward
# Author:	David Ljung Madison <DaveSource.com>
# See License:	http://MarginalHacks.com/License
# Description:	Forwards a tcp connection, allows snooping of both directions
# Version:	1.01
use strict;
use IO::Socket;
use Net::hostent;	# for OO version of gethostbyaddr

##################################################
# Setup the variables
##################################################
my $PROGNAME	= $0;
$PROGNAME	=~ s|.*/||;

# This gets and receives the streams by lines.
#   Pro:  Easier for filtering
#   Con:  Can't do tty or character based stuff (like logins, etc..)
#   Con:  "Lines" can get long for binary data - wastes memory
my $SEND_LINE_BY_LINE	= 0;
my $RECV_LINE_BY_LINE	= 0;

# Try this to see the example filter
#   Then do:  tcp_forward -f 2000 -t getdave.com:80 -send
#   And then point a browser to localhost:2000
#   (Try it with and without the filter)
my $EXAMPLE_FILTER	= 0;
$SEND_LINE_BY_LINE = 1 if ($EXAMPLE_FILTER);

# Reap zombies
$SIG{CHLD} = 'IGNORE';

##################################################
# Usage
##################################################
sub usage {
  my $msg;
  foreach $msg (@_) { print "ERROR:  $msg\n"; }
  print "\n";
  print "Usage:\t$PROGNAME [-options]\n";
  print "\tTCP port forwarder.  Forwards all traffic on a TCP port.\n";
  print "\t-f <port>      From port (listen here)\n";
  print "\t-t <host:port> To location (send to here)\n";
  print "\t-recv          Listen to all traffic we get back\n";
  print "\t-send          Listen to all traffic we send (forward)\n";
  print "\n";
  exit -1;
}

sub parse_args {
  my ($from,$to_host,$to_port,$recv,$send);

  while ($#ARGV>=0) {
    my $arg=shift(@ARGV);
    if ($arg =~ /^-h$/) { usage(); }
    if ($arg =~ /^-f(rom)?$/) { $from = shift(@ARGV); next; }
    if ($arg =~ /^-to?$/) {
      $to_port = shift(@ARGV);  $to_host = "localhost";
      ($to_host,$to_port) = ($`,$') if ($to_port =~ /:/);
      next;
    }
    if ($arg =~ /^-s(end)?$/) { $send = 1; next; }
    if ($arg =~ /^-r(ecv)?$/) { $recv = 1; next; }
    if ($arg =~ /^-/) { usage("Unknown option: $arg"); }
    usage("Unknown argument [$arg]");
  }

  usage("No 'from' port defined") unless (defined $from);
  usage("No 'to' host defined") unless (defined $to_host);
  usage("No 'to' port defined") unless (defined $to_port);

  ($from,$to_host,$to_port,$recv,$send);
}

##################################################
# Outbound Connection
##################################################
sub outbound {
  my ($host,$port) = @_;

  $host = $host || "localhost";
  $port = $port || 23;

  my $handle = IO::Socket::INET->new(Proto    => "tcp",
                                     PeerAddr => $host, PeerPort => $port)
               or die "cannot connect to port [$port] at localhost";
  $handle->autoflush(1);

  $handle;
}

##################################################
# Server side
##################################################
sub server {
  my ($from,$to_host,$to_port,$recv,$send) = @_;

  my $server = IO::Socket::INET->new( Proto     => 'tcp',
                                      LocalPort => $from,
                                      Listen    => SOMAXCONN,
                                      Reuse     => 1);

  die "Can't setup server: $!\n" unless $server;
  print STDERR "Server started on port $from\n";

  #########################
  # Get an incoming connection
  #########################
  my $client;
  while ($client = $server->accept()) {
    print STDERR "Incoming connection\n";
    $client->autoflush(1);

    #########################
    # Fork so we can get more connections
    #########################
    my $pid = fork;
    die("[$PROGNAME] Couldn't fork: $!\n") if (!defined $pid);
    if ($pid) {
      close $client;
      next;
    }

    #########################
    # Setup an outbound connection
    #########################
    my $out_handle = outbound($to_host,$to_port);
    print STDERR "Forwarding to $to_host:$to_port\n";

    my $pid = fork;
    die("[$PROGNAME] Couldn't fork: $!\n") if (!defined $pid);

    if ($pid) {

      #########################
      # Receive data from the outbound/forwarded port
      #########################
      my ($c,$line);
      if ($RECV_LINE_BY_LINE) {
        while($line = <$out_handle>) {
          print $client $line;
          $line =~ s/[\r\n]//g;
          print STDERR "R [$line]\n" if ($recv);
        }
      } else {
        while(sysread($out_handle,$c,1)) {
          if ($recv) {
            if ($c =~ /[\r\n]/) {
              print STDERR "R [$line]\n" if ($line);
              undef $line;
            } else {
              $line.=$c;
            }
          }
          syswrite($client,$c);
        }
        print STDERR "R [$line] <no eol>\n" if ($line && $recv);
      }
      # Kill the child
      kill 9, $pid;

    } else {

      #########################
      # Send data to the forward
      #########################
      my ($c,$line);
      if ($SEND_LINE_BY_LINE) {
        while($line = <$client>) {
          $line =~ s|GET /(.+) HTTP/1.0|GET http://GetDave.com/$1 HTTP/1.1|
            if ($EXAMPLE_FILTER);	# See top of script
          print $out_handle $line;
          $line =~ s/[\r\n]//g;
          print STDERR "S [$line]\n" if ($send);
        }
      } else {
        while(sysread($client,$c,1)) {
          if ($send) {
            if ($c =~ /[\r\n]/) {
              print STDERR "S [$line]\n" if ($line);
              undef $line;
            } else {
              $line.=$c;
            }
          }
          syswrite($out_handle,$c);
        }
        print STDERR "S [$line] <no eol>\n" if ($line && $send);
      }
    }

    print STDERR "Closed connection\n";
    close $client;
    close $out_handle;

    exit;
  }
}


##################################################
# Main code
##################################################
sub main {
  server(parse_args());
} main();

