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