aboutsummaryrefslogtreecommitdiff
path: root/contrib/sendmail/contrib/cidrexpand
blob: ee24ee865275beb3626ede1e736871a6dcd35640 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#!/usr/bin/perl -w
#
# usage:
#  cidrexpand < /etc/mail/access | makemap -r hash /etc/mail/access
#
# v 0.4
#
# 17 July 2000 Derek J. Balling (dredd@megacity.org)
#
# Acts as a preparser on /etc/mail/access_db to allow you to use address/bit
# notation.
#
# 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
# -------------
# 26 Jul 2001 Derek Balling (dredd@megacity.org)
#     Now uses Net::CIDR because it makes life a lot easier.
#
#  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.
#
#  26 Jul 2006 Richard Rognlie (richard@sendmail.com)
#     Added code to strip "comments" (anything after a non-escaped #)
#     # characters after a \ or within quotes (single and double) are
#     left intact.
#
#     e.g.
#	From:1.2.3.4	550 Die spammer # spammed us 2006.07.26
#     becomes
#	From:1.2.3.4	550 Die spammer
#
#  3 August 2006
#     Corrected a bug to have it handle the special case of "0.0.0.0/0"
#     since Net::CIDR doesn't handle it properly.
#
#  27 April 2016
#     Corrected IPv6 handling.  Note that UseCompressedIPv6Addresses must
#     be turned off for this to work; there are three reasons for this:
#       1) if the MTA uses compressed IPv6 addresses then CIDR 'cuts'
#          in the compressed range *cannot* be matched, as the MTA simply
#          won't look for them.  E.g., there's no way to accurately
#          match "IPv6:fe80::/64" when for the address "IPv6:fe80::54ad"
#          the MTA doesn't lookup up "IPv6:fe80:0:0:0"
#       2) cidrexpand only generates uncompressed addresses, so CIDR
#          'cuts' to the right of the compressed range won't be matched
#          either.  Why doesn't it generate compressed address output?
#          Oh, because:
#       3) compressed addresses are ambiguous when colon-groups are
#          chopped off!  You want an access map entry for
#               IPv6:fe80::0:5420
#          but not for
#               IPv6:fe80::5420:1234
#          ?  Sorry, the former is really
#               IPv6:fe80::5420
#          which will also match the latter!
#
#  25 July 2016
#     Since cidrexpand already requires UseCompressedIPv6Addresses to be
#     turned off, it can also canonicalize non-CIDR IPv6 addresses to the
#     format that sendmail looks up, expanding compressed addresses and
#     trimming superfluous leading zeros.
#
# Report bugs to: <dredd@megacity.org>
#


use strict;
use Net::CIDR qw(cidr2octets cidrvalidate);
use Getopt::Std;

sub print_expanded_v4network;
sub print_expanded_v6network;

our %opts;
getopts('ct:', \%opts);

# Delimiter between the key and value
my $space_re = exists $opts{t} ? $opts{t} : '\s+';

# Regexp that matches IPv4 address literals
my $ipv4_re = qr"(?:\d+\.){3}\d+";

# Regexp that matches IPv6 address literals, plus a lot more.
# Further checks are required for verifying that it's really one
my $ipv6_re = qr"[0-9A-Fa-f:]{2,39}(?:\.\d+\.\d+\.\d+)?";

while (<>)
{
    chomp;
    my ($prefix, $network, $len, $right);

    if ( (/\#/) && $opts{c} )
    {
	# print "checking...\n";
	my $i;
	my $qtype='';
	for ($i=0 ; $i<length($_) ; $i++)
	{
	    my $ch = substr($_,$i,1);
	    if ($ch eq '\\')
	    {
		$i++;
		next;
	    }
	    elsif ($qtype eq '' && $ch eq '#')
	    {
		substr($_,$i) = '';
		last;
	    }
	    elsif ($qtype ne '' && $ch eq $qtype)
	    {
		$qtype = '';
	    }
	    elsif ($qtype eq '' && $ch =~ /[\'\"]/)
	    {
		$qtype = $ch;
	    }
	}
    }

    if (($prefix, $network, $len, $right) =
	    m!^(|\S+:)(${ipv4_re})/(\d+)(${space_re}.*)$!)
    {
	print_expanded_v4network($network, $len, $prefix, $right);
    }
    elsif ((($prefix, $network, $len, $right) =
	    m!^((?:\S+:)?[Ii][Pp][Vv]6:)(${ipv6_re})(?:/(\d+))?(${space_re}.*)$!) &&
	    (!defined($len) || $len <= 128) &&
	    defined(cidrvalidate($network)))
    {
	print_expanded_v6network($network, $len // 128, $prefix, $right);
    }
    else
    {
	print "$_\n";
    }
}

sub print_expanded_v4network
{
    my ($network, $len, $prefix, $suffix) = @_;

    # cidr2octets() doesn't handle a prefix-length of zero, so do
    # that ourselves
    foreach my $nl ($len == 0 ? (0..255) : cidr2octets("$network/$len"))
    {
	print "$prefix$nl$suffix\n";
    }
}

sub print_expanded_v6network
{
    my ($network, $len, $prefix, $suffix) = @_;

    # cidr2octets() doesn't handle a prefix-length of zero, so do
    # that ourselves.  Easiest is to just recurse on bottom and top
    # halves with a length of 1
    if ($len == 0) {
	print_expanded_v6network("::", 1, $prefix, $suffix);
	print_expanded_v6network("8000::", 1, $prefix, $suffix);
    }
    else
    {
	foreach my $nl (cidr2octets("$network/$len"))
	{
	    # trim leading zeros from each group
	    $nl =~ s/(^|:)0+(?=[^:])/$1/g;
	    print "$prefix$nl$suffix\n";
	}
    }
}