#!/usr/bin/perl
#
# $Id: NmapFile.pm,v 1.14 2000/12/16 09:04:50 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 PortScan::DataStore;
use PortScan::ScannedHost;
use PortScan::PortSpec;


package PortScan::NmapFile;


use strict;
no strict 'refs';


@PortScan::NmapFile::ISA = qw(PortScan::DataStore);

sub new
{
    my ($type, $parms) = @_;

    my $self = PortScan::DataStore::new($type, $parms);

    $self->{attach_file} = "";
    $self->{attach_metafile} = "";


    return $self;
}

sub retrieve_scanset
{
    my ($self, $tag) = @_;
    my ($filepath, $metapath) = $self->calc_filenames($tag);

    my $set =  new PortScan::ScanSet($tag, undef, undef, undef);
    hosts_from_nmap_scan_file($filepath, $set);

    my $props = read_metafile($metapath);
    $props = {} if !defined($props);
    $set->properties($props);

    return $set;
}
 
sub put_scanset
{
    my ($self, $set) = @_;

    my ($filepath, $metapath) = $self->calc_filenames($set->tag());
    hosts_to_nmap_file($filepath, $set);
    write_metafile($metapath, $set->properties());
}

sub attach_file
{
    my ($self, $f) = @_;
    $self->{attach_file} = $f;
}

sub attach_metafile
{
    my ($self, $f) = @_;
    $self->{attach_metafile} = $f;
}





sub calc_filenames
{
    my($self, $tag) = @_;

    my $filepath = $self->{attach_file};
    my $metapath = $self->{attach_metafile};

    $filepath = $self->full_path_to_nmap_file($tag)
	if (!length($filepath));

    $metapath = $self->full_path_to_meta_file($tag)
	if (!length($metapath));

    ($filepath, $metapath);
}

sub write_metafile
{
    my ($filepath, $properties) = @_;

    open (OUT, ">$filepath") || return 0;

    while( my($k, $v) = each %$properties)
    {
	printf OUT "$k = $v\n";
    }
    
    close OUT;
    return 1;
}

sub read_metafile
{
    my $filepath = shift;

    open (IN, "<$filepath") || return undef;

    my @lines = <IN>;
    close IN;
    chomp @lines;

    my $h = {};

    foreach my $line (@lines)
    {
	my ($k, $v) = split /=/, $line;
	$h->{$k} = $v;
    }

    $h;
}

sub hosts_to_nmap_file
{
    my ($filepath, $scan_set) = @_;

    open (OUT, ">$filepath") || return 0;

    printf OUT scanned_ports_to_nmap_format($scan_set->all_scanned_ports()). "\n";

    foreach my $host (PortScan::ScannedHost::sorted_list values %{$scan_set->hosts()})
    {
#	print $host;
	printf OUT host_to_nmap_format($host). "\n";
    }
    
    close OUT;
    return 1;
}


sub scanned_ports_to_nmap_format
{
    my($h) = shift;
    my(@tcp, @udp);

    foreach my $spec (PortScan::PortSpec::sorted_list values %$h)
    {
	($spec->proto() eq "tcp") && (push @tcp, $spec->number());
	($spec->proto() eq "udp") && (push @udp, $spec->number());
    }

    "# Ports scanned: TCP(" . (1+ $#tcp) . ";" . (join ",", @tcp) 
	. ") UDP(" . (1 + $#udp) . ";" . (join "," ,@udp) . ")";
}

sub host_to_nmap_format
{
    my $host = shift;

    my $result = "Host: " . $host->addr() . " ()     Ports: ";

    my $pcount = 0;

    foreach my $spec ($host->port_specs_sorted_list())
    {
	$result .= portspec_to_nmap_format($spec) . ", ";
	++$pcount;
    }
    (chop $result, chop $result) if $pcount;

    $result .= "    Ignored State: " . $host->default_state();

    $result;
}

sub portspec_to_nmap_format
{
    my $sp = shift;
    return sprintf ("%s/%s/%s/%s/%s/%s/%s", 
		    $sp->number, $sp->state, $sp->proto, $sp->u1, $sp->service, $sp->u2, $sp->u3);
}

sub host_from_nmap_format
{
    my $text = shift;

    my $o = new PortScan::ScannedHost;
    
    $text =~ s/Host://g;
    $text =~ s/Ports:/|/g;
    $text =~ s/Ignored State:/|/g;

    my ($h, $p, $d) = split /\|/, $text;

    $h =~ /\s+([0-9\.]+)/;
    my $addr = $1;
    $addr =~ s/\s*//g;

    $o->addr($addr);
    
    my $raw_ports_text = $p;

    $d =~ /\s*(\w*)/;
    my $default_state = $1;
    $default_state =~ s/\s*//g;

    $o->default_state($default_state);

    $raw_ports_text =~ s/ //g;

    my @raw_ports_list = split /,/, $raw_ports_text;

    my $ports_hash = {};
    my $port_specs_hash = {};

    if ( scalar(@raw_ports_list) > 0 ) 
    {
	foreach my $port_spec (@raw_ports_list)
	{
	    my ($port, $state, $proto, $u1, $service, $u2, $u3) = split /\//, $port_spec;

	    $port =~ s/\s*//g;
	    $port = 0 + $port;	# be extra sure it's numeric


	    $state =~ s/\s*//g;
	    $proto =~ s/\s*//g;

	    $ports_hash->{$port} = $state;

	    my $ps = new PortScan::PortSpec(
					    $port, $state, $proto, $u1,
					    $service, $u2, $u3
					    );
	    $port_specs_hash->{$ps->key_for()} = $ps;
	}

	$o->port_specs($port_specs_hash);

    }

    $o;
}


sub hosts_from_nmap_scan_file
{
    my  ($filename, $scan_set) = @_;

    my $hosts = {};		# hash of ScannedHost objects keys on IP addr
    my $ports = {};		# hash of PortSpec objects keyed on PortSpec::key_for() values

    open (IN, "<$filename") || return undef;

    my @lines = <IN>;
    chomp @lines;
    close IN;

  LINE:
    foreach my $line (@lines)
    {
	($line =~ /^# Ports scanned:/) 
	 && do 
	 {
	     $scan_set->all_scanned_ports( parse_ports($line) ); 
	     next LINE;
	 };

	 next LINE if $line =~ /^#/;
	 next LINE if $line =~ /^[\s]*$/;

	 my $scan_host =  host_from_nmap_format($line);

	 $scan_set->add_host($scan_host);
     }

 }

sub parse_ports
{
    my ($line) = @_;

    my $h = {};

    $line =~ /TCP\(\d*;([0-9,-]*)\)/;

    my @l = split /,/, $1;
    my (@tcp_specs, @udp_specs);

    foreach my $port (@l)
    {
	if ($port =~ /(\d*)-(\d*)/) {
	    push @tcp_specs, ($1 .. $2);
	} else {
	    push @tcp_specs, $port;
	}
    }

    foreach my $port (@tcp_specs)
    {
	my $ps =  new PortScan::PortSpec($port, "", "tcp", "", "", "", "");
	$h->{$ps->key_for()} = $ps;
    }

    $line =~ /UDP\(\d*;([0-9,-]*)\)/;
    @l = split /,/, $1;
    foreach my $port (@l)
    {
	if ($port =~ /(\d*)-(\d*)/) {
	    push @udp_specs, ($1 .. $2);
	} else {
	    push @udp_specs, $port;
	}
    }

    foreach my $port (@udp_specs)
    {
	my $ps = new PortScan::PortSpec($port, "", "udp", "", "", "", "");
	$h->{$ps->key_for()} = $ps;
    }

    $h;
}


sub full_path_to_nmap_file
{
    my ($self, $tag) = @_;

    $self->path_to_dir() . "/${tag}.nm";
}

sub full_path_to_meta_file
{
    my ($self, $tag) = @_;

    $self->path_to_dir() . "/${tag}.info";
}

sub path_to_dir
{
    my $self = shift;

    my $path = $self->get_user_property("root");
    $path = "." if !length($path);

    $path;
}


sub tests
{


}


1;











