#!/usr/local/bin/perl -i
($IDENT = '@(#)zsu: update DNS zone serial number') =~ s/^[^:]*: *//;
#
# edit zone file in-place
# only update files where the SOA indicates we are authoritative
# only update serial numbers in format (yy)yymmddn(n)
# otherwise assume a deliberate forced zone refresh and preserve serial
# handles timewarp (old datestamp is in future) by using future date
#
# Returns: 0 if success, 1 if failure
#
# This software is covered by the GPL, version 2 or later.
#

($BCMD = $0) =~ s/.*\///;
($REVISION) = ('$Revision: 1.11 $' =~ /[^\d\.]*([\d\.]*)/);
$HELPSTRING = "For help, type: $BCMD -h";

$USAGE = "Usage: $BCMD [-dfLv] zone";

# parse command line arguments
#-----------------------------
require('getopts.pl');
if (! &Getopts('dfhLv')) {
    print STDERR "$USAGE\n$HELPSTRING\n";
    exit 2;
}
if ($opt_h) {
    print <<EOT;
$BCMD $REVISION: $IDENT
$USAGE
 -d			print extra debugging information
 -f			force update, even if this is not the host in the SOA
 -L			display software license
 -v			turn on verbose mode (currently same as -d)
 zone			DNS zone file to update
EOT
	exit 0;
} elsif ($opt_L) {
    print <<EOT;
    Copyright 1994-6 Andras Salamon <andras\@dns.net>
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    If you do not already have a copy of the GNU General Public License,
    you can obtain a copy by anonymous ftp from prep.ai.mit.edu
    (file COPYING in directory /pub/gnu) or write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
EOT
	exit 0;
}
$VERBOSE = $opt_v;
$DEBUG = $opt_d || $VERBOSE;
if (@ARGV < 1) {
    print STDERR "$USAGE\n$HELPSTRING\n";
    exit 2;
}


# Set up fully qualified host name
#---------------------------------
# zone files must have originating host fully qualified, since information
# from outside zone may be necessary to understand non-FQ domain names
if (! $opt_f) {
    ($myhost = `hostname` || `uname -n`) =~ s/\s*$//;
    if ($myhost !~ /\./) {
	if (! ($mydom = `domainname`)) {
	    warn "cannot get FQDN of host" if $DEBUG;
	} else {
	    if ($mydom !~ /^\./) {
		$mydom = ".$mydom";
	    }
	    $myhost .= $mydom;
	}
    }
    if ($myhost !~ /\.$/) {
	$myhost .= '.';
    }
}

# generate a sensible serial number
#----------------------------------
sub generate_new {
    local($oldserial) = (@_);

    $newserial = $oldserial;
    if ((length($oldserial) < 7) || (length($oldserial) > 10)) {
	warn "serial $oldserial appears to be nonstandard, leaving as is";
	$exitcode ++;
    } else {
	($c, $d, $oldcount) = ($oldserial =~ /^([12][09])?([0-9][0-9][01][0-9][0-3][0-9])(..?)/);
	$olddate = $c.$d;
	if ($d !~ /^[0-9][0-9][01][0-9][0-3][0-9]/) {
	    warn "serial $oldserial does not match heuristics, leaving as is";
	    $exitcode ++;
	} else {
	    ($mday,$mon,$year) = (localtime(time))[3..5];
	    $mon ++; $year %= 100 if $year > 100;
	    $newdate = sprintf("%02d%02d%02d", $year, $mon, $mday);
	    $newcount = $oldcount + 1;
	    if (($newcount >= 100) && ($newdate <= $olddate)) {
		warn "cannot increment $oldserial, leaving as is";
		$exitcode ++;
	    } else {
		if ($newdate > $olddate) {
		    $newcount = 0;
		}
		$newserial = $newdate . sprintf("%02d", $newcount);
		if ($newdate < $olddate) {
		    # actually need 2^31 hack here
		    warn "serial $oldserial is in future" if $DEBUG;
		    $newserial = $olddate . sprintf("%02d", $newcount);
		}
	    }
	}
    }
    $newserial;
}


# now parse zone file
#--------------------
# state table:	0 looking for SOA
#		1 found SOA, looking for serial
#		2 found serial, looking for next file
$state = 0;
while (<>) {
    if ($state == 0) {
	if (/^([^;]*\bSOA\s+)([^\s;]+)(\s+[^\s;]+\s+\(\s*)([^\s;]*)(.*)/i) {
	    if ($opt_f || ($2 eq $myhost)) {
		$ours = 1;
	    } else {
		$ours = 0;
		$state = 2;
	    }
	    if ($ours) {
		if ("$4" ne '') {
		    $oldserial = $4;
		    if ($oldserial !~ /^[\d.]+$/) {
			warn "cannot parse serial number, skipping" if $DEBUG;
			$ours = 0; $exitcode ++;
		    } else {
			$_ = "$1$2$3". &generate_new($oldserial) ."$5\n";
		    }
		    $state = 2;
		} else {
		    $state = 1;
		}
	    }
	}
    } elsif ($state == 1) {
	if (/^(\s*)([0-9.]+)(.*)/) {
	    $_ = "$1". &generate_new($2) ."$3\n";
	    $state = 2;
	}
    } elsif ($state == 2) {
	$state = 0 if eof;
    }
} continue {
    print;
}

if ($state == 2) {
    warn "internal error: did not detect end of last file" if $DEBUG;
    $exitcode ++;
} elsif ($state == 1) {
    warn "could not locate serial number, skipping" if $DEBUG;
    $exitcode ++;
}

exit $exitcode;

# $Log: zsu,v $
# Revision 1.11  1996/04/02  10:20:43  andras
# updated contact info
#
# Revision 1.10  1995/12/28  11:52:06  andras
# perl5: quoted @'s
#
# Revision 1.9  1995/11/17  13:50:05  andras
# added force option
#
# Revision 1.8  1995/11/17  12:49:06  andras
# now handles yyyymmddn(n) formats also
#
# Revision 1.7  1995/10/27  11:19:56  andras
# fixed multifile handling and $ours detection
#
# Revision 1.6  1995/10/27  10:45:06  andras
# fixed state table; now goes back to 0 at eof
# used to ignore second and subsequent files
#
# Revision 1.5  1995/05/15  16:16:23  andras
# fixed hstname typo
#
# Revision 1.4  1995/05/09  00:38:34  andras
# added in command line processing
#
# Revision 1.3  1995/05/08  20:45:59  andras
# now supposed to understand a fairly general zone format
# tested OK on standard format primary
# tested OK on easy secondary
# tested OK on secondary with serial in future
#
# Revision 1.2  1995/04/26  07:52:57  andras
# cleaner logic
#
# Revision 1.1  1994/10/25  10:13:47  andras
# Initial revision
#
