#!/usr/bin/perl
#
# Process live or saved network traffic into files representing TCP streams
#
# Usage: ./split_into_sessions.pl [FILE]
#
# Yes, this program is very ugly.  Patches are welcome, flames are not.
#
# 	- Andrew Sayers, andrew-code-split_into_sessions@ccl.bham.ac.uk
#

use Net::Pcap;
use NetPacket::Ethernet qw(:strip);
use NetPacket::IP qw(:strip IP_PROTO_TCP);
use NetPacket::TCP qw(:strip FIN);

my %filehandles;

{ package stream_handler;

  sub print {
      my $self = shift;
      my ($ip_obj, $tcp_obj, $timestamp) = @_;
      $data = $tcp_obj->{data};

      if ($tcp_obj->{flags} == 0x0018) { # 0x0018 == PSH,ACK

	  $data =~ s/&/&amp;/g;
	  $data =~ s/</&lt;/g;
	  $data =~ s/>/&gt;/g;
	  $data =~ s/\r\n/<code class="newline">\\r\\n<\/code><br \/>\n/g;

	  if ($data =~ /<br \/>\n$/) {
	      $data =~ s/<br \/>\n$//g;
	  } else {
	      $data .= " <code class=\"other\">(no newline)</code>";
	  };

	  print { $self->{OUT} }
	  	"<!-- packet source=\"$ip_obj->{src_ip}\" time=\"$timestamp\" -->\n	<p><code class=\""
	      . (($ip_obj->{src_ip} eq $self->{client})?"client\">&gt;&gt;&gt; ":"server\">&lt;&lt;&lt; ") 
	      . "$data</code></p>\n<!-- End of packet -->\n";

      };

  }

  sub new {
      my $class = shift;

      my ($ip_obj, $tcp_obj, $time) = @_;

      my $FILE;

      my ($port)=$tcp_obj->{dest_port};
      if ($port==1863) {
	  $port="msn";
      } else {
	  open FILE, "</etc/services";
	  while (<FILE>) {
	      if (/^([^ 	]*)[ 	]*$port\/tcp/) {
		  $port=$1;
		  last;
	      }
	  };
      };

      $source=`dig -x $ip_obj->{src_ip} +short | head -1`; chomp($source); if ($source eq "") {$source = $ip_obj->{src_ip};};
      $dest=`dig -x $ip_obj->{dest_ip} +short | head -1`; chomp($dest); if ($dest eq "") {$dest = $ip_obj->{dest_ip};};

      system("mkdir -p $source/$dest");
      open  $FILE, ">", "$source/$dest/$time.$port.html";

      print $FILE
"<html>
 <head>
  <title>Dump of a single TCP stream</title>
  <link rel=\"stylesheet\" href=\"../../default.css\" type=\"text/css\" />
 </head>

 <body>

 <h1>Session details</h1>
 <p>Client: $source, port: $tcp_obj->{src_port}</p>
 <p>Server: $dest, port: $port</p>

 <p>Session started at $time, UNIX time
 <h1>Log</h1>

";

      bless { OUT => $FILE, client => $ip_obj->{src_ip}, turn => "client" }, $class;

  }
};

# Sort each packet into its respective stream
sub process_pkt {
    my($arg, $hdr, $pkt) = @_;

    my $ip_obj = NetPacket::IP->decode(eth_strip($pkt));

    if ($ip_obj->{proto} == IP_PROTO_TCP) {

	my $tcp_obj = NetPacket::TCP->decode($ip_obj->{data});

	my $stream = $filehandles{$tcp_obj->{src_port} . $ip_obj->{src_ip} .
				  $tcp_obj->{dest_port} . $ip_obj->{dest_ip}};

	if (!$stream) {
	    # Packet is first in stream
	    $stream = stream_handler->new($ip_obj, $tcp_obj, $hdr->{tv_sec});
	    $filehandles{$tcp_obj->{src_port} . $ip_obj->{src_ip} . $tcp_obj->{dest_port} . $ip_obj->{dest_ip}} = $stream;
	    $filehandles{$tcp_obj->{dest_port} . $ip_obj->{dest_ip} . $tcp_obj->{src_port} . $ip_obj->{src_ip}} = $stream;
	 };

	$stream->print($ip_obj, $tcp_obj, $hdr->{tv_sec});
	
    };
}

# Open the saved file specified on the command-line, process it
my($dev, $err, $pcap_t, $filter);

if ($ARGV[0]) {
    $pcap_t = Net::Pcap::open_offline($ARGV[0], \$err);

    if (!defined($pcap_t)) {
	die("Net::Pcap::open_offline returned error $err");
    };

} else {
    $dev = Net::Pcap::lookupdev(\$err);

    if (!defined($dev)) {
	die("Net::Pcap::lookup returned error $err");
    }

    $pcap_t = Net::Pcap::open_live($dev, 65536, 1, 0, \$err);

    if (!defined($pcap_t)) {
	die("Net::Pcap::open_live returned error $err");
    }

};

Net::Pcap::loop($pcap_t, 0, \&process_pkt, "");
Net::Pcap::close($pcap_t);

for $stream (values %filehandles) {
    print { $stream->{OUT} } "</code></p>\n</body>\n</html>\n";
    close $stream->{OUT};
};
