#!/usr/bin/perl
#
# $Id: PortSpec.pm,v 1.13 2001/11/24 22:28:15 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.
#
####################################################################

#
# Encapsulates the properties of a "port", in the general
# sense for TCP and UDP
#



use strict;

package PortScan::PortSpec;



# generate a string for the port in the normal 
# nmap human-readable format

sub nmap_default_human_format
{
    my $me = shift;

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

# TODO: document u1, u2, u3 from nmap source 
sub new
{
    my $type = shift;
    my( $number, $state, $proto, $u1, $service, $u2, $u3 ) = @_;

    my $self =
    {   
	number => '',	
	state => '',     # 'open' | 'closed' | 'filtered' | 'unfiltered' | 'unknown'
	proto => '',     # 'tcp' | 'udp'
	u1 => '',
	service => '',   # well-known service name from /etc/services
	u2 => '',
	u3 => '',
    };

    bless $self, $type;

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

    return $self;
}

# generate a canonical key from a ( port, proto ) pair
sub make_key
{
    my( $port, $proto ) = @_;
    return( "$port/$proto" );
}


# reverse-map a canonical key into ( $port, $proto )
sub decode_key
{				# returns ($port, $proto)
    my $key = shift;

    split /\//, $key;
}

# generate the canonical key for this instance 
# for use in hashes
# public.
sub key_for
{
    my( $self ) = shift; return make_key( $self->number(), $self->proto() );
}



# make an exact copy, it's deep since all members are scalars
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() );

    $n;
}


# generic field-accessor method
sub set_or_get
{
    my $field = shift;
    my $self = shift;
    my $val = shift;

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

    $self->{ $field };
}

# specificly exposed field accessor methods
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',  @_ ); }


# map an nmap state name to a single character mnemonic
# TODO: U and X appear to be reversed
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";			# unknown
}



# maps ( service-name, proto ) to a canonical key
sub known_port_by_name
  {
    my ($name, $proto) = @_;

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

# maps ( port-number, proto ) to a canonical key -- is this necessary?
sub known_port_by_number
  {
    my ($number, $proto) = @_;
    $PortScan::PortSpec::by_number->{ make_key( $number, $proto ) };
  }

my $by_name = {};		# global well-known-ports by name
my $by_number = {};		# global well-known ports by number


# returns the list of all well-known udp services
sub known_udp_ports
{
    my @udp = grep /udp/, keys %$PortScan::PortSpec::by_number;
    map { s/\/udp//} @udp;

    @udp;
}

# returns the list of all well-known tcp services
sub known_tcp_ports
{
    my @tcp = grep /tcp/, keys %$PortScan::PortSpec::by_number;
    map { s/\/tcp//} @tcp;

    @tcp;
}


# returns a pair listref [ @tcp, @udp ] of all known services
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 ];
}

# reads the well-known ports from a file in /etc/services format,
# inserts into by_name and by_number globals

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;
      }
  }



# sorts a list of PortSpecs by proto and number
sub sorted_list
{
    return sort compare @_;
}


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

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

}

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

# set up globals, especially well-known ports
BEGIN 
  {
    ports_from_file( "/etc/services" );
    ports_from_file( "/usr/local/share/nmap/nmap-services" );
    ports_from_file( $ENV{"NDIFF_SERVICES_FILE"} );
  }



1;














