#!perl
=head1 NAME
virani - PCAP fetch tool for use with FPCs that save to PCAP format.
=head1 SYNOPSIS
virani B<-s> <start> B<-e> <end> B<-f> <filter> [B<-t> <type>] [B<--set> <set>]
[B<--config> <file>] [B<-w> <output] [B<--nc>]
virani B<-s> <start> B<-e> <end> [B<-t> <type>] [B<--set> <set>]
[B<--config> <file>] [B<-w> <output] [B<--nc>] <filter>
virani B<-r> <remote> B<-s> <start> B<-e> <end> B<-f> <filter> [B<-t> <type>] [B<--set> <set>]
B<--config> <file>] [B<-w> <output] [B<--nc>] [B<-a> <apikey>] [B<-k>]
virani B<-r> <remote> B<-s> <start> B<-e> <end> [B<-t> <type>] [B<--sett> <set>]
B<--config> <file>] [B<-w> <output] [B<--nc>] [B<-a> <apikey>] [B<-k>] <filter>
=head1 DESCRIPTION
=head1 LOCAL
Will read in the config '/usr/local/etc/virani.toml' and search the specified PCAP dirs.
For information on the config, please see L<Virani>.
=head2 REMOTE
When used with B<-r>, it connects up to a remote location running mojo-virani.
If the item specified by that switch is a HTTP or HTTPS url it will use that
for with L<Viarni::Client>. Otherwise it will use that as part of a config file name
or path to a config file. Searching in the order below.
$remote
$remote.toml
/usr/local/etc/virani.d/$remote
/usr/local/etc/virani.d/$remote.toml
/etc/virani.d/$remote
/etc/virani.d/$remote.toml
If a API key is needed, it is read in in the order below.
-a
$ENV{virani_api_key}
$config{apikey}
If using HTTPS, cert verification is read in the order below.
'-k' is true and the rest are boolean.
-k
$ENV{VIRANI_VERIFY_HOSTNAME}
$ENV{HTTPS_VERIFY_HOSTNAME}
$ENV{PERL_LWP_VERIFY_HOSTNAME}
$config{verify_hostname}
=head1 FLAGS
=head2 -r <remote>
Remote URL or config file for remote info.
=head2 -a <apikey>
API key for remote URL if needed.
=head2 -f <filter>
Filter for use with tshark or tcpdump.
If this is undef, ARGV will be used instead for filter info.
If filter points to a file, teasted via -f, then that file will be
read in and used the filter.
=head2 -t <type>
tcpdump, tshark, or bpf2tshark
If not specified will default to what ever the default is for that set.
=head2 --set <set>
Set to use. If undef, uses whatever the default is.
Default :: undef
=head2 --config <config>
Config file to use.
Default :: /usr/local/etc/virani.toml
=head2 -s <timestamp>
Start timestamp. Any format supported by
Time::Piece::Guess is usable.
=head2 -e <timestamp>
End timestamp. Any format supported by
Time::Piece::Guess is usable.
=head2 -w <output>
The file to write the PCAP to.
Default :: out.pcap
=head2 --nc
If cached, do not use it.
=head2 -k
Do not check the SSL cert for HTTPS for remote.
=head2 --buffer <seconds>
Apply this many seconds before and after the start time.
Default: undef
=cut
use strict;
use Virani;
use TOML;
use JSON;
sub version {
print 'viarni v. ' . $Virani::VERSION . "\n";
}
sub help {
&version;
print '
--help Print this.
-h Print this.
--version Print version.
-v Print version..
-r <remote> Remote URL or config file for remote info.
-a <apikey> API key for remote URL if needed.
-f <filter> Filter for use with tshark or tcpdump.
-t <type> tcpdump or tshark
Default :: tcpdump
--set <set> Set to use. If undef, uses whatever the default is.
Default :: undef
--config <config> Config file to use.
Default :: /usr/local/etc/virani.toml
-s <timestamp> Start timestamp. Any format supported by
Time::Piece::Guess is usable.
-e <timestamp> End timestamp. Any format supported by
Time::Piece::Guess is usable.
-w <output> The file to write the PCAP to.
Default :: out.pcap
--nc If cached, do not use it.
-k Do not check the SSL cert for HTTPS for remote.
--buffer <secs> Apply this many seconds before and after the start time.
Default: undef
';
} ## end sub help
# get the cli optiosn
my $help = 0;
my $version = 0;
my $filter = undef;
my $start;
my $end;
my $write = 'out.pcap';
my $format;
my $remote;
my $url;
my $apikey;
my $set = 'default';
my $config_virani = '/usr/local/etc/virani.toml';
my $tmpdir;
my $no_cache = 0;
my $quiet = 0, my $verbose = 1;
my $type;
my $verify_hostname = 1;
my $verify_hostname_flag = 0;
my $timeout;
my $buffer;
Getopt::Long::Configure('no_ignore_case');
Getopt::Long::Configure('bundling');
GetOptions(
'version' => \$version,
'v' => \$version,
'help' => \$help,
'h' => \$help,
'f=s' => \$filter,
's=s' => \$start,
'e=s' => \$end,
'w=s' => \$write,
'r=s' => \$remote,
'set=s' => \$set,
'nc' => \$no_cache,
'q' => \$quiet,
'config=s' => \$config_virani,
'a=s' => \$apikey,
't=s' => \$type,
'k' => \$verify_hostname_flag,
'timeout=s' => \$timeout,
'buffer=s' => \$buffer,
);
if ($help) {
&help;
exit;
}
if ($version) {
&version;
exit;
}
# if not specified via -f use ARGV as for the filter
if ( !defined($filter) ) {
if ( defined( $ARGV[0] ) ) {
$filter = join( ' ', @ARGV );
}
}
# remove ending and trailing spaces
if ( defined($filter) ) {
$filter =~ s/^\s+//;
$filter =~ s/\s+$//;
} else {
$filter = '';
}
# if the filter is file, read the file and use it as the filter
# once read in new lines will be replaced with spaces and commented lines removed
if ( -f $filter ) {
my $raw_filter;
eval { $raw_filter = read_file($filter); };
if ($@) {
die( 'Failed to read "' . $filter . '" ... ' . $@ );
}
$filter = '';
# split the read data appart, removing blank lines and lines with juts
foreach my $line ( grep( !/^\s*\#/, grep( !/^\s*$/, split( /\n/, $raw_filter ) ) ) ) {
# remove leading and trailing white space
$line =~ s/^\s+//;
$line =~ s/\s+$//;
# comments at the end of a line
$line =~ s/\#.*$//;
$filter = $filter . ' ' . $line;
}
# remove the space that the start that the code above will result in
$filter =~ s/^\ //;
} ## end if ( -f $filter )
if ( !defined($start) ) {
die('No start time set via -s');
} elsif ( !defined($end) ) {
die('No end time set via -e');
}
if ($quiet) {
$verbose = 0;
}
if ($verify_hostname_flag) {
$verify_hostname = 0;
}
# make sure the buffer is numeric if specified
if ( defined($buffer) && $buffer !~ /^\d+$/ ) {
die( '--buffer is set to "' . $buffer . '" which is non-numeric' );
} # buffer is numeric, so apply it to the start and end
elsif ( defined($buffer) ) {
my $start_obj = Time::Piece::Guess->guess_to_object( $start, 1 );
if ( !defined($start_obj) ) {
die( '-s value of "' . $start . '" could not be parsed by Time::Piece::Guess' );
}
my $end_obj = Time::Piece::Guess->guess_to_object( $end, 1 );
if ( !defined($end_obj) ) {
die( '-e value of "' . $end . '" could not be parsed by Time::Piece::Guess' );
}
# apply the offsets
$start_obj = $start_obj - $buffer;
$end_obj = $end_obj + $buffer;
# get the new start time
$start = $start_obj->epoch;
$end = $end_obj->epoch;
} ## end elsif ( defined($buffer) )
my $start_obj;
eval { $start_obj = Time::Piece::Guess->guess_to_object( $start, 1 ); };
if ( $@ || !defined($start_obj) ) {
die( 'Failed to parse the start stamp,"' . $start . '",' );
}
my $end_obj;
eval { $end_obj = Time::Piece::Guess->guess_to_object( $end, 1 ); };
if ( $@ || !defined($end_obj) ) {
die( 'Failed to parse the end timestamp,"' . $end . '",' );
}
# handles it if it is not remote
if ( !$remote ) {
my $virani = Virani->new_from_conf( conf => $config_virani );
$virani->set_verbose($verbose);
$virani->set_verbose_to_syslog(0);
my $returned = $virani->get_pcap_local(
start => $start_obj,
end => $end_obj,
filter => $filter,
file => $write,
no_cache => $no_cache,
verbose => $verbose,
set => $set,
type => $type,
);
if ( $returned->{using_cache} ) {
my $json = JSON->new->allow_nonref->pretty->canonical(1);
print "Cache metadata...\n" . $json->encode($returned);
}
exit 0;
} else {
# check to see if remote is a config file
my $config_file;
if ( -f $remote ) {
$config_file = $remote;
} elsif ( -f $remote . '.toml' ) {
$config_file = $remote . '.toml';
} elsif ( -f '/usr/local/etc/virani.d/' . $remote ) {
$config_file = '/usr/local/etc/virani.d/' . $remote;
} elsif ( -f '/usr/local/etc/virani.d/' . $remote . '.toml' ) {
$config_file = '/usr/local/etc/virani.d/' . $remote . '.toml';
} elsif ( -f '/etc/virani.d/' . $remote ) {
$config_file = '/etc/virani.d/' . $remote;
} elsif ( -f '/etc/virani.d/' . $remote . '.toml' ) {
$config_file = '/etc/virani.d/' . $remote . '.toml';
}
my $toml;
if ( defined($config_file) ) {
print 'Config File: ' . $config_file . "\n";
my $err;
my $raw_toml = read_file($config_file) || die( 'Failed to read "' . $config_file . '"' );
( $toml, $err ) = from_toml($raw_toml);
unless ($toml) {
die( "Error parsing '" . $config_file . "':" . $err );
}
} elsif ( $remote =~ /^[Hh][Tt][Tt][Pp][Ss]*\:\/\// ) {
print 'URL: ' . $remote . "\n";
$url = $remote;
} else {
die( "'" . $remote . "' does not appear to be a config file or a HTTP or HTTPS URL" );
}
if ( defined($apikey) ) {
print "API key: specified via -a\n";
} elsif ( defined( $ENV{virani_api_key} ) ) {
print 'API key: specified via $ENV{virani_api_key}' . "\n";
$apikey = $ENV{virani_api_key};
} elsif ( defined($toml) && defined( $toml->{apikey} ) ) {
print 'API key: specified via config file at ' . $config_file . "\n";
$apikey = $toml->{apikey};
}
if ( defined($timeout) ) {
print "Timeout: specified via --timeout\n";
} elsif ( defined( $ENV{virani_timeout} ) ) {
print 'Timeout: specified via $ENV{virani_timeout}' . "\n";
$timeout = $ENV{virani_timeout};
} elsif ( defined($toml) && defined( $toml->{timeout} ) ) {
print 'Timeout: specified via config file at ' . $config_file . "\n";
$timeout = $toml->{timeout};
}
if ( !defined($url) ) {
if ( defined( $toml->{url} ) ) {
$url = $toml->{url};
} else {
die( "No url specified in '" . $config_file . "'" );
}
}
my $vc = Virani::Client->new( url => $url, apikey => $apikey, verify_hostname => $verify_hostname );
$vc->fetch(
start => $start_obj,
end => $end_obj,
filter => $filter,
file => $write,
no_cache => $no_cache,
verbose => $verbose,
set => $set,
type => $type,
timeout => $timeout,
);
print 'Written to ' . $write . "\n";
exit 0;
} ## end else [ if ( !$remote ) ]