#!/bin/sh

# "$Revision: 1.5 $"

# Synopsis:
# -v		be verbose
# -t		test; generate new files but do not install them
# -d domain	choose a NIS domain other than the current NIS domain
# -i file	use the contents of 'file' instead of `ypcat hosts`
#			/etc/hosts is the most likely file.
# -n nets	blank separated, quoted list of network numbers to generate
#		    .rev files, even if no host of the target domain is on
#		    them.  For example, -n "192.26.81 192.99.44"
# -N nets	blank separated, quoted list of networks to exclude
#		    from .rev files
# -m first-last,mask	first and last IP addresses of subnet with mask
# -h hosts	blank separated, quoted list of hosts on which
#		    'rsh host -l guest ypcat hosts' will produce  interesing
#		    hostnames.
# -H name	hostname of this machine, if not available by the
#		    hostname command.
# -A ouraddr	IP address of this machine, if not available by looking
#		    up the hostname of this machine in /etc/hosts.
# -s prf	MX preference for ourself
# -x 'prf xchg'	a pair of MX exchanger and preference, as in
#			-x '10 foo.foo.com.'
#		    additional pairs can be given with additional occurances
#		    of -x
# -O dir	save previous versions of data files in this directory


# This script generates data files for the BIND DNS nameserver, named, from
# a file (e.g. /etc/hosts) or a NIS hostname database.  Because it can use
# NIS to obtain the data, the DNS master need not run on the same machine
# as the NIS master.

# This script is usually run from cron if it is generating the DNS database
# files from /etc/hosts.  If cooperating with NIS, it is usually run
# from /var/yp/local.make.script.  A sample local.make.script can
# be found in /var/named/mkdns.

# It uses two prototype SOA files, domain.soa (where "domain" is the NIS
# domain) and hosts.soa, to generate SOA records.  It modifies the words
# SERIALNUMBER, HOSTNAME, and HOSTADDR in the prototypes, so that the
# prototypes can serve for more than one domain.  Sample SOA prototype
# files are also in the examples directory.

# Besides generating the hosts data file and the reverse map data file, it
# also modifies the named.boot file to point to the other files.  These
# modifications of the named.boot file are delimited by a pair of lines,
# allowing this script to modify only the part of named.boot that it owns.

# "Foreign" DNS hosts that happen to be in the the NIS or /etc/hosts input
# can be excluded from the results, even if the foreigners have aliases in
# the target domain.  This is the purpose of the -N arg.

# Much of the complexity of this script comes from generating CNAME
# records and the reverse maps.  Both are done with awk scripts.  The
# difficulty with the CNAME records is recognizing only those aliases that
# should appear in the DNS database.  The difficulty with the reverse maps
# is generating separate files for each of the IN-ADDR.ARPA domains implied
# by the set of host numbers present in the input data.

# When new versions of the files have been generated, this script
# installs them only if they differ substantitively.  If it does install
# new files, it signals the nameserver daemon.



USAGE="`basename $0`: [-vt] [-d domain] [-i file] [-n nets] [-N nets] [-h hosts] [-H ourname] [-A ouraddr] [-s prf] [-x 'prf xchg'] "

if [ -x /usr/bin/domainname ]
then
	domain=`domainname`
fi

name=`hostname | sed -e "s/^[^.]*$/&.$domain/"`
olddir=old

while getopts "vtd:i:n:N:m:h:H:A:s:x:O:" c; do
    case $c in
    v) set -x;;
    t) dotest="y";;
    d) domain=`echo "$OPTARG" | tr '[A-Z]' [a-z]'`;;
    i) ifile="$OPTARG"
	if test ! -f $ifile; then
	    echo "`basename $0`: $OPTARG: no such file"
	    exit 1
	fi
	;;
    n) nets="$nets "`echo "$OPTARG" | sed -e 's@[^ ]*@/^&./bprint@g'`;;
    N) nets="$nets "`echo "$OPTARG" | sed -e 's@[^ ]*@/^&./d@g'`;;
    m) if test -z "$masks"; then
	    masks="$OPTARG"
	else
	    masks="$masks $OPTARG"
	fi
	;;
    h) hosts="$hosts $OPTARG";;
    H) name="$OPTARG";;
    A) addr="$OPTARG";;
    s) mypref="$OPTARG";;
    x) mxs="$mxs\t\t\t MX  `echo $OPTARG | sed 's/  */ \\\t/'`\n";;
    O) olddir="$OPTARG"
	if test ! -d "$olddir"; then
	    echo "`basename $0`: $OPTARG: no such directory"
	    exit 1
	fi
	;;
    \?) echo $USAGE; exit 1;;
    esac
done

if [ -z "$domain" ]
then
	echo "please used -d option to specify domain name"
	exit
fi

shift `expr $OPTIND - 1`
if test "$#" != 0; then
    echo $USAGE
    exit 1
fi

if test -n "$dotest"; then
    tmpfile="/tmp/mkdns.tmp"
    tmp2file="/tmp/gen2n"
    tmp3file="/tmp/gen3n"
    tmpsoa="/tmp/gensoa"
    tmpsed="/tmp/gensed"
    testdir=/tmp/
else
    tmpfile="/tmp/mkdns$$"
    tmp2file="/tmp/gen2n$$"
    tmp3file="/tmp/gen3n$$"
    tmpsoa="/tmp/gensoa$$"
    tmpsed="/tmp/gensed$$"
    testdir=""
    rem="$tmpfile $tmp2file $tmp3file $tmpsoa $tmpsed"
    trap "/bin/rm -f $rem" 0 1 2 15
fi

cd /var/named

# chose short version of domain name
subdomain=`expr "$domain" : '\([^.]*\)\..*'`

# literalized version
litdomain=`echo ".$domain" | sed 's/\./\\\./g'`

# find our network address, the addresss of the server
if test "$addr" = ""; then
    addr=`egrep "^[0-9.]*[ 	][ 	]*$name[. 	]|^[0-9.]*[ 	][ 	]*[ 	]$name'$'" /etc/hosts \
		| sed -e '2,$d' -e "s/[ 	].*//"`
fi
new="new"
hostfile="$subdomain.hosts"
revfile="$subdomain.%s.rev"
nboot="named.boot"
nnboot="$testdir$nboot.$new"
soa="$subdomain.soa"

if test -f $testdir${subdomain}*.$new $nnboot $rem; then
    # remove previous crop of new files
    rm -f $testdir${subdomain}*.$new $nnboot $rem
fi


# generate a sed script to find network numbers and hostnames for the
# interesting networks.
echo "s/ \{2,\}/ /g" > $tmpsed
echo "/^[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\} [^ ]/!d" >> $tmpsed
echo "s/^\([^.]*\).\([^.]*\).\([^.]*\).\([^ ]*\) \([^ ]*\).*/\1 \2 \3 \4 \5/" >> $tmpsed
echo "$nets" | tr ' ' '\12' | sed -e '/^$/d' -e 's/\./ /g'  >> $tmpsed


if test ! -s "$soa"; then
    echo "`basename $0`: $soa is missing"
    exit 1
fi
serial=`date '+%y%j%H%M'`
sed -e "s/SERIALNUMBER/$serial/g" \
	-e "s/HOSTNAME/$name/g" -e "s/HOSTADDR/$addr/g" $soa > $tmpsoa

if test ! -s "$nboot"; then
    echo "`basename $0`: $nboot is missing"
    exit 1
fi
fline="; do not delete this 1st line--generated by mkdns"
lline="; do not delete this 2nd line--generated by mkdns"
sed -e "/^$fline/,"'$d' $nboot > $nnboot
echo "$fline" >> $nnboot

# Generate the SOA records.
cp $tmpsoa $testdir$hostfile.$new
if test -s ${subdomain}.hosts.soa; then
    sed -e "s/SERIALNUMBER/$serial/g" \
	    -e "s/HOSTNAME/$name/g" -e "s/HOSTADDR/$addr/g" \
		${subdomain}.hosts.soa >> $testdir$hostfile.$new
fi


# Get the data from NIS

if test $ifile; then
    cat $ifile | tr '[A-Z]	' '[a-z] ' > $tmpfile
else
    #it seems to take more than one try to get a server
    ypcat -d $domain hosts.byaddr 2>/dev/null \
		| tr '[A-Z]	' '[a-z] ' >$tmpfile
    # it sometimes takes a while to find a server
    if test ! -s "$tmpfile"; then
	sleep 5
	ypcat -d $domain hosts.byaddr | tr '[A-Z]	' '[a-z] ' >$tmpfile
	if test ! -s "$tmpfile"; then
	    echo "`basename $0`: unable to obtain NIS host information"
	    exit 1
	fi
    fi
fi

cp $tmpfile $tmp3file
for nm in $hosts; do
    rsh $nm -n -l guest ypcat hosts > $tmp2file
    if test ! -s $tmp2file; then
	echo "`basename $0`: no hosts from $nm"
	exit 1
    fi
    cat $tmp2file | tr '[A-Z]	' '[a-z] ' >> $tmp3file
done


# Delete non-host lines, add a trailing blank, and remove extra blanks.
#	Delete hosts at least one of whose names does not include the
#	    target domain.
#	Delete aliases with the wrong domain name.
#	Remove the domain from all names.
#	Remove domain-names of the wrong domain.
#	Then replace the periods in the address with blanks.
sed -e "/^[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\} /!d" \
	    -e "s/.*/& /" -e "s/ \{2,\}/ /g" \
	    -e "/ [^.]*$litdomain /!d" \
	    -e "s/$litdomain / /g" \
	    -e "s/ [^ ]*\.[^ ]*//g" \
	    -e "/^[0-9.]*  *$/d" \
	    -e "s/^\([^.]*\).\([^.]*\).\([^.]*\)./\1 \2 \3 /" \
	    -e "s/ \{2,\}/ /g" \
	$tmp3file \
    | sort -u -n +1n -2 +2n -3 +3n -4 \
    | nawk 'BEGIN {
	    new = "'"$new"'"
	    hostfile = "'"$hostfile"'"
	    newhostfile = "'"$testdir$hostfile.$new"'"
	    domain = "'"$domain"'"
	    newnboot = "'"$nnboot"'"
	    tmpsed = "'"$tmpsed"'"
	    mypref = "'"$mypref"'"
	    mxs = "'"$mxs"'"

	    printf "primary   %-30s %s\n",
		    domain, hostfile >> newnboot
	}

	{
	    host = $5

	    printf "%-23s IN   A      %s\n", host,
		    ($1 "." $2 "." $3 "." $4) >> newhostfile
	    if (mypref != "") {
		printf "\t\t\t MX  %-2d \t%s\n",
			mypref, host >> newhostfile
	    }
	    printf mxs >> newhostfile
	    for (i = 6; i <= NF; i++) {
		for (j = 5; j < i; j++) {
		    if ($i == $j)
			break
		}
		if (j >= i) {
		    printf "%-23s  IN  CNAME\t%s\n", $i,
				host >> newhostfile
		}
	    }

	    newpat = "/^" $1 " "
	    if ($1 >= 128) {
		newpat = newpat $2 " "
		if ($1 >= 192) {
		    newpat = newpat $3 " "
		}
	    }
	    if (newpat != pat) {
		pat = newpat
		print (pat "/bprint") >> tmpsed
	    }
	}'


echo "d\n:print" >> $tmpsed
sed -f $tmpsed $tmp3file \
    | sort -u -n +1n -2 +2n -3 +3n -4 \
    | nawk 'BEGIN {
	    OFMT = "%10.0f"

	    new = "'"$new"'"
	    testdir = "'"$testdir"'"
	    revfile = "'"$revfile"'"
	    domain = "'"$domain"'"
	    newnboot = "'"$nnboot"'"
	    tmpsoa = "'"$tmpsoa"'"
	    net = -1
	    maskz = 256*256*256*256
	    maska = 256*256*256
	    maskb = 256*256
	    maskc = 256

	    split("'"$masks"'", t, " ")
	    for (i in t) {
		split(t[i], m, ",")
		if (m[1] ~ /\./) {
		    split(m[1], a, ".")
		    m[1] = ((a[1] * 256 + a[2]) * 256 + a[3]) * 256 + a[4]
		}
		if (m[2] ~ /\./) {
		    split(m[2], a, ".")
		    m[2] = ((a[1] * 256 + a[2]) * 256 + a[3]) * 256 + a[4]
		}
		if (m[3] ~ /\./) {
		    split(m[3], a, ".")
		    m[3] = ((a[1] * 256 + a[2]) * 256 + a[3]) * 256 + a[4]
		}
		m[3] = maskz - m[3]
		masks[m[1]] = m[3]
		hi[m[1]] = m[2]
	    }
	}

	func revhost(n,mask) {
	    n %= mask
	    r = n%256
	    n = int(n/256)
	    if (mask > maskc) {
		r = r "." (n%256)
		n = int(n/256)
		if (mask > maskb) {
		    r = r "." (n%256)
		    n = int(n/256)
		    if (mask > maska) {
			r = r "." (n%256)
		    }
		}
	    }
	    return r
	}

	{
	    host = $5
	    addr = (($1 * 256 + $2) * 256 + $3) * 256 + $4

	    if ($1 == "127" && $2 == "0" && $3 == "0") {
		netmask = maskc
	    } else {
		if (host !~ /\./) host = host "." domain
		if ($1 < 128) {
		    netmask = maska
		} else if ($1 < 192) {
		    netmask = maskb
		} else {
		    netmask = maskc
		}
		for (lo in masks) {
		    if (addr >= lo && addr <= hi[lo]) {
			netmask = masks[lo]
			break
		    }
		}
	    }

	    newnet = int(addr/netmask)
	    if (newnet != net) {
		if (curfile != "") {
		    close(curfile)
		}
		net = newnet
		revnet = revhost(net,int(maskz/netmask))
		curfile = sprintf(revfile,revnet)
		printf "primary   %-30s %s\n",
			(revnet ".IN-ADDR.ARPA"), curfile >> newnboot
		curfile = (testdir curfile "." new)
		system("cp " tmpsoa " " curfile)
	    }
	    printf "%-10s IN     PTR      %s.\n",
			revhost(addr,netmask), host >> curfile
	}'

echo "$lline" >> $nnboot
sed -e "1,/^$lline/d" $nboot >> $nnboot

if test "$dotest"; then
    exit 0
fi


# do not stop now
trap "" 1 2 15

# delete obsolete files
if test -d "$olddir" -a -n "$dotest"; then
    for oldnm in ${subdomain}*.hosts ${subdomain}*.rev $nboot; do
	if test -f $oldnm -a ! -f $oldnm.$new; then
	    oldnms="$oldnms $oldnm"
	    rm -f $olddir/$oldnm
	    mv $oldnm $olddir/$oldnm
	fi
    done
fi

# install new versions
for newnm in ${testdir}${subdomain}*.new $nnboot; do
    oldnm=`expr $newnm : '\(.*\)\.'"$new"`
    if cmp -s $newnm $oldnm; then
	rm -f $newnm
    elif test ! -f $oldnm; then
	mv -f $newnm $oldnm
    elif test 0 -ne `diff $newnm $oldnm \
		| sed -e '/^[-0-9]/d' -e '/^[<>][0-9; 	]*Serial$/d' \
		| wc -l`; then : ;
	if test -d "$olddir"; then
	    rm -f $olddir/$oldnm
	    ln $oldnm $olddir/$oldnm
	fi
	mv -f $newnm $oldnm
    else
	rm -f $newnm
    fi
done

rm -f $oldnms

killall 1 named
exit 0
