package Parse::CPinfo;

use 5.006_001;
use strict;
no warnings;
use base qw/Exporter/;
use Carp;
use IO::File;
require Exporter;

our $VERSION = '0.882';

# extracted from Regexp::Common
my $re_mac =
'(?:(?:[0-9a-fA-F]{1,2}):(?:[0-9a-fA-F]{1,2}):(?:[0-9a-fA-F]{1,2}):(?:[0-9a-fA-F]{1,2}):(?:[0-9a-fA-F]{1,2}):(?:[0-9a-fA-F]{1,2}))';
my $re_net_ipv4 =
'(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))';

sub new {
	my $class = shift;
	my $self  = {};
	bless $self, $class;
	return $self;
}

sub readfile {
	my $self     = shift;
	my $filename = shift;

	# keep copy of filename in object
	$self->{'_filename'} = $filename;
	my $fh = new IO::File($filename, 'r');
	if (!defined($fh)) {
		croak "Unable to open $filename for reading";
	}

	# ensure we are in binary mode as some system (win32 for example)
	# will assume we are handling text otherwise.
	binmode $fh, ':raw';

	my @lines = <$fh>;
	chomp @lines;
	my $linenumber = 0;
	while ($linenumber < $#lines) {
		$linenumber++;
		if ($lines[$linenumber] =~ m/^\={46}$/o) {
			$linenumber++;
			my $section = $lines[$linenumber];
			$linenumber++;

			foreach (0 .. $linenumber) {
				$linenumber++;
				if ($lines[$linenumber] !~ m/^\={46}$/o) {
					$lines[$linenumber] =~ s/\r\n//g;
					$lines[$linenumber] =~ s/\r//g;
					$lines[$linenumber] =~ s/\n//g;
					$self->{'config'}->{$section} = $self->{'config'}->{$section} . "$lines[$linenumber]\n";
				}
				else {
					$linenumber--;
					last;
				}
			}
		}
	}
	$self->_parseinterfacelist();
	return 1;
}

sub _parseinterfacelist {
	my $self         = shift;
	my $ifconfigtext = $self->{'config'}->{'IP Interfaces'};
	if (!$ifconfigtext) {
		return;
	}
	my @s = split /\n/, $ifconfigtext;
	my ($int);
	foreach my $line (@s) {
		chomp $line;
		if ($line =~ m/^(\w+)\s+/o) {
			my $match = $1;
			if ($match !~ m/ifconfig/o) {
				$int = $1;
				$self->{'interface'}->{$int}->{'name'} = $int;
			}
		}
		if ($line =~ m/Link encap:(\w+)\s+/io) {
			$self->{'interface'}->{$int}->{'encap'} = $1;
		}
		if ($line =~ m/HWaddr ($re_mac)/io) {
			$self->{'interface'}->{$int}->{'hwaddr'} = $1;
		}
		if ($line =~ m/inet addr:($re_net_ipv4)/io) {
			$self->{'interface'}->{$int}->{'inetaddr'} = $1;
		}
		if ($line =~ m/bcast:($re_net_ipv4)/io) {
			$self->{'interface'}->{$int}->{'broadcast'} = $1;
		}
		if ($line =~ m/mask:($re_net_ipv4)/io) {
			$self->{'interface'}->{$int}->{'mask'}       = $1;
			$self->{'interface'}->{$int}->{'masklength'} = $self->_ipv4_msk2cidr($self->{'interface'}->{$int}->{'mask'});
		}
		if ($line =~ m/MTU:(\d+)/io) {
			$self->{'interface'}->{$int}->{'mtu'} = $1;
		}
	}
	return 1;
}

sub _ipv4_msk2cidr {
	my $self = shift;
	my $mask = shift;
	($mask) = $mask =~ m/(\d+\.\d+\.\d+\.\d+)/o;

	if (! defined($mask)) {
		return undef;
	}

	for (split /\./, $mask) {
		if ($_ < 0 or $_ > 255) {
			return undef;
		}
	}
	my @bytes = split /\./, $mask;

	my $cidr = 0;
	for (@bytes) {
		my $bits = unpack("B*", pack("C", $_));
		$cidr += $bits =~ tr /1/1/;
	}
	return $cidr;
}

sub getInterfaceList {
	my $self = shift;
	return keys %{$self->{'interface'}};
}

sub getInterfaceInfo {
	my $self      = shift;
	my $interface = shift;
	return $self->{'interface'}{$interface};
}

sub getSectionList {
	my $self = shift;
	my @r;
	foreach my $section (sort keys %{$self->{config}}) {
		push @r, $section;
	}
	return @r;
}

sub getSection {
	my $self    = shift;
	my $query   = shift;
	my $section = $self->{'config'}->{$query};
	return $section;
}

sub getHostname {
	my $self = shift;
	my @section = split(/\n/, $self->getSection('System Information'));
	my $hostname;
	foreach my $linenumber (0 .. $#section) {
		if ($section[$linenumber] =~ m/Issuing 'hostname'/i) {
			$hostname = $section[$linenumber + 2];
			chomp $hostname;
			last;
		}
	}
	if (defined($hostname)) {
		return $hostname;
	}
	else {
		return undef;
	}
}

1;
__END__

=head1 NAME

Parse::CPinfo - Perl extension to parse output from cpinfo 

=head1 SYNOPSIS

  use Parse::CPinfo;
  my $p = Parse::CPinfo->new();
  $p->readfile('cpinfofile');

  # print the section containing the fwm version string 
  print $p->getSection('FireWall-1 Management (fwm) Version Information');

  # Get a list of interfaces
  my @l = $p->getInterfaceList();

  foreach my $int(@l) {
      print "Interface $int\n";
	  print "IP Address: " . $int->{'inetaddr'} . "\n";
  }


=head1 DESCRIPTION

This module parses the output from B<cpinfo>.  B<cpinfo> is a utility provided
by Check Point Software, used for diagnostic purposes.


=head1 SUBROUTINES/METHODS

The following are the object methods:

=head2 new

Create a new parser object like this:
my $p = Parse::CPinfo->new();

=head2 readfile

After creating the parser object, ask it to read the B<cpinfo> file
for you:
$parser->readfile('/full/path/to/cpinfofile');

=head2 getSectionList

Use this method to get a list of valid sections.  Returns an array.

=head2 getSection

Use this method to get a section of the cpinfo file.  Returns a scalar.

=head2 getHostname

Use this method to get the hostname of the server.  Returns a scalar.

=head2 getInterfaceList

Use this method to get a list of the active interfaces.  Returns an array.

=head2 getInterfaceInfo

Use this method to get information about a specific interface.  Takes a
scalar (interface name) and returns a hash.

=head1 SEE ALSO

Check Point Software Technologies, Ltd., at
http://www.checkpoint.com/

=head1 AUTHOR

Matthew M. Lange, E<lt>mmlange@cpan.orgE<gt>

=head1 BUGS AND LIMITATIONS

This library hasn't been extensively tested.  I'm sure there are bugs in the code.
Please file a bug report at http://rt.cpan.org/ if you find a bug.

=head1 LICENSE AND COPYRIGHT

Copyright (C) 2007 by Matthew M. Lange

This library is released under the GNU Public License.

=cut