#!/bin/sh # # $NetBSD: postinstall,v 1.44 2002/12/30 13:16:43 tron 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 # - de* -> tlp* migration (/etc/ifconfig.de*, $ifconfig_de*, # dhclient.conf, ...) ? # - support quiet/verbose mode ? # - differentiate between failures caused by missing source # and real failures # # # 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 _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 } # 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="${fs} != ${fd}" 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 op 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 3 ] || err 2 "USAGE: rcconf_is_set op name var" _rcis_op=$1 _rcis_name=$2 _rcis_var=$3 _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 if eval "[ -n \"\${${_rcis_var}}\" \ -o \"\${${_rcis_var}-UNSET}\" != \"UNSET\" ]"; then msg \ "Obsolete rc.conf(5) variable '\$${_rcis_var}' found.${_rcis_notfixed}" exit 0 else exit 1 fi ) } # find_file_in_dirlist() file dir1 [...] -- # find which directory file is in, and sets ${dir} to match # returns 0 if matched, otherwise 1 (and sets ${dir} to ""). # find_file_in_dirlist() { [ $# -ge 2 ] || err 2 "USAGE: find_file_in_dirlist file dir1 [...]" _file=$1 ; shift _dir1st= for dir in $*; do : ${_dir1st:=${dir}} if [ -f "${dir}/${_file}" ]; then if [ "${_dir1st}" != "${dir}" ]; then msg "Checking from ${dir} instead of ${_dir1st}" fi return 0 fi done msg "Can't find source directory" return 1 } # # 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 } # # 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 } # # 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 cgd 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 mixerctl named ndbootd network newsyslog nfsd \ nfslocking ntpd ntpdate poffd postfix ppp pwcheck \ quota racoon rpcbind raidframe raidframeparity rarpd rbootd \ root route6d routed rtadvd rtsold rwho savecore \ screenblank sendmail securelevel sshd swap1 swap2 \ sysdb sysctl syslogd timed ttys virecover \ wdogctl wscons wsmoused \ xdm xfs ypbind yppasswdd ypserv failed=$(( ${failed} + $? )) # check for obsolete rc.d files for f in NETWORK fsck.sh kerberos nfsiod servers systemfs \ daemon gated login portmap sunndd xntpd; 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 ${op} $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 dirsrc=${SRC_DIR}/crypto/dist/ssh diretc=${SRC_DIR}/etc modulidir= if [ -f "${dirsrc}"/moduli ]; then modulidir=$dirsrc elif [ -f "${diretc}"/moduli ]; then modulidir=$diretc msg "Checking for moduli from ${modulidir} instead of ${dirsrc}" else msg "Can't find source directory for etc/moduli" failed=1 fi if [ -n "${modulidir}" ]; then if ! compare_dir ${op} ${modulidir} \ ${DEST_DIR}/etc 444 moduli; then failed=1 fi fi if ! check_dir "${op}" "${DEST_DIR}/var/chroot/sshd" 755 ; then failed=1 fi if rcconf_is_set ${op} 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 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} } # # makedev # additem makedev "/dev/MAKEDEV being up to date" do_makedev() { [ -n "$1" ] || err 2 "USAGE: do_makedev fix|check" find_file_in_dirlist MAKEDEV \ ${SRC_DIR}/etc/etc.${MACHINE} ${SRC_DIR}/dev \ || return 1 compare_dir $1 ${dir} ${DEST_DIR}/dev 555 MAKEDEV || return 1 } # # postfix # additem postfix "/etc/postfix/ being up to date" do_postfix() { [ -n "$1" ] || err 2 "USAGE: do_postfix fix|check" op=$1 failed=0 find_file_in_dirlist postfix-script \ ${SRC_DIR}/gnu/dist/postfix/conf \ ${DEST_DIR}/usr/share/examples/postfix \ || return 1 compare_dir ${op} ${dir} ${DEST_DIR}/etc/postfix 555 postfix-script failed=$(( ${failed} + $? )) compare_dir ${op} ${dir} \ ${DEST_DIR}/etc/postfix 444 post-install postfix-files failed=$(( ${failed} + $? )) return ${failed} } # # obsolete # additem obsolete "obsolete file sets" do_obsolete() { [ -n "$1" ] || err 2 "USAGE: do_obsolete fix|check" op=$1 setdir=${SRC_DIR}/distrib/sets makeobs=${setdir}/makeobsolete if [ ! -x "${makeobs}" ]; then warn "Can't find program \"${makeobs}\"" return 1 fi ( cd ${setdir} && ./makeobsolete -s . -t ${SCRATCHDIR} ) failed=0 for obssrcfile in ${SCRATCHDIR}/*_obsolete; do while read ofile; do ofile=${DEST_DIR}${ofile#.} cmd="rm" ftype="file" if [ -h "${ofile}" ]; then ftype="link" elif [ -d "${ofile}" ]; then ftype="directory" cmd="rmdir" elif [ ! -e "${ofile}" ]; then continue 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 < ${obssrcfile} done return ${failed} } # # 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" # 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 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}" if [ -n "${items_failed}" -a "${op}" = "check" ]; then cat <<_Fix_me_ To fix, run: ${0} -s ${SRC_DIR} -d ${DEST_DIR:-/} fix${items_failed} _Fix_me_ fi ;; *) 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="/" : ${MACHINE:=$( uname -m )} # assume native build if $MACHINE is not set NOT_FIXED=" [NOT FIXED]" SCRATCHDIR=$( mkdtemp ) || err 1 "Can't create scratch directory" trap "/bin/rm -rf ${SCRATCHDIR} ; exit 0" 0 1 2 3 15 # EXIT HUP INT QUIT TERM umask 022 main "$@" exit 0