#!/usr/local/bin/perl
#
# $Id: OspfParser.pl,v 1.1 2004/08/20 21:51:46 rdilley Exp $
#
# author: ron dilley
#
# desc: this perl script converts cisco route dumps to net lists
#
# Copyright (C) 2004  Ron A. Dilley
#
# 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; version 2
# of the License.
#
# 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.
#
# ron.dilley@uberadmin.com
#
############################################################################

#
# modules
#
use Getopt::Std;

#
# pragmas
#
use strict;

#
# set environment
#
$ENV{PATH} = "/usr/bin:/bin:/usr/sbin:/sbin:/usr/ucb";

#
# turn on autoflush
#
select STDERR; $| = 1;
select STDOUT; $| = 1;

#
# defines
#
$::TRUE = 1;
$::FALSE = 0;
$::FAILED = -1;

$::VERSION = '$Id: OspfParser.pl,v 1.1 2004/08/20 21:51:46 rdilley Exp $';
$::PROGNAME = "OspfParser.pl";

%::Config = ();
$::Config{'debug'} = $::FALSE;

#
# main routine
#
if ( &main() != $::TRUE ) {
  exit( 1 );
}

exit( 0 );

############################################################################
#
# sub-routines
#

#
# main routine
#
sub main {
  my $arg;
  my $net;

  #
  # display script banner
  #
  &show_banner();

  #
  # parse command-line
  #
  &parse_command_line();

  # process args that are left
  while( $arg = shift( @::ARGV ) ) {
    &parse_file( $arg );
  }

  #
  # print it all out
  #
  foreach $net ( sort nets_by_ipv4_addr ( @::netlist )) {
    print "$net\n";
  }

  # done
  return $::TRUE;
}

#
# display banner info
#
sub show_banner {
  print "$::VERSION\n";
  print "By: Ron Dilley\n";
  print "\n";
  print "$::PROGNAME comes with ABSOLUTELY NO WARRANTY.\n";
  print "This is free software, and you are welcome\n";
  print "to redistribute it under certain conditions;\n";
  print "See the GNU General Public License for details.\n";
  print "\n";

  return $::TRUE;
}

#
# display help info
#
sub show_help {
  print "Syntax:\n";
  print "\n";
  print "$::PROGNAME [options] {dir} [{dir} ...]\n";
  print "\n";
  print "-d {0-9}   Display debug information during program run\n";
  print "\n";

  return $::TRUE;
}

#
# parse command-line arguments
#
sub parse_command_line {
  no strict 'vars';

  if ( getopts( 'd:' ) == $::FALSE ) {
    &show_help();
    return $::FAILED;
  }
  if ( defined $opt_d ) {
    if ( $opt_d > 0 ) {
      # set debug mode
      $::Config{'debug'} = $opt_d;
    }
  }

  return $::TRUE;
}

#
# parse file
#
sub parse_file {
  my ( $fname ) = @_;
  my $line;
  my $netaddr;
  my $netmask;

  if ( ! defined open( FILE, $fname ) ) {
    print "ERROR - Unable to open [$fname]\n";
    return $::FAILED;
  }

  while ( $line = <FILE> ) {
    chomp( $line );
    if ( $line =~ m/[O|S|C]\s.{2}\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(\d+) \[.*/ ) {
      $netaddr = $1;
      $netmask = $2;
      push( @::netlist, "$netaddr/$netmask" );
    } elsif ( $line =~ m/[O|S|C]\s.{2}\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) \[.*/ ) {
      $netaddr = $1;
      $netmask = 32;
      push( @::netlist, "$netaddr/$netmask" );
    } elsif ( $line =~ m/[O|S|C]\s.{2}\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(\d+) is directly/ ) {
      $netaddr = $1;
      $netmask = $2;
      push( @::netlist, "$netaddr/$netmask" );
    } else {
      if ( $::Config{'debug'} >= 1 ) {
	print "WARNING - Unknown [$line]\n";
      }
    }
  }

  close( FILE );

  return $::TRUE;
}

#
# get start and end address
#
sub get_start_and_stop_int {
  my ( $segment ) = @_;

  my $seg_addr;
  my $seg_mask;
  my $bit_mask;
  my $int_seg_addr;
  my $int_seg_mask;
  my $low_int;
  my $high_int;

  # get subnet mask
  if ( $segment =~ m/^(\d{1,3})(.*)\/(\d+)$/ ) {
    # bit mask
    $seg_addr = $1 . $2;
    if ( $1 >= 1 && $1 <= 126 ) {
      # class a
      $seg_mask = "255.0.0.0";
    } elsif ( $1 >= 128 & $1 <= 191 ) {
      # class b
      $seg_mask = "255.255.0.0";
    } elsif ( $1 >= 192 & $1 <= 223 ) {
      # class c
      $seg_mask = "255.255.255.0";
    } elsif ( $1 >= 224 & $1 <= 239 ) {
      # class d (multicast)
      print "ERROR[$$] - [$segment] is a class-d address\n";
      return $::FAILED;
    } else {
      # class e (research)
      print "ERROR[$$] - [$segment] is a class-e address\n";
      return $::FAILED;
    }
    $seg_mask = &bitmask_to_ipv4( $3 );
  } elsif ( $segment =~ m/^(\d{1,3})(.*)\:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/ ) {
    # fully qualified subnet mask
    $seg_addr = $1 . $2;
    $seg_mask = $3;
  } elsif ( $segment =~ m/^(\d{1,3})(.*)$/ ) {
    # default subnet mask
    $seg_addr = $1 . $2;
    if ( $1 >= 1 && $1 <= 126 ) {
      # class a
      $seg_mask = "255.0.0.0";
    } elsif ( $1 >= 128 & $1 <= 191 ) {
      # class b
      $seg_mask = "255.255.0.0";
    } elsif ( $1 >= 192 & $1 <= 223 ) {
      # class c
      $seg_mask = "255.255.255.0";
    } elsif ( $1 >= 224 & $1 <= 239 ) {
      # class d (multicast)
      print "ERROR[$$] - [$segment] is a class-d address\n";
      return $::FAILED;
    } else {
      # class e (research)
      print "ERROR[$$] - [$segment] is a class-e address\n";
      return $::FAILED;
    }
  } else {
    print "ERROR[$$] - [$segment] is indecypherable\n";
    return $::FAILED;
  }

  # deal with partial addresses
  if ( $seg_addr =~ m/^(\d{1,3})\.$/ ) { # x.
    $seg_addr = "$1.0.0.0";
    $seg_mask = "255.0.0.0";
  } elsif ( $seg_addr =~ m/^(\d{1,3})\.(\d{1,3})\.$/ ) { # x.x.
    $seg_addr = "$1.$2.0.0";
    $seg_mask = "255.255.0.0";
  } elsif ( $seg_addr =~ m/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.$/ ) { # x.x.x.
    $seg_addr = "$1.$2.$3.0";
    $seg_mask = "255.255.255.0";
  } elsif ( $seg_addr =~ m/^(\d{1,3})\.\*\.\*\.\*$/ ) { # x.*.*.*
    $seg_addr = "$1.0.0.0";
    $seg_mask = "255.0.0.0";
  } elsif ( $seg_addr =~ m/^(\d{1,3})\.(\d{1,3})\.\*\.\*$/ ) { # x.x.*.*
    $seg_addr = "$1.$2.0.0";
    $seg_mask = "255.255.0.0";
  } elsif ( $seg_addr =~ m/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.\*$/ ) { # x.x.x.*
    $seg_addr = "$1.$2.$3.0";
    $seg_mask = "255.255.255.0";
  }

  if ( $::Config{'debug'} >= 8 ) {
    print "DEBUG[$$] - [$seg_addr] [$seg_mask]\n";
  }

  $int_seg_addr = &ipv4_to_int( $seg_addr );
  $int_seg_mask = &ipv4_to_int( $seg_mask );

  $low_int = $int_seg_addr;
  $high_int = ( $int_seg_mask ^ 0xffffffff ) + $low_int;

  if ( $::Config{'debug'} >= 7 ) {
    printf( "DEBUG[$$] - [%s - %s]\n", &int_to_ipv4( $low_int ), &int_to_ipv4( $high_int ) );
  }

  return ( $int_seg_addr, $int_seg_mask, $low_int, $high_int );
}

#
# sort hosts by int_addr
#
sub hosts_by_intaddr {
  return ( unpack( $::HOSTS_PACK, $a ) )[2] <=> ( unpack( $::HOSTS_PACK, $b ) )[2];
}

#
# sort nets by ipv4_addr and int_mask
#
sub nets_by_ipv4_addr {
  my $a_addr;
  my $a_mask;
  my $b_addr;
  my $b_mask;

  ( $a_addr, $a_mask ) = split( /\//, $a );
  ( $b_addr, $b_mask ) = split( /\//, $b );

  my $a_intaddr = ipv4_to_int( $a_addr );
  my $b_intaddr = ipv4_to_int( $b_addr );

  if ( $a_intaddr < $b_intaddr ) {
    return $::TRUE;
  } elsif ( $a_intaddr > $b_intaddr ) {
    return $::FAILED;
  } else {
    if ( $a_mask < $b_mask ) {
      return $::TRUE;
    } elsif ( $a_mask > $b_mask ) {
      return $::FAILED;
    }
  }
  return $::FALSE;
}

#
# sort numerically
#
sub numerically {
  $a <=> $b;
}

# SOF - [/home/amgen/rdilley/Projects/Perl/Modules/SCCS/s.ip_conversion.pl]

# @(#)ip_conversion.pl  1.1 [05/05/00 - 15:04:56]
#
#  author: ron dilley
#    desc: the following functions allow conversion to/from ipv4 addresses
#
############################################################################

#
#    desc: convert ipv4 addr to int
#    type: function
#    args: ipv4 address
# returns: int on no error, $::FAILED on error
#   notes: none
#
############################################################################

sub ipv4_to_int {
  my ( $addr ) = @_;

  if ( $addr =~ m/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/ ) {
    return ( ( $1 << 24 ) & 0xff000000 ) + ( ( $2 << 16 ) & 0x00ff0000 ) + ( ( $3 << 8 ) & 0x0000ff00 ) + ( $4 & 0x000000ff );
  }

  # not an ipv4 addr
  return $::FAILED;
}

#
#    desc: convert int to ipv4 addr
#    type: function
#    args: int
# returns: ipv4 address on no error, $::FAILED on error
#   notes: none
#
############################################################################

sub int_to_ipv4 {
  my ( $int_addr ) = @_;

  my @addr = ();

  $addr[0] = ( $int_addr >> 24 ) & 0x000000ff;
  $addr[1] = ( $int_addr >> 16 ) & 0x000000ff;
  $addr[2] = ( $int_addr >> 8 ) & 0x000000ff;
  $addr[3] = $int_addr & 0x000000ff;

  return "$addr[0].$addr[1].$addr[2].$addr[3]";
}

#
#    desc: convert bitmask to ipv4 addr
#    type: function
#    args: bitmask (int)
# returns: ipv4 address on no error, $::FAILED on error
#   notes: (count bits from left to right for netmasks)
#
############################################################################

sub bitmask_to_ipv4 {
  my ( $bit_mask ) = @_;

  return int_to_ipv4( &bitmask_to_int( $bit_mask ) );
}

#
#    desc: convert ipv4 addr to bitmask
#    type: function
#    args: ipv4 address
# returns: bitmask (int) on no error, $::FAILED on error
#   notes: (count bits from left to right for netmasks)
#
############################################################################

sub ipv4_to_bitmask {
  my ( $netmask ) = @_;

  return &int_to_bitmask( &ipv4_to_int( $netmask ) );
}

#
#    desc: convert bitmask to int
#    type: function
#    args: bitmask
# returns: int on no error, $::FAILED on error
#   notes: (count bits from left to right for netmasks)
#
############################################################################

sub bitmask_to_int {
  my ( $bit_mask ) = @_;

  my $i;
  my $mask = "";

  for( $i = 0; $i < 32; $i++ ) {
    if ( $bit_mask > $i ) {
      $mask = $mask . "1";
    } else {
      $mask = $mask . "0";
    }
  }

  return unpack( "N", pack( "B32", substr( "0" x 32 . $mask, -32 ) ) );
}

#
#    desc: convert int to bitmask
#    type: function
#    args: int
# returns: bitmask (int) on no error, $::FAILED on error
#   notes: (count bits from left to right for netmasks)
#
############################################################################

sub int_to_bitmask {
  my ( $netmask ) = @_;

  my @mask;
  my $i;

  @mask = split( //, unpack( "B32", pack( "N", $netmask ) ) );

  for( $i = 0; $i < 32 && $mask[$i] == 1; $i++ ) {
    # count the bits until we hit a '0'
  }

  return $i;
}

# EOF - [/home/amgen/rdilley/Projects/Perl/Modules/SCCS/s.ip_conversion.pl]
