#!/bin/sh
#
# $NetBSD: postinstall,v 1.31.2.2.2.3 2008/01/06 05:01:16 wrstuden Exp $
#
# Copyright (c) 2002-2005 The NetBSD Foundation, Inc.
# All rights reserved.
#
# This code is derived from software contributed to The NetBSD Foundation
# by Luke Mewburn.
#
# 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.
# 3. All advertising materials mentioning features or use of this software
#    must display the following acknowledgement:
#        This product includes software developed by the NetBSD
#        Foundation, Inc. and its contributors.
# 4. Neither the name of The NetBSD Foundation nor the names of its
#    contributors may be used to endorse or promote products derived
#    from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 THE FOUNDATION 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.
#
# postinstall
#	Check for or fix configuration changes that occur
#	over time as NetBSD evolves.
#

#
# checks to add:
#	- sysctl(8) renames (net.inet6.ip6.bindv6only -> net.inet6.ip6.v6only)
#	- de* -> tlp* migration (/etc/ifconfig.de*, $ifconfig_de*,
#	  dhclient.conf, ...) ?
#	- support quiet/verbose mode ?
#	- differentiate between failures caused by missing source
#	  and real failures
#	- install moduli into usr/share/examples/ssh and use from there?
#	- differentiate between "needs fix" versus "can't fix" issues
#

#
#	helper functions
#

err()
{
	exitval=$1
	shift
	echo 1>&2 "${PROGNAME}: $@"
	if [ -n "${SCRATCHDIR}" ]; then
	    /bin/rm -rf ${SCRATCHDIR}
	fi
	exit ${exitval}
}

warn()
{
	echo 1>&2 "${PROGNAME}: $@"
}

msg()
{
	echo "	$@"
}

mkdtemp()
{
	# Make sure we don't loop forever if mkdir will always fail.
	[ -d /tmp ] || err 2 /tmp is not a directory
	[ -w /tmp ] || err 2 /tmp is not writable

	_base=/tmp/_postinstall.$$
	_serial=0

	while true; do
		_dir=${_base}.${_serial}
		mkdir -m 0700 ${_dir} && break
		_serial=$((${_serial} + 1))
	done
	echo ${_dir}
}

# additem item description
#	Add item to list of supported items to check/fix,
#	which are checked/fixed by default if no item is requested by user.
#
additem()
{
	[ $# -eq 2 ] || err 3 "USAGE: additem item description"
	defaultitems="${defaultitems}${defaultitems:+ }$1"
	eval desc_$1=\"$2\"
}

# adddisableditem item description
#	Add item to list of supported items to check/fix,
#	but execute the item only if the user asks for it explicitely.
#
adddisableditem()
{
	[ $# -eq 2 ] || err 3 "USAGE: adddisableditem item description"
	otheritems="${otheritems}${otheritems:+ }$1"
	eval desc_$1=\"$2\"
}

# checkdir op dir mode
#	Ensure dir exists, and if not, create it with the appropriate mode.
#	Returns 0 if ok, 1 otherwise.
#
check_dir()
{
	[ $# -eq 3 ] || err 3 "USAGE: check_dir op dir mode"
	_cdop=$1
	_cddir=$2
	_cdmode=$3
	[ -d "${_cddir}" ] && return 0
	if [ "${_cdop}" = "check" ]; then
		msg "${_cddir} is not a directory"
		return 1
	elif ! mkdir -m "${_cdmode}" "${_cddir}" ; then
		msg "Can't create missing ${_cddir}"
		return 1
	else
		msg "Missing ${_cddir} created"
	fi
	return 0
}

# check_ids op type file id [...]
#	Check if file of type "users" or "groups" contains the relevant IDs
#	Returns 0 if ok, 1 otherwise.
#	
check_ids()
{
	[ $# -ge 4 ] || err 3 "USAGE: checks_ids op type file id [...]"
	_op=$1
	_type=$2
	_file=$3
	shift 3
	_ids="$@"

	if [ ! -f "${_file}" ]; then
		msg "${_file} doesn't exist; can't check for missing ${_type}"
		return 1
	fi
	if [ ! -r "${_file}" ]; then
		msg "${_file} is not readable; can't check for missing ${_type}"
		return 1
	fi
	_notfixed=""
	if [ "${_op}" = "fix" ]; then
		_notfixed=${NOT_FIXED}
	fi
	_missing=$(awk -F: '
		BEGIN {
			for (x = 1; x < ARGC; x++)
				idlist[ARGV[x]]++
			ARGC=1
		}
		{
			found[$1]++
		}
		END {
			for (id in idlist) {
				if (! (id in found))
					print id
			}
		}
	' ${_ids} < ${_file})	|| return 1
	if [ -n "${_missing}" ]; then
		msg "Missing ${_type}${_notfixed}:" $(echo ${_missing})
		return 1
	fi
	return 0
}

# populate_dir op onlynew src dest mode file [file ...]
#	Perform op ("check" or "fix") on files in src/ against dest/
#	If op = "check" display missing or changed files, optionally with diffs.
#	If op != "check" copies any missing or changed files.
#	If onlynew evaluates to true, changed files are ignored.
#	Returns 0 if ok, 1 otherwise.
#
populate_dir()
{
	[ $# -ge 5 ] || err 3 "USAGE: populate_dir op onlynew src dest mode file [...]"
	_op=$1
	_onlynew=$2
	_src=$3
	_dest=$4
	_mode=$5
	shift 5
	_files="$@"

	if [ ! -d "${_src}" ]; then
		msg "${_src} is not a directory; skipping check"
		return 1
	fi
	check_dir ${_op} ${_dest} 755 || return 1

	_cmpdir_rv=0
	for f in ${_files}; do
		fs=${_src}/${f}
		fd=${_dest}/${f}
		_error=""
		if [ ! -f "${fd}" ]; then
			_error="${fd} does not exist"
		elif ! cmp -s ${fs} ${fd} ; then
			if $_onlynew; then	# leave existing ${fd} alone
				continue;
			fi
			_error="${fs} != ${fd}"
		else
			continue
		fi
		if [ "${_op}" = "check" ]; then
			msg ${_error}
			if [ -n "${DIFF_STYLE}" -a -f "${fd}" ]; then
				diff -${DIFF_STYLE} ${DIFF_OPT} ${fd} ${fs}
			fi
			_cmpdir_rv=1
		elif ! rm -f ${fd} ||
		     ! cp -f ${fs} ${fd}; then
			msg "Can't copy ${fs} to ${fd}"
			_cmpdir_rv=1
		elif ! chmod ${_mode} ${fd}; then
			msg "Can't change mode of ${fd} to ${_mode}"
			_cmpdir_rv=1
		else
			msg "Copied ${fs} to ${fd}"
		fi
	done
	return ${_cmpdir_rv}
}

# compare_dir op src dest mode file [file ...]
#	Perform op ("check" or "fix") on files in src/ against dest/
#	If op = "check" display missing or changed files, optionally with diffs.
#	If op != "check" copies any missing or changed files.
#	Returns 0 if ok, 1 otherwise.
#
compare_dir()
{
	[ $# -ge 4 ] || err 3 "USAGE: compare_dir op src dest mode file [...]"
	_op=$1
	_src=$2
	_dest=$3
	_mode=$4
	shift 4
	_files="$@"

	populate_dir $_op false $_src $_dest $_mode $_files
}

# move_file op src dest --
#	Check (op == "check") or move (op != "check") from src to dest.
#	Returns 0 if ok, 1 otherwise.
#
move_file()
{
	[ $# -eq 3 ] || err 3 "USAGE: move_file op src dest"
	_fm_op=$1
	_fm_src=$2
	_fm_dest=$3

	if [ -f "${_fm_src}" -a ! -f "${_fm_dest}" ]; then
		if [ "${_fm_op}" = "check" ]; then
			msg "Move ${_fm_src} to ${_fm_dest}"
			return 1
		fi
		if ! mv ${_fm_src} ${_fm_dest}; then
			msg "Can't move ${_fm_src} to ${_fm_dest}"
			return 1
		fi
		msg "Moved ${_fm_src} to ${_fm_dest}"
	fi
	return 0
}

# rcconf_is_set op name var [verbose] --
#	Load the rcconf for name, and check if obsolete rc.conf(5) variable
#	var is defined or not.
#	Returns 0 if defined (even to ""), otherwise 1.
#	If verbose != "", print an obsolete warning if the var is defined.
#
rcconf_is_set()
{
	[ $# -ge 3 ] || err 3 "USAGE: rcconf_is_set op name var [verbose]"
	_rcis_op=$1
	_rcis_name=$2
	_rcis_var=$3
	_rcis_verbose=$4
	_rcis_notfixed=""
	if [ "${_rcis_op}" = "fix" ]; then
		_rcis_notfixed=${NOT_FIXED}
	fi
	(
		for f in \
		    ${DEST_DIR}/etc/rc.conf \
		    ${DEST_DIR}/etc/rc.conf.d/${_rcis_name}; do
			[ -f "${f}" ] && . "${f}"
		done
		eval echo -n \"\${${_rcis_var}}\" 1>&3
		if eval "[ -n \"\${${_rcis_var}}\" \
			    -o \"\${${_rcis_var}-UNSET}\" != \"UNSET\" ]"; then
			if [ -n "${_rcis_verbose}" ]; then
				msg \
	"Obsolete rc.conf(5) variable '\$${_rcis_var}' found.${_rcis_notfixed}"
			fi
			exit 0
		else
			exit 1
		fi
	)
}

# find_file_in_dirlist() file message dir1 [...] --
#	Find which directory file is in, and sets ${dir} to match.
#	Returns 0 if matched, otherwise 1 (and sets ${dir} to "").
#
#	Generally, check the directory for the "checking from source" case,
#	and then the directory for the "checking from extracted etc.tgz" case.
#
find_file_in_dirlist()
{
	[ $# -ge 3 ] || err 3 "USAGE: find_file_in_dirlist file msg dir1 [...]"

	_file=$1 ; shift
	_msg=$1 ; shift
	_dir1st=
	for dir in $*; do
		: ${_dir1st:=${dir}}
		if [ -f "${dir}/${_file}" ]; then
			if [ "${_dir1st}" != "${dir}" ]; then
				msg \
		  "(Checking for ${_msg} from ${dir} instead of ${_dir1st})"
			fi
			return 0
		fi
	done
	msg "Can't find source directory for ${_msg}"
	return 1
}

# stat op format target value
#	Call stat(1) on the given target according to the given format,
#	if stat(1) is available (it is presumed to live in /usr/bin).
#	If it is not available, this routine will always succeed, otherwise
#	it returns 0 or 1, depending on whether or not the output from
#	stat(1) matches the expected value.
#
stat()
{
	_stop=$1
	_stfmt=$2
	_sttgt=$3
	_stval=$4

	if [ ! -x /usr/bin/stat ]; then
		msg "(/usr/bin/stat not available; skipping ${_stop} on ${_sttgt})"
		return 0
	fi

	_stres=$(/usr/bin/stat -q -f "${_stfmt}" "${_sttgt}")
	[ "${_stres}" = "${_stval}" ]
	return $?
}

# file_exists_exact path
#	Returns true if file exists matching exact case for path
#	Each path is relative to ${DEST_DIR}, and should
#	be an absolute path or start with `./'.
#
file_exists_exact()
{
	[ -n "1" ] || err 3 "USAGE: file_exists_exact path"
	_path=${1#.}
	[ -h "${DEST_DIR}${_path}" ] || \
        	[ -e "${DEST_DIR}${_path}" ] || return 1
	while [ "${_path}" != "/" ] ; do
		_dirname=$(dirname "${_path}" 2>/dev/null)
		_basename=$(basename "${_path}" 2>/dev/null)
		ls -fa "${DEST_DIR}${_dirname}" 2> /dev/null \
			| grep -q -x -F "${_basename}" \
			|| return 1
		_path=${_dirname}
	done
	return 0
}

# obsolete_paths op
#	Obsolete the list of paths provided on stdin.
#	Each path is relative to ${DEST_DIR}, and should
#	be an absolute path or start with `./'.
#
obsolete_paths()
{
	[ -n "$1" ] || err 3 "USAGE: obsolete_paths  fix|check"
	op=$1

	failed=0
	while read ofile; do
                if ! file_exists_exact "${ofile}"; then
                    continue
                fi
		ofile=${DEST_DIR}${ofile#.}
		cmd="rm"
		ftype="file"
		if [ -h "${ofile}" ]; then
			ftype="link"
		elif [ -d "${ofile}" ]; then
			ftype="directory"
			cmd="rmdir"
		fi
		if [ "${op}" = "check" ]; then
			msg "Remove obsolete ${ftype} ${ofile}"
			failed=1
		elif ! eval ${cmd} ${ofile}; then
			msg "Can't remove obsolete ${ftype} ${ofile}"
			failed=1
		else
			msg "Removed obsolete ${ftype} ${ofile}"
		fi
	done
	return ${failed}
}

# obsolete_libs dir
#	Display the minor/teeny shared libraries in dir that are considered
#	to be obsolete.
#
#	The implementation supports removing obsolete major libraries
#	if the awk variable AllLibs is set, although there is no way to
#	enable that in the enclosing shell function as this time.
#
obsolete_libs()
{
	[ $# -eq 1 ] || err 3 "USAGE: obsolete_libs dir"
	dir=$1

	(

	cd "${DEST_DIR}/${dir}" || err 2 "can't cd to ${DEST_DIR}/${dir}"
	echo lib*.so.* \
	| tr ' ' '\n' \
	| awk -v LibDir="${dir}/" '
#{

function digit(v, c, n) { return (n <= c) ? v[n] : 0 }

function checklib(results, line, regex) {
	if (! match(line, regex))
		return
	lib = substr(line, RSTART, RLENGTH)
	rev = substr($0, RLENGTH+1)
	if (! (lib in results)) {
		results[lib] = rev
		return
	}
	orevc = split(results[lib], orev, ".")
	nrevc = split(rev, nrev, ".")
	maxc = (orevc > nrevc) ? orevc : nrevc
	for (i = 1; i <= maxc; i++) {
		res = digit(orev, orevc, i) - digit(nrev, nrevc, i)
		if (res < 0) {
			print LibDir lib results[lib]
			results[lib] = rev
			return
		} else if (res > 0) {
			print LibDir lib rev
			return
		}
	}
}

/^lib.*\.so\.[0-9]+\.[0-9]+(\.[0-9]+)?$/ {
	if (AllLibs)
		checklib(minor, $0, "^lib.*\.so\.")
	else
		checklib(found, $0, "^lib.*\.so\.[0-9]+\.")
}

/^lib.*\.so\.[0-9]+$/ {
	if (AllLibs)
		checklib(major, $0, "^lib.*\.so\.")
}

#}'
	
	)
}

# modify_file op srcfile scratchfile awkprog
#	Apply awkprog to srcfile sending output to scratchfile, and
#	if appropriate replace srcfile with scratchfile.
#
modify_file()
{
	[ $# -eq 4 ] || err 3 "USAGE: modify_file op file scratch awkprog"

	_mfop=$1
	_mffile=$2
	_mfscratch=$3
	_mfprog=$4
	_mffailed=0

	awk "${_mfprog}" < "${_mffile}" > "${_mfscratch}"
	if ! cmp -s "${_mffile}" "${_mfscratch}"; then
		diff "${_mffile}" "${_mfscratch}" > "${_mfscratch}.diffs"
		if [ "${_mfop}" = "check" ]; then
			msg "${_mffile} needs the following changes:"
			_mffailed=1
		elif ! rm -f "${_mffile}" ||
		     ! cp -f "${_mfscratch}" "${_mffile}"; then
			msg "${_mffile} changes not applied:"
			_mffailed=1
		else
			msg "${_mffile} changes applied:"
		fi
		while read _line; do
			msg "	${_line}"
		done < "${_mfscratch}.diffs"
	fi
	return ${_mffailed}
}


# contents_owner op directory user group
#	Make sure directory and contents are owned (and group-owned)
#	as specified.
#
contents_owner()
{
	[ $# -eq 4 ] || err 3 "USAGE: contents_owner op dir user group"

	_op=$1
	_dir=$2
	_user=$3
	_grp=$4

	if [ "${_op}" = "check" ]; then
		if [ ! -z "`find ${_dir} \( ! -user ${_user} \) -o \
		\( ! -group ${_grp} \)`" ]
		then
			msg "${_dir} and contents not all owned by ${_user}:${_grp}"
			return 1
		else
			return 0
		fi
	elif [ "${_op}" = "fix" ]; then
		find ${_dir} \( \( ! -user ${_user} \) -o \
		\( ! -group ${_grp} \) \) -a -print0 \
		| xargs -0 chown ${_user}:${_grp}
	fi	
}


#
#	items
#	-----
#

#
#	bluetooth
#
additem bluetooth "bluetooth configuration is up to date"
do_bluetooth()
{
	[ -n "$1" ] || err 3 "USAGE: do_bluetooth fix|check"
	op=$1
	failed=0

	populate_dir ${op} true \
		${SRC_DIR}/etc/bluetooth ${DEST_DIR}/etc/bluetooth 644 \
		hosts protocols btdevctl.conf
	failed=$(( ${failed} + $? ))

	move_file ${op} ${DEST_DIR}/var/db/btdev.xml \
			${DEST_DIR}/var/db/btdevctl.plist
	failed=$(( ${failed} + $? ))

	return ${failed}
}

#
#	defaults
#
additem defaults "/etc/defaults/ being up to date"
do_defaults()
{
	[ -n "$1" ] || err 3 "USAGE: do_defaults  fix|check"
	op=$1
	failed=0

	compare_dir $op ${SRC_DIR}/etc/defaults ${DEST_DIR}/etc/defaults 444 \
		daily.conf monthly.conf rc.conf security.conf weekly.conf
	failed=$(( ${failed} + $? ))

	find_file_in_dirlist pf.boot.conf "pf.boot.conf" \
	    ${SRC_DIR}/usr.sbin/pf/etc/defaults ${SRC_DIR}/etc/defaults \
	    || return 1

	compare_dir $op ${dir} ${DEST_DIR}/etc/defaults 444 pf.boot.conf
	failed=$(( ${failed} + $? ))

	return ${failed}
}

#
#	gid
#
additem gid "required groups in /etc/group"
do_gid()
{
	[ -n "$1" ] || err 3 "USAGE: do_gid  fix|check"

	check_ids $1 groups "${DEST_DIR}/etc/group" \
	    named ntpd postfix sshd authpf _pflogd _rwhod _proxy _sdpd
}

#
#	hosts
#
additem hosts "/etc/hosts being up to date"
do_hosts()
{
	[ -n "$1" ] || err 3 "USAGE: do_hosts  fix|check"

	modify_file "$1" ${DEST_DIR}/etc/hosts ${SCRATCHDIR}/hosts '
		/^(127\.0\.0\.1|::1)[ 	]+[^\.]*$/ {
			print $0, "localhost."
			next
		}
		{ print }
	'
	return $?
}

#
#	iscsi
#
additem iscsi "/etc/iscsi is populated"
do_iscsi()
{
	[ -n "$1" ] || err 3 "USAGE: do_iscsi  fix|check"

	populate_dir ${op} true ${SRC_DIR}/etc/iscsi ${DEST_DIR}/etc/iscsi 600 auths
	populate_dir ${op} true ${SRC_DIR}/etc/iscsi ${DEST_DIR}/etc/iscsi 644 targets
	return $?
}

#
#	makedev
#
additem makedev "/dev/MAKEDEV being up to date"
do_makedev()
{
	[ -n "$1" ] || err 3 "USAGE: do_makedev   fix|check"

	if [ -f "${SRC_DIR}/etc/MAKEDEV.tmpl" ]; then
			# generate MAKEDEV from source if source is available
		env MACHINE=${MACHINE} \
		    MACHINE_ARCH=${MACHINE_ARCH} \
		    NETBSDSRCDIR="${SRC_DIR}" \
		    awk -f ${SRC_DIR}/etc/MAKEDEV.awk \
		    ${SRC_DIR}/etc/MAKEDEV.tmpl > ${SCRATCHDIR}/MAKEDEV
	fi

	find_file_in_dirlist MAKEDEV "MAKEDEV" \
	    ${SCRATCHDIR} ${SRC_DIR}/dev \
	    || return 1

	compare_dir $1 ${dir} ${DEST_DIR}/dev 555 MAKEDEV
}

#
#	motd
#
additem motd "contents of motd"
do_motd()
{
	[ -n "$1" ] || err 3 "USAGE: do_motd  fix|check"

	if grep 'http://www.NetBSD.org/Misc/send-pr.html' ${DEST_DIR}/etc/motd \
	    >/dev/null 2>&1
	then
		tmp1=$(mktemp /tmp/postinstall.motd.XXXXXXXX)
		tmp2=$(mktemp /tmp/postinstall.motd.XXXXXXXX)
		sed '1,2d' <${SRC_DIR}/etc/motd >${tmp1}
		sed '1,2d' <${DEST_DIR}/etc/motd >${tmp2}

		if [ "$1" = check ]; then
			cmp -s ${tmp1} ${tmp2}
			result=$?
			if [ ${result} -ne 0 ]; then
				msg "bug reporting messages do not seem to" \
				    "match the installed release"
			fi
		else
			head -n 2 ${DEST_DIR}/etc/motd >${tmp1}
			sed '1,2d' <${SRC_DIR}/etc/motd >>${tmp1}
			cp ${tmp1} ${DEST_DIR}/etc/motd
			result=0
		fi

		rm -f ${tmp1} ${tmp2}
	else
		result=0
	fi

	return ${result}
}

#
#	mtree
#
additem mtree "/etc/mtree/ being up to date"
do_mtree()
{
	[ -n "$1" ] || err 3 "USAGE: do_mtree  fix|check"

	compare_dir $1 ${SRC_DIR}/etc/mtree ${DEST_DIR}/etc/mtree 444 \
		NetBSD.dist special
}

#
#	named
#
additem named "named configuration update"
do_named()
{
	[ -n "$1" ] || err 3 "USAGE: do_named  fix|check"
	op=$1

	move_file ${op} \
		${DEST_DIR}/etc/namedb/named.conf \
		${DEST_DIR}/etc/named.conf
}

#
#	pam
#
additem pam "/etc/pam.d is populated"
do_pam()
{
	[ -n "$1" ] || err 3 "USAGE: do_pam  fix|check"
	op=$1
	failed=0

	populate_dir ${op} true ${SRC_DIR}/etc/pam.d ${DEST_DIR}/etc/pam.d 644 \
		README display_manager ftpd gdm imap kde login other passwd \
		pop3 ppp rexecd rsh sshd su system telnetd xdm xserver

	failed=$(( ${failed} + $? ))

	return ${failed}
}

#
#	periodic
#
additem periodic "/etc/{daily,weekly,monthly,security} being up to date"
do_periodic()
{
	[ -n "$1" ] || err 3 "USAGE: do_periodic  fix|check"

	compare_dir $1 ${SRC_DIR}/etc ${DEST_DIR}/etc 644 \
		daily weekly monthly security
}

#
#	pf
#
additem pf "pf configuration being up to date"
do_pf()
{
	[ -n "$1" ] || err 3 "USAGE: do_pf  fix|check"
	op=$1
	failed=0

	find_file_in_dirlist pf.os "pf.os" \
	    ${SRC_DIR}/dist/pf/etc ${SRC_DIR}/etc \
	    || return 1

	populate_dir ${op} true \
	    ${dir} ${DEST_DIR}/etc 644 \
	    pf.conf pf.os
	failed=$(( ${failed} + $? ))

	return ${failed}
}

#
#	postfix
#
additem postfix "/etc/postfix/ being up to date"
do_postfix()
{
	[ -n "$1" ] || err 3 "USAGE: do_postfix  fix|check"
	op=$1
	failed=0

	find_file_in_dirlist postfix-script "postfix scripts" \
	    ${SRC_DIR}/gnu/dist/postfix/conf \
	    ${DEST_DIR}/usr/share/examples/postfix \
	    || return 1

	compare_dir ${op} ${dir} ${DEST_DIR}/etc/postfix 555 \
	    post-install postfix-script
	failed=$(( ${failed} + $? ))
	compare_dir ${op} ${dir} ${DEST_DIR}/etc/postfix 444 postfix-files
	failed=$(( ${failed} + $? ))

	return ${failed}
}

#
#	rc
#
additem rc "/etc/rc* and /etc/rc.d/ being up to date"
do_rc()
{
	[ -n "$1" ] || err 3 "USAGE: do_rc  fix|check"
	op=$1
	failed=0

	compare_dir ${op} ${SRC_DIR}/etc ${DEST_DIR}/etc 644 \
		rc rc.subr rc.shutdown
	failed=$(( ${failed} + $? ))

	compare_dir ${op} ${SRC_DIR}/etc/rc.d ${DEST_DIR}/etc/rc.d 555 \
		DAEMON LOGIN NETWORKING SERVERS \
		accounting altqd amd apmd \
		bootconf.sh bootparams btconfig btdevctl bthcid \
		ccd cgd cleartmp cron \
		dhclient dhcpd dhcrelay dmesg downinterfaces \
		fixsb fsck ftpd \
		hostapd \
		identd ifwatchd inetd ipfilter ipfs ipmon ipnat ipsec \
		irdaattach iscsi_target isdnd \
		kdc \
		ldconfig lkm1 lkm2 lkm3 local lpd \
		mixerctl mopd motd mountall mountcritlocal mountcritremote \
		mountd moused mrouted \
		named ndbootd network newsyslog nfsd nfslocking ntpd ntpdate \
		pf pf_boot pflogd poffd postfix powerd ppp pwcheck \
		quota \
		racoon rpcbind raidframe raidframeparity rarpd rbootd root \
		route6d routed rtadvd rtclocaltime rtsold rwho \
		savecore screenblank sdpd securelevel sshd \
		staticroute swap1 swap2 sysctl sysdb syslogd \
		timed tpctl ttys \
		veriexec virecover wdogctl wpa_supplicant wscons wsmoused \
		xdm xfs \
		ypbind yppasswdd ypserv
	failed=$(( ${failed} + $? ))

		# check for obsolete rc.d files
	for f in NETWORK btcontrol fsck.sh kerberos nfsiod servers systemfs \
	    daemon gated login portmap sunndd xntpd; do
		fd=/etc/rc.d/${f}
		[ -e "${DEST_DIR}${fd}" ] && echo "${fd}"
	done | obsolete_paths ${op}
	failed=$(( ${failed} + $? ))

		# check for obsolete rc.conf(5) variables
	set --	amd amd_master \
		btcontrol btcontrol_devices \
		critical_filesystems critical_filesystems_beforenet \
		defcorename \
		ip6forwarding \
		mountcritlocal mountcritremote \
		network nfsiod_flags \
		sdpd sdpd_control \
		sdpd sdpd_groupname \
		sdpd sdpd_username \
		sysctl
	while [ $# -gt 1 ]; do
		if rcconf_is_set ${op} $1 $2 1; then
			failed=1
		fi
		shift 2
	done

	return ${failed}
}

#
#	sendmail
#
adddisableditem sendmail "remove obsolete sendmail configuration files and scripts"
do_sendmail()
{
	[ -n "$1" ] || err 3 "USAGE: do_sendmail  fix|check"
	op=$1
	failed=0

	# Don't complain if the "sendmail" package is installed because the
	# files might still be in use.
	if /usr/sbin/pkg_info -qe sendmail >/dev/null 2>&1
	then
		return 0
	fi

	for f in /etc/mail/helpfile /etc/mail/local-host-names \
	    /etc/mail/sendmail.cf /etc/mail/submit.cf /etc/rc.d/sendmail \
	    /etc/rc.d/smmsp /usr/share/misc/sendmail.hf \
	    $(find /usr/share/sendmail -type f) \
	    $(find /usr/share/sendmail -type d) /var/log/sendmail.st \
	    /var/spool/clientmqueue /var/spool/mqueue; do
		[ -e "${DEST_DIR}${f}" ] && echo "${f}"
	done | obsolete_paths ${op}
	failed=$(( ${failed} + $? ))

	return ${failed}
}

#
#	mailerconf
#
adddisableditem mailerconf "update /etc/mailer.conf after sendmail removal"
do_mailerconf()
{
	[ -n "$1" ] || err 3 "USAGE: do_mailterconf  fix|check"
	op=$1

	failed=0
	mta_path=$(awk '/^sendmail[ \t]/{print$2}' /etc/mailer.conf)
	old_sendmail_path="/usr/libexec/sendmail/sendmail"
	if [ "${mta_path}" = "${old_sendmail_path}" ]; then
	    if [ "$op" = check ]; then
		msg "mailer.conf points to obsolete ${old_sendmail_path}"
		failed=1;
	    else
		populate_dir ${op} false \
		${SRC_DIR}/etc ${DEST_DIR}/etc 644 mailer.conf
		failed=$?
	    fi
	fi
	
	return ${failed}
}

#
#	ssh
#
additem ssh "ssh configuration update"
do_ssh()
{
	[ -n "$1" ] || err 3 "USAGE: do_ssh  fix|check"
	op=$1

	failed=0
	_etcssh=${DEST_DIR}/etc/ssh
	if ! check_dir ${op} ${_etcssh} 755; then
		failed=1
	fi

	if [ ${failed} -eq 0 ]; then
		for f in \
			    ssh_known_hosts ssh_known_hosts2 \
			    ssh_host_dsa_key ssh_host_dsa_key.pub \
			    ssh_host_rsa_key ssh_host_rsa_key.pub \
			    ssh_host_key ssh_host_key.pub \
		    ; do
			if ! move_file ${op} \
			    ${DEST_DIR}/etc/${f} ${_etcssh}/${f} ;
			then
				failed=1
			fi
		done
		for f in sshd.conf ssh.conf ; do
				# /etc/ssh/ssh{,d}.conf -> ssh{,d}_config
				#
			if ! move_file ${op} \
			    ${_etcssh}/${f} ${_etcssh}/${f%.conf}_config ;
			then
				failed=1
			fi
				# /etc/ssh{,d}.conf -> /etc/ssh/ssh{,d}_config
				#
			if ! move_file ${op} \
			    ${DEST_DIR}/etc/${f} ${_etcssh}/${f%.conf}_config ;
			then
				failed=1
			fi
		done
	fi

	sshdconf=""
	for f in \
	    ${_etcssh}/sshd_config \
	    ${_etcssh}/sshd.conf \
	    ${DEST_DIR}/etc/sshd.conf ; do
		if [ -f "${f}" ]; then
			sshdconf=${f}
			break
		fi
	done
	if [ -n "${sshdconf}" ]; then
		modify_file ${op} "${sshdconf}" "${SCRATCHDIR}/sshdconf" '
			/^[^#$]/ {
				kw = tolower($1)
				if (kw == "hostkey" &&
				    $2 ~ /^\/etc\/+ssh_host(_[dr]sa)?_key$/ ) {
					sub(/\/etc\/+/, "/etc/ssh/")
				}
				if (kw == "rhostsauthentication" ||
				    kw == "verifyreversemapping" ||
				    kw == "reversemappingcheck") {
					sub(/^/, "# DEPRECATED:\t")
				}
			}
			{ print }
		'
		failed=$(( ${failed} + $? ))
	fi

	if ! find_file_in_dirlist moduli "moduli" \
	    ${SRC_DIR}/crypto/dist/ssh ${SRC_DIR}/etc ; then
		failed=1
	elif ! compare_dir ${op} ${dir} ${DEST_DIR}/etc 444 moduli; then
		failed=1
	fi

	if ! check_dir "${op}" "${DEST_DIR}/var/chroot/sshd" 755 ; then
		failed=1
	fi

	if rcconf_is_set ${op} sshd sshd_conf_dir 1; then
		failed=1
	fi

	return ${failed}
}

#
#	wscons
#
additem wscons "wscons configuration file update"
do_wscons()
{
	[ -n "$1" ] || err 3 "USAGE: do_wscons  fix|check"
	op=$1

	[ -f ${DEST_DIR}/etc/wscons.conf ] || return 0

	failed=0
	notfixed=""
	if [ "${op}" = "fix" ]; then
		notfixed=${NOT_FIXED}
	fi
	while read _type _arg1 _rest; do
		if [ "${_type}" = "mux" -a "${_arg1}" = "1" ]; then
			msg \
    "Obsolete wscons.conf(5) entry \""${_type} ${_arg1}"\" found.${notfixed}"
			failed=1
		fi
	done < ${DEST_DIR}/etc/wscons.conf

	return ${failed}
}

#
#	X11
#
additem x11 "x11 configuration update"
do_x11()
{
	[ -n "$1" ] || err 3 "USAGE: do_x11  fix|check"
	op=$1

	failed=0
	_etcx11=${DEST_DIR}/etc/X11
	if [ ! -d "${_etcx11}" ]; then
		msg "${_etcx11} is not a directory; skipping check"
		return 1
	fi
	_libx11=${DEST_DIR}/usr/X11R6/lib/X11
	if [ ! -d "${_libx11}" ]; then
		msg "${_libx11} is not a directory; skipping check"
		return 1
	fi

	_notfixed=""
	if [ "${op}" = "fix" ]; then
		_notfixed=${NOT_FIXED}
	fi

	for d in \
		    fs lbxproxy proxymngr rstart twm xdm xinit xserver xsm \
	    ; do
		sd=${_libx11}/${d}
		ld=/etc/X11/${d}
		td=${DEST_DIR}${ld}
		if [ -h ${sd} ]; then
			continue
		elif [ -d ${sd} ]; then
			tdfiles=$(find ${td} \! -type d)
			if [ -n "${tdfiles}" ]; then
				msg "${sd} exists yet ${td} already" \
				    "contains files${_notfixed}"
			else
				msg "Migrate ${sd} to ${td}${_notfixed}"
			fi
			failed=1
		elif [ -e ${sd} ]; then
			msg "Unexpected file ${sd}${_notfixed}"
			continue
		else
			continue
		fi
	done

	return ${failed}
}

#
#	uid
#
additem uid "required users in /etc/master.passwd"
do_uid()
{
	[ -n "$1" ] || err 3 "USAGE: do_uid  fix|check"

	check_ids $1 users "${DEST_DIR}/etc/master.passwd" \
	    named ntpd postfix sshd _pflogd _rwhod _proxy _sdpd
}


#
#	varrwho
#
additem varrwho "required ownership of files in /var/rwho"
do_varrwho()
{
	[ -n "$1" ] || err 3 "USAGE: do_varrwho  fix|check"

	contents_owner $1 "${DEST_DIR}/var/rwho" _rwhod _rwhod
}


#
#	obsolete
#	(this item is last to allow other items to move obsolete files)
#
additem obsolete "remove obsolete file sets and minor libraries"
do_obsolete()
{
	[ -n "$1" ] || err 3 "USAGE: do_obsolete  fix|check"
	op=$1
	failed=0

	sort -ru ${DEST_DIR}/var/db/obsolete/* | obsolete_paths ${op}
	failed=$(( ${failed} + $? ))

	(
		obsolete_libs /lib
		obsolete_libs /usr/lib
		obsolete_libs /usr/lib/i18n
		obsolete_libs /usr/X11R6/lib
	) | obsolete_paths ${op}
	failed=$(( ${failed} + $? ))

	return ${failed}
}


#
#	end of items
#	------------
#


usage()
{
	cat 1>&2 << _USAGE_
Usage: ${PROGNAME} [-s srcdir] [-d destdir] [-m mach] [-a arch] op [item [...]]
	Perform post-installation checks and/or fixes on a system's
	configuration files.
	If no items are provided, a default set of checks or fixes is applied.

	Options:
	-s {srcdir|tgzfile|tempdir}
			Location of the source files.  This may be any
			of the following:
			* A directory that contains a NetBSD source tree;
			* A distribution set file such as "etc.tgz" or
			  "xetc.tgz", or a colon-separated list of such
			  files;
			* A temporary directory in which one or both of
			  "etc.tgz" and "xetc.tgz" have been extracted.
							[${SRC_DIR:-/}]
	-d destdir	Destination directory to check. [${DEST_DIR:-/}]
	-m mach		MACHINE.			[${MACHINE}]
	-a arch		MACHINE_ARCH.			[${MACHINE_ARCH}]

	Operation may be one of:
		help	Display this help.
		list	List available items.
		check	Perform post-installation checks on items.
		diff [diff(1) options ...]
			Similar to 'check' but also output difference of files.
		fix	Apply fixes that 'check' determines need to be applied.
		usage	Display this usage.
_USAGE_
	exit 2
}


list()
{
	echo "Default set of items (to apply if no items are provided by user):"
	echo "  Item          Description"
	echo "  ----          -----------"
	for i in ${defaultitems}; do
		eval desc="\${desc_${i}}"
		printf "  %-12s  %s\n" "${i}" "${desc}"
	done
	echo "Items disabled by default (must be requested explicitely):"
	echo "  Item          Description"
	echo "  ----          -----------"
	for i in ${otheritems}; do
		eval desc="\${desc_${i}}"
		printf "  %-12s  %s\n" "${i}" "${desc}"
	done

}


main()
{
	while getopts s:d:m:a: ch; do
		case ${ch} in
		s)
			if [ -f "${OPTARG%%:*}" ]; then
				# arg refers to a *.tgz file, or a
				# colon-separated list.
				# This may happen twice, for both etc.tgz and
				# xetc.tgz, so we build up a colon-separated
				# list in TGZLIST.
				TGZLIST="${TGZLIST}${TGZLIST:+:}${OPTARG}"
				SRC_ARG="${TGZLIST}"
			elif [ -d "${OPTARG}" ]; then
				SRC_ARG=${OPTARG}
			else
				err 2 "Invalid argument for -s option"
			fi
			;;
		d)
			DEST_DIR=${OPTARG}
			;;
		m)
			MACHINE=${OPTARG}
			;;
		a)
			MACHINE_ARCH=${OPTARG}
			;;
		*)
			usage
			;;
		esac
	done
	shift $((${OPTIND} - 1))
	[ $# -gt 0 ] || usage

	#
	# If '-s etc.tgz' or '-s etc.tgz:xetc.tgz', extract tgz files
	# to a scratch directory.
	#
	if [ -n "${TGZLIST}" ]; then
		ETCTGZDIR="${SCRATCHDIR}/etc.tgz"
		echo "Note: -s ${SRC_ARG} is a file,"
		echo "      temporarily extracting to ${ETCTGZDIR}"
		if ! mkdir "${ETCTGZDIR}"; then
			err 2 "Can't create ${ETCTGZDIR}"
		fi
		oldIFS="${IFS}"
		IFS=:
		for tgz in ${TGZLIST}; do
			cat "${tgz}" | (
				cd "${ETCTGZDIR}" &&
				tar -zxf -
			) || err 2 "Can't extract ${tgz}"
		done
		IFS="${oldIFS}"
		SRC_DIR="${ETCTGZDIR}"
	else
		SRC_DIR="${SRC_ARG}"
	fi

	[ -d "${SRC_DIR}" ]	|| err 2 "${SRC_DIR} is not a directory"
	[ -d "${DEST_DIR}" ]	|| err 2 "${DEST_DIR} is not a directory"
	[ -n "${MACHINE}" ]	|| err 2 "\${MACHINE} is not defined"
	[ -n "${MACHINE_ARCH}" ] || err 2 "\${MACHINE_ARCH} is not defined"

		# If directories are /, clear them, so various messages
		# don't have leading "//".   However, this requires
		# the use of ${foo:-/} to display the variables.
		#
	[ "${SRC_DIR}" = "/" ]	&& SRC_DIR=""
	[ "${DEST_DIR}" = "/" ]	&& DEST_DIR=""

	op=$1
	shift

	case "${op}" in
	diff)
		op=check
		DIFF_STYLE=n			# default style is RCS
		OPTIND=1
		while getopts bcenpuw ch; do
			case ${ch} in
			c|e|n|u)
				if [ ${DIFF_STYLE} != n -a \
				    ${DIFF_STYLE} != ${ch} ]; then
					err 2 "conflicting output style: ${ch}"
				fi
				DIFF_STYLE=${ch}
				;;
			b|p|w)
				DIFF_OPT="${DIFF_OPT} -${ch}"
				;;
			*)
				err 2 "unknown diff option"
				;;
			esac
		done
		shift $((${OPTIND} - 1))
		;;
	esac

	case "${op}" in

	usage|help)
		usage
		;;

	list)
		echo "Source directory: ${SRC_DIR:-/}"
		echo "Target directory: ${DEST_DIR:-/}"
		if [ "${SRC_DIR}" != "${SRC_ARG}" ]; then
			echo " (extracted from: ${SRC_ARG})"
		fi
		list
		;;

	check|fix)
		todo="$@"
		: ${todo:=${defaultitems}}

		# ensure that all supplied items are valid
		#
		for i in ${todo}; do
			eval desc=\"\${desc_${i}}\"
			[ -n "${desc}" ] || err 2 "Unsupported ${op} '"${i}"'"
		done

		# perform each check/fix
		#
		echo "Source directory: ${SRC_DIR:-/}"
		if [ "${SRC_DIR}" != "${SRC_ARG}" ]; then
			echo " (extracted from: ${SRC_ARG})"
		fi
		echo "Target directory: ${DEST_DIR:-/}"
		items_passed=
		items_failed=
		for i in ${todo}; do
			echo "${i} ${op}:"
			( eval do_${i} ${op} )
			if [ $? -eq 0 ]; then
				items_passed="${items_passed} ${i}"
			else
				items_failed="${items_failed} ${i}"
			fi
		done

		if [ "${op}" = "check" ]; then
			plural="checks"
		else
			plural="fixes"
		fi

		echo "${PROGNAME} ${plural} passed:${items_passed}"
		echo "${PROGNAME} ${plural} failed:${items_failed}"
		if [ -n "${items_failed}" ]; then
		    exitstatus=1;
		    if [ "${op}" = "check" ]; then
			[ "$MACHINE" = "$(uname -m)" ] && m= || m=" -m $MACHINE"
			cat <<_Fix_me_
To fix, run:
    ${0} -s ${SRC_ARG} -d ${DEST_DIR:-/}$m fix${items_failed}
_Fix_me_
		    fi
		fi

		;;
		
	*)
		warn "Unknown operation '"${op}"'"
		usage
		;;

	esac
}

# defaults
#
PROGNAME=${0##*/}
SRC_ARG="/usr/src"
DEST_DIR="/"
: ${MACHINE:=$( uname -m )}	# assume native build if $MACHINE is not set
: ${MACHINE_ARCH:=$( uname -p )}# assume native build if not set

DIFF_STYLE=
NOT_FIXED=" (FIX MANUALLY)"
SCRATCHDIR=$( mkdtemp ) || err 2 "Can't create scratch directory"
trap "/bin/rm -rf ${SCRATCHDIR} ; exit 0" 1 2 3 15	# HUP INT QUIT TERM

umask 022
exec 3>/dev/null
exec 4>/dev/null
exitstatus=0

main "$@"
/bin/rm -rf ${SCRATCHDIR}
exit $exitstatus
