#!/usr/local/bin/perl5.00502
#
# gensubnetlist
#
# Generate list of all possible subnet addresses given a network, netmask,
# and subnetmask.
#
# by Paul Balyoz
#
# Copyright (C) 1993-2000 Paul A. Balyoz <pab@domtools.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., 675 Mass Ave, Cambridge, MA 02139, USA.
#

# Global variables

@octetmask = (
	0x000000FF,
	0x0000FF00,
	0x00FF0000,
	0xFF000000
);


#
# Main
#

# Name of ourself

$prog = $0;

# Must give us all 3 arguments

if ($#ARGV != 2) {
	print STDERR "usage: $prog network netmask subnetmask\n";
	print "ERROR\n";
	exit 1;
}

# Process the 3 arguments into long integers.
# Make sure all 3 arguments are purely numeric digits and periods

if ($ARGV[0] !~ /^[0-9.]+$/) {
	print STDERR "$0: first arg must be dotted-quad IP address, not '$ARGV[0]'.\n";
	print "ERROR\n";
	exit 1;
}
$network = &str2ip($ARGV[0]);

if ($ARGV[1] !~ /^[0-9.]+$/) {
	print STDERR "$0: second arg must be dotted-quad IP netmask, not '$ARGV[1]'.\n";
	print "ERROR\n";
	exit 1;
}
$netmask = &str2ip($ARGV[1]);

if ($ARGV[2] !~ /^[0-9.]+$/) {
	print STDERR "$0: third arg must be dotted-quad IP subnetmask, not '$ARGV[2]'.\n";
	print "ERROR\n";
	exit 1;
}
$subnetmask = &str2ip($ARGV[2]);

if ($netmask == $subnetmask) {
	print STDERR "$prog: netmask and subnetmask are the same.\n";
	print "ERROR\n";
	exit 1;
}

# Find the difference in netmasks and make sure there aren't any bits
# turned on in netmask that aren't on in subnetmask.

$maskdiff = $netmask ^ $subnetmask;

if (($netmask & $maskdiff) != 0) {
	print STDERR "$prog: netmask has bits turned on that are off in subnetmask.\n";
	print "ERROR\n";
	exit 1;
}

# Generate a list of what bits are turned on in maskdiff by putting their
# bit position numbers in the sequential array @subbits.

for ($i=$numbits=0; $i<32; $i++) {
	if ($maskdiff & (1<<$i)) {
		$subbits[$numbits++] = $i;
	}
}

# Strip off all host bits from the "network" address just in case

$network &= $netmask;

# Loop through all possible configurations for the bits in maskdiff and
# print all IP addresses of subnets within network this way.
# Be sure to print the all-bits-off and all-bits-on combinations,
# because those are legal in networks (just not in hosts).

for ($i=0; $i < (1<<$numbits); $i++) {
	&printip($network | &makemask($i));
}

# All worked well.

exit 0;


#
# str2ip	Convert a string containing a dotted-quad IP address into
#		a 32-bit integer IP address.
#

sub str2ip {
	my($str) = @_;

	my($i, $octet, $ip, @quads);

# Split IP address string into dotted quads, make sure there's exactly 4.

	@quads = split(/\./, $str);
	if ($#quads != 3) {
		print STDERR "$prog: $str is not a dotted-quad IP address.\n";
		print "ERROR\n";
		exit 1;
	}

# Process all 4 octets

	$ip = 0;
	for ($i=4; $i>0; $i--) {

# If any octet string is blank, they didn't give us a real IP type thing

		$octet = $quads[4-$i];
		if ($octet eq "") {
			print STDERR "$prog: $str is not a dotted-quad IP address.\n";
			print "ERROR\n";
			exit 1;
		}

# Make sure we have a legal octet

		if ($octet > 255) {
			print STDERR "$prog: $str has an octet greater than 255.\n";
			print "ERROR\n";
			exit 1;
		}

# Add this octet at the correct position within the IP address

		$ip += $octet * (1 << ($i-1)*8);
	}

# Return the answer

	$ip;
}


#
# makemask	Convert an index number into a mask given the maskdiff.
#		No check is made to be sure that the index number lies within
#		the capabilities of the mask, so don't pass in bad numbers!
#

sub makemask {
	my($index) = @_;

	my($j);
	my($answ) = 0;

# Loop through all bits in maskdiff, extract each bit from index and
# shift it up into its proper bit position according to maskdiff.

	for ($j=0; $j<$numbits; $j++) {
		$answ += ((($index & (1<<$j)) ? 1 : 0) << $subbits[$j]);
	}

	$answ;
}


#
# printip	Print a long integer network IP address in standard
#		dot-separated, decimal, four-octet format.
#

sub printip {
	my($ip) = @_;

	my($i);

	for ($i=4; $i>0; $i--) {
		# keyword STDOUT necessary because old Perl's freak out here w/o it!
		print STDOUT ($ip & $octetmask[$i-1]) >> (($i-1)*8);
		print STDOUT ($i>1) ? "." : "\n";
	}
}
