#!/usr/bin/env perl

use strict;
use warnings;
use Getopt::Long;
use Net::Connection::ncnetstat;

sub version{
	print "ncnetstat v. 0.4.0\n";
}

sub help{
	print '
-a         Show all connections.
--drp      Do not resolve port names.
--dump     Show the Net::Connection::Match filter and exit.
-i         Invert the sort.
-l         Show the listening ports.
-n         Do not resolve the PTRs.
--nc       Do not use colors.
--pct      Show memory and CPU usage percent.
-S <sort>  The Net::Connection::Sort to use.
-t         Show only TCP connections.
-u         Show only UDP connections.
-W         Show the wchan.

-c <CIDRs>  A comma seperated list of CIDRs to search for.
--ci        Invert the CIDR search.

-C    Show the command to the first space.
--Cl  Show the whole command.

--cmd <cmds> A comma seperated list of commands to search for.
--cmdi       Invert the command search.

--cpu <pct> Show connections belonging to procs matching this CPU usage percent.
--cpui      Invert the CPU search.

--mem <pct> Show connections belonging to procs matching this memory usage percent.
--memi      Invert the memory usage search.

-p <ports>  A comma seperated list of ports to search for.
--pi        Invert the port search.

-P <protos> A comma seperated list of protocols to search for.
--Pi        Invert your protocol search.

--pid <pids> A comma separated list of PIDs to search for.
--pidi       Invert the pid search.

--ptrr <rgx>   A comma seperated list of regex to use for a PTR search.
--ptrri        Invert the RegexPTR search.
--lptrr <rgx>  A comma seperated list of regex to use for a local PTR search.
--lptrri       Invert the local RegexPTR search.
--rptrr <rgx>  A comma seperated list of regex to use for a remote PTR search.
--rptrri       Invert the remote RegexPTR search.

--ptr <PTRs>   A comma seperated list of PTRs to search for.
--ptri         Invert the PTR search.
--lptr <PTRs>  A comma seperated list of local PTRs to search for.
--lptri        Invert the local PTR search.
--rptr <PTRs>  A comma seperated list of remote PTRs to search for.
--rptri        Invert the remote PTR search.

-s <states>  A comma seperated list of states to search for.
--si         Invert the state search.

-U <users>   A comma seperated list of usernames to search for.
--Ui         Invert the username search.

--uid <uids> A comma separated list of UIDs to search for.
--uidi       Invert the UID search.

-w <rgx>     A comma separated list of regexp to use for matching wchan values.
--wi         Invert the wchan search.

The default available sort methods are as below.
host_f   foreign host
host_fl  foreign host, local host
host_l   local host
host_lf  local host, foreign host
pid      process ID
port_f   foreign port, numerically
port_fa  foreign port, alphabetically
port_l   local port, numerically
port_la  local port, alphabetically
proto    protocol
ptr_f    foreign PTR
ptr_l    local PTR
state    state
uid      user ID
unsorted Unsorted  ((default))
user     username

For CPU, memory, PID, and UID searches, the equalities below can be
used, by directly prepending them to the number.
<
<=
>
>=
';
}

# command line option holders
my $tcp=0;
my $udp=0;
my $help=0;
my $version=0;
my $dont_resolve_ports=0;
my $sort='unsorted';
my $cidr_string;
my $lcidr_string;
my $rcidr_string;
my $ports_string;
my $states_string;
my $protocols_string;
my $all=0;
my $listening;
my $invert=0;
my $ptrs_string;
my $ptrrs_string;
my $rptrs_string;
my $lptrs_string;
my $rptrrs_string;
my $lptrrs_string;
my $ports_invert;
my $rptrs_invert;
my $lptrs_invert;
my $rptrrs_invert;
my $lptrrs_invert;
my $ptrs_invert;
my $ptrrs_invert;
my $cidr_invert;
my $lcidr_invert;
my $rcidr_invert;
my $states_invert;
my $protocols_invert;
my $no_color=0;
my $no_use_ptr=0;
my $ptr=1;
my $command=0;
my $command_long=0;
my $uid_string;
my $uid_invert=0;
my $users_string;
my $users_invert=0;
my $pids_string;
my $pids_invert=0;
my $commands_string;
my $commands_invert;
my $wchan_string;
my $wchan_invert=0;
my $wchan_show=0;
my $pct_show=0;
my $cpu_string;
my $cpu_invert=0;
my $mem_string;
my $mem_invert=0;
my $dump=0;

# get the commandline options
Getopt::Long::Configure ('no_ignore_case');
Getopt::Long::Configure ('bundling');
GetOptions(
		   't'=>\$tcp,
		   'u'=>\$udp,
		   'version' => \$version,
		   'v' => \$version,
		   'help' => \$help,
		   'h' => \$help,
		   'a' => \$all,
		   'l' => \$listening,
		   'i' => \$invert,
		   'drp' => \$dont_resolve_ports,
		   'c=s' => \$cidr_string,
		   'ci'=> \$cidr_invert,
		   'lc=s' => \$lcidr_string,
		   'lci'=> \$lcidr_invert,
		   'rc=s' => \$rcidr_string,
		   'rci'=> \$rcidr_invert,
		   'S=s' => \$sort,
		   'p=s' => \$ports_string,
		   'pi' => \$ports_invert,
		   's=s' => \$states_string,
		   'si' => \$states_invert,
		   'P=s' => \$protocols_string,
		   'Pi' => \$protocols_invert,
		   'pid=s' => \$pids_string,
		   'pidi' => \$pids_invert,
		   'ptrr=s' => \$ptrrs_string,
		   'ptr=s' => \$ptrs_string,
		   'ptri' => \$ptrs_invert,
		   'ptrri' => \$ptrrs_invert,
		   'rptrr=s' => \$rptrrs_string,
		   'rptr=s' => \$rptrs_string,
		   'rptrri' => \$rptrrs_invert,
		   'rptri' => \$rptrs_invert,
		   'lptrr=s' => \$lptrrs_string,
		   'lptr=s' => \$lptrs_string,
		   'lptri' => \$lptrs_invert,
		   'lptrri' => \$lptrrs_invert,
		   'nc' => \$no_color,
		   'n' => \$no_use_ptr,
		   'C' => \$command,
		   'Cl' => \$command_long,
		   'uid=s' => \$uid_string,
		   'uidi'=> \$uid_invert,
		   'U=s' => \$users_string,
		   'Ui' => \$users_invert,
		   'cmd=s' => \$commands_string,
		   'cmdi' => \$commands_invert,
		   'w=s' => \$wchan_string,
		   'wi' => \$wchan_invert,
		   'W' => \$wchan_show,
		   'pct' => \$pct_show,
		   'cpu=s' => \$cpu_string,
		   'cpui' => \$cpu_invert,
		   'mem=s' => \$mem_string,
		   'memi' => \$mem_invert,
		   'dump' => \$dump,
		   );

my @filters;

# print the version info if requested
if ( $version ){
	&version;
	exit;
}

if ( $help ){
	&version;
	&help;
	exit;
}

#
# XOR various command line options
#
if (defined( $ENV{'ncnetstat_pct'} )) {
	$pct_show= $pct_show ^ $ENV{'ncnetstat_pct'};
}
if (defined( $ENV{'ncnetstat_C'} )) {
	$command = $command ^ $ENV{'ncnetstat_C'};
}
if (defined( $ENV{'ncnetstat_Cl'} )) {
	$command_long = $command_long ^ $ENV{'ncnetstat_Cl'};
}
if (defined( $ENV{'ncnetstat_W'} )) {
	$wchan_show = $wchan_show ^ $ENV{'ncnetstat_W'};
}


# add the filters for the -l and -a option
if (
	( ! $all ) &&
	( ! $listening )
	){
	# If -a is not given, we don't want the listen ports
	push( @filters,
		  {
		   type=>'Ports',
		   invert=>1,
		   args=>{
				  ports=>[
						   '*',
						   ],
				  }
		   },
		   {
					 type=>'States',
					 invert=>1,
					 args=>{
							states=>['LISTEN']
							}
					 }
		 );
}elsif(
	   $listening &&
	   ( ! $all )
	   ){
	# if -l we only want the ports in the LISTEN state
	push( @filters, {
					 type=>'States',
					 invert=>0,
					 args=>{
							states=>['LISTEN']
							}
					 }
		 );
}

#
# Handle stats search
#
if ( defined( $states_string ) ){
	my @states=split(/\,/, $states_string );
	push( @filters, {
					 type=>'States',
					 invert=>$states_invert,
					 args=>{
							states=>\@states,
							},
					 }
		 );
}

#
# handles the protocol search
#
if ( defined( $protocols_string ) ){
	my @protos=split(/\,/, $protocols_string );
	push( @filters, {
					 type=>'Protos',
					 invert=>$protocols_invert,
					 args=>{
							protos=>\@protos,
							},
					 }
		 );
}

#
# Handle CIDR searches
#
if ( defined( $cidr_string ) ){
	my @cidrs=split(/\,/, $cidr_string );
	push( @filters, {
					 type=>'CIDR',
					 invert=>$cidr_invert,
					 args=>{
							cidrs=>\@cidrs,
							},
					 }
		  );
}

#
# Handle UID searches
#
if ( defined( $uid_string ) ){
	my @uids=split(/\,/, $uid_string );
	push( @filters, {
					 type=>'UID',
					 invert=>$uid_invert,
					 args=>{
							uids=>\@uids,
							},
					 }
		  );
}

#
# Handle username searches
#
if ( defined( $users_string ) ){
	my @users=split(/\,/, $users_string );
	push( @filters, {
					 type=>'Username',
					 invert=>$users_invert,
					 args=>{
							usernames=>\@users,
							},
					 }
		  );
}

#
# Handle local CIDR searches
#
#if ( defined( $lcidr_string ) ){
#	my @cidrs=split(/\,/, $lcidr_string );
#	push( @filters, {
#					 type=>'CIDR',
#					 invert=>$cidr_invert,
#					 args=>{
#							lcidrs=>\@cidrs,
#							},
#					 }
#		  );
#}

#
# Handle local CIDR searches
#
#if ( defined( $fcidr_string ) ){
#	my @cidrs=split(/\,/, $fcidr_string );
#	push( @filters, {
#					 type=>'CIDR',
#					 invert=>$cidr_invert,
#					 args=>{
#							fcidrs=>\@cidrs,
#							},
#					 }
#		  );
#}

#
# Handle the ports search.
#
if ( defined( $ports_string ) ){
	my @ports=split(/\,/, $ports_string);
	push( @filters, {
					 type=>'Ports',
					 invert=>$ports_invert,
					 args=>{
							ports=>\@ports,
							},
					 }
		 );
}

#
# Handle the regex PTR searches
#
if ( defined( $ptrrs_string ) ){
	my @ptrs=split(/\,/, $ptrrs_string);
	push( @filters, {
					 type=>'RegexPTR',
					 invert=>$ptrrs_invert,
					 args=>{
							ptrs=>\@ptrs,
							},
					 }
		 );
}

#
# Handle the regex local PTR searches
#
if ( defined( $lptrrs_string ) ){
	my @ptrs=split(/\,/, $lptrrs_string);
	push( @filters, {
					 type=>'RegexPTR',
					 invert=>$lptrrs_invert,
					 args=>{
							lptrs=>\@ptrs,
							},
					 }
		 );
}

#
# Handle the regex remote PTR searches
#
if ( defined( $rptrrs_string ) ){
	my @ptrs=split(/\,/, $rptrrs_string);
	push( @filters, {
					 type=>'RegexPTR',
					 invert=>$rptrrs_invert,
					 args=>{
							fptrs=>\@ptrs,
							},
					 }
		 );

}

#
# Handle the ptrs searches
#
if ( defined( $ptrs_string ) ){
	my @ptrs=split(/\,/, $ptrs_string);
	push( @filters, {
					 type=>'PTR',
					 invert=>$rptrs_invert,
					 args=>{
							ptrs=>\@ptrs,
							},
					 }
		 );
}

#
# Handle the remote ptrs searches
#
if ( defined( $rptrs_string ) ){
	my @ptrs=split(/\,/, $rptrs_string);
	push( @filters, {
					 type=>'PTR',
					 invert=>$rptrs_invert,
					 args=>{
							fptrs=>\@ptrs,
							},
					 }
		 );
}

#
# Handle the local ptrs searches
#
if ( defined( $lptrs_string ) ){
	my @ptrs=split(/\,/, $lptrs_string);
	push( @filters, {
					 type=>'PTR',
					 invert=>$lptrs_invert,
					 args=>{
							lptrs=>\@ptrs,
							},
					 }
		 );
}

#
# Handle the PID searches
#
if ( defined( $pids_string ) ){
	my @pids=split(/\,/, $pids_string);
	push( @filters, {
					 type=>'PID',
					 invert=>$pids_invert,
					 args=>{
							pids=>\@pids,
							},
					 }
		 );
}

#
# Handle the command searches
#
if ( defined( $commands_string ) ){
	my @commands=split(/\,/, $commands_string);
	push( @filters, {
					 type=>'Command',
					 invert=>$commands_invert,
					 args=>{
							commands=>\@commands,
							},
					 }
		 );
}

#
# Handle the wait channel searches
#
if ( defined( $wchan_string ) ){
	my @wchans=split(/\,/, $wchan_string);
	push( @filters, {
					 type=>'WChan',
					 invert=>$wchan_invert,
					 args=>{
							wchans=>\@wchans,
							},
					 }
		 );
}

#
# Handle the pctcpu searches
#
if ( defined( $cpu_string ) ){
	my @cpus=split(/\,/, $cpu_string);
	push( @filters, {
					 type=>'PctCPU',
					 invert=>$cpu_invert,
					 args=>{
							pctcpus=>\@cpus,
							},
					 }
		 );
}

#
# Handle the pctmem searches
#
if ( defined( $mem_string ) ){
	my @mems=split(/\,/, $mem_string);
	push( @filters, {
					 type=>'PctMem',
					 invert=>$mem_invert,
					 args=>{
							pctmems=>\@mems,
							},
					 }
		 );
}

# handle the -t -u options
# only add a filter if one is specified...
# adding both is just pointless
if (
	( ! $tcp ) &&
	$udp
	){
	push( @filters, {
					 type=>'Protos',
					 invert=>0,
					 args=>{
							protos=>[ 'udp4', 'udp6' ],
							}
					 },
		 );
}elsif(
	   $tcp &&
	   ( ! $udp )
	   ){
	push( @filters, {
					 type=>'Protos',
					 invert=>0,
					 args=>{
							protos=>[ 'tcp4', 'tcp6' ],
							}
					 }
		 );
}

if ( $no_use_ptr ){
	$ptr=0;
}

# XOR the -i if needed
if ( defined( $ENV{NCNETSTAT_invert} ) ){
	$invert= $invert ^ $ENV{NCNETSTAT_invert};
}
# XOR the -n value if needed
if ( defined( $ENV{NCNETSTAT_ptr} ) ){
	$ptr = $ptr ^ $ENV{NCNETSTAT_ptr};
}
# same for the no color
if ( defined( $ENV{NO_COLOR} ) ){
	$no_color = $no_color ^ 1;
}
# disable the color if requested
if ( $no_color ){
	$ENV{ANSI_COLORS_DISABLED}=1;
}
# set C if Cl is set
if ( $command_long && ! $command ){
	$command=1;
}

#dump the filters if asked
if ($dump) {
	use Data::Dumper;
	print Dumper( \@filters );
	exit;
}

my $ncnetstat=Net::Connection::ncnetstat->new(
											  {
											   ptr=>$ptr,
											   command=>$command,
											   command_long=>$command_long,
											   wchan=>$wchan_show,
											   pct=>$pct_show,
											   sorter=>{
														invert=>$invert,
														type=>$sort,
														},
											   match=>{
													   checks=>\@filters,
													   }
											   }
											  );
print $ncnetstat->run;

=head1 NAME

ncnetstat - a netstat like utility that supports color and searching

=head1 SYNOPSIS

ncnetstat [B<-a>] [B<--drp>] [B<-l>] [B<-n>] [B<--nc>] [B<-S <sort>>] [B<-t>] [B<-u>]
[B<-c <CIDRs>>] [B<--ci -p <ports>>] [B<--pi>] [B<-P <protocols>>] [B<--Pi>]
[B<--ptr <PTRs>>] [B<--ptri>] [B<--lptr <PTRs>>] [B<--lptri>] [B<--rptr <PTRs>>] [B<--rptri>]
[B<-s <states>>] [B<--si>] ...

=head1 FLAGS

=head2 -a

Show all connections.

=head2 -c <CIDRs>

A comma seperated list of CIDRs to search for.

=head2 --ci

Invert the CIDR search.

=head2 -C

Show the command to the first space.

=head2 --Cl

Show the whole command.

=head2 --cmd <cmds>

A comma seperated list of commands to search for.

=head2 --cmdi

Invert the command search.

=head2 --cpu <pct>

Show connections belonging to procs matching this CPU usage percent.

=head2--cpui

Invert the CPU search.

=head2 --drp

Don't resolve port names.

=head2 -i

Invert the sort.

=head2 -l

Show the listening ports.

=head2 --mem <pct>

Show connections belonging to procs matching this memory usage percent.


=head2 --memi

Invert the memory usage search.

=head2 -n

Don't resolve the PTRs.

=head2 --nc

Don't use colors.

=head2 -p <ports>

A comma seperated list of ports to search for.

=head2 --pct

Show memory and CPU usage of the process that has the connection.

=head2 --pi

Invert the port search.

=head2 -P <protocols>

A comma seperated list of protocols to search for.

=head2 --Pi

Invert your protocol search.

=head2 --pid <pids>

A comma separated list of PIDs to search for.

=head2 --pidi

Invert the pid search.

=head2 --ptr <PTRs>

A comma seperated list of PTRs to search for.

=head2 --ptri

Invert the PTR search.

=head2 --ptrr <rgx>

A comma seperated list of regex to use for a PTR search.

=head2 --ptrri

Invert the RegexPTR search.

=head2 --lptr <PTRs>

A comma seperated list of local PTRs to search for.

=head2 --lptri

Invert the local PTR search.

=head2 --lptrr <rgx>

A comma seperated list of regex to use for a local PTR search.

=head2 --lptrri

Invert the local RegexPTR search.

=head2 --rptr <PTRs>

A comma seperated list of remote PTRs to search for.

=head2 --rptri

Invert the remote PTR search.

=head2 --rptr <PTRs>

A comma seperated list of remote PTRs to search for.

=head2 --rptri

Invert the remote PTR search.

=head2 -s <states>

A comma seperated list of states to search for.

=head2 --si

Invert the state search.

=head2 -S <sort>

The L<Net::Connection::Sort> to use.

The default available sort methods are as below.

    host_f   foreign host
    host_fl  foreign host, local host *default*
    host_l   local host
    host_lf  local host, foreign host
    pid      process ID
    port_f   foreign port, numerically
    port_fa  foreign port, alphabetically
    port_l   local port, numerically
    port_la  local port, alphabetically
    proto    protocol
    ptr_f    foreign PTR
    ptr_l    local PTR
    state    state
    uid      user ID
    user     username

=head2 -t

Show only TCP connections.

=head2 -u

Show only UDP connections.

=head2 -U <users>

A comma seperated list of usernames to search for.

=head2 --Ui

Invert the username search.

=head2 --uid <uids>

A comma separated list of UIDs to search for.

=head2 --uidi

Invert the UID search.

=head2 -W

Show the wait channel.

=head2 -w <rgx>

A comma separated list of regexp to use for matching wchan values.

=head2 --wi

Invert the wchan search

=head1 PID/UID/CPU/Mem EQUALITIES

For some searches, the equalities below can be used, by
directly prepending them to the number.

    <
    <=
    >
    >=

So if you wanted to find every connection from a UID greater than 1000, would
do '--uid \>1000'.

=head1 ENVIRONMENT VARIABLES

=head2 NCNETSTAT_invert

This is either 0 or 1. If defined it will be used for XORing the -i flag.

    export CNETSTAT_invert=1
    # run ncnetstat inverted
    ncnetstat
    # run it non-inverted, the opposite of what the -i flag normally is
    ncnetstat -i

=head2 NCNETSTAT_sort

Sets the default sort method. -S overrides this.

=head2 NO_COLOR

If this is set, The output will not be colorized. If this is set, the --nc
flag is also inverted.

=head2 RES_NAMESERVERS

A space-separated list of nameservers to query used by L<Net::DNS::Resolver>.

There are a few more possible ones, but this is the most useful one and that documentation
really belongs to that module.

=head2 ncnetstat_C

Used to xor the -C switch.

Set to either 0 or 1, boolean, for setting the default.

=head2 ncnetstat_Cl

Used to xor the --Cl switch.

Set to either 0 or 1, boolean, for setting the default.

=head2 ncnetstat_W

Used to xor the -W switch.

Set to either 0 or 1, boolean, for setting the default.

=head2 ncnetstat_pct

Used to xor the --pct switch.

Set to either 0 or 1, boolean, for setting the default.

=head1 EXAMPLES

    ncnetstat -s established,time_wait

Return a list of connection that are in the established or time_wait state.

    ncnetstat -c ::/0

Return a list of all IPv6 addresses.

    ncnetstat -c ::1/128,127.0.0.1/32

Return all connections to localhost.

    ncnetstat -c 192.168.15.2/32 -l

Display all connections listening explicitly on 192.168.15.2.

    ncnetstat -S host_f -i

Sort the connections by the foreign host and invert the results.

    ncnetstat -c 10.0.0.0/24 --ci

Show connections that are either not locally or remotely part of the
10.0.0.0/24 subnet.

    ncnetstat --ptr foo.bar

Find connections to/from IPs that have a PTR record of foo.bar.

    ncnetstat --ptr foo.bar --ptri

Find connections to/from IPs that do not have a PTR record of foo.bar.

    ncnetstat -n --uid '>1000' --Cl

Show every connection by a user with a UID greater than 1000, do not resolve
PTR info and print the whole command.

    ncnetstat -U www -p 80,443 --pi

Show every connecttion by the user www that is not a HTTP or HTTPS connection.

   ncnetstat --cpu '>5' --Cl --pct -W

Search for connections from procs using more than 5% of the CPU time. Show memory
and CPU usage as well whole command and wait channel.

=cut
