NetBSD/etc/postinstall

1307 lines
29 KiB
Bash
Executable File

#!/bin/sh
#
# $NetBSD: postinstall,v 1.88 2005/02/24 04:06:53 lukem 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
# - 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?
# - allow installation with "etc.tgz" as source file ?
# - differentiate between "needs fix" versus "can't fix" issues
# - deprecate etc_release now that it's part of the base set ?
#
#
# 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
}
# 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 2 "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 $only_new; 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 2 "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 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 [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 2 "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 2 "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 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 $?
}
# 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 2 "USAGE: obsolete_paths fix|check"
op=$1
failed=0
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
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 2 "USAGE: obsolete_paths dir"
dir=$1
(
cd "${DEST_DIR}/${dir}" || exit 1
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 2 "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}
}
#
# items
# -----
#
#
# postinstall
#
additem postinstall "/etc/postinstall being up to date"
do_postinstall()
{
[ -n "$1" ] || err 2 "USAGE: do_postinstall fix|check"
compare_dir $1 ${SRC_DIR}/etc ${DEST_DIR}/etc 555 \
postinstall
}
#
# release
#
additem etc_release "/etc/release being up to date"
do_etc_release()
{
[ -n "$1" ] || err 2 "USAGE: do_etc_release fix|check"
if [ -f "${SRC_DIR}/etc/Makefile" -a \
! -f "${SRC_DIR}/etc/release" ] ; then
msg "etc/release is built by ${SRC_DIR}/etc/Makefile; skipping ${op}"
return 0
fi
compare_dir $1 ${SRC_DIR}/etc ${DEST_DIR}/etc 555 \
release
}
#
# 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 authpf
}
#
# 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 fixsb fsck \
identd 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 pf pflogd poffd postfix powerd ppp \
pwcheck quota racoon rpcbind raidframe raidframeparity rarpd \
rbootd root route6d routed rtadvd rtclocaltime rtsold rwho \
savecore screenblank sendmail securelevel smmsp sshd \
staticroute swap1 swap2 sysdb sysctl syslogd timed tpctl ttys \
veriexec 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=/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 \
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 1; then
failed=1
fi
shift 2
done
return ${failed}
}
#
# pam
#
# disabled for now
additem pam "/etc/pam.d being up to date"
do_pam()
{
[ -n "$1" ] || err 2 "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 ftpd gdm imap kde login other passwd \
pop3 rexecd rsh sshd su system telnetd xdm
failed=$(( ${failed} + $? ))
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
modify_file ${op} "${sshdconf}" "${SCRATCHDIR}/sshdconf" '
$1 ~ /^[Hh][Oo][Ss][Tt][Kk][Ee][Yy]$/ &&
$2 ~ /^\/etc\/+ssh_host(_[dr]sa)?_key$/ {
sub(/\/etc\/+/, "/etc/ssh/");
}
{ 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}
}
#
# X11
#
additem x11 "x11 configuration update"
do_x11()
{
[ -n "$1" ] || err 2 "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}
}
#
# 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"
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
}
#
# 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 "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 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 and minor libraries"
do_obsolete()
{
[ -n "$1" ] || err 2 "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}
}
#
# sendmail
#
additem sendmail "sendmail configuration being up to date"
do_sendmail()
{
[ -n "$1" ] || err 2 "USAGE: do_sendmail fix|check"
_op=$1
#
# check owner and mode of sendmail (and "root" setting)
# check .cf file version
# check submit.cf existence (and version)
# uid and gid are checked elsewhere
# look at clientmqueue's owner, group, and mode
#
failed=0
_etcmail="${DEST_DIR}/etc/mail"
_sendmailcf="${_etcmail}/sendmail.cf"
_submitcf="${_etcmail}/submit.cf"
_sample_sendmailcf="${DEST_DIR}/usr/share/sendmail/cf/netbsd-proto.cf"
_sample_submitcf="${DEST_DIR}/usr/share/sendmail/cf/netbsd-msp.cf"
_sendmail=${DEST_DIR}/usr/libexec/sendmail/sendmail
#
# check for "root" setting first, so that checks can be
# adjusted accordingly
#
_notfixed=""
_root=$(rcconf_is_set ${_op} sendmail sendmail_suidroot 3>&1 1>&4) 4>&1
if [ $? != 0 ]; then
if [ "${_op}" = "fix" ]; then
_notfixed=${NOT_FIXED}
fi
msg "sendmail_suidroot variable not set (assuming \`\`no'')${_notfixed}"
_root=NO
failed=1
fi
# normalize the "root" setting
_root=$(. ${DEST_DIR}/etc/rc.subr
warn() {} # eliminate complaints
if checkyesno _root; then
echo true
else
echo false
fi)
if "${_root}"; then
_smownerfmt="%p %Su"
_smownermode="4555"
_smownerreq="root"
else
_smownerfmt="%p %Su:%Sg"
_smownermode="2555"
_smownerreq="root:smmsp"
fi
# check that owner and mode match what it required
_notfixed=""
if ! stat "${_op}" "${_smownerfmt}" "${_sendmail}" \
"10${_smownermode} ${_smownerreq}"; then
if [ "${_op}" = "fix" ]; then
_notfixed=${NOT_FIXED}
if chown "${_smownerreq}" "${_sendmail}" 2>/dev/null &&
chmod "${_smownermode}" "${_sendmail}" 2>/dev/null
then
_notfixed=" [ FIXED ]"
else
failed=1
fi
else
_notfixed=""
failed=1
fi
msg "${_sendmail} binary has wrong owner/mode${_notfixed}"
fi
_notfixed=""
if [ ! -f "${_sendmailcf}" ]; then
if [ "${_op}" = "fix" ]; then
_notfixed=${NOT_FIXED}
if cp "${_sample_sendmailcf}" \
"${_sendmailcf}" 2>/dev/null; then
_notfixed=" [ FIXED ]"
else
failed=1
fi
fi
msg "${_sendmailcf} is missing${_notfixed}"
fi
if [ ! -f "${_sendmailcf}" ]; then
_cfversion="10" # pretend it's okay
else
_cfversion=$(sed -n 's/^V *\([0-9]*\).*/\1/p' "${_sendmailcf}")
fi
_notfixed=""
if [ "${_cfversion}" -lt 10 ]; then
# XXX no fix here
if [ "${_op}" = "fix" ]; then
_notfixed=${NOT_FIXED}
fi
msg "Version of ${_sendmailcf} is ${_cfversion}," \
"should be 10${_notfixed}"
failed=1
fi
_notfixed=""
if $_root; then
if [ -f "${_submitcf}" ]; then
if [ "${_op}" = "fix" ]; then
_notfixed=${NOT_FIXED}
if rm -f "${_submitcf}" 2>/dev/null; then
_notfixed=" [ FIXED ]"
else
failed=1
fi
else
_notfixed=""
failed=1
fi
msg "${_submitcf} exists${_notfixed}"
fi
_checkcq=false
else
if [ ! -f "${_submitcf}" ]; then
_notfixed=""
if [ "${_op}" = "fix" ]; then
_notfixed=${NOT_FIXED}
if cp "${_sample_submitcf}" \
"${_submitcf}" 2>/dev/null; then
_notfixed=" [ FIXED ]"
else
failed=1
fi
fi
msg "${_submitcf} is missing${_notfixed}"
fi
_checkcq=true
fi
_cqueuemode="770"
_cqueueowner="smmsp:smmsp"
if "${_checkcq}"; then
if [ -f "${_submitcf}" ]; then
_cqueuepath=$(awk 'match($0,"^O *QueueDirectory=") {
print(substr($0, RSTART+RLENGTH))
}' "${_submitcf}")
else
_cqueuepath="/var/spool/clientmqueue"
fi
_cqueuefmt="%.1Sp %Lp %Su:%Sg"
else
_cqueuepath="/"
_cqueuefmt="d ${_cqueuemode} ${_cqueueowner}"
fi
_notfixed=""
if ! stat "${_op}" "${_cqueuefmt}" "${_cqueuepath}" \
"d ${_cqueuemode} ${_cqueueowner}"; then
if [ "${_op}" = "fix" ]; then
_notfixed=${NOT_FIXED}
mkdir -p "${_cqueuepath}" &&
chown "${_cqueueowner}" "${_cqueuepath}" &&
chmod "${_cqueuemode}" "${_cqueuepath}" &&
_notfixed=" [ FIXED ]" ||
failed=1
else
failed=1
fi
msg "Client queue ${_cqueuepath} has wrong owner/mode${_notfixed}"
fi
return ${failed}
}
#
# hosts
#
additem hosts "/etc/hosts being up to date"
do_hosts()
{
[ -n "$1" ] || err 2 "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 $?
}
#
# 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, all checks or fixes are applied.
Options:
-s srcdir Source directory to compare from.
This can either be src/etc or
an extracted copy of "etc.tgz". [${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 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:m:a: ch; do
case ${ch} in
s)
SRC_DIR=${OPTARG}
;;
d)
DEST_DIR=${OPTARG}
;;
m)
MACHINE=${OPTARG}
;;
a)
MACHINE_ARCH=${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"
[ -n "${MACHINE}" ] || err 1 "\${MACHINE} is not defined"
[ -n "${MACHINE_ARCH}" ] || err 1 "\${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 1 "conflicting output style: ${ch}"
fi
DIFF_STYLE=${ch}
;;
b|p|w)
DIFF_OPT="${DIFF_OPT} -${ch}"
;;
*)
err 1 "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:-/}"
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
[ "$MACHINE" = "$(uname -m)" ] && m= || m=" -m $MACHINE"
cat <<_Fix_me_
To fix, run:
${0} -s ${SRC_DIR} -d ${DEST_DIR:-/}$m 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 writable
_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
: ${MACHINE_ARCH:=$( uname -p )}# assume native build if not set
DIFF_STYLE=
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
exec 3>/dev/null
exec 4>/dev/null
main "$@"
exit 0