#! /bin/sh

# PROVIDE: dhcpd
# REQUIRE: DAEMON
# BEFORE: LOGIN
# KEYWORD: shutdown
#
# Add the following line to /etc/rc.conf to enable dhcpd:
#
# dhcpd_enable="YES"

. /etc/rc.subr

# service(8) does not create an authentic environment, try to guess,
# and as of 10.3-RELEASE-p0, it will not find the indented name=
# assignments below. So give it a default.
# Trailing semicolon also for service(8)'s benefit:
name="$file" ;

case $0 in
/etc/rc*|/usr/sbin/service)
	# during boot (shutdown) $0 is /etc/rc (/etc/rc.shutdown),
	# so get the name of the script from $_file
	name=$_file
	;;
*)
	name=$0
	;;
esac

# default name to "dhcpd" if guessing failed
# Trailing semicolon also for service(8)'s benefit:
name="${name:-dhcpd}" ;
name=${name##*/isc-}

paranoia=%%PARANOIAOPT%%				# compiled in paranoia?

load_rc_config ${name}

# override these variables in /etc/rc.conf
eval ": \${${name}_enable:=\"NO\"}"
# dhcpd_flags=""		# -q -early_chroot	# command option(s)
# dhcpd_ifaces=""					# ethernet interface(s)
eval ": \${${name}_conf:=%%PREFIX%%/etc/${name}.conf}"	# configuration file
eval ": \${${name}_withumask:=022}"			# file creation mask

eval ": \${${name}_chuser_enable:=\"%%PARANOIAOPT%%\"}"	# runs w/o privileges?
eval ": \${${name}_withuser:=dhcpd}"			# user name to run as
eval ": \${${name}_withgroup:=dhcpd}"			# group name to run as

eval ": \${${name}_chroot_enable:=\"NO\"}"		# runs chrooted?
eval ": \${${name}_devfs_enable:=\"YES\"}"		# devfs if available?
eval ": \${${name}_chrootdir:=/var/db/${name}}"		# directory to run in
# dhcpd_includedir=""			# directory for included config files

safe_run ()	# rc command [args...]
{
	local _rc

	_rc=$1
	shift

	if [ "${_rc}" -eq 0 ]; then
		debug safe_run: "$@"
		"$@" || _rc=1
	else
		warn safe_run: "$@"
	fi
	return ${_rc}
}

precious ()	# entry...
{
	local _entry _rc

	_rc=0
	for _entry; do
		# do nothing if /dev, /var/run or /var/db
		echo ${_entry} | egrep -q '^//*(dev|var//*(run|db))?/*$' || _rc=1
	done
	debug precious: "$@" rc=${_rc}
	return ${_rc}
}

lsmod ()	# user group file...
{
	local _entry _user _group _rc

	_user=$1 _group=$2
	shift 2

	_rc=0
	for _entry; do
		ls -ld ${_entry} 2> /dev/null |
		awk -v u=${_user} -v g=${_group} '{
		    exit ((u && $3 != u) || (g && $4 != g))
		}' || _rc=1
	done
	debug lsmod: "$@" rc=${_rc}
	return ${_rc}
}

safe_chmog ()	# entry...
{
	local _entry _user _group _usergroup _rc

	eval "_user=\${${name}_withuser}"
	eval "_group=\${${name}_withgroup}"

	_rc=0
	if [ -n "${_user}" -o -n "${_group}" ]; then
		_usergroup=${_user}${_group:+:${_group}}
		for _entry; do
			if [ -d ${_entry} ] && mounted ${_entry}; then
				continue
			fi
			if [ -e ${_entry} ] &&
			   ! precious ${_entry} &&
			   ! lsmod ${_user} ${_group} ${_entry} &&
			   ! safe_run ${_rc} chown ${_usergroup} ${_entry}; then
				warn "unable to change permissions of ${_entry}"
				_rc=1
			fi
		done
	fi
	return ${_rc}
}

safe_mkdir ()	# dir...
{
	local _dir _rc

	_rc=0
	for _dir; do
		if [ ! -d ${_dir} ] &&
		   ! precious ${_dir} &&
		   ! safe_run ${_rc} mkdir -p ${_dir}; then
			err 1 "unable to create directory ${_dir}"
			_rc=1
		fi
	done
	safe_run ${_rc} safe_chmog "$@" || _rc=1
	return ${_rc}
}

safe_rmdir ()	# dir...
{
	local _dir _rc

	_rc=0
	for _dir; do
		if [ -d ${_dir} ] &&
		   ! precious ${_dir} &&
		   ! mounted ${_dir}; then
			if safe_run ${_rc} rmdir ${_dir}; then
				safe_run ${_rc} safe_rmdir ${_dir%/*} || _rc=1
			else
				warn "unable to remove directory ${_dir}"
				_rc=1
			fi
		fi
	done
	return ${_rc}
}

safe_touch ()	# file...
{
	local _file _rc

	_rc=0
	for _file; do
		if [ ! -e ${_file} ] &&
		   ! safe_run ${_rc} touch ${_file}; then
			err 1 "unable to create file ${_file}"
			_rc=1
		fi
	done
	safe_run ${_rc} safe_chmog "$@" || _rc=1
	return ${_rc}
}

safe_remove ()	# entry...
{
	local _entry _rc

	_rc=0
	for _entry; do
		if [ -f ${_entry} ]; then
			if ! safe_run ${_rc} rm -f ${_entry}; then
				warn "unable to remove file ${_entry}"
				_rc=1
			fi
		elif [ -d ${_entry} ] &&
		     ! precious ${_entry} &&
		     ! mounted ${_entry}; then
			if ! safe_run ${_rc} rm -rf ${_entry}; then
				warn "unable to remove directory ${_entry}"
				_rc=1
			fi
		fi
	done
	return ${_rc}
}

safe_copy ()	# src dst
{
	local _src _dst _rc

	_src=$1 _dst=$2

	_rc=0
	if [ -f ${_src} ]; then
		if ! safe_run ${_rc} safe_remove ${_dst} ||
		   ! safe_run ${_rc} cp -p ${_src} ${_dst}; then
			err 1 "unable to copy file ${_src} to ${_dst}"
			_rc=1
		fi
		safe_run ${_rc} safe_chmog ${_dst} || _rc=1
	elif [ -d ${_src} ] &&
	     ! precious ${_dst} &&
	     ! mounted ${_dst}; then
		if ! safe_run ${_rc} pax -rw -pe -ts "|^${_src}||" \
		   ${_src} ${_dst}; then
			err 1 "unable to copy directory ${_src} to ${_dst}"
			_rc=1
		fi
	else
		err 1 "unable to copy ${_src} to ${_dst}" \
		      "-- not a file or a directory"
		_rc=1
	fi
	return ${_rc}
}

mounted ()	# dir...
{
	local _rc

	_rc=1
	if checkyesno ${name}_devfs_enable; then
		mount -t devfs | awk '
		    BEGIN { n = ARGC; ARGC = 2 }
		    { for (i = 2; i != n; i++) if ($3 == ARGV[i]) exit 1 }
		' - "$@" || _rc=0
	fi
	debug mounted: "$@" rc=${_rc}
	return ${_rc}
}

safe_mount ()	# dir
{
	local _dir _rc

	_dir=$1

	_rc=0
	if checkyesno ${name}_devfs_enable &&
	   ! mounted ${_dir} &&
	   ! safe_run ${_rc} mount -t devfs devfs ${_dir}; then
		err 1 "unable to mount ${_dir}"
		_rc=1
	fi
	return ${_rc}
}

safe_umount ()	# dir
{
	local _dir _rc

	_dir=$1

	_rc=0
	if checkyesno ${name}_devfs_enable &&
	   mounted ${_dir} &&
	   ! safe_run ${_rc} umount ${_dir}; then
		warn "unable to unmount ${_dir}"
		_rc=1
	fi
	return ${_rc}
}

check_chuser ()
{
	if checkyesno paranoia; then
		if checkyesno ${name}_chuser_enable &&
		   eval "[ -z \"\${${name}_withuser}\" -a -z \"\${${name}_withgroup}\" ]"; then
			err 1 "one of ${name}_withuser and ${name}_withgroup" \
			      "must be set if ${name}_chuser_enable is enabled"
		fi
	else
		if checkyesno ${name}_chuser_enable; then
			warn "${name}_chuser_enable disabled -- not compiled in"
			eval "${name}_chuser_enable=NO"
		fi
	fi
}

check_chroot ()
{
	if checkyesno paranoia; then
		if checkyesno ${name}_chroot_enable; then
			if eval "[ -z \"\${${name}_chrootdir}\" ]"; then
				err 1 "${name}_chrootdir must be set" \
				      "if ${name}_chroot_enable is enabled"
			fi
		else
			eval "${name}_devfs_enable=NO"
		fi
	else
		if checkyesno ${name}_chroot_enable; then
			warn "${name}_chroot_enable disabled -- not compiled in"
			eval "${name}_chroot_enable=NO"
		fi
		eval "${name}_devfs_enable=NO"
	fi
}

_rcvar_chuser ()
{
	if checkyesno paranoia && checkyesno ${name}_chuser_enable; then
		eval "${name}_piddir=\${__dhcpd_piddir}/\${name}"
		eval "${name}_leasesdir=\${__dhcpd_leasesdir}/\${name}"
	else
		eval unset "${name}_withuser= ${name}_withgroup="
	fi
}

_rcvar_chroot ()
{
	if ! checkyesno paranoia || ! checkyesno ${name}_chroot_enable; then
		eval "${name}_chrootdir="
	elif checkyesno paranoia && checkyesno ${name}_chroot_enable; then
		eval "${name}_devdir=\${__dhcpd_devdir}"
		eval "${name}_etcdir=\${__dhcpd_etcdir}"
	fi
}

_rcvar_pidnleases ()
{
	if ! checkyesno ${name}_chuser_enable; then
		eval "${name}_piddir=\${__dhcpd_piddir}"
		eval "${name}_leasesdir=\${__dhcpd_leasesdir}"
	fi
	eval "${name}_pidfile=\${${name}_piddir}/\${name}.pid"
	eval "${name}_leasesfile=\${${name}_leasesdir}/\${name}.leases"
	eval "${name}_conffile=\${${name}_conf}"	# for convenience only
	eval "${name}_confdir=\$(dirname \${${name}_conffile})"
}

_rcvar_rooted ()
{
	eval "_dhcpd_chrootdir=\${${name}_chrootdir}"
	eval "_dhcpd_devdir=\${${name}_chrootdir}\${${name}_devdir}"
	eval "_dhcpd_etcdir=\${${name}_chrootdir}\${${name}_etcdir}"
	eval "_dhcpd_confdir=\${${name}_chrootdir}\${${name}_confdir}"
	eval "_dhcpd_includedir=\${${name}_chrootdir}\${${name}_includedir}"
	eval "_dhcpd_piddir=\${${name}_chrootdir}\${${name}_piddir}"
	eval "_dhcpd_leasesdir=\${${name}_chrootdir}\${${name}_leasesdir}"
	eval "_dhcpd_conffile=\${${name}_chrootdir}\${${name}_conffile}"
	eval "_dhcpd_pidfile=\${${name}_chrootdir}\${${name}_pidfile}"
	eval "_dhcpd_leasesfile=\${${name}_chrootdir}\${${name}_leasesfile}"
}

setup_umask ()
{
	if eval "[ -n \"\${${name}_withumask}\" ]"; then
		eval "umask \${${name}_withumask}"
	fi
}

setup_chroot ()
{
	local _hconf _hosts _ltime _rconf

	_hconf=host.conf
	_hosts=hosts
	_ltime=localtime
	_rconf=resolv.conf

	if checkyesno paranoia && checkyesno ${name}_chroot_enable; then
		if ! mounted ${_dhcpd_devdir}; then
			safe_mkdir ${_dhcpd_devdir}/_
			# XXX /_ hack! so, .../dev is root owned.
		fi
		safe_mkdir ${_dhcpd_chrootdir} ${_dhcpd_etcdir}/_ ${_dhcpd_confdir}
		# XXX /_ hack! so, .../etc is root owned.
		if checkyesno ${name}_devfs_enable; then
			safe_mount ${_dhcpd_devdir}
		else
			eval "safe_copy \${${name}_devdir} \${_dhcpd_devdir}"
		fi
		eval "safe_copy \${${name}_conffile} \${_dhcpd_conffile}"
		# copy host.conf only if it exists
		if eval "[ -f \"\${${name}_etcdir}/\$_hconf\" ]"; then
			eval "safe_copy \${${name}_etcdir}/\$_hconf \${_dhcpd_etcdir}/\$_hconf"
		fi
		eval "safe_copy \${${name}_etcdir}/\$_hosts \${_dhcpd_etcdir}/\$_hosts"
		# copy localtime only if it exists
		if eval "[ -f \"\${${name}_etcdir}/\$_ltime\" ]"; then
			eval "safe_copy \${${name}_etcdir}/\$_ltime \${_dhcpd_etcdir}/\$_ltime"
		fi
		eval "safe_copy \${${name}_etcdir}/\$_rconf \${_dhcpd_etcdir}/\$_rconf"
		# copy dhcpd_includedir if defined and available
		if eval "[ -d \"\${${name}_includedir}\" ]"; then
			safe_mkdir ${_dhcpd_includedir}
			eval "safe_copy \${${name}_includedir} \${_dhcpd_includedir}"
		fi
	fi
}

setup_chuser ()
{
	if checkyesno paranoia && {
	   checkyesno ${name}_chuser_enable || checkyesno ${name}_chroot_enable
	}; then
		safe_mkdir ${_dhcpd_piddir} ${_dhcpd_leasesdir}
	fi
}

setup_leases ()
{
	safe_touch ${_dhcpd_leasesfile}
}

setup_flags ()
{
	if [ "${name}" = "dhcpd6" ]; then
		rc_flags="${rc_flags} -6"
	fi
	if eval "[ -n \"\${${name}_conf}\" ]"; then
		eval "rc_flags=\"\${rc_flags} -cf \${${name}_conf}\""
	fi
	if eval "[ -n \"\${${name}_leasesfile}\" ]"; then
		eval "rc_flags=\"\${rc_flags} -lf \${${name}_leasesfile}\""
	fi
	if eval "[ -n \"\${${name}_pidfile}\" ]"; then
		eval "rc_flags=\"\${rc_flags} -pf \${${name}_pidfile}\""
	fi
	if eval "[ -n \"\${${name}_withuser}\" ]"; then
		eval "rc_flags=\"\${rc_flags} -user \${${name}_withuser}\""
	fi
	if eval "[ -n \"\${${name}_withgroup}\" ]"; then
		eval "rc_flags=\"\${rc_flags} -group \${${name}_withgroup}\""
	fi
	if eval "[ -n \"\${${name}_chrootdir}\" ]"; then
		eval "rc_flags=\"\${rc_flags} -chroot \${${name}_chrootdir}\""
	fi
	eval "rc_flags=\"\${rc_flags} \${${name}_ifaces}\""
}

cleanup_chroot ()
{
	if checkyesno paranoia && checkyesno ${name}_chroot_enable; then
		safe_umount ${_dhcpd_devdir}
	fi
}

dhcpd_stop ()
{
	if sh $0 forcestatus; then
		sh $0 forcestop
	fi
}

dhcpd_check ()
{
	check_chuser
	check_chroot
}

dhcpd_rcvar ()
{
	_rcvar_chuser
	_rcvar_chroot
	_rcvar_pidnleases
	_rcvar_rooted
}

dhcpd_precmd ()
{
	setup_umask
	setup_chroot
	setup_chuser
	setup_leases
	setup_flags
}

dhcpd_postcmd ()
{
	cleanup_chroot
}

dhcpd_restartprecmd ()
{
	setup_chroot
	setup_flags
	dhcpd_checkconfig
}

dhcpd_checkconfig ()
{
	setup_flags
	# Run dhcpcd -t quietly to check for errors.  If this fails,
	# run it again unquietly so the errors are shown to the user,
	# then error out.  The -q and -Q flags need to be at the end
	# to override contradictory flags in ${rc_flags}.
	if ! ${command} -t ${rc_flags} -q; then
		${command} -t ${rc_flags} -Q
		err 1 "Configuration file sanity check failed"
	fi
}

rcvar=${name}_enable
load_rc_config ${name}

__dhcpd_uninstall="NO"			# internal use only
__dhcpd_devdir=/dev			# devices directory
__dhcpd_etcdir=/etc			# etc directory
__dhcpd_piddir=/var/run			# pid file directory
__dhcpd_leasesdir=/var/db		# leases file directory
#__dhcpd_chrootdir=/var/db/${name}	# root directory

dhcpd_check
dhcpd_rcvar

command=%%PREFIX%%/sbin/dhcpd
pidfile=${_dhcpd_pidfile}
eval "required_files=\${${name}_conf}"
start_precmd=dhcpd_precmd
stop_postcmd=dhcpd_postcmd
restart_precmd="dhcpd_restartprecmd"
uninstall_cmd=dhcpd_uninstall
configtest_cmd="dhcpd_restartprecmd"
extra_commands="uninstall configtest"

run_rc_command "$1"
