#!/usr/bin/perl
#
##########################################################################
#
# Filename:     djb_update.pl
# Description:  Dynamic DNS-DHCP update scrypt
# Author:       Michael Stella
# Created:      November 10, 1998
# Last Updated: December 05, 2002
# Email:        kazin@thismetalsky.org
# HomePage:     http://www.thismetalsky.org/magic/projects/dhcp_dns.html
#
###########################################################################
#
#  This code is Copyright (c) 1998-2002 by Michael Stella
#
#  NO WARRANTY is given for this program.  If it doesn't
#  work on your system, sorry.  If it eats your hard drive, 
#  again, sorry.  It works fine on mine.  Good luck!
#
#  Note the lack of the GPL statement.  I'm done with that, this is free
#  software.  Just let me know if you're using it, I'm curious.
#
###########################################################################
#
# This script reads a dhcpd.leases file and dynamically updates tinydns with
# hostname and ip information.  
#
# It assumes that your DHCP server recieves hostnames from the
# clients, and that your clients offer their hostnames to the server.
# Some versions of Linux DHCP clients don't do that.  I use ISC's
# DHCPD, found at http://www.isc.org - though others may work just
# fine.
#
# As of version 1.0 you MUST use djbdns.  
#
###########################################################################
#
# 11/23/1998 - fixed the rarp stuff
# 02/23/1999 - added support for multiple subnets
# 02/24/1999 - improved lease file processing
# 04/15/1999 - switched to dynamic DNS update - BIND 8 dynamic capabilities
# 04/18/1999 - improved handling of client hostnames, to catch dumb user
# 				problems improved error handling Thanks to Wayne Roberts
# 				<wroberts1@cx983858-b.orng1.occa.home.com>
# 04/20/1999 - bugfix, error messages now show up correctly.
# 08/24/1999 - switched to using logger for logging to system log files
#              Thanks to Michael Matsumura <michael@limit.org>
# 08/27/1999 - Actually figured out how to update the reverse-lookup zones,
#              Thanks again to Michael Matsumura.
# 02/13/2001 - Ditched BIND, moved to djbdns
# 05/25/2001 - Thanks to Nate Keegan <nate.keegan@cityofprescott.net> - 
# 		       apparently I didn't think to allow configuring the dnscache
# 		       path in the makefile.  That's been fixed.
# 12/05/2002 - Thanks to Ryan VanderBijl <rvbijl-dhcpdns@vanderbijlfamily.com>
#              for noticing that we should make sure that an entry doesn't
#              exist in the static file! 
#              And thanks for writing his own changelog entry to MY code,
#              praising himself.  :)
#
###########################################################################

use strict;
$|=1;

###########################################################################
### Globals - you can change these as needed

# Domain name
my $domain_name        = `hostname -f`;
chomp($domain_name);

# DHCPD lease file
my $lease_file         = "/var/state/dhcp/dhcpd.leases";

## where does tinydns stuff live?
my $tinydnspath        = "/home/system/tinydns";

# tinydns text database files
my $dhcp_dnsfile       = "/home/system/tinydns/dhcp.conf";
my $static_dnsfile     = "/home/system/tinydns/static.conf";

# number of seconds to check the lease file for updates
my $update_freq        = 5;

my $debug = 0;

###########################################################################
### Don't mess with anything below unless you REALLY need to modify the
### code.  And if you do, please let me know, I'm always interested in
### in improving this program.

# Make a pid file
`echo $$ > /var/run/djb_update.pid`;

my $logstr;

# last modified time
my $modtime = 0;

use vars qw (%db %static);

my $version = "1.1.0";

###########################################################################
# Main Loop
while (1) {

  # check the file's last updated time, if it's been changed, update
  # the DNS and save the modified time.  This will ALWAYS run once - on
  # startup, since $modtime starts at zero.

  my @stats = stat ($lease_file);
  if ($stats[9] > $modtime) {

	# clear the old hash
	undef %db;

	printf STDERR "dhcpd.leases changed - updating DNS\n";
	$modtime = $stats[9];

        ## if error reading static dns file, dont do any update
        next unless (&read_static_dns);  

        &read_lease_file;
        &update_dns;
  } 

  # wait till next check time
  sleep $update_freq;

} # end main
###########################################################################


## read in the static file,  return 1 if we couldn't
sub read_static_dns {
    %static = (); ## clear the list
    unless (open(DNSFILE, $static_dnsfile)) {
            print STDERR "Can't open static DNS file: won't generate dns from dhcp\n";
            return 0;
    }
    while(defined($_ = <DNSFILE>)) {
            chop;
            next if /^\s*#/ || /^\s*$/;  # skip comments/blanks
            s/^.(([^:]+):([^:]+)):?.*$/$1/;
            $static{lc $2} = $3;
    }
    close(DNSFILE);
    return 1;
}

### write out the tinydns import file
sub update_dns {
	my ($ip, $hostname);

	unless (open(DNSFILE, ">$dhcp_dnsfile")) {
		print STDERR "Can't open dhcp DNS file\n";
		return;
	}

	while (($hostname,$ip) = each (%db)) {
		# in the future, set this to the end of the lease time, 
		# i.e. do some math on that.
		print DNSFILE "=$hostname.$domain_name:$ip:5\n";
	}
	close DNSFILE;

	`cd $tinydnspath && make > /dev/null`;
}


### reads the lease file & makes a hash of what's in there.
sub read_lease_file {

  unless (open(LEASEFILE,$lease_file)) {
	#`logger -t dns_update.pl error opening dhcpd lease file`;
	print STDERR "Can't open lease file\n";
	return;
  }

  my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(time);
  my $curdate = sprintf "%04d%02d%02d%02d%02d%02d",
  		($year+1900),($mon+1),$mday,$hour,$min,$sec;

  ## Loop here, reading from LEASEFILE
  while (<LEASEFILE>) {
	my ($ip, $hostname, $mac, $enddate,$endtime);

	if (/^\s*lease/i) {
		
	  # find ip address
	  $_ =~ /^\s*lease\s+(\S+)/;
	  $ip = $1;
	  
	  # do the rest of the block - we're interested in hostname,
	  # mac address, and the lease time
	  while ($_ !~ /^}/) {
	    $_ = <LEASEFILE>;
		# find hostname
		if ($_ =~ /^\s*client/i) {
		  #chomp $_;
		  #chop $_;
		  $_ =~ /\"(.*)\"/;
		  $hostname = $1;
		  
		  # change spaces to dash, remove dots - microsoft
		  # really needs to not do this crap
		  $hostname =~ s/\s+/-/g;
		  $hostname =~ s/\.//g;
		}
		# get the lease end date
		elsif ($_ =~ /^\s*ends/i) {
			$_ =~ m/^\s*ends\s+\d\s+([^;]+);/;
			$enddate = $1;
			$enddate =~ s|[/: ]||g;
		}
	  }
	  # lowercase it - stupid dhcp clients
	  $hostname = lc $hostname;  

	  ($debug < 1 ) || print STDERR "$hostname $ip $enddate $curdate\n";
	  
          ## if the hostname is defined in the static list, skip it
          if (exists $static{"$hostname.$domain_name"}) {
              print STDERR "Skipping $hostname.$domain_name dhcp dns " .
                           "entry: static entry with same name exists\n";
              next;
          }

	  # Store hostname/ip in hash - this way we can do easy dupe checking
	  if (($hostname ne "") and ($enddate > $curdate)) {
              $db{$hostname} = $ip;
	  }
	}
  }
  close LEASEFILE;
}

