#!/bin/sh

# dnsseczonetool : Simple DNSSEC key management and zone signing tool
#
# $Id: dnsseczonetool.20100420,v 1.1 2010/05/10 11:16:31 fujiwara Exp $
#
# http://member.wide.ad.jp/~fujiwara/dnssec/dnsseczonetool
#
# Installation and Configuration:
#   1. Copy dnsseczonetool into some directory.
#   2. Create dnsseczonetool.conf into the same directory of dnsseczonetool.
#
#   dnsseczonetool.conf:
#     MASTERDIR: Zone file directory
#                Default: MASTERDIR="/etc/namedb/master"
#     KSK_PARAM: Default dnssec-keygen's options for KSK
#                Default: KSK_PARAM_DEFAULT="-n zone -a RSASHA1 -b 2048 -f ksk"
#     KSK_PARAM_$zone: dnssec-keygen's options for zone's KSK
#                Default: KSK_PARAM
#     ZSK_PARAM: Default dnssec-keygen's options for ZSK
#                Default: ZSK_PARAM_DEFAULT="-n zone -a RSASHA1 -b 1024"
#     ZSK_PARAM_$zone: dnssec-keygen's options for zone's ZSK
#                Default: ZSK_PARAM
#     SIGN_PARAM: Default dnssec-signzone options
#                Default: SIGN_PARAM_DEFAULT="-N unixtime"
#     SIGN_PARAM_$zone: dnssec-signzone options for zone
#                Default: SIGN_PARAM
#     keygen:    dnssec-keygen path
#                Default: keygen="/usr/local/sbin/dnssec-keygen"
#     signzone:  dnssec-signzone path
#                Default: signzone="/usr/local/sbin/dnssec-signzone"
#     dsfromkey: dnssec-dsfromkey path
#                Default: dsfromkey="/usr/local/sbin/dnssec-dsfromkey"
#     rndc:      rndc path
#                Default: rndc="/usr/local/sbin/rndc"
#     CONFIGDIR: directory where dnsseczonetool uses.
#                Default: CONFIGDIR="$MASTERDIR/config"
#     KEYDIR:    directory where dnsseczonetool puts zone keys.
#                Default: KEYDIR="$MASTERDIR/config/keydir"
#     KEYBACKUPDIR: directory where dnsseczonetool puts old keys.
#                Default: KEYBACKUPDIR="$MASTERDIR/config/backup"
#     RNDC_OPTION: rndc options or OFF
#                Default: RNDC_OPTION="-k $MASTERDIR/rndc.key"
#     ZONE_PREPROCESS: zone preprocess command
#                Default: cat
#     RELOADALL_COMMAND:  reload all command
#                Default: none
#     DOUBLE_KSK: YES/NO if YES, DNSKEY RRs are signed both KSK and standby KSK.
#                Default: NO
#
#     caution: $zone is zone name
#              whose '.' and '-' characters are replaced by '_'.
#		 All zone name must be lowercase.
#
# Usage:
#        
#        1. Generate KSK and ZSK
#                dnsseczonetool keygen2 zone(s)
#        2. Sign zone using keys generated in step 1
#                dnsseczonetool sign zone(s)
#        3. ZSK rollover
#                dnsseczonetool zskroll zone(s)
#        4. KSK rollover
#                dnsseczonetool kskroll zone(s)
#        5. Zone key status
#                dnsseczonetool status zone(s)

# Copyright (c) 2009 Kazunori Fujiwara <fujiwara@wide.ad.jp>.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.

PROG=$0

DIR=`dirname $0`
CONFIGFILE="$DIR/dnsseczonetool.conf"

keygen="/usr/local/sbin/dnssec-keygen"
signzone="/usr/local/sbin/dnssec-signzone"
dsfromkey="/usr/local/sbin/dnssec-dsfromkey"
rndc="/usr/local/sbin/rndc"
MASTERDIR="/etc/namedb/master"
RNDC_OPTION="-k $MASTERDIR/rndc.key"

KSK_PARAM="-n zone -a RSASHA1 -b 2048 -f ksk"
ZSK_PARAM="-n zone -a RSASHA1 -b 1024"
SIGN_PARAM="-N unixtime"
ZONE_PREPROCESS="cat"
DOUBLE_KSK="NO"

if [ -f $CONFIGFILE ]; then
	. $CONFIGFILE
fi

if [ "$1" = "" -a "$DEFAULT_ACTION" != "" ]; then
	echo "Doing $DEFAULT_ACTION"
	set $DEFAULT_ACTION
	echo $1 $2 $3
fi
CMD="$1"
shift

if [ "$CONFIGDIR" = "" ]; then
	CONFIGDIR="$MASTERDIR/config"
fi
if [ "$KEYDIR" = "" ]; then
	KEYDIR="$CONFIGDIR/keydir"
fi
if [ "$KEYBACKUPDIR" = "" ]; then
	KEYBACKUPDIR="$CONFIGDIR/backup"
fi

LOCKF=""

HEAD_ZSKNAME="zsk-"
HEAD_KSKNAME="ksk-"
HEAD_ZSSNAME="zss-"
HEAD_KSSNAME="kss-"

NEED_RELOAD="NO"

# setup
if [ ! -d $CONFIGDIR ]; then
	mkdir -p $CONFIGDIR
fi
if [ ! -d $KEYBACKUPDIR ]; then
	mkdir -p $KEYBACKUPDIR
fi
if [ ! -d $KEYDIR ]; then
	mkdir -p $KEYDIR
fi

cd $MASTERDIR

_check_file()
{
	while [ "$1" != "" ]; do
		if [ ! -f "$1" ]; then
			echo "$1 does not exist."
			_usage
		fi
		shift
	done
}
_check_nofile()
{
	while [ "$1" != "" ]; do
		if [ -f "$1" ]; then
			echo "$1 exist."
			_usage
		fi
		shift
	done
}

_usage()
{
	if [ "$LOCKF" != "" ]; then
		rm $LOCKF
	fi
	cat <<EOF
$PROG: Simple DNSSEC key management and zone signing tool
Usage:  $PROG command zone(s)
  Available commands:
     keygen  : Generate KSK and ZSK for the zone(s)
     keygen2 : Generate KSK and ZSK and standby sets for the zone(s)
     sign    : (Re-)Sign the zone(s)
     zskroll : Roll ZSK for the zone(s)
     kskroll : Roll KSK for the zone(s)
     status  : Show key status for the zone(s)
     standby-zsk-keygen :
               Generate standby ZSK key for the zone(s)
     standby-ksk-keygen :
               Generate standby KSK key for the zone(s)
EOF
	exit 1
}

sign()
{
	_check_file $ZONEFILE $KSK_FILE $ZSK_FILE
	KSK=`head -1 $KSK_FILE`
	ZSK=`head -1 $ZSK_FILE`
	KSS=""
	if [ -f $KSK_S_FILE -a "$DOUBLE_KSK" = "YES" ]; then
		KSS=`head -1 $KSK_S_FILE`
	fi
	_check_file "$KEYDIR/$KSK.private" "$KEYDIR/$ZSK.private"
	$ZONE_PREPROCESS $ZONE > $ZONE.tmp
	cat $KSK_FILE $ZSK_FILE | while read keyfile
	do
		_check_file "$KEYDIR/$keyfile.key"
		cat "$KEYDIR/$keyfile.key" >> $ZONE.tmp
	done
	for i in $KSK_S_FILE $ZSK_S_FILE
	do
		if [ -f $i ]; then
			keyfile=`head -1 $i`
			_check_file "$KEYDIR/$keyfile.key"
			cat "$KEYDIR/$keyfile.key" >> $ZONE.tmp
		fi
	done
	cmdname=`basename $signzone`
	if [ "$cmdname" = "ldns-signzone" ]; then
		if [ "$KSS" != "" ]; then
			KSS="$KEYDIR/$KSS"
		fi
		echo $signzone $_SIGN_PARAM -o $ZONE -f "$ZONEFILE.signed" $ZONEFILE.tmp $KEYDIR/$ZSK $KEYDIR/$KSK $KSS
		$signzone $_SIGN_PARAM -o $ZONE -f "$ZONEFILE.signed" $ZONEFILE.tmp $KEYDIR/$ZSK $KEYDIR/$KSK $KSS
	else
		if [ "$KSS" != "" ]; then
			KSS="-k $KEYDIR/$KSS.private"
		fi
		echo $signzone $_SIGN_PARAM -o $ZONE -k $KEYDIR/$KSK.private $KSS -f "$ZONEFILE.signed" $ZONEFILE.tmp $KEYDIR/$ZSK.private 2>&1
		$signzone $_SIGN_PARAM -o $ZONE -k $KEYDIR/$KSK.private $KSS -f "$ZONEFILE.signed" $ZONEFILE.tmp $KEYDIR/$ZSK.private 2>&1
	fi
	rm $ZONE.tmp

	echo "signzone returns $?"

	if [ "$RNDC_OPTION" != "OFF" ]; then
		$rndc $RNDC_OPTION reload $ZONE
	fi
	NEED_RELOAD="YES"
}

status()
{
	if [ -f $KSK_FILE ]; then
		echo -n "$ZONE's KSK = "
		cat $KSK_FILE;
		$dsfromkey -2 $KEYDIR/`cat $KSK_FILE`.key
	fi
	if [ -f $KSK_S_FILE ]; then
		echo -n "$ZONE's standby KSK = "
		cat $KSK_S_FILE;
		$dsfromkey -2 $KEYDIR/`cat $KSK_S_FILE`.key
	fi
	if [ -f $ZSK_FILE ]; then
		echo -n "$ZONE's ZSK = "
		cat $ZSK_FILE;
	fi
	if [ -f $ZSK_S_FILE ]; then
		echo -n "$ZONE's standby ZSK = "
		cat $ZSK_S_FILE;
	fi
}

keygensub()
{
	(
	cd $KEYDIR;
	$keygen $1 $2 > $3;
	_FILE=`cat $3`
	if [ -f $_FILE.ds ]; then
		rm $_FILE.ds
	fi
	)
}

if [ "$CMD" = "" ]; then
	_usage
fi

if [ "$1" = "" -a "$ZONELIST" != "" ]; then
	set $ZONELIST
fi

for ZONE in $*
do
	LOCKF="$CONFIGDIR/$ZONE.lock"
	TMPF="$CONFIGDIR/$ZONE.$$"
	OUTF="$ZONE.signed"
	KSK_FILE="$CONFIGDIR/$HEAD_KSKNAME$ZONE"
	ZSK_FILE="$CONFIGDIR/$HEAD_ZSKNAME$ZONE"
	KSK_S_FILE="$CONFIGDIR/$HEAD_KSSNAME$ZONE"
	ZSK_S_FILE="$CONFIGDIR/$HEAD_ZSSNAME$ZONE"
	if [ $ZONE = "." ]; then
		ZONEFILE="root"
	else
		ZONEFILE=$ZONE
	fi

	ZONE_=`echo $ZONE | tr .- __`
	eval _SIGN_PARAM=\${SIGN_PARAM_$ZONE_:-$SIGN_PARAM}
	eval _KSK_PARAM=\${KSK_PARAM_$ZONE_:-$KSK_PARAM}
	eval _ZSK_PARAM=\${ZSK_PARAM_$ZONE_:-$ZSK_PARAM}

	touch $TMPF
	if ln $TMPF $LOCKF; then
		:
	else
		rm $TMPF
		echo "zone $ZONE locked"
		continue
	fi
	rm $TMPF
	case $CMD in
	keygen)
		_check_nofile $KSK_FILE $ZSK_FILE
		keygensub "$_KSK_PARAM" $ZONE $KSK_FILE
		keygensub "$_ZSK_PARAM" $ZONE $ZSK_FILE
		status
		;;
	keygen2)
		_check_nofile $KSK_FILE $ZSK_FILE $KSK_S_FILE $ZSK_S_FILE
		keygensub "$_KSK_PARAM" $ZONE $KSK_FILE
		keygensub "$_ZSK_PARAM" $ZONE $ZSK_FILE
		keygensub "$_KSK_PARAM" $ZONE $KSK_S_FILE
		keygensub "$_ZSK_PARAM" $ZONE $ZSK_S_FILE
		status
		;;
	standby-zsk-keygen)
		_check_nofile $ZSK_S_FILE
		keygensub "$_ZSK_PARAM" $ZONE $ZSK_S_FILE
		status
		;;
	standby-ksk-keygen)
		_check_nofile $KSK_S_FILE
		keygensub "$_KSK_PARAM" $ZONE $KSK_S_FILE
		status
		;;
	zskroll)
		_check_file $ZONE $ZSK_FILE $ZSK_S_FILE
		ZSK=`head -1 $ZSK_FILE`
		ZSS=`head -1 $ZSK_S_FILE`
		_check_file $KEYDIR/$ZSK.key $KEYDIR/$ZSS.key $KEYDIR/$ZSK.private $KEYDIR/$ZSS.private
		mv $KEYDIR/$ZSK.key $KEYDIR/$ZSK.private $KEYBACKUPDIR/
		mv $ZSK_S_FILE $ZSK_FILE
		keygensub "$_ZSK_PARAM" $ZONE $ZSK_S_FILE
		OLDZSK="$ZSK"
		ZSK="$ZSS"
		ZSS=`head -1 $ZSK_S_FILE`
		echo "$ZONE 's ZSK: valid -> removed: $OLDZSK"
		echo "$ZONE 's ZSK: standby -> valid: $ZSK"
		echo "$ZONE 's ZSK:      new standby: $ZSS"
		sign
		;;
	kskroll)
		_check_file $ZONE $KSK_FILE $KSK_S_FILE
		KSK=`head -1 $KSK_FILE`
		KSS=`head -1 $KSK_S_FILE`
		_check_file $KEYDIR/$KSK.key $KEYDIR/$KSS.key $KEYDIR/$KSK.private $KEYDIR/$KSS.private
		mv $KEYDIR/$KSK.key $KEYDIR/$KSK.private $KEYBACKUPDIR/
		mv $KSK_S_FILE $KSK_FILE
		keygensub "$_KSK_PARAM" $ZONE $KSK_S_FILE
		OLDKSK="$KSK"
		KSK="$KSS"
		KSS=`head -1 $KSK_S_FILE`
		echo "$ZONE 's KSK: valid -> removed: $OLDKSK"
		echo "$ZONE 's KSK: standby -> valid: $KSK"
		echo "$ZONE 's KSK:      new standby: $KSS"
		sign
		;;
	sign)
		sign
		;;
	status)
		status
		;;
	*)
		echo "unknown command: $CMD"
		_usage
		;;
	esac
	rm $LOCKF
done

if [ "$NEED_RELOAD" = "YES" -a "$RELOADALL_COMMAND" != "" ]; then
	eval $RELOADALL_COMMAND
fi

exit 0
