#!/usr/bin/perl # # resrot v1.1 - an automated /etc/resolv.conf rotator # (c) 1997, 1998 James W. Abendschan # # 28 Dec 1997 - initial code # 16 May 1998 - added syslog error notification & -resort option & little bug fixes # 2 Jun 1998 - added -exclude option to avoid particular nameserver # # resrot is a perl script that will try to keep your /etc/resolv.conf # file fresh and free of bad or bogus name servers, and will automatically # update resolv.conf with any new nameservers added. resrot first # sends an NS query for your domain to find a list of name servers # available; it then pokes each of these name servers to make sure # they're responding properly, and then it rewrites your /etc/resolv.conf # file to reflect the changes, all without mangling any important or # site-specific information in /etc/resolv.conf. It even optionally # "randomizes" the order in which the nameservers appear in your resolv.conf # file to better distribute your DNS queries among multiple nameservers. # # you can also re-sort your existing resolv.conf instead of soliciting # nameservers via a NS query. # # in the event no valid nameservers can be found, no action is taken. # (this is probably the right thing to do.) # # you should run it out of cron every so often (5-30 minutes) # # usage: resrot [-debug] [-random] [-resort] # # todo- sort on response time # use Sys::Syslog; # modify these to suit your setup $domain="jammed.com"; # this is all you'll probably want to change $nstimeout = 120; # unless your servers are reaaaally loaded $resolv_conf = "/etc/resolv.conf"; # or you have a strange flavour UNIX # nslookup needs to be in the PATH. This is a pretty complete path, # so unless you're on a braindead system (AIX?) it shouldn't be a problem. $ENV{PATH}="/bin:/usr/bin:/usr/ucb:/etc:/usr/etc:/sbin:/usr/sbin:/usr/local/bin:/usr/share/bin:/usr/share/etc:/usr/share/sbin:/usr/local/etc:/usr/local/sbin"; # parse our args while ($arg = shift @ARGV) { push (@exclude, shift @ARGV) if ($arg eq "-exclude"); $resort = 1 if ($arg eq "-resort"); $debug = 1 if ($arg eq "-debug"); $random = 1 if ($arg eq "-random"); if ($arg =~ /\?|h/) { print "usage: $0 [-debug] [-random] [-resort]\n"; exit 1; } } srand($$); # do the deed if ($resort) { @servers = enumerate_servers(); } else { @servers = find_servers($domain); } @valid_servers = test_servers(@servers); rewrite_resolv_conf(@valid_servers); # that wasn't too complicated, was it? exit 0; # # read in resolv.conf & return a list of name servers sub enumerate_servers { my (@servers, $line, $ns); open (RESOLV, $resolv_conf) || error("can't read $resolv_conf: $!"); while ($line = ) { if ($line =~ /^nameserver /) { $ns = (split(" ", $line))[1]; print "Adding $ns to enumerated servers\n" if ($debug); push(@servers, $ns); } } close (RESOLV); return @servers; } sub rewrite_resolv_conf { my (@servers) = @_; my ($line, @lines, $stamp); $stamp = localtime(time); open (RESOLV, $resolv_conf) || error("can't read $resolv_conf: $!"); while ($line = ) { print "Read: $line" if ($debug); if (($line !~ /^nameserver /) && ($line !~ /^##-resrot /)) { push (@lines, $line); print "Pushed $line" if ($debug); } } close (RESOLV); if ($debug) { print "I will put the following into $resolv_conf:\n"; open(RESOLV, "| cat"); # cheap dup :) } else { open (RESOLV, ">$resolv_conf") || error("can't write to $resolv_conf: $!"); } while ($line = shift @lines) { print RESOLV $line; } print RESOLV "##-resrot generated automatically by $0 $stamp\n"; while ($server = shift @servers) { print RESOLV "nameserver $server\n"; } print RESOLV "##-resrot end\n"; close (RESOLV); } sub test_servers { my (@servers) = @_; my (@nsdata, @good_servers, $server); my (@excludes) = @exclude; my ($exre); $exre = shift @excludes; while ($ex = shift @excludes) { $exre .= "|$ex"; } $SIG{ALRM} = sub { die "timeout" }; TEST_SERVER: while ($server = shift @servers) { if (defined($exre)) { next TEST_SERVER if ($server =~ /$exre/i); } alarm($nstimeout); eval { open (NSL, "nslookup -type=any $domain $server 2>&1 |") || error("test_servers() can't start nslookup: $!"); @nsdata = ; close (NSL); }; close(NSL); alarm (0); $nsdata = join(" ", @nsdata); #print "** $server\n$nsdata\n" if ($debug); if (($@ =~ /timeout/) || ($nsdata =~ /no response from server/i)) { logger("warning: nameserver $server not responding. removing from resolv.conf"); } else { push (@good_servers, $server); } } # if there are no reachable nameservers, just quit. # we're probably unplugged from the net or someone made a big goof # we should syslog a message though.. $count = @good_servers; if ($count == 0) { print "No good servers found?\n" if ($debug); logger("no reachable nameservers available!"); exit -1; } @good_servers = shuffle(@good_servers) if ($random); return @good_servers; } sub find_servers { my ($line, @foo, @list, @good_servers, @query_servers, $server); $SIG{ALRM} = sub { die "timeout" }; @query_servers = enumerate_servers(); alarm($nstimeout); eval { while ($server = shift @query_servers) { open (NSL, "nslookup -type=ns $domain $server 2>&1 |") || error("find_servers() can't start nslookup: $!"); while ($line = ) { push(@foo, split(" ", $line)); } close(NSL); } }; close (NSL); alarm(0); if ($@ =~ /timeout/) { # oops, just bail. print "find_servers timeout out\n" if ($debug); logger("find_servers() timed out-- no reachable default NS?"); exit 1; } while ($line = shift @foo) { if ($line =~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) { push(@list, $line); } } @good_servers = uniq(@list); $server_count = @good_servers; error("FATAL: no valid nameservers found! please update /etc/resolv.conf") if ($server_count == 0); return @good_servers; } # # uniq a list # sub uniq { my (@list) = @_; my ($data, $olddata, @results, @tmplist, $tmpdat); UNIQ_LIST: while ($data = shift @list) { @tmplist = @results; while ($tmpdat = shift @tmplist) { next UNIQ_LIST if ($data eq $tmpdat); } push (@results, $data); } return @results; } # # shuffle a list # sub shuffle { my (@list) = @_; my ($data, @shuffled); while (@list) { $data = splice(@list, rand @list, 1); push(@shuffled, $data); } return @shuffled; } sub logger { my ($what) = shift @_; openlog("resrot", 'pid', 'user'); $what = "nothing?" if ($what eq ""); syslog('err|daemon', $what); closelog(); } sub error { my ($why) = shift @_; logger($why); print STDERR $why if ($debug); exit 1; }