#!/usr/bin/perl
#
# $Id: PortSpec.pm,v 1.10 2000/12/16 09:02:41 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;

package PortScan::PortSpec;

sub nmap_default_human_format
{
    my $me = shift;

    return sprintf ("%-8d%-12s%-10s%-10s",
		    $me->number(), $me->state(), $me->proto(), $me->service());
}

sub new
{
    my $type = shift;
    my ($number, $state, $proto, $u1, $service, $u2, $u3) = @_;

    my $self =
    {   number => '',
	state => '',
	proto => '',
	u1 => '',
	service => '',
	u2 => '',
	u3 => '',
    };

    bless $self, $type;

    $self->number($number);
    $self->state($state);
    $self->proto($proto);
    $self->service($service);

    return $self;
}

sub make_key
{
    my ($port, $proto) = @_;
    return ("$port/$proto");
}

sub decode_key
{				# returns ($port, $proto)
    my $key = shift;

    return split /\//, $key;
}

sub key_for
{
    my ($self) = shift; return make_key($self->number(), $self->proto());
}


sub clone
{
    my $self = shift;
    my $n = new PortScan::PortSpec;

    $n->number ($self->number());
    $n->state  ($self->state());
    $n->proto  ($self->proto());
    $n->service($self->service());
    $n->u1     ($self->u1());
    $n->u2     ($self->u2());
    $n->u3     ($self->u3());

    return $n;
}
sub set_or_get
{
    my $field = shift;
    my $self = shift;
    my $val = shift;

    $self->{$field} = $val if defined $val;

    return $self->{$field};
}

sub number   { set_or_get('number',   @_); }
sub state    { set_or_get('state',    @_); }
sub proto    { set_or_get('proto',    @_); }
sub service  { set_or_get('service',  @_); }
sub u1  { set_or_get('u1',  @_); }
sub u2  { set_or_get('u2',  @_); }
sub u3  { set_or_get('u3',  @_); }


sub state_sm
{
    my $self = shift;
    my $s = $self->state();

    ($s eq "open") && return "O";
    ($s eq "closed") && return "C";
    ($s eq "filtered") && return "F";
    ($s eq "unfiltered") && return "U";
    return "X";

}





sub known_port_by_name
  {
    my ($name, $proto) = @_;

    $PortScan::PortSpec::by_name->{make_key($name, $proto)};
  }

sub known_port_by_number
  {
    my ($number, $proto) = @_;
    $PortScan::PortSpec::by_number->{make_key($number, $proto)};
  }

my $by_name = {};
my $by_number = {};

sub known_udp_ports
{
    my @udp = grep /udp/, keys %$PortScan::PortSpec::by_number;
    map { s/\/udp//} @udp;

    @udp;
}

sub known_tcp_ports
{
    my @tcp = grep /tcp/, keys %$PortScan::PortSpec::by_number;
    map { s/\/tcp//} @tcp;

    @tcp;
}

sub expand_all_known_ports
{
    my @t;
    map { push @t, new PortScan::PortSpec($_, "open", "tcp", "", "", "", "") } known_tcp_ports();

    my @u;
    map { push @u, new PortScan::PortSpec($_, "open", "udp", "", "", "", "") } known_udp_ports();

    return [ @t, @u ];
}


sub ports_from_file
  {
    my ($f) = @_;

    open IN, "<$f" || return 0;

    my @lines = <IN>;
    close IN;

    chomp @lines;
    
  L:
    foreach my $l (@lines)
      {
	next L if !length($l);

	$l =~ /^\s*(.)/;
        next L if ($1 eq '#');

	$l =~ /^(\S*)\s*(\S*)/;
	
	my $label = $1;
	
	my($num, $proto) = split /\//, $2;

	next L if $num < 1;
	
	$PortScan::PortSpec::by_name->{make_key($label, $proto)} = $num;
	$PortScan::PortSpec::by_number->{make_key($num, $proto)} = $label;
      }
  }



sub sorted_list
{
    return sort compare @_;
}


sub compare
{
    return $a->number() <=> $b->number() if $a->proto() eq $b->proto();

    return -1 if $a->proto() eq "tcp";
    return 1;

}

sub compare_pair
{
    return compare( $_[0]->[0], $_[1]->[0] );
}

BEGIN 
  {
    ports_from_file( "/etc/services" );
    ports_from_file( "/usr/local/share/nmap/nmap-services" );
    ports_from_file( $ENV{"NDIFF_SERVICES_FILE"} );
  }



1;














