1132943Sgshapiro#!/usr/bin/perl -w
264562Sgshapiro#
3363466Sgshapiro# usage:
4363466Sgshapiro#  cidrexpand < /etc/mail/access | makemap -r hash /etc/mail/access
5363466Sgshapiro#
6132943Sgshapiro# v 0.4
7132943Sgshapiro#
864562Sgshapiro# 17 July 2000 Derek J. Balling (dredd@megacity.org)
9363466Sgshapiro#
1064562Sgshapiro# Acts as a preparser on /etc/mail/access_db to allow you to use address/bit
11363466Sgshapiro# notation.
1264562Sgshapiro#
13110560Sgshapiro# If you have two overlapping CIDR blocks with conflicting actions
14110560Sgshapiro# e.g.   10.2.3.128/25 REJECT and 10.2.3.143 ACCEPT
15110560Sgshapiro# make sure that the exceptions to the more general block are specified
16110560Sgshapiro# later in the access_db.
1764562Sgshapiro#
18110560Sgshapiro# the -r flag to makemap will make it "do the right thing"
19110560Sgshapiro#
20110560Sgshapiro# Modifications
21110560Sgshapiro# -------------
22132943Sgshapiro# 26 Jul 2001 Derek Balling (dredd@megacity.org)
23132943Sgshapiro#     Now uses Net::CIDR because it makes life a lot easier.
24132943Sgshapiro#
25132943Sgshapiro#  5 Nov 2002 Richard Rognlie (richard@sendmail.com)
26110560Sgshapiro#     Added code to deal with the prefix tags that may now be included in
27110560Sgshapiro#     the access_db
28110560Sgshapiro#
29363466Sgshapiro#     Added clarification in the notes for what to do if you have
30110560Sgshapiro#     exceptions to a larger CIDR block.
31110560Sgshapiro#
32363466Sgshapiro#  26 Jul 2006 Richard Rognlie (richard@sendmail.com)
33168515Sgshapiro#     Added code to strip "comments" (anything after a non-escaped #)
34168515Sgshapiro#     # characters after a \ or within quotes (single and double) are
35363466Sgshapiro#     left intact.
36168515Sgshapiro#
37168515Sgshapiro#     e.g.
38168515Sgshapiro#	From:1.2.3.4	550 Die spammer # spammed us 2006.07.26
39168515Sgshapiro#     becomes
40363466Sgshapiro#	From:1.2.3.4	550 Die spammer
41168515Sgshapiro#
42161389Sgshapiro#  3 August 2006
43161389Sgshapiro#     Corrected a bug to have it handle the special case of "0.0.0.0/0"
44161389Sgshapiro#     since Net::CIDR doesn't handle it properly.
45161389Sgshapiro#
46363466Sgshapiro#  27 April 2016
47363466Sgshapiro#     Corrected IPv6 handling.  Note that UseCompressedIPv6Addresses must
48363466Sgshapiro#     be turned off for this to work; there are three reasons for this:
49363466Sgshapiro#       1) if the MTA uses compressed IPv6 addresses then CIDR 'cuts'
50363466Sgshapiro#          in the compressed range *cannot* be matched, as the MTA simply
51363466Sgshapiro#          won't look for them.  E.g., there's no way to accurately
52363466Sgshapiro#          match "IPv6:fe80::/64" when for the address "IPv6:fe80::54ad"
53363466Sgshapiro#          the MTA doesn't lookup up "IPv6:fe80:0:0:0"
54363466Sgshapiro#       2) cidrexpand only generates uncompressed addresses, so CIDR
55363466Sgshapiro#          'cuts' to the right of the compressed range won't be matched
56363466Sgshapiro#          either.  Why doesn't it generate compressed address output?
57363466Sgshapiro#          Oh, because:
58363466Sgshapiro#       3) compressed addresses are ambiguous when colon-groups are
59363466Sgshapiro#          chopped off!  You want an access map entry for
60363466Sgshapiro#               IPv6:fe80::0:5420
61363466Sgshapiro#          but not for
62363466Sgshapiro#               IPv6:fe80::5420:1234
63363466Sgshapiro#          ?  Sorry, the former is really
64363466Sgshapiro#               IPv6:fe80::5420
65363466Sgshapiro#          which will also match the latter!
6664562Sgshapiro#
67363466Sgshapiro#  25 July 2016
68363466Sgshapiro#     Since cidrexpand already requires UseCompressedIPv6Addresses to be
69363466Sgshapiro#     turned off, it can also canonicalize non-CIDR IPv6 addresses to the
70363466Sgshapiro#     format that sendmail looks up, expanding compressed addresses and
71363466Sgshapiro#     trimming superfluous leading zeros.
7264562Sgshapiro#
73132943Sgshapiro# Report bugs to: <dredd@megacity.org>
74132943Sgshapiro#
7564562Sgshapiro
76132943Sgshapiro
77132943Sgshapirouse strict;
78363466Sgshapirouse Net::CIDR qw(cidr2octets cidrvalidate);
79168515Sgshapirouse Getopt::Std;
80132943Sgshapiro
81363466Sgshapirosub print_expanded_v4network;
82363466Sgshapirosub print_expanded_v6network;
83168515Sgshapiro
84363466Sgshapiroour %opts;
85363466Sgshapirogetopts('ct:', \%opts);
8664562Sgshapiro
87363466Sgshapiro# Delimiter between the key and value
88363466Sgshapiromy $space_re = exists $opts{t} ? $opts{t} : '\s+';
89363466Sgshapiro
90363466Sgshapiro# Regexp that matches IPv4 address literals
91363466Sgshapiromy $ipv4_re = qr"(?:\d+\.){3}\d+";
92363466Sgshapiro
93363466Sgshapiro# Regexp that matches IPv6 address literals, plus a lot more.
94363466Sgshapiro# Further checks are required for verifying that it's really one
95363466Sgshapiromy $ipv6_re = qr"[0-9A-Fa-f:]{2,39}(?:\.\d+\.\d+\.\d+)?";
96363466Sgshapiro
9764562Sgshapirowhile (<>)
9864562Sgshapiro{
99168515Sgshapiro    chomp;
100363466Sgshapiro    my ($prefix, $network, $len, $right);
10164562Sgshapiro
102363466Sgshapiro    if ( (/\#/) && $opts{c} )
103168515Sgshapiro    {
104168515Sgshapiro	# print "checking...\n";
105168515Sgshapiro	my $i;
106168515Sgshapiro	my $qtype='';
107363466Sgshapiro	for ($i=0 ; $i<length($_) ; $i++)
108168515Sgshapiro	{
109168515Sgshapiro	    my $ch = substr($_,$i,1);
110363466Sgshapiro	    if ($ch eq '\\')
111168515Sgshapiro	    {
112168515Sgshapiro		$i++;
113168515Sgshapiro		next;
114168515Sgshapiro	    }
115363466Sgshapiro	    elsif ($qtype eq '' && $ch eq '#')
116168515Sgshapiro	    {
117168515Sgshapiro		substr($_,$i) = '';
118168515Sgshapiro		last;
119168515Sgshapiro	    }
120363466Sgshapiro	    elsif ($qtype ne '' && $ch eq $qtype)
121168515Sgshapiro	    {
122168515Sgshapiro		$qtype = '';
123168515Sgshapiro	    }
124363466Sgshapiro	    elsif ($qtype eq '' && $ch =~ /[\'\"]/)
125168515Sgshapiro	    {
126168515Sgshapiro		$qtype = $ch;
127168515Sgshapiro	    }
128168515Sgshapiro	}
129363466Sgshapiro    }
130363466Sgshapiro
131363466Sgshapiro    if (($prefix, $network, $len, $right) =
132363466Sgshapiro	    m!^(|\S+:)(${ipv4_re})/(\d+)(${space_re}.*)$!)
133132943Sgshapiro    {
134363466Sgshapiro	print_expanded_v4network($network, $len, $prefix, $right);
135132943Sgshapiro    }
136363466Sgshapiro    elsif ((($prefix, $network, $len, $right) =
137363466Sgshapiro	    m!^((?:\S+:)?[Ii][Pp][Vv]6:)(${ipv6_re})(?:/(\d+))?(${space_re}.*)$!) &&
138363466Sgshapiro	    (!defined($len) || $len <= 128) &&
139363466Sgshapiro	    defined(cidrvalidate($network)))
140363466Sgshapiro    {
141363466Sgshapiro	print_expanded_v6network($network, $len // 128, $prefix, $right);
142363466Sgshapiro    }
143132943Sgshapiro    else
144132943Sgshapiro    {
145363466Sgshapiro	print "$_\n";
146132943Sgshapiro    }
14764562Sgshapiro}
148363466Sgshapiro
149363466Sgshapirosub print_expanded_v4network
15064562Sgshapiro{
151363466Sgshapiro    my ($network, $len, $prefix, $suffix) = @_;
152363466Sgshapiro
153363466Sgshapiro    # cidr2octets() doesn't handle a prefix-length of zero, so do
154363466Sgshapiro    # that ourselves
155363466Sgshapiro    foreach my $nl ($len == 0 ? (0..255) : cidr2octets("$network/$len"))
156132943Sgshapiro    {
157363466Sgshapiro	print "$prefix$nl$suffix\n";
158363466Sgshapiro    }
159363466Sgshapiro}
160161389Sgshapiro
161363466Sgshapirosub print_expanded_v6network
162363466Sgshapiro{
163363466Sgshapiro    my ($network, $len, $prefix, $suffix) = @_;
164363466Sgshapiro
165363466Sgshapiro    # cidr2octets() doesn't handle a prefix-length of zero, so do
166363466Sgshapiro    # that ourselves.  Easiest is to just recurse on bottom and top
167363466Sgshapiro    # halves with a length of 1
168363466Sgshapiro    if ($len == 0) {
169363466Sgshapiro	print_expanded_v6network("::", 1, $prefix, $suffix);
170363466Sgshapiro	print_expanded_v6network("8000::", 1, $prefix, $suffix);
171363466Sgshapiro    }
172363466Sgshapiro    else
173363466Sgshapiro    {
174363466Sgshapiro	foreach my $nl (cidr2octets("$network/$len"))
17564562Sgshapiro	{
176363466Sgshapiro	    # trim leading zeros from each group
177363466Sgshapiro	    $nl =~ s/(^|:)0+(?=[^:])/$1/g;
178363466Sgshapiro	    print "$prefix$nl$suffix\n";
17964562Sgshapiro	}
180132943Sgshapiro    }
18164562Sgshapiro}
182