4 # cidrexpand < /etc/mail/access | makemap -r hash /etc/mail/access
8 # 17 July 2000 Derek J. Balling (dredd@megacity.org)
10 # Acts as a preparser on /etc/mail/access_db to allow you to use address/bit
13 # If you have two overlapping CIDR blocks with conflicting actions
14 # e.g. 10.2.3.128/25 REJECT and 10.2.3.143 ACCEPT
15 # make sure that the exceptions to the more general block are specified
16 # later in the access_db.
18 # the -r flag to makemap will make it "do the right thing"
22 # 26 Jul 2001 Derek Balling (dredd@megacity.org)
23 # Now uses Net::CIDR because it makes life a lot easier.
25 # 5 Nov 2002 Richard Rognlie (richard@sendmail.com)
26 # Added code to deal with the prefix tags that may now be included in
29 # Added clarification in the notes for what to do if you have
30 # exceptions to a larger CIDR block.
32 # 26 Jul 2006 Richard Rognlie (richard@sendmail.com)
33 # Added code to strip "comments" (anything after a non-escaped #)
34 # # characters after a \ or within quotes (single and double) are
38 # From:1.2.3.4 550 Die spammer # spammed us 2006.07.26
40 # From:1.2.3.4 550 Die spammer
43 # Corrected a bug to have it handle the special case of "0.0.0.0/0"
44 # since Net::CIDR doesn't handle it properly.
47 # Corrected IPv6 handling. Note that UseCompressedIPv6Addresses must
48 # be turned off for this to work; there are three reasons for this:
49 # 1) if the MTA uses compressed IPv6 addresses then CIDR 'cuts'
50 # in the compressed range *cannot* be matched, as the MTA simply
51 # won't look for them. E.g., there's no way to accurately
52 # match "IPv6:fe80::/64" when for the address "IPv6:fe80::54ad"
53 # the MTA doesn't lookup up "IPv6:fe80:0:0:0"
54 # 2) cidrexpand only generates uncompressed addresses, so CIDR
55 # 'cuts' to the right of the compressed range won't be matched
56 # either. Why doesn't it generate compressed address output?
58 # 3) compressed addresses are ambiguous when colon-groups are
59 # chopped off! You want an access map entry for
62 # IPv6:fe80::5420:1234
63 # ? Sorry, the former is really
65 # which will also match the latter!
68 # Since cidrexpand already requires UseCompressedIPv6Addresses to be
69 # turned off, it can also canonicalize non-CIDR IPv6 addresses to the
70 # format that sendmail looks up, expanding compressed addresses and
71 # trimming superfluous leading zeros.
73 # Report bugs to: <dredd@megacity.org>
79 use Net::CIDR qw(cidr2octets cidrvalidate);
81 $Getopt::Std::STANDARD_HELP_VERSION = 1;
85 sub print_expanded_v4network;
86 sub print_expanded_v6network;
89 getopts('cfhOSt:', \%opts);
92 HELP_MESSAGE(\*STDOUT);
96 # Delimiter between the key and value
97 my $space_re = exists $opts{t} ? $opts{t} : '\s+';
99 # Regexp that matches IPv4 address literals
100 my $ipv4_re = qr"(?:\d+\.){3}\d+";
102 # Regexp that matches IPv6 address literals, plus a lot more.
103 # Further checks are required for verifying that it's really one
104 my $ipv6_re = qr"[0-9A-Fa-f:]{2,39}(?:\.\d+\.\d+\.\d+)?";
110 my ($prefix, $network, $len, $right);
112 next if /^#/ && $opts{S};
113 if ( (/\#/) && $opts{c} )
115 # print "checking...\n";
118 for ($i=0 ; $i<length($_) ; $i++)
120 my $ch = substr($_,$i,1);
126 elsif ($qtype eq '' && $ch eq '#')
131 elsif ($qtype ne '' && $ch eq $qtype)
135 elsif ($qtype eq '' && $ch =~ /[\'\"]/)
142 if (($prefix, $network, $len, $right) =
143 m!^(|[^\s:]+:)(${ipv4_re})/(\d+)(${space_re}.*)$!)
145 print_expanded_v4network($network, $len, $prefix, $right);
147 elsif ((($prefix, $network, $len, $right) =
148 m!^((?:[^\s:]+:)?[Ii][Pp][Vv]6:)(${ipv6_re})(?:/(\d+))?(${space_re}.*)$!) &&
149 (!defined($len) || $len <= 128) &&
150 defined(cidrvalidate($network)))
152 print_expanded_v6network($network, $len // 128, $prefix, $right);
156 if (%pending && m!^(.+?)${space_re}!)
158 delete $pending{$opts{f} ? $1 : lc($1)};
163 print foreach values %pending;
165 sub print_expanded_v4network
167 my ($network, $len, $prefix, $suffix) = @_;
168 my $fp = $opts{f} ? $prefix : lc($prefix);
170 # cidr2octets() doesn't handle a prefix-length of zero, so do
172 foreach my $nl ($len == 0 ? (0..255) : cidr2octets("$network/$len"))
174 my $val = "$prefix$nl$suffix\n";
177 $pending{"$fp$nl"} = $val;
184 sub print_expanded_v6network
186 my ($network, $len, $prefix, $suffix) = @_;
188 # cidr2octets() doesn't handle a prefix-length of zero, so do
189 # that ourselves. Easiest is to just recurse on bottom and top
190 # halves with a length of 1
192 print_expanded_v6network("::", 1, $prefix, $suffix);
193 print_expanded_v6network("8000::", 1, $prefix, $suffix);
197 my $fp = $opts{f} ? $prefix : lc($prefix);
198 foreach my $nl (cidr2octets("$network/$len"))
200 # trim leading zeros from each group
201 $nl =~ s/(^|:)0+(?=[^:])/$1/g;
202 my $val = "$prefix$nl$suffix\n";
205 $pending{"$fp$nl"} = $val;
216 print $fh "cidrexpand - Version $VERSION\n";
223 Usage: cidrexpand [-cfhOS] [-t regexp] files...
225 Expand CIDR format inside the keys of map entries for makemap.
227 -c Truncate lines at the first unquoted '#'
229 -f Treat keys as case-sensitive when doing override detection
230 for the -O option. By default overlap detection is
235 -O When a CIDR expansion would generate a partial conflict
236 with a later entry, suppress the overlap from the earlier
239 -S Skip lines that start with '#'
242 Use 'regexp' to match the delimiter between key and value,