#!/usr/local/bin/perl -w

# Copyright (c) 2001 Carnegie Mellon University
# All rights reserved.
# See Copyright section at end of file use and distribution information.

#
# trace -h <host> -f <filter>  [files]
#
# trace -help   will print a description of what the output means.
#
# this is a simple Net::Argus::HostPairs program that prints out a detailed
# trace of what a host has been up to.  
#
use strict;
use Getopt::Long;
use Net::Argus::HostPairs;
use POSIX;
use Socket;
################################################## 
#
# to make this program work:  
#   explicitly set $ra and $rarc
# or 
#   set basenames and paths so that they can be found.

my $ra = "";
my $rarc = "";

my $ra_basename = "ra";
my $rarc_basename = "rarc.hostpairs";

my @ra_paths = qw($ARGUSHOME/bin $HOME/bin);
my @rarc_paths = qw($ARGUSHOME $HOME);

#################################################


my $out_format = 
"%8.8s %8.8s  %-15.15s %-12.12s   %5.5s  %5.5s %6.6s   %8s %8s %10s %10s %7s\n";


my @options = ("h=s","f=s","e=s","help");

my %opts;
my $filter;
my $target;


GetOptions(\%opts, @options);
my @files = ();

help() if ($opts{'help'});

my $hp = Net::Argus::HostPairs->new();

if ($opts{'h'}) {
    $target = $opts{'h'};
    if ($target =~  m/,/) {
        print STDERR "Only specify 1 host\n";
        exit 1;
    }        
} else {
    print STDERR "Must specify host\n";
    exit 1;
}    


if (@ARGV) {
    @files = @ARGV;
} elsif ($ENV{'ARGUSFILES'}) {
    @files = split ' ', $ENV{'ARGUSFILES'};
} else {
    print STDERR "No files to process\n";
    exit 1;
}    

if ($ra) {
    $hp->ra_prog($ra);
} else {
    my $raprog = find_file($ra_basename, @ra_paths);
    if (-x $raprog && -f _) {
        $hp->ra_prog($raprog);
    } else {
        print STDERR
         "Need to specify location of ra program. See source for details.\n";
        exit 1;
    }
}    

if ($rarc) {
    $hp->rarc($rarc);
} else {
    my $rarc = find_file($rarc_basename, @rarc_paths);
    if (-x $rarc && -f _) {
        $hp->rarc($rarc);
    } else {
        print STDERR
         "Need to specify location of rarc. See source for details.\n";
        exit 1;
    }
}


if ($opts{'filter'}) {
    $filter = $opts{'f'};
} else {
    $filter = "(host $target)";
}    


$hp->mode('portpair');
$hp->hostset1($target);
$hp->hostpair_output('h1_ip,h2_ip,h1_name,h2_name');
$hp->portpair_output(
 'stime,etime,h1_port,h2_port,proto,h1_pkts,h2_pkts,h1_bytes,h2_bytes');

$hp->filter($filter);
$hp->argus_files(@files);

if ($opts{'e'}) {
    $hp->ra_extra_args(split(' ', $opts{'e'}));
}    

$hp->accumulate();
$hp->_derive_fields();   # this was originally designed only for internal use.

my $pairs = $hp->get_pairs();

my @keys = ();

my @pairkeys = keys %$pairs;

unless (@pairkeys) {
    print "# No matches for host  $target  found in files:\n";
    foreach my $f (@files) {
        print "#    $f\n";
    }
    exit 0;
}

foreach my $hostkey (@pairkeys) {
    foreach my $portkey (keys %{$pairs->{$hostkey}->{'ports'}}) {
        push @keys, ([$hostkey, $portkey]);
    }        
}        

@keys = sort { $pairs->{$a->[0]}->{'ports'}->{$a->[1]}->{'stime'} <=> 
                  $pairs->{$b->[0]}->{'ports'}->{$b->[1]}->{'stime'}} @keys;


my $h1_ip = $pairs->{$keys[0]->[0]}->{'h1'};
my $h1_name = $pairs->{$keys[0]->[0]}->{'h1_name'};


print "# Trace output for $h1_ip = $h1_name\n";
print "# Files processed:\n";
foreach my $f (@files) {
    print "#    $f\n";
}

print "\n";

my $first_time =
    $pairs->{$keys[0]->[0]}->{'ports'}->{$keys[0]->[1]}->{'stime'};
my $first_timestamp = POSIX::strftime("%a %b %d, %Y", localtime($first_time));
print "-- $first_timestamp --\n\n";
my $day = POSIX::strftime("%j", localtime($first_time));
foreach my $k (@keys) {
    my $hostkey = $k->[0];
    my $portkey = $k->[1];
    my $t1 =  int($pairs->{$hostkey}->{'ports'}->{$portkey}->{'stime'});
    my $t2 =  int($pairs->{$hostkey}->{'ports'}->{$portkey}->{'etime'});
    my $stime = POSIX::strftime("%T", localtime($t1));
    my $etime = POSIX::strftime("%T", localtime($t2));
    my $h1_bytes = $pairs->{$hostkey}->{'ports'}->{$portkey}->{'h1_bytes'};
    my $h2_bytes = $pairs->{$hostkey}->{'ports'}->{$portkey}->{'h2_bytes'};
    my $h1_pkts = $pairs->{$hostkey}->{'ports'}->{$portkey}->{'h1_pkts'};
    my $h2_pkts = $pairs->{$hostkey}->{'ports'}->{$portkey}->{'h2_pkts'};
    my $h1_port = $pairs->{$hostkey}->{'ports'}->{$portkey}->{'h1_port'};
    my $h2_port = $pairs->{$hostkey}->{'ports'}->{$portkey}->{'h2_port'};
    $h1_port = '*' if ($h1_port == 77777);  # port number that signifies no port.
    $h2_port = '*' if ($h2_port == 77777);
    my $proto =   $pairs->{$hostkey}->{'ports'}->{$portkey}->{'proto'};    
    my $records =   $pairs->{$hostkey}->{'ports'}->{$portkey}->{'records'};
    my $h2_ip =   $pairs->{$hostkey}->{'h2'};
    my $h2_name = $pairs->{$hostkey}->{'h2_name'};
    if (POSIX::strftime("%j", localtime($t1)) ne $day) {
        my $timestamp = POSIX::strftime("%a %b %d, %Y", localtime($t1));
        print "\n-- $timestamp --\n\n";
        $day = POSIX::strftime("%j", localtime($t1));
    }        
    printf "$out_format", $stime, $etime, $h2_ip, $h2_name, $h1_port,
                          $h2_port, $proto, $h1_pkts, $h2_pkts, $h1_bytes, 
                          $h2_bytes,$records;
}                       



############################################################################
# given a basename and a "path" of form ($FOO/bin $FOOB) etc, looks for
# basename under that path.  Returns full path to first one found, or
# false value if not found.  

sub find_file {
    my $basename = shift;
    my @paths = @_;
    if (substr($basename,0,1) eq '/') {
        return (-e $basename) ? $basename : 0;
    }
    return 0 unless (@paths);
PATH: foreach my $p (@paths) {
        my @components = split /\//, $p;
        my @result = ();
        foreach my $comp (@components) {
            if (substr($comp,0,1) eq '$') {
                $comp = substr $comp,1;
                next PATH unless $ENV{$comp};
                push @result, $ENV{$comp};
            } else {
                push @result, $comp;
            }
        }
        my $fullpath = join('/', @result, $basename);
        return $fullpath if (-e $fullpath);
    }
    return 0;
}    


{ 

my %host_cache;

sub resolve_hosts {
        my @hosts = @_;
        my $h = {};
        return $h unless (@hosts);
        foreach my $host (@hosts) {
                        if (exists $host_cache{$host}) {
                                $$h{$host} = $host_cache{$host};
                        } else {        
                my $hname = gethostbyaddr((inet_aton $host), AF_INET);
                                $hname = (defined $hname) ? $hname : '???';
                                $host_cache{$host} = $hname;
                                $$h{$host} = $hname;
                          }

        }
        return $h;
}

}


sub help {

    print <<'_END_';
    
    Usage: trace -h <host> -f <'filter'> -e <'ra_args'> [files]

    trace <host> will produce a sorted, according to time, list of hosts,
    port-pairs, and traffic that provides some indication of what the target
    host has been up to.  Exactly 1 line is printed out per distinct
    hostpair/portpair combination.  If no filter is given, the filter 
    'host <host>' will be used by default (though any command line option 
    will over-ride this).  trace works by running ra and parsing its output.
    The -e option allows one to pass additional options (such as time ranges
    or -A) to ra.
        
    Output format:
    
    stime etime h2_ip h2_name h1_p h2_p proto h1_pkt h2_pkt h1_byt h2_byt rec
    
    Where:
    
        stime   =   Start time of first record with these hosts and ports
        etime   =   End time of last record with these hosts and ports
        h2_ip   =   IP address of host that target host was talking to
        h2_name =   Name (if resolvable, '???' if not) of h2_ip
        h1_p    =   Port on target host involved in transaction ('*' if no port)
        h2_p    =   Port on h2 host involved in transaction
        proto   =   Name of protocol
        h1_pkt  =   Packets transmitted by target host
        h2_pkt  =   Packets transmitted by h2 host
        h1_byt  =   Bytes transmitted by target host
        h2_byt  =   Bytes transmitted by h2 host
        rec     =   Number of Argus records matching hostpair/portpair.

    Note:  Output is wide (more than 80 columns).

_END_

    exit 0;
}

=head1 NAME

trace - Print sorted hostpair and portpair info about a host

=head1 SYNOPSIS

 trace -h <host> [-f <filter>] [-e <ra args>]

=head1 DESCRIPTION

Trace prints out a sorted (by time of start of argus records) list of 
host and port information for the given host.  Argus records are aggregated
in that all records with the same pair of hosts and ports are printed on
one line, with summary packet/byte counts, along with the earliest start 
time and latest end time of the matching Argus records.  The total number
of matching Argus records is also printed.

=head1 USAGE

 trace -help

will print out usage information.

=head1 AUTHOR

Clauss Strauch (cbs@cs.cmu.edu)

=head1 COPYRIGHT

Copyright (c) 2001 Carnegie Mellon University
All Rights Reserved.

Permission to use, copy, modify, and distribute this software and
its documentation for any purpose and without fee is hereby granted, 
provided that the above copyright notice appear in all copies and
that both that copyright notice and this permission notice appear
in supporting documentation, and that the name of CMU not be
used in advertising or publicity pertaining to distribution of the
software without specific, written prior permission.  

CMU DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.

Carnegie Mellon requests users of this software to return to

Software_Distribution@CS.CMU.EDU or

Software Distribution Coordinator
School of Computer Science
Carnegie Mellon University
Pittsburgh PA 15213-3890

any improvements or extensions that they make and grant Carnegie Mellon
the rights to redistribute these changes.

=cut
