#!/usr/bin/perl -w # # Written by Simon Mudd, G0FNB, EA4ELS # sjmudd@pobox.com # # $Header: /home/sjmudd/CVSROOT/hades/ampr_extractor,v 1.8 1998/11/02 22:31:27 sjmudd Exp $ # # # Useage: # # ampr_extractor # ampr_extractor 44.133.228.0 255.255.255.192 madrid-2m 3 # # # This script is intended to extract sub-domain information from the file # ampr.org, stored from the DNS server at ftp.ucsd.edu. (This file holds all # the DNS information for the "ampr.org" domain.) # # The sub-domain information can be useful for those of us who want to # create a smaller domain file for a small region, and don't need the full # dns file. # # This script is based on my original ea_extractor script, but I've made # changes to improve the flexibility. # # You now supply the network address and netmask on the command line, # together with the name of the sub-zone, and the number of bytes to fix when # extracting the reverse domain information from the file. # # KNOWN BUGS # 1. if the ampr.org domain has an entry with the same fully qualified # domain name, as the "local" name, it's entry will appear twice. # # i.e. the following hostnames are NOT considered identical # my.callsign # my.callsign.ampr.org. # # Personally, it might be a good idea if the DNS robot didn't # allow this, but it does, although it is not very important. # As my program converts all FQDN ampr.org names to the local # equivalent it doesn't notice this error. # # OTHER POINTS # 2. It is valid to have several mapping of hostname --> ip address # I now return several pointers to the original hostname if more # than one hostname maps to an ip address. # # I didn't do this before (I left a warning and only kept the # first entry), but think I may have been wrong. # # However I'll keep the warning as a comment # # Any error reports, comments, fixes or whatever, please let me know, # Though this script is without doubt simple and by no means very elegant. # It works for me, hope it works for you too. my $revision = '$Id: ampr_extractor,v 1.8 1998/11/02 22:31:27 sjmudd Exp $'; my $author = 'S J Mudd, EA4ELS / G0FNB'; my $email = 'sjmudd@pobox.com'; my @field = (); # temporary fields created during run my %hostname = (); # hostname hash lookup my %cname = (); # cname hash lookup my %ns = (); # nameserver hash lookup my $domain_file = "ampr.org"; # file used to get the normal lookups my $domain_suffix = "ampr.org"; # domain suffix to search my $domain_search = join( '\.', split( /\./, $domain_suffix )); my $pass = 1; # pass through the file, looking for stuff my $hostcount = 0; # number of hosts in our ip address range my $changes; # number of changes in passes 2..n my $run_time = `date +\"%y%m%d\"`; my $serial = ""; # used to capture serial of ampr.org my $soa = ""; # used to capture SOA info of ampr.org my $debug = 0; # to get extra messages # treatment of the reverse domain # can return unmodified IN PTR entries or alternatively use a given # reverse domain # # 0 : disable # 44.133.228.5 IN PTR phoenix.ea4els.ampr.org. # 44.133.228.2 IN PTR unicorn.ea4els.ampr.org. # 1 : # $ORIGIN 44.in-addr.arpa. # 5.228.133 IN PTR phoenix.ea4els.ampr.org # 2.228.133 IN PTR unicorn.ea4els.ampr.org # 2 : # $ORIGIN 133.44.in-addr.arpa. # 5.228 IN PTR phoenix.ea4els.ampr.org. # 2.228 IN PTR unicorn.ea4els.ampr.org. # 3 : # $ORIGIN 228.133.44.in-addr.arpa. # 5 IN PTR phoenix.ea4els.ampr.org. # 2 IN PTR unicorn.ea4els.ampr.org. # my $partial_reverse = 3; my $ip_network = "44.133.228.0"; my $ip_netmask = "255.255.255.192"; my $sub_domain_name = "madrid-2m"; print "Using "; if ( $#ARGV >= 1 ) { $ip_network = $ARGV[0]; $ip_netmask = $ARGV[1]; } $sub_domain_name = $ARGV[2] if $#ARGV >= 2; $partial_reverse = $ARGV[3] if $#ARGV >= 3; my @ip_network = split( /\./, $ip_network ); my @ip_netmask = split( /\./, $ip_netmask ); my $outfile = $domain_file . "." . $sub_domain_name; my $revfile = $domain_file . "." . $sub_domain_name . ".rev"; open(INFILE, $domain_file ) || die "Cannot open file $domain_file"; open(OUTFILE, ">$outfile" ) || die "Cannot create output file"; open(REVFILE, ">$revfile" ) || die "Cannot create reverse file"; if ( $debug ) { print "; $revision\n"; print "; Written by : $author\n"; print "; Comments to : $email\n"; print ";\n"; # should fix $run_time to NOT have trailing nl print "; extract of sub-zone `$sub_domain_name' $ip_network/$ip_netmask from $domain_file at $run_time"; } print OUTFILE "; $revision\n"; print OUTFILE "; Written by : $author\n"; print OUTFILE "; Comments to : $email\n"; print OUTFILE ";\n"; print OUTFILE "; extract of sub-zone `$sub_domain_name' $ip_network/$ip_netmask from $domain_file at $run_time"; print OUTFILE "; contains IN A \n"; print OUTFILE "; IN CNAME and\n"; print OUTFILE "; IN MX entries\n"; print OUTFILE ";\n"; print REVFILE "; $revision\n"; print REVFILE "; Written by : $author\n"; print REVFILE "; Comments to : $email\n"; print REVFILE ";\n"; print REVFILE "; extract of sub-zone `$sub_domain_name' $ip_network/$ip_netmask from $domain_file at $run_time"; print REVFILE "; contains IN PTR entries\n"; print REVFILE ";\n"; if ( $debug ) { print "; pass $pass: extracting IN A / IN PTR entries from $domain_file.\n"; print "\n"; print "; forward entries ---> $outfile\n"; print "; reverse entries ---> $revfile\n"; print "\n"; print "; (not showing normal matching entries)\n"; print ";\n"; } if ( $partial_reverse ) { my ( $tmp, $first, $rest, $net ); $net = $ip_network; my $parts = "in-addr.arpa."; foreach $tmp ( 1 .. $partial_reverse ) { ( $first, $rest ) = $net =~ /^(\d+)\.(.+)$/; $parts = $first . "." . $parts; $net = $rest; } print REVFILE "\$ORIGIN $parts\n"; print "\$ORIGIN $parts\n" if $debug; } # # first pass of file : look for IN A $ip_prefix.x.x # and add to %hostname # # this stops the perl compiler complaining about the lines below. while ( ) { chomp; # remove trailing newline s/^\s+//; # remove leading white space s/\s+$//; # removing trailing white space s/^;//; # remove lines starting with comments next if /^$/; # skip if blank line ######################### "IN A" DECLARATIONS GO HERE ##################### # # IN A declarations are of the form: # IN A # # may be fully qualified domain names, though being in our domain, # it shouldn't need to be. # "should be in" x.x.x.x (this can now be any value) # # note we use the IN A declarations to setup the IN PTR definitions for the # reverse file. See below if ( /^(\S+)\s+IN\s+A\s+(\d+)\.(\d+)\.(\d+)\.(\d+)$/i ) { $hostname = $1; @ip_address = ( $2, $3, $4, $5 ); # now establish if the ip address is in our network next if ! ip_same_network( @ip_address ); $ipaddress="$ip_address[0].$ip_address[1].$ip_address[2].$ip_address[3]"; # hostnames should normally be defined locally to the domain # $domain_suffix, so check this is so and warn if it is not. # # check for fully qualified domain name (shouldn't have?) $strange = 0; if ( &is_fqdn( $hostname ) ) { $strange = 1; if ( &is_inthisdomain( $hostname, $domain_search) ) { # print "; WARNING '$hostname' is fully qualified, should be local\n"; print OUTFILE "; WARNING '$hostname' is fully qualified, should be local\n"; } else { # print "; WARNING '$hostname' is in another domain\n"; print OUTFILE "; WARNING '$hostname' is in another domain\n"; } } if ( $strange ) { # print "$hostname\tIN\tA\t$ipaddress\n"; } print OUTFILE "$hostname\tIN\tA\t$ipaddress\n"; $hostcount++; # update the number of entries found $hostname{ $hostname } = 1; # add hostname to hash ######################### "IN PTR" DECLARATIONS GO HERE ################## # # IN PTR declarations are of the form: # IN PTR # # these are not extracted from ucsd.edu's ampr.org.rev file because # I'd have to do more reading of the domain. # # I now accept duplicate ip address --> hostname entries # # convert the ip address if necessary $partial = &convert_ip( $partial_reverse, $ipaddress ); if ( defined( $ipaddress{ $ipaddress } ) ) { # leave a warning in the the file that this is a duplicate # print "$partial\tIN PTR\t$hostname.$domain_suffix. ; Duplicate IP shared with $ipaddress{ $ipaddress }.$domain_suffix.)\n"; print REVFILE "$partial\tIN PTR\t$hostname.$domain_suffix. ; Duplicate IP shared with $ipaddress{ $ipaddress }.$domain_suffix.\n"; } else { $ipaddress{ $ipaddress } = $hostname; # print "$partial\tIN PTR\t$hostname.$domain_suffix.\n"; print REVFILE "$partial\tIN PTR\t$hostname.$domain_suffix.\n"; } } ######################### "IN SOA" DECLARATIONS GO HERE ################### # # IN SOA declarations should be added better than they are at the # moment. currently I'm using them as a reminder of the timestamp of the # file taken from ucsd.edu # # NEVERTHELESS output the information to the files if ( /IN\ +\SOA/ ) { $soa = $_; } if ( /; Serial/ ) { $serial = $_; } if ( !( $soa eq "" ) && !( $serial eq "" ) ) { # print "; based on SOA record:\n"; # print "; $soa\n"; # print "; $serial\n"; # print ";\n"; print OUTFILE "; based on SOA record:\n"; print OUTFILE "; $soa\n"; print OUTFILE "; $serial\n"; print OUTFILE ";\n"; print OUTFILE "\$origin ampr.org.\n"; print OUTFILE ";\n"; print REVFILE "; based on SOA record:\n"; print REVFILE "; $soa\n"; print REVFILE "; $serial\n"; print REVFILE ";\n"; $soa = ""; $serial = ""; } } print "; --- end of pass $pass [found $hostcount host(s)] ---\n" if $debug==1; print OUTFILE "; --- found $hostcount host(s) ---\n"; print REVFILE "; --- found $hostcount host(s) ---\n"; # # start again, now looking for hostnames within MX and CNAME lines # note: this has to be repeated until no new entries are found # do { $changes = 0; $pass++; open(INFILE, "ampr.org") || die "Cannot open the ampr.org file"; while ( ) { chomp; # remove trailing newline ######################### CNAME DECLARATIONS GO HERE ###################### # # CNAME declarations are of the form: # IN CNAME # # both host and host2 may be fully qualified domain names, though # host, being in our domain, shouldn't need to be. # also host2 should be a valid hostname, and not point to another # cname, warn if this is the case if ( /^[a-z0-9._-]+\tIN\tCNAME\t[a-z0-9._-]+$/i ) { @field = split("\t", $_ ); $add = 0; # do we add this entry to list? $strange = 0; # do we have a strange entry - if so warn # simplest case, breakout if we've done this one (on a previous pass) next if defined( $cname{ $field[0] } ) && ( $cname{ $field[0] } eq $field[3] ); # check the right hand side term is ok and has a %hostname entry # complain about fully qualified local names # and complain about a rhs which is itself a CNAME entry if ( $hostname{ $field[3] } ) { $add = 1; if ( &is_fqdn( $field[3] ) ) { $strange = 1; if ( &is_inthisdomain( $field[3], $domain_search) ) { # print "; WARNING '$field[3]' is fully qualified, should be local\n"; print OUTFILE "; WARNING '$field[3]' is fully qualified, should be local\n"; } else { # shouldn't get here I think? # print "; WARNING '$field[3]' is outside this domain\n"; print OUTFILE "; WARNING '$field[3]' is outside this domain\n"; } } } # check the left hand-side # if we find a $hostname entry this is an error if ( $hostname{ $field[0] } ) { $add = 1; if ( &is_fqdn( $field[0] ) ) { $strange = 1; if ( &is_inthisdomain( $field[0], $domain_search) ) { # print "; WARNING '$field[0]' is fully qualified, should be local\n"; print OUTFILE "; WARNING '$field[0]' is fully qualified, should be local\n"; } else { # shouldn't get here I think? # print "; WARNING '$field[0]' is outside this domain\n"; print OUTFILE "; WARNING '$field[0]' is outside this domain\n"; } } } # if we find another alias, this too is an error, but accept it if ( defined( $cname{ $field[0] } ) ) { if ( $cname{ $field[0] } != $field[3] ) { $strange = 1; # print "; WARNING previous definition '$field[0]\tIN\tCNAME\t$field[3]\n"; print OUTFILE "; WARNING previous definition '$field[0]\tIN\tCNAME\t$field[3]\n"; $add = 1; } } # this is bad, but accept it anyway if ( defined( $cname{ $field[3] } ) ) { if ( $cname{ $field[3] } ) { $add = 1; $strange = 1; # print "; WARNING should read '$field[0]\t\IN\tCNAME\t$cname{ $field[3] }'\n"; print OUTFILE "; WARNING should read '$field[0]\t\IN\tCNAME\t$cname{ $field[3] }'\n"; } } if ( $add ) { if ( $strange == 1) { # print "$field[0]\tIN\t\CNAME\t$field[3]\n"; } print OUTFILE "$field[0]\tIN\t\CNAME\t$field[3]\n"; $changes++; $cname{ $field[0] } = $field[3]; } } ######################### # MX DECLARATIONS GO HERE ######################### # MX declarations are of the form: # host IN MX nnn host2 # # both host and host2 may be fully qualified domain names, though # host, being in our domain, shouldn't need to be. In either case # complain about any full names in ampr.org # host2 and host can map to any names # in theory "looping" should be avoided, but I'm not going to check # here yet # if ( /^[a-z0-9._-]+\tIN\tMX\t[0-9]+\t[a-z0-9._-]+$/i ) { @field = split("\t", $_ ); $add = 0; # do we add this entry to list? $strange = 0; # do we have strange entries - if so warn # simplest case, breakout if we've done this one (on a previous pass) next if defined( $mx{ $field[0] . ":" . $field[4] } ) && ( $mx{ $field[0] . ":" . $field[4] } eq $field[3] ); # check the right hand side term is ok and has a %hostname entry # complain about fully qualified local names if ( $hostname{ $field[4] } ) { $add = 1; if ( &is_fqdn( $field[4] ) ) { if ( &is_inthisdomain( $field[4], $domain_search) ) { $strange = 1; # print "; WARNING '$field[4]' is fully qualified, should be local\n"; print OUTFILE "; WARNING '$field[4]' is fully qualified, should be local\n"; } } } # check the left hand-side # if we find a $hostname entry this is an error if ( $hostname{ $field[0] } ) { $add = 1; if ( &is_fqdn( $field[0] ) ) { if ( &is_inthisdomain( $field[0], $domain_search) ) { $strange = 1; # print "; WARNING '$field[0]' is fully qualified, should be local\n"; print OUTFILE "; WARNING '$field[0]' is fully qualified, should be local\n"; } else { # don't warn this is ok # print "; WARNING '$field[0]' is outside this domain\n"; # print OUTFILE "; WARNING '$field[0]' is outside this domain\n"; } } } if ( defined( $mx{ $field[0] . ":" . $field[4] } ) ) { if ( $mx{ $field[0] . ":" . $field[4] } != $field[3] ) { $strange = 1; print "$field[0]\tIN\tMX\t$field[3]\t$field[4] ; WARNING previous priority $mx{$field[0].':'.$field[4]}\n"; print OUTFILE "$field[0]\tIN\tMX\t$field[3]\t$field[4] ; WARNING previous priority $mx{$field[0].':'.$field[4]}\n"; $add = 0; } } if ( $add ) { print OUTFILE "$field[0]\tIN\t\MX\t$field[3]\t$field[4]\n"; $changes++; $mx{ $field[0] . ":" . $field[4] } = $field[3]; } } } print "; --- end of pass $pass [$changes addition(s)] ---\n" if $debug==1; print OUTFILE "; --- end of pass $pass [$changes addition(s)] ---\n"; print REVFILE "; --- end of pass $pass [$changes addition(s)] ---\n"; } while ( $changes > 0 ); ################################################################### # is a fully qualified domain name? # check for terminating "." sub is_fqdn { my ( $name ) = @_; my $result = 0; $_ = $name; if ( /.+\.$/ ) { $result = 1; } # print "is_fqdn( $name ) = $result\n"; return $result; } sub is_inthisdomain { my ( $name, $ip_search ) = @_; my $result = 0; $ip_search .= "\.\$"; $_ = $name; if ( /$ip_search/ ) { $result = 1; } # print "is_inthisdomain( $name, $ip_search ) = $result\n"; return $result; } # # convert the given address into the reverse form, according to convert: # # if convert == 0 : no conversion takes place # if convert == 1 : return the last 3 bytes # if convert == 2 : return the last 2 bytes # if convert == 3 : return the last 1 byte # sub convert_ip { my ( $convert, $address ) = @_; my @parts; my $converted; if ( $convert == 0 ) { $converted = $address; } else { # this part converts the ip address into a partial reverse address # 44.133.28.118 ---> 118 (convert == 3 ) # 44.133.28.118 ---> 118.28 (convert == 2 ) # 44.133.28.118 ---> 118.28.133 (convert == 1 ) @parts = split( /\./, $address ); # chop off leading $conver numbers @parts = splice( @parts, $convert, 4 - $convert ); $converted = join( '.', reverse( @parts ) ); } return $converted; } # compare an ip address with the network address and netmask # to decide if we are in the network being searched for sub ip_same_network { use integer; my @tmp_ip = @_; my $same = 1; foreach $i ( 0..$#tmp_ip ) { $ip_network[$i] += 0; $ip_netmask[$i] += 0; $tmp_ip[$i] += 0; $xxx = ( $tmp_ip[$i] & $ip_netmask[$i] ); $same = 0 if $ip_network[$i] != $xxx; } return $same; }