#!/bin/sh # # $NetBSD: postinstall,v 1.76.2.16.2.2 2009/05/14 00:26:09 snj Exp $ # # Copyright (c) 2002-2008 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. # # 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 # # This script is executed as part of a cross build. Allow the build # environment to override the locations of some tools. : ${AWK:=awk} : ${FGREP:=fgrep} : ${GREP:=grep} : ${MAKE:=make} # # 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}" } # Quote args to make them safe in the shell. # Usage: quotedlist="$(shell_quote args...)" # # After building up a quoted list, use it by evaling it inside # double quotes, like this: # eval "set -- $quotedlist" # or like this: # eval "\$command $quotedlist \$filename" shell_quote() { local result='' local arg for arg in "$@" ; do # Append a space if necessary result="${result}${result:+ }" # Convert each embedded ' to '\'', # then insert ' at the beginning of the first line, # and append ' at the end of the last line. result="${result}$(printf "%s\n" "$arg" | \ sed -e "s/'/'\\\\''/g" -e "1s/^/'/" -e "\$s/\$/'/")" done printf "%s\n" "$result" } # 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 explicitly. # 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 } } ' "$@" < "${_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 "$@"; 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" "$@" } # 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= # first dir in list 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 \ | ${FGREP} -x "${_basename}" >/dev/null \ || 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" ( if [ ! -e "${DEST_DIR}/${dir}" ] then return 0 fi 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 } # get_makevar var [var ...] # Retrieve the value of a user-settable system make variable get_makevar() { $SOURCEMODE || err 3 "get_makevar must be used in source mode" [ $# -eq 0 ] && err 3 "USAGE: get_makevar var [var ...]" for _var in "$@"; do _value="$(echo '.include <bsd.own.mk>' | \ ${MAKE} -f - -V "${_var}")" eval ${_var}=\"${_value}\" done } # detect_x11 # Detect if X11 components should be analysed and set values of # relevant variables. detect_x11() { if $SOURCEMODE; then get_makevar MKX11 X11ROOTDIR else if [ -f "${SRC_DIR}/etc/mtree/set.xetc" ]; then MKX11=yes X11ROOTDIR=/this/value/isnt/used/yet else MKX11=no X11ROOTDIR= fi 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 btattach.conf btdevctl.conf failed=$(( ${failed} + $? )) move_file "${op}" "${DEST_DIR}/var/db/btdev.xml" \ "${DEST_DIR}/var/db/btdevctl.plist" failed=$(( ${failed} + $? )) return ${failed} } # # ddbonpanic # additem ddbonpanic "verify ddb.onpanic is configured in sysctl.conf" do_ddbonpanic() { [ -n "$1" ] || err 3 "USAGE: do_ddbonpanic fix|check" if ${GREP} -E '^#*[[:space:]]*ddb\.onpanic[[:space:]]*\??=[[:space:]]*[[:digit:]]+' \ "${DEST_DIR}/etc/sysctl.conf" >/dev/null 2>&1 then result=0 else if [ "$1" = check ]; then msg \ "The ddb.onpanic behaviour is not explicitly specified in /etc/sysctl.conf" result=1 else echo >> "${DEST_DIR}/etc/sysctl.conf" sed < "${SRC_DIR}/etc/sysctl.conf" \ -e '/^ddb\.onpanic/q' | \ sed -e '1,/^$/d' >> \ "${DEST_DIR}/etc/sysctl.conf" result=$? fi fi return ${result} } # # 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 # ${dir} is set by find_file_in_dirlist() compare_dir "$op" "${dir}" "${DEST_DIR}/etc/defaults" 444 pf.boot.conf failed=$(( ${failed} + $? )) return ${failed} } # # dhcpcd # additem dhcpcd "dhcpcd configuration is up to date" do_dhcpcd() { [ -n "$1" ] || err 3 "USAGE: do_dhcpcd fix|check" op="$1" failed=0 find_file_in_dirlist dhcpcd.conf "dhcpcd.conf" \ "${SRC_DIR}/external/bsd/dhcpcd/dist" "${SRC_DIR}/etc" || return 1 # ${dir} is set by find_file_in_dirlist() populate_dir "$op" true "${dir}" "${DEST_DIR}/etc" 644 dhcpcd.conf failed=$(( ${failed} + $? )) return ${failed} } # # envsys # additem envsys "envsys configuration is up to date" do_envsys() { [ -n "$1" ] || err 3 "USAGE: do_envsys fix|check" op="$1" failed=0 populate_dir "$op" true "${SRC_DIR}/etc" "${DEST_DIR}/etc" 644 \ envsys.conf failed=$(( ${failed} + $? )) populate_dir "$op" true "${SRC_DIR}/etc/powerd/scripts" \ "${DEST_DIR}/etc/powerd/scripts" 555 sensor_battery \ sensor_drive sensor_fan sensor_indicator sensor_power \ sensor_resistance sensor_temperature sensor_voltage 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 _timedc \ _sdpd _httpd } # # 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 # ${dir} is set by find_file_in_dirlist() compare_dir "$1" "${dir}" "${DEST_DIR}/dev" 555 MAKEDEV find_file_in_dirlist MAKEDEV.local "MAKEDEV.local" \ "${SRC_DIR}/etc" "${SRC_DIR}/dev" \ || return 1 # ${dir} is set by find_file_in_dirlist() compare_dir "$1" "${dir}" "${DEST_DIR}/dev" 555 MAKEDEV.local } # # motd # additem motd "contents of motd" do_motd() { [ -n "$1" ] || err 3 "USAGE: do_motd fix|check" if ${GREP} -i 'http://www.NetBSD.org/Misc/send-pr.html' \ "${DEST_DIR}/etc/motd" >/dev/null 2>&1 \ || ${GREP} -i 'http://www.NetBSD.org/support/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" compare_dir "${op}" "${SRC_DIR}/etc/namedb" "${DEST_DIR}/etc/namedb" \ 644 \ root.cache } # # nosa # additem nosa "verify scheduler activation support is configured in sysctl.conf" do_nosa() { [ -n "$1" ] || err 3 "USAGE: do_nosa fix|check" if ${GREP} -E '^#*[[:space:]]*kern\.no_sa_support[[:space:]]*\??=[[:space:]]*[[:digit:]]+' \ "${DEST_DIR}/etc/sysctl.conf" >/dev/null 2>&1 then result=0 else if [ "$1" = check ]; then msg \ "SA support is not explicitly specified in /etc/sysctl.conf" result=1 else echo >> "${DEST_DIR}/etc/sysctl.conf" sed < "${SRC_DIR}/etc/sysctl.conf" \ -e '/^kern\.no_sa_support/q' | \ sed -e '1,/^$/d' >> \ "${DEST_DIR}/etc/sysctl.conf" result=$? fi fi return ${result} } # # 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 # ${dir} is set by find_file_in_dirlist() populate_dir "${op}" true \ "${dir}" "${DEST_DIR}/etc" 644 \ pf.conf failed=$(( ${failed} + $? )) compare_dir "${op}" "${dir}" "${DEST_DIR}/etc" 444 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 # ${dir} is set by find_file_in_dirlist() 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 generated_scripts="" if [ "${MKX11}" != "no" ]; then generated_scripts="${generated_scripts} xdm xfs" fi compare_dir "${op}" "${SRC_DIR}/etc" "${DEST_DIR}/etc" 644 \ rc rc.subr rc.shutdown failed=$(( ${failed} + $? )) if ! $SOURCEMODE; then extra_scripts="${generated_scripts}" else extra_scripts="" fi 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 btattach btconfig btdevctl bthcid \ ccd cgd cleartmp cron \ dhclient dhcpd dhcrelay dmesg downinterfaces envsys \ fsck ftp_proxy ftpd \ hostapd httpd \ 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 \ perusertmp pf pf_boot pflogd poffd postfix powerd ppp pwcheck \ quota \ racoon rpcbind raidframe raidframeparity rarpd rbootd rndctl \ 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 \ ypbind yppasswdd ypserv \ ${extra_scripts} failed=$(( ${failed} + $? )) if $SOURCEMODE && [ -n "${generated_scripts}" ]; then # generate scripts mkdir "${SCRATCHDIR}/rc" for f in ${generated_scripts}; do sed -e "s,@X11ROOTDIR@,${X11ROOTDIR},g" \ < "${SRC_DIR}/etc/rc.d/${f}.in" \ > "${SCRATCHDIR}/rc/${f}" done compare_dir "${op}" "${SCRATCHDIR}/rc" \ "${DEST_DIR}/etc/rc.d" 555 \ ${generated_scripts} failed=$(( ${failed} + $? )) fi # check for obsolete rc.d files for f in NETWORK btcontrol btuartd 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 # ${dir} is set by find_file_in_dirlist() 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 0 fi _libx11="${DEST_DIR}/usr/X11R6/lib/X11" if [ ! -d "${_libx11}" ]; then msg "${_libx11} is not a directory; skipping check" return 0 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 _timedc \ _sdpd _httpd } # # 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_libs /usr/X11R7/lib [ "$MACHINE" = "amd64" ] && obsolete_libs /usr/lib/i386 [ "$MACHINE" = "sparc64" ] && obsolete_libs /usr/lib/sparc ) | 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 explicitly):" echo " Item Description" echo " ---- -----------" for i in ${otheritems}; do eval desc=\"\${desc_${i}}\" printf " %-12s %s\n" "${i}" "${desc}" done } main() { TGZLIST= # quoted list list of tgz files SRC_ARGLIST= # quoted list of one or more "-s" args N_SRC_ARGS=0 # number of "-s" args TGZMODE=false # true if "-s" specifies a tgz file DIRMODE=false # true if "-s" specified a directory SOURCEMODE=false # true if "-s" specified a source directory while getopts s:d:m:a: ch; do case "${ch}" in s) qarg="$(shell_quote "${OPTARG}")" N_SRC_ARGS=$(( $N_SRC_ARGS + 1 )) SRC_ARGLIST="${SRC_ARGLIST}${SRC_ARGLIST:+ }-s ${qarg}" if [ -f "${OPTARG}" ]; then # arg refers to a *.tgz file. # This may happen twice, for both # etc.tgz and xetc.tgz, so we build up a # quoted list in TGZLIST. TGZMODE=true TGZLIST="${TGZLIST}${TGZLIST:+ }${qarg}" # Note that, when TGZMODE is true, # SRC_ARG is used only for printing # human-readable messages. SRC_ARG="${TGZLIST}" elif [ -d "${OPTARG}" ]; then # arg refers to a directory. # It might be a source directory, or a # directory where the sets have already # been extracted. DIRMODE=true SRC_ARG="${OPTARG}" if [ -f "${OPTARG}/etc/Makefile" ]; then SOURCEMODE=true fi elif [ -f "${OPTARG%%:*}" -a -f "${OPTARG##*:}" \ -a ! -f "${OPTARG}" ] then # Backward compatibility: allow arg to refer # to a colon-separated list of tgz files. # Remove this after NetBSD-5.0 is released. cat >&2 <<EOF *** WARNING: The "-s tgzfile1:tgzfile2" option is deprecated. Please use "-s tgzfile1 -s tgzfile2" in future. EOF TGZMODE=true TGZLIST="${TGZLIST}${TGZLIST:+ }$( \ IFS=: eval shell_quote \${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 [ "$N_SRC_ARGS" -gt 1 ] && $DIRMODE; then err 2 "Multiple -s args are allowed only with tgz files" fi if [ "$N_SRC_ARGS" -eq 0 ]; then # The default SRC_ARG was set elsewhere DIRMODE=true SOURCEMODE=true SRC_ARGLIST="-s $(shell_quote "${SRC_ARG}")" fi # # If '-s' arg or args specified tgz files, extract them # to a scratch directory. # if $TGZMODE; then ETCTGZDIR="${SCRATCHDIR}/etc.tgz" echo "Note: Creating temporary directory ${ETCTGZDIR}" if ! mkdir "${ETCTGZDIR}"; then err 2 "Can't create ${ETCTGZDIR}" fi ( # subshell to localise changes to "$@" eval "set -- ${TGZLIST}" for tgz in "$@"; do echo "Note: Extracting files from ${tgz}" cat "${tgz}" | ( cd "${ETCTGZDIR}" && tar -zxf - ) || err 2 "Can't extract ${tgz}" done ) 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 ! $SOURCEMODE && ! [ -f "${SRC_DIR}/etc/mtree/set.etc" ]; then err 2 "Files from the etc.tgz set are missing" fi # 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="" detect_x11 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} ${SRC_ARGLIST} -d ${DEST_DIR:-/}$m fix${items_failed} Note that this may overwrite local changes. _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