#!/usr/bin/perl
#
# $Id: nrun,v 1.23 2000/12/17 09:43:10 levine Exp $
#
# Copyright (C) 2000  James D. Levine (jdl@vinecorp.com)
#
#
#   This program is free software; you can redistribute it and/or
#   modify it under the terms of the GNU General Public License
#   as published by the Free Software Foundation; either version 2
#   of the License, or (at your option) any later version.
# 
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
# 
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 
#   02111-1307, USA.
#
####################################################################

use strict;
use Getopt::Long;
use Carp;

use PortScan::NmapFile;
use PortScan::DataStore;

my @nrun_command_line = @ARGV;
&quote_list_whitespace(\@nrun_command_line);


sub usage
{
    print <<DONE;

nrun <nrun options> -- <nmap options>

    <nrun options> =

     [-o|-output <filename-or-:tag>] [-b|-baseline <filename-or:tag>]
     [-no|-ndiff-options "<ndiff options>"]  [-nm|-nmap <path-to-nmap-binary>]
     [-l|-logfile]  

DONE
    ;
    exit 1;
}

my $output_tag;
my $baseline_tag;
my $quiet_nmap = 0;
my $nmap = "nmap";		
my $ndiff_options;
my $log_file;
my $help = 0;


GetOptions(
	   "o|output=s"            => \$output_tag, 
	   "b|baseline=s"          => \$baseline_tag,
           "no|ndiff-options=s"    => \$ndiff_options,
	   "nm|nmap=s"             => \$nmap,
	   "qn|quiet-nmap!"        => \$quiet_nmap,
	   "l|log=s"               => \$log_file,
	   "h|help!" => \$help,
	  );

&usage if  $help || !scalar( @ARGV );


$output_tag = "%F-%Y%m%D.nm" if !length($output_tag); # default if undef or user set blank
$output_tag = PortScan::DataStore::prepare_tag($output_tag);

$log_file = PortScan::DataStore::prepare_tag($log_file) if length($log_file);

my $run_ndiff = 0;
my $ndiff_commandline;



if (length($baseline_tag))
{
    $run_ndiff = 1;
    $ndiff_commandline = "ndiff -b $baseline_tag $ndiff_options";
# print $ndiff_commandline;
}

my $tmp_output_file = "nmap$$.nm";



#
# generate name of the machine-parseable file if not specified on the
# command line
#

$\ = "\n";

push @ARGV, ("-v", "-m", $tmp_output_file);
push @ARGV, ("-oN", $log_file) if length($log_file);

# 
# quote any command-line arguments containing spaces
#

&quote_list_whitespace(\@ARGV);


my $hush_nmap = "";
$hush_nmap = "> /dev/null" if $quiet_nmap;

system "$nmap @ARGV $hush_nmap";

my $nmap_result = $? / 256;

(print STDERR "$0: nmap returned an error. exiting..." , exit $nmap_result)
    if ($nmap_result);


#
# Attach a DataStore object to the nmap results file and suck up
# the results
#

my $ds = new PortScan::NmapFile;
$ds->attach_file($tmp_output_file);
my $ss = $ds->retrieve_scanset();

#
# Now associate some interesting information with the results set
#

my $date = `date`;
chomp $date;

$ss->set_property("run date", $date);
$ss->set_property("nmap args", "@ARGV");
$ss->set_property("nrun command line", "@nrun_command_line");
$ss->set_property("nmap result", $nmap_result);

#
# Now create new data store of the default/configured type, and write
# the scan results and metadata...
#

my ($processed_output_tag, $ods) = PortScan::DataStore::data_store_for($output_tag);

$ndiff_commandline .= " -o $output_tag" if $run_ndiff;
$ss->tag($processed_output_tag);
$ods->put_scanset($ss);


#
# Delete the original nmap machine-parseable file if it wasn't
# specified by the user on the command line.
#

system "rm -f $tmp_output_file";




#
# Run ndiff if nmap ran OK and the user requested
#

system $ndiff_commandline
    if ( (! $nmap_result) && $run_ndiff);

exit $nmap_result;






sub quote_list_whitespace
{
    # wraps list arguments that contain spaces in double-quotes 

    my $list = shift;
    
    for (my $i = 0; $i <= $#{@$list}; ++$i)
    {
	my $e = $list->[$i];
	$list->[$i] = "\"$e\"" if $e =~ /\s/;
    }
}







=head1 NAME

nrun - run nmap, storing results in an organized fashion, optionally 
       running ndiff afterwards

=head1 SYNOPSIS

nrun <nrun options> -- <nmap options>

    <nrun options> =

     [-o|-output <filename-or-:tag>] [-b|-baseline <filename-or:tag>]
     [-no|-ndiff-options "<ndiff options>"]  [-nm|-nmap <path-to-nmap-binary>]
     [-l|-logfile]  


=head1 DESCRIPTION

Nrun passes <nmap options> through to nmap verbatim, except that it
instructs nmap to store results in machine-parseable format
via nmap's B<-m> switch, and adds the B<-v> switch to generate a port listing
in the output file.  

Note that the "--" string must be present or <nmap options> will be
missed by nrun.

Since nrun inserts the B<-m> and B<-v> switches into the nmap command line, 
you should avoid putting those switches in <nmap options> yourself.

In addition, nrun writes a meta-data file containing some interesting
information about the nmap run.  

Nrun can automatically invoke ndiff to generate differences with a
specified baseline after the scan completes -- see L<"OPTIONS"> below.

=head1 OPTIONS

=over 4

=item -o <filename-or-:tag>

=item -output <filename-or-:tag>

Use <filename-or-:tag> as the name of the stored results.  By default this is treated
as a filename.  Nrun also writes a metadata file, "<filename-or-:tag>.info" 
containing a some information about the scan.

<filename-or-:tag> may contain %-style substitutions to dynamically add time/date fields
or the local host name.  See L<"SUBSTITUTIONS"> below.

The default for tag is "<hostname>-YYYYMMDD.nm" ( "%F-%Y%w%D.nm" ).

If <filename-or-:tag> starts with a colon (:), <filename-or-:tag> is treated
as a key into a data store, colon removed.  See L<"DATA STORES"> for
more information.  

=item -b <filename-or-:tag>

=item -baseline <filename-or-:tag>

If nmap returns a successful result, instructs nrun to execute ndiff
with the new results against the (pre-existing) baseline results 
specified with <filename-or-:tag>.

As with the B<-o> option above, if <filename-or-:tag> starts with a colon, the tag
is treated as a data store key.  See L<"DATA STORES"> below.


=item -no "< ndiff options ... >"

=item -ndiff-options "< ndiff options ... >"

Pass the "<ndiff options>" to ndiff.  Use with the B<-b> or B<-baseline> switch.  Note that
the options must be a single (quoted).

=item -nm <path-to-nmap-binary>

=item -nmap <path-to-nmap-binary>

Instructs nrun to execute <path-to-nmap-binary> explicitly instead
of using "nmap" as found in the search path.


=item -qn

=item -quiet-nmap

Instructs nrun to redirect nmap's human readable output to /dev/null.


=item -l <logfile>

=item -log <logfile>

Instructs nrun to redirect nmap's human readable output to <logfile>.
Nrun's % substitutions are expanded in <logfile>.  Currently there is no
support for storing logfiles with results data through data stores, but
this may be added later.


=back

=head1 DATA STORES

Nrun and its related tools can manipulate results in regular nmap-format
files, in any user-specified location, or they can handle storing and organizing
the data on behalf of the user, through a user-configurable "data store".  

Whenever you precede a results tag with a colon (:), the tag will be treated
as a unique key into a data store, identifying the results set.  

Currently the only supported data store is  nmap format files placed
in a preconfigured directory.  Other types may be added at a later date.  

A legal tag may contain any alphanumeric string, plus dash, underscore, and dot.
%-style substitutions in the ilk of the "date" command are also supported,
allowing a tag to contain date, time, or the local hostname.  See L<"SUBSTITUTIONS">
below for more information.

=head1 SUBSTITUTIONS


%-style substitutions supported in tags as follows:

=over 4

=item %H = hour

=item %M = minute

=item %S = second 

=item %D = day of month

=item %m = month of year (01-12)

=item %Y = year, four digits

=item %j = day of year, three digits

=item %w = day of week (0-6) one digit

=back

Except where noted, the above items are two digits, and local time.  All are zero-padded
as appropriate.

In addtion-

=over 4

=item %F = output of "hostname" on the local machine

=back



=head1 BUGS


Ports Scanned lists in results files don't currently condense sequential
ranges of ports, resulting in larger than necessary files.  This will
be fixed in a future update.


=head1 AUTHOR

James Levine <jdl@vinecorp.com>



=cut
















