#!/bin/sh # # $NetBSD: postinstall,v 1.20 2002/06/27 02:26:39 itojun Exp $ # # Copyright (c) 2002 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 # - postfix config # - de* -> tlp* migration (/etc/ifconfig.de*, $ifconfig_de*, # dhclient.conf, ...) ? # - support quiet/verbose mode ? # - check obsolete file lists -- need to remove non obsolete files from # the sets first. # # # helper functions # err() { exitval=$1 shift echo 1>&2 "${PROGNAME}: $*" exit ${exitval} } warn() { echo 1>&2 "${PROGNAME}: $*" } msg() { echo " $*" } # additem item description # add item to list of supported items to check/fix # additem() { [ $# -eq 2 ] || err 2 "USAGE: additem item description" items="${items}${items:+ }$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 2 "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 2 "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 _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}:" $(echo ${_missing}) return 1 fi return 0 } # compare_dir op src dest mode file [file ...] # perform op ("check" or "fix") on files in src/ against dest/ # returns 0 if ok, 1 otherwise. # compare_dir() { [ $# -ge 5 ] || err 2 "USAGE: compare_dir op src dest mode file [...]" _op=$1 _src=$2 _dest=$3 _mode=$4 shift 4 _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 error="${fd} != ${fs}" else continue fi if [ "${_op}" = "check" ]; then msg ${error} _cmpdir_rv=1 elif ! 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} } # 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 2 "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 name var -- # 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. # rcconf_is_set() { [ $# -eq 2 ] || err 2 "USAGE: rcconf_is_set name var" _name=$1 _var=$2 ( for f in \ ${DEST_DIR}/etc/rc.conf \ ${DEST_DIR}/etc/rc.conf.d/${_name}; do [ -f "${f}" ] && . "${f}"; done if eval "[ -n \"\${${_var}}\" \ -o \"\${${_var}-UNSET}\" != \"UNSET\" ]"; then msg "Obsolete rc.conf(5) variable '\$${_var}' found." exit 0 else exit 1 fi ) } # # items # ----- # # # defaults # additem defaults "/etc/defaults being up to date" do_defaults() { [ -n "$1" ] || err 2 "USAGE: do_defaults fix|check" compare_dir $1 ${SRC_DIR}/etc/defaults ${DEST_DIR}/etc/defaults 444 \ daily.conf monthly.conf rc.conf security.conf weekly.conf } # # mtree # additem mtree "/etc/mtree being up to date" do_mtree() { [ -n "$1" ] || err 2 "USAGE: do_mtree fix|check" compare_dir $1 ${SRC_DIR}/etc/mtree ${DEST_DIR}/etc/mtree 444 \ NetBSD.dist special } # # gid # additem gid "required GIDs" do_gid() { [ -n "$1" ] || err 2 "USAGE: do_gid fix|check" check_ids $1 groups "${DEST_DIR}/etc/group" \ named ntpd sshd smmsp return $? } # # uid # additem uid "required UIDs" do_uid() { [ -n "$1" ] || err 2 "USAGE: do_uid fix|check" check_ids $1 users "${DEST_DIR}/etc/master.passwd" \ named ntpd sshd smmsp return $? } # # periodic # additem periodic "/etc/{daily,weekly,monthly,security} being up to date" do_periodic() { [ -n "$1" ] || err 2 "USAGE: do_periodic fix|check" compare_dir $1 ${SRC_DIR}/etc ${DEST_DIR}/etc 644 \ daily weekly monthly security } # # rc # additem rc "/etc/rc* and /etc/rc.d/ being up to date" do_rc() { [ -n "$1" ] || err 2 "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 bootparams bootconf.sh ccd cleartmp cron \ dhclient dhcpd dhcrelay dmesg downinterfaces fsck \ ifwatchd inetd ipfilter ipfs ipmon ipnat ipsec isdnd \ kdc ldconfig lkm1 lkm2 lkm3 local lpd mopd motd \ mountall mountcritlocal mountcritremote mountd moused \ mrouted named ndbootd network newsyslog nfsd \ nfslocking ntpd ntpdate poffd postfix ppp pwcheck \ quota racoon rpcbind raidframe rarpd rbootd root \ route6d routed rtadvd rtsold rwho savecore \ screenblank sendmail securelevel sshd swap1 swap2 \ sysdb sysctl syslogd timed ttys virecover wscons xdm \ xfs ypbind yppasswdd ypserv failed=$(( ${failed} + $? )) # check for obsolete rc.d files for f in NETWORK gated; do fd=${DEST_DIR}/etc/rc.d/${f} [ ! -e "${fd}" ] && continue if [ "${op}" = "check" ]; then msg "Remove obsolete ${fd}" failed=1 elif ! rm ${fd}; then msg "Can't remove obsolete ${fd}" failed=1 else msg "Removed obsolete ${fd}" fi done # check for obsolete rc.conf(5) variables set -- amd amd_master \ mountcritlocal critical_filesystems_beforenet \ mountcritremote critical_filesystems \ network ip6forwarding \ sysctl defcorename \ sysctl nfsiod_flags while [ $# -gt 1 ]; do if rcconf_is_set $1 $2; then failed=1 fi shift 2 done return ${failed} } # # ssh # additem ssh "ssh configuration update" do_ssh() { [ -n "$1" ] || err 2 "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 awk ' $1 ~ /^[Hh][Oo][Ss][Tt][Kk][Ee][Yy]$/ && $2 ~ /^\/etc\/+ssh_host(_[dr]sa)?_key$/ { sub(/\/etc\/+/, "/etc/ssh/"); } { print } ' < ${sshdconf} > ${SCRATCHDIR}/sshd_config if ! cmp -s ${sshdconf} ${SCRATCHDIR}/sshd_config; then diff ${sshdconf} ${SCRATCHDIR}/sshd_config > \ ${SCRATCHDIR}/sshd_config.diffs if [ "${op}" = "check" ]; then msg "${sshdconf} needs the following changes:" failed=1 elif ! cp -f ${SCRATCHDIR}/sshd_config ${sshdconf}; then msg "${sshdconf} changes not applied:" failed=1 else msg "${sshdconf} changes applied:" fi while read _line; do msg " ${_line}" done < ${SCRATCHDIR}/sshd_config.diffs fi fi if ! check_dir "${op}" "${DEST_DIR}/var/chroot/sshd" 755 ; then failed=1 fi if rcconf_is_set sshd sshd_conf_dir ; then failed=1 fi return ${failed} } # # wscons # additem wscons "wscons configuration file update" do_wscons() { [ -n "$1" ] || err 2 "USAGE: do_wscons fix|check" op=$1 [ -f ${DEST_DIR}/etc/wscons.conf ] || return 0 failed=0 while read _type _arg1 _rest; do if [ "${_type}" = "mux" -a "${_arg1}" = "1" ]; then msg \ "Obsolete wscons.conf(5) entry \""${_type} ${_arg1}"\" found." failed=1 fi done < ${DEST_DIR}/etc/wscons.conf return ${failed} } # # makedev # additem makedev "/dev/MAKEDEV being up to date" do_makedev() { [ -n "$1" ] || err 2 "USAGE: do_makedev fix|check" compare_dir $1 ${SRC_DIR}/etc/etc.${MACHINE} \ ${DEST_DIR}/dev 555 MAKEDEV if [ "${op}" = "fix" ]; then (cd ${DEST_DIR}/dev; /bin/sh ./MAKEDEV all) fi } # # end of items # ------------ # usage() { cat 1>&2 << _USAGE_ Usage: ${PROGNAME} [-s srcdir] [-d destdir] operation [item [...]] Perform post-installation checks and/or fixes on a system's configuration files. If no items are provided, all checks or fixes are applied. Options: -s srcdir Source directory to compare from. [${SRC_DIR}] -d destdir Destination directory to check. [${DEST_DIR}] Operation may be one of: help display this help list list available items check perform post-installation checks on items fix apply fixes that 'check' determines need to be applied usage display this usage _USAGE_ exit 1 } list() { echo "Supported items:" echo " Item Description" echo " ---- -----------" for i in ${items}; do eval desc="\${desc_${i}}" printf " %-12s %s\n" "${i}" "${desc}" done } main() { while getopts s:d: ch; do case ${ch} in s) SRC_DIR=${OPTARG} ;; d) DEST_DIR=${OPTARG} ;; *) usage ;; esac done shift $((${OPTIND} - 1)) [ $# -gt 0 ] || usage [ -d "${SRC_DIR}" ] || err 1 "${SRC_DIR} is not a directory" [ -d "${DEST_DIR}" ] || err 1 "${DEST_DIR} is not a directory" op=$1 shift case "${op}" in usage|help) usage ;; list) echo "Source directory: ${SRC_DIR}" echo "Target directory: ${DEST_DIR}" list ;; check|fix) todo=$* : ${todo:=${items}} # ensure that all supplied items are valid # for i in ${todo}; do eval desc=\"\${desc_${i}}\" [ -n "${desc}" ] || err 1 "Unsupported ${op} '"${i}"'" done # perform each check/fix # echo "Source directory: ${SRC_DIR}" 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}" ;; *) warn "Unknown operation '"${op}"'" usage ;; esac } mkdtemp () { # Make sure we don't loop forever if mkdir will always fail. [ -d /tmp ] || err 1 /tmp is not a directory [ -w /tmp ] || err 1 /tmp is not writeable _base=/tmp/_postinstall.$$ _serial=0 while true; do _dir=${_base}.${_serial} mkdir -m 0700 ${_dir} && break _serial=$((${_serial} + 1)) done echo ${_dir} } # defaults # PROGNAME=${0##*/} SRC_DIR="/usr/src" DEST_DIR="/" SCRATCHDIR=$( mkdtemp ) || err 1 "Can't create scratch directory" # assume native build if $MACHINE is not set if [ -z "${MACHINE}" ]; then MACHINE=`/usr/bin/uname -m` fi trap "/bin/rm -rf ${SCRATCHDIR} ; exit 0" 0 1 2 3 15 # EXIT HUP INT QUIT TERM umask 022 main $* exit 0