#!/usr/local/bin/perl -w

# v 0.2-very-beta
#
# 17 July 2000 Derek J. Balling (dredd@megacity.org)
#
# The $SENDMAIL flag tells the code to lump networks in sendmail format
# if applicable. If this flag is disabled, cidrexpand will literally create
# a single line for each entry, which may or may not be what you want. :)
# makes for a rather large hash table...
#
# Acts as a preparser on /etc/mail/access_db to allow you to use address/bit
# notation. Caveat: the address portion MUST be the start address or your
# results will NOT be what what you want.
#
# If you have two overlapping CIDR blocks with conflicting actions
# e.g.   10.2.3.128/25 REJECT and 10.2.3.143 ACCEPT
# make sure that the exceptions to the more general block are specified
# later in the access_db.
#
# the -r flag to makemap will make it "do the right thing"
#
# Modifications
# -------------
#  5 Nov  2002 Richard Rognlie (richard@sendmail.com)
#     Added code to deal with the prefix tags that may now be included in
#     the access_db
#
#     Added clarification in the notes for what to do if you have 
#     exceptions to a larger CIDR block.
#
# usage:
#  cidrexpand < /etc/mail/access | makemap -r hash /etc/mail/access
#
# Report bugs to: dredd@megacity.org
#

my $spaceregex = '\s+';

while (my $arg = shift @ARGV)
{
     if ($arg eq '-t')
     {
	$spaceregex = shift;
     }
}

use strict;

my $SENDMAIL = 1;

while (<>)
{
     my ($prefix,$left,$right,$space);

     if (! /^(|\S\S*:)(\d+\.){3}\d+\/\d\d?$spaceregex.*/ )
     {
	print;
     }
     else
     {
	($prefix,$left,$space,$right) = /^(|\S\S*:)((?:\d+\.){3}\d+\/\d\d?)($spaceregex)(.*)$/;

	my @new_lefts = expand_network($left);
	foreach my $nl (@new_lefts)
	{
	    print "$prefix$nl$space$right\n";
	}

     }
}
    
sub expand_network
{
     my ($network,$mask) = split /\//, shift;
     my @diffs = calc_changes($network,$mask);
     my ($first,$second,$third,$fourth) = split /\./, $network;

     my @rc = ();

     for my $f ($first..($first+$diffs[0]))
     {
	if ( ( $SENDMAIL ) and ($diffs[1] == 255))
	{
	    push @rc, "$f";
	}
	else
	{
	    for my $s ($second..($second+$diffs[1]))
	    {
		if ( ($SENDMAIL) and ($diffs[2] == 255) )
		{
		    push @rc,"$f\.$s";
		}
		else
		{
		    for my $t ($third..($third+$diffs[2]))
		    {
			if ( ($SENDMAIL) and ($diffs[3] == 255))
			{
			    push @rc, "$f\.$s\.$t";
			}
			else
			{
			    for my $fr ($fourth..($fourth+$diffs[3]))
			    {
				push @rc, "$f\.$s\.$t\.$fr";
			    }
			}
		    }
		}
	    }
	}
     }
     return @rc;
}

sub calc_changes
{
     my ($network,$mask) = @_;
    
     my @octs = split /\./, $network;
    
     my ($first,$second,$third,$fourth) = (0,0,0,0);
    
     my $power = 32 - $mask;
    
     if ($mask > 24)
     {
	$fourth = 2**$power - 1;
     }
     elsif ($mask > 16)
     {
	$fourth = 255;
	$third = 2**($power-8) - 1;
     }
     elsif ($mask > 8)
     {
	$fourth = 255;
	$third  = 255;
	$second = 2**($power-16) - 1;
     }
     elsif ($mask > 0)
     {
	$fourth = 255;
	$third = 255;
	$second = 255;
	$first = 2**($power-24) - 1;
     }
     elsif ($mask == 0)
     {
	$fourth = 255;
	$third = 255;
	$second = 255;
	$first = 255;
     }

     return ($first,$second,$third,$fourth);
}
