Chapter 5. TCP/IP Name Services
5.3. Domain Name Service (DNS)
5.3.1. Generating DNS Configuration Files
5.3.1.2. Generating multiple configuration files
Our code attempts to read in the previous DNS configuration file to determine the last serial number in use.
This number then gets split into date and counter fields. If the date we've read is the same as the current date, we need to increment the counter. If not, we create a serial number based on the current date with a counter value of 00. Once we have our serial number, the rest of the code concerns itself with writing out a pretty header in the proper form.
5.3.1.2. Generating multiple configuration files
Now that we've covered the process of writing a correct header for our DNS configuration files, there is one more complication we need to address. A well−configured DNS server has both forward (name−to−IP address) and reverse (IP address−to−name) mapping information available for every domain, or zone, it controls. This requires two configuration files per zone. The best way to keep these synchronized is to create them both at the same time.
This is the last file generation script we'll see in this chapter, so let's put everything we've done so far together.
Our script will take a simple database file and generate the necessary DNS zone configuration files.
To keep this script simple, I've made a few assumptions about the data, the most important of which has to do with the topology of the network and namespace. This script assumes that the network consists of a single class C subnet with a single DNS zone. As a result, we only create a single forward mapping file and its reverse map sibling file. Adding code to handle multiple subnets and zones (i.e., creating separate files for each) would be an easy addition.
Here's a quick walk−through:
1.
Read in the database file into a hash of hashes, checking the data as we go.
2.
Generate a header.
3.
Write out the forward mapping (name−to−IP address) file and check it into RCS.
4.
Write out the reverse mapping (IP address−to−name) file and check it into RCS.
Here is the code and its output:
use Rcs;
$datafile = "./database"; # our host database
The Five−Minute RCS Tutorial (Perl for System Administration)
5.3.1.2. Generating multiple configuration files 175
$outputfile = "zone.$$"; # our temporary output file
$target = "zone.db"; # our target output
$revtarget = "rev.db"; # out target output for the reverse mapping
$defzone = ".oog.org"; # the default zone being created
$recordsep = "−=−\n";
# get today's date in the form of YYYYMMDD
@localtime = localtime;
$today = sprintf("%04d%02d%02d",$localtime[5]+1900, $localtime[4]+1, $localtime[3]);
# get username on either NT/2000 or Unix
$user = ($^O eq "MSWin32")? $ENV{USERNAME} :
(getpwuid($<))[6]." (".(getpwuid($<))[0].")";
# read in the database file
open(DATA,$datafile) or die "Unable to open datafile:$!\n";
while (<DATA>) { unless ($record−>{address}) {
warn "!!!! " . $record−>{name} .
" does not have an IP address, skipping...\n";
next;
}
# check for duplicate address
if (defined $addrs{$record−>{address}}) {
warn "!!!! Duplicate IP addr:" . $record−>{name}.
$entries{$record−>{name}} = $record; # add this to a hash of hashes }
close(DATA);
$header = &GenerateHeader;
# create the forward mapping file open(OUTPUT,"> $outputfile") or
5.3.1.2. Generating multiple configuration files 176
die "Unable to write to $outputfile:$!\n";
print OUTPUT $header;
foreach my $entry (sort byaddress keys %entries) { print OUTPUT
$entries{$entry}−>{name},$entries{$entry}−>{address};
# print any CNAMES (aliases)
if (defined $entries{$entry}−>{aliases}){
foreach my $alias (split(' ',$entries{$entry}−>{aliases})) {
die "Unable to rename $outputfile to $target:$!\n";
$rcsobj−>ci("−u","−m"."Converted by $user on ".scalar(localtime));
# now create the reverse mapping file open(OUTPUT,"> $outputfile") or
die "Unable to write to $outputfile:$!\n";
print OUTPUT $header;
foreach my $entry (sort byaddress keys %entries) { print OUTPUT (split/\./,$entries{$entry}−>{address})[3], $entries{$entry}−>{name};
}
close(OUTPUT);
$rcsobj−>file($revtarget);
$rcsobj−>co('−l'); # assumes target has been checked out at least once rename($outputfile,$revtarget) or
die "Unable to rename $outputfile to $revtarget:$!\n";
$rcsobj−>ci("−u","−m"."Converted by $user on ".scalar(localtime));
sub GenerateHeader{
my($header);
if (open(OLDZONE,$target)){
while (<OLDZONE>) {
The Five−Minute RCS Tutorial (Perl for System Administration)
5.3.1.2. Generating multiple configuration files 177
} else {
$oldserial = "000000";
}
$olddate = substr($oldserial,0,6);
$count = ($olddate == $today) ? substr($oldserial,6,2)+1 : 0;
$serial = sprintf("%6d%02d",$today,$count);
$header .= "; dns zone file − GENERATED BY $0\n";
$header .= "; DO NOT EDIT BY HAND!\n;\n";
$header .= "; Converted by $user on ".scalar(localtime)."\n;\n";
# count the number of entries in each department and then report foreach $entry (keys %entries){
$depts{$entries{$entry}−>{department}}++;
}
foreach $dept (keys %depts) {
$header .= "; number of hosts in the $dept department:
$depts{$dept}.\n";
}
$header .= "; total number of hosts: ".scalar(keys %entries)."\n#\n\n";
$header .= <<"EOH";
@ IN SOA dns.oog.org. hostmaster.oog.org. ( $serial ; serial
@a = split(/\./,$entries{$a}−>{address});
@b = split(/\./,$entries{$b}−>{address});
($a[0]<=>$b[0]) ||
($a[1]<=>$b[1]) ||
($a[2]<=>$b[2]) ||
($a[3]<=>$b[3]);
}
Here's the forward mapping file (zone.db) that gets created:
; dns zone file − GENERATED BY createdns
; DO NOT EDIT BY HAND!
;
; Converted by David N. Blank−Edelman (dnb); on Fri May 29 15:46:46 1998
;
; number of hosts in the design department: 1.
; number of hosts in the software department: 1.
; number of hosts in the IT department: 2.
; total number of hosts: 4
;
@ IN SOA dns.oog.org. hostmaster.oog.org. ( 1998052900 ; serial 10800 ; refresh 3600 ; retry
5.3.1.2. Generating multiple configuration files 178
604800 ; expire 43200) ; TTL
@ IN NS dns.oog.org.
; Owned by Cindy Coltrane (marketing): west/143
bendir IN A 192.168.1.3 ben IN CNAME bendir bendoodles IN CNAME bendir
; Owned by David Davis (software): main/909
shimmer IN A 192.168.1.11 shim IN CNAME shimmer shimmy IN CNAME shimmer shimmydoodles IN CNAME shimmer
; Owned by Ellen Monk (design): main/1116
sulawesi IN A 192.168.1.12 sula IN CNAME sulawesi su−lee IN CNAME sulawesi
; Owned by Alex Rollins (IT): main/1101
sander IN A 192.168.1.55 sandy IN CNAME sander micky IN CNAME sander mickydoo IN CNAME sander
And here's the reverse mapping file (rev.db):
; dns zone file − GENERATED BY createdns
; DO NOT EDIT BY HAND!
;
; Converted by David N. Blank−Edelman (dnb); on Fri May 29 15:46:46 1998
;
; number of hosts in the design department: 1.
; number of hosts in the software department: 1.
; number of hosts in the IT department: 2.
; total number of hosts: 4
;
@ IN SOA dns.oog.org. hostmaster.oog.org. ( 1998052900 ; serial
; Owned by Cindy Coltrane (marketing): west/143 3 IN PTR bendir.oog.org.
; Owned by David Davis (software): main/909 11 IN PTR shimmer.oog.org.
; Owned by Ellen Monk (design): main/1116 12 IN PTR sulawesi.oog.org.
; Owned by Alex Rollins (IT): main/1101 55 IN PTR sander.oog.org.
This method of creating files opens up many more possibilities. Up to now, we've generated files using content from a single text file database. We read a record from the database and we write it out to our file, perhaps with a dash of nice formatting. Only data that appeared in the database found its way into the files we created.
The Five−Minute RCS Tutorial (Perl for System Administration)
5.3.1.2. Generating multiple configuration files 179
Sometimes it is useful to have content added in the conversion process by the script itself. For instance, in the case of DNS configuration files generation, you may wish to embellish the conversion script so it inserts MX (Mail eXchange) records pointing to a central mail server for every host in your database. A trivial code change from:
# print A record
printf OUTPUT "%−20s\tIN A %s\n",
$entries{$entry}−>{name},$entries{$entry}−>{address};
to:
# print A record
printf OUTPUT "%−20s\tIN A %s\n",
$entries{$entry}−>{name},$entries{$entry}−>{address};
# print MX record
print OUTPUT " IN MX 10 $mailserver\n";
will configure DNS so that mail destined for any host in the domain is received by the machine
$mailserver instead. If that machine is configured to handle mail for its domain, we've activated a really useful infrastructure component (i.e., centralized mail handling) with just a single line of Perl code.