#!/bin/bash # fixfiles # # Script to restore labels on a SELinux box # # Copyright (C) 2004-2013 Red Hat, Inc. # Authors: Dan Walsh # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA set -o nounset # # seclabel support was added in 2.6.30. This function will return a positive # number if the current kernel version is greater than 2.6.30, a negative # number if the current is less than 2.6.30 and 0 if they are the same. # function useseclabel { VER=`uname -r` SUP=2.6.30 expr '(' "$VER" : '\([^.]*\)' ')' '-' '(' "$SUP" : '\([^.]*\)' ')' '|' \ '(' "$VER.0" : '[^.]*[.]\([^.]*\)' ')' '-' '(' "$SUP.0" : '[^.]*[.]\([^.]*\)' ')' '|' \ '(' "$VER.0.0" : '[^.]*[.][^.]*[.]\([^.]*\)' ')' '-' '(' "$SUP.0.0" : '[^.]*[.][^.]*[.]\([^.]*\)' ')' } # # Get all mount points that support labeling. Use the 'seclabel' field if it # is available. Else fall back to known fs types which likely support xattrs # and we know were not context mounted. # get_all_labeled_mounts() { FS="`cat /proc/self/mounts | sort | uniq | awk '{print $2}'`" for i in $FS; do if [ `useseclabel` -ge 0 ] then grep -F " $i " /proc/self/mounts | awk '{print $4}' | grep -E --silent '(^|,)seclabel(,|$)' && echo $i else grep -F " $i " /proc/self/mounts | grep -v "context=" | grep -E --silent '(ext[234]| ext4dev | gfs2 | xfs | jfs | btrfs )' && echo $i fi done } get_rw_labeled_mounts() { FS=`get_all_labeled_mounts | sort | uniq` for i in $FS; do grep -F " $i " /proc/self/mounts | awk '{print $4}' | grep -E --silent '(^|,)rw(,|$)' && echo $i done } get_ro_labeled_mounts() { FS=`get_all_labeled_mounts | sort | uniq` for i in $FS; do grep -F " $i " /proc/self/mounts | awk '{print $4}' | grep -E --silent '(^|,)ro(,|$)' && echo $i done } SELINUX_CONFDIRS="/etc/selinux /usr/lib/selinux" selinux_resolve() { for d in $SELINUX_CONFDIRS; do if [ -e "$d/$1" ]; then echo "$d/$1" return 0 fi done return 1 } exclude_dirs_from_relabelling() { local f exclude_from_relabelling= f=$(selinux_resolve fixfiles_exclude_dirs) || return 0 while read -r i || [ -n "$i" ] do # skip blank line and comment # skip not absolute path # skip not directory [ -z "${i}" ] && continue [[ "${i}" =~ ^[[:blank:]]*# ]] && continue [[ ! "${i}" =~ ^/.* ]] && continue [[ ! -d "${i}" ]] && continue exclude_from_relabelling="$exclude_from_relabelling -e $i" done < "$f" echo "$exclude_from_relabelling" } # # Set global Variables # fullFlag=0 BOOTTIME="" VERBOSE="-p" FORCEFLAG="" DIGESTFLAG="" THREADS="" RPMFILES="" PREFC="" RESTORE_MODE="" BIND_MOUNT_FILESYSTEMS="" SETFILES=/sbin/setfiles RESTORECON=/sbin/restorecon FILESYSTEMSRW=`get_rw_labeled_mounts` FILESYSTEMSRO=`get_ro_labeled_mounts` SELINUXTYPE="targeted" SELINUXCONFIG=$(selinux_resolve config) if [ -n "$SELINUXCONFIG" ]; then . "$SELINUXCONFIG" fi FC=$(selinux_resolve "${SELINUXTYPE}/contexts/files/file_contexts") if [ -z "$FC" ]; then FC=/etc/security/selinux/file_contexts fi # # Log all Read Only file systems # LogReadOnly() { if [ ! -z "$FILESYSTEMSRO" ]; then echo "Warning: Skipping the following R/O filesystems:" echo "$FILESYSTEMSRO" fi } # # Log directories excluded from relabelling by configuration file # LogExcluded() { for i in ${EXCLUDEDIRS//-e / }; do echo "skipping the directory $i" done } # # Drop mount points that are excluded, or that sit under an excluded # directory, from the list printed and passed to setfiles. -e is still # passed so the same paths are skipped if reached from a parent mount. # filesystems_to_relabel() { local m e skip result result= for m in ${FILESYSTEMSRW}; do skip= for e in ${EXCLUDEDIRS//-e /}; do e="${e%/}" [ -z "$e" ] && e=/ case "$m" in "$e"|"$e"/*) skip=1 break ;; esac done [ -n "$skip" ] && continue result="${result} ${m}" done echo "${result# }" } # # Find files newer then the passed in date and fix the label # newer() { DATE=$1 shift LogReadOnly for m in `echo $FILESYSTEMSRW`; do find $m -mount -newermt $DATE -print0 2>/dev/null | ${RESTORECON} ${FORCEFLAG} ${DIGESTFLAG} ${VERBOSE} ${THREADS} $* -i -0 -f - done; } # # Compare PREVious File Context to currently installed File Context and # run restorecon on all files affected by the differences. # diff_filecontext() { EXCLUDEDIRS="`exclude_dirs_from_relabelling`" for i in /sys /proc /mnt /var/tmp /var/lib/BackupPC /home /root /tmp; do [ -e $i ] && EXCLUDEDIRS="${EXCLUDEDIRS} -e $i"; done LogExcluded if [ -f ${PREFC} -a -x /usr/bin/diff ]; then TEMPFILE=`mktemp` test -z "$TEMPFILE" && exit PREFCTEMPFILE=`mktemp` sed -r -e 's,:s0, ,g' $PREFC | sort -u > ${PREFCTEMPFILE} sed -r -e 's,:s0, ,g' $FC | sort -u | /usr/bin/diff -b ${PREFCTEMPFILE} - | grep '^[<>]'|cut -c3-| grep ^/ | grep -Ev '(^/home|^/root|^/tmp)' | sed -r -e 's,[[:blank:]].*,,g' \ -e 's|\(([/[:alnum:]]+)\)\?|{\1,}|g' \ -e 's|([/[:alnum:]])\?|{\1,}|g' \ -e 's|\?.*|*|g' \ -e 's|\{.*|*|g' \ -e 's|\(.*|*|g' \ -e 's|\[.*|*|g' \ -e 's|\.\*.*|*|g' \ -e 's|\.\+.*|*|g' | # These two sorts need to be separate commands sort -u | sort -d | while read pattern ; do if ! echo "$pattern" | grep -q -f ${TEMPFILE} 2>/dev/null; then echo "$pattern"; case "$pattern" in *"*") echo "$pattern" | sed -e 's,^,^,' -e 's,\*$,,g' >> ${TEMPFILE};; esac; fi; done | ${RESTORECON} ${VERBOSE} ${EXCLUDEDIRS} ${FORCEFLAG} ${DIGESTFLAG} ${THREADS} $* -i -R -f -; rm -f ${TEMPFILE} ${PREFCTEMPFILE} fi } rpmlist() { rpm -q --qf '[%{FILESTATES} %{FILENAMES}\n]' "$1" | grep '^0 ' | cut -f2- -d ' ' [ ${PIPESTATUS[0]} != 0 ] && echo "$1 not found" >/dev/stderr } # unmount tmp bind mount before exit umount_TMP_MOUNT() { if [ -n "$TMP_MOUNT" ]; then umount "${TMP_MOUNT}${m}" || exit 130 rm -rf "${TMP_MOUNT}" || echo "Error cleaning up." fi exit 130 } fix_labels_on_mountpoint() { test -z ${TMP_MOUNT+x} && echo "Unable to find temporary directory!" && exit 1 mkdir -p "${TMP_MOUNT}${m}" || exit 1 mount --bind "${m}" "${TMP_MOUNT}${m}" || exit 1 ${SETFILES} ${VERBOSE} ${EXCLUDEDIRS} ${FORCEFLAG} ${DIGESTFLAG} ${THREADS} $* -q ${FC} -r "${TMP_MOUNT}" "${TMP_MOUNT}${m}" umount "${TMP_MOUNT}${m}" || exit 1 rm -rf "${TMP_MOUNT}" || echo "Error cleaning up." } export -f fix_labels_on_mountpoint # # restore # if called with -n will only check file context # restore () { OPTION=$1 shift # [-B | -N time ] if [ -n "$BOOTTIME" ]; then newer $BOOTTIME $* return fi # -C PREVIOUS_FILECONTEXT if [ "$RESTORE_MODE" == PREFC ]; then diff_filecontext $* return fi [ -x /usr/sbin/genhomedircon ] && /usr/sbin/genhomedircon EXCLUDEDIRS="`exclude_dirs_from_relabelling`" LogExcluded case "$RESTORE_MODE" in RPMFILES) for i in `echo "$RPMFILES" | sed 's/,/ /g'`; do rpmlist $i | ${RESTORECON} ${VERBOSE} ${EXCLUDEDIRS} ${FORCEFLAG} ${DIGESTFLAG} ${THREADS} $* -i -R -f - done ;; FILEPATH) ${RESTORECON} ${VERBOSE} ${EXCLUDEDIRS} ${FORCEFLAG} ${DIGESTFLAG} ${THREADS} $* -R -- "$FILEPATH" ;; *) if [ -n "${FILESYSTEMSRW}" ]; then LogReadOnly FILESYSTEMS_TO_RELABEL="`filesystems_to_relabel`" if [ -z "${FILESYSTEMS_TO_RELABEL}" ]; then echo >&2 "fixfiles: No suitable file systems found" else echo "${OPTION}ing `echo ${FILESYSTEMS_TO_RELABEL}`" if [ -z "$BIND_MOUNT_FILESYSTEMS" ]; then ${SETFILES} ${VERBOSE} ${EXCLUDEDIRS} ${FORCEFLAG} ${DIGESTFLAG} $* -q ${THREADS} ${FC} ${FILESYSTEMS_TO_RELABEL} else # we bind mount so we can fix the labels of files that have already been # mounted over for m in `echo $FILESYSTEMS_TO_RELABEL`; do TMP_MOUNT="$(mktemp -p /run -d fixfiles.XXXXXXXXXX)" export SETFILES VERBOSE EXCLUDEDIRS FORCEFLAG DIGESTFLAG THREADS FC TMP_MOUNT m if type unshare &> /dev/null; then unshare -m bash -c "fix_labels_on_mountpoint $*" || exit $? else trap umount_TMP_MOUNT EXIT fix_labels_on_mountpoint $* trap EXIT fi done; fi fi else echo >&2 "fixfiles: No suitable file systems found" fi if [ ${OPTION} != "Relabel" ]; then return fi ;; esac } fullrelabel() { echo "Cleaning out /tmp" find /tmp/ -mindepth 1 -delete restore Relabel } relabel() { if [ -n "$RESTORE_MODE" -a "$RESTORE_MODE" != DEFAULT ]; then usage exit 1 fi if [ $fullFlag == 1 ]; then fullrelabel return fi echo -n " Files in the /tmp directory may be labeled incorrectly, this command can remove all files in /tmp. If you choose to remove files from /tmp, a reboot will be required after completion. Do you wish to clean out the /tmp directory [N]? " read answer if [ "$answer" = y -o "$answer" = Y ]; then fullrelabel else restore Relabel fi } process() { # # Make sure they specified one of the three valid commands # case "$1" in restore) restore Relabel;; check) VERBOSE="-v"; restore Check -n;; verify) VERBOSE="-v"; restore Verify -n;; relabel) relabel;; onboot) if [ -n "$RESTORE_MODE" -a "$RESTORE_MODE" != DEFAULT ]; then usage exit 1 fi > /.autorelabel || exit $? [ -z "$FORCEFLAG" ] || echo -n "$FORCEFLAG " >> /.autorelabel [ -z "$DIGESTFLAG" ] || echo -n "$DIGESTFLAG " >> /.autorelabel [ -z "$BOOTTIME" ] || echo -n "-N $BOOTTIME " >> /.autorelabel [ -z "$BIND_MOUNT_FILESYSTEMS" ] || echo -n "-M " >> /.autorelabel [ -z "$THREADS" ] || echo -n "$THREADS " >> /.autorelabel # Force full relabel if SELinux is not enabled selinuxenabled || echo -F > /.autorelabel echo "System will relabel on next boot" ;; *) usage exit 1 esac } usage() { echo $""" Usage: $0 [-v] [-F] [-I|-D] [-M] [-f] [-T nthreads] relabel or Usage: $0 [-v] [-F] [-I|-D] [-B | -N time ] [-T nthreads] { check | restore | verify } or Usage: $0 [-v] [-F] [-I|-D] [-T nthreads] { check | restore | verify } dir/file ... or Usage: $0 [-v] [-F] [-I|-D] [-T nthreads] -R rpmpackage[,rpmpackage...] { check | restore | verify } or Usage: $0 [-v] [-F] [-I|-D] [-T nthreads] -C PREVIOUS_FILECONTEXT { check | restore | verify } or Usage: $0 [-F] [-I|-D] [-M] [-B] [-T nthreads] onboot """ } if [ $# -eq 0 ]; then usage exit 1 fi set_restore_mode() { if [ -n "$RESTORE_MODE" ]; then # can't specify two different modes usage exit 1 fi RESTORE_MODE="$1" } # See how we were called. while getopts "N:BC:FIDfR:l:vMT:" i; do case "$i" in B) BOOTTIME=`/bin/who -b | awk '{print $3}'` set_restore_mode DEFAULT ;; N) BOOTTIME=$OPTARG set_restore_mode BOOTTIME ;; R) RPMFILES=$OPTARG set_restore_mode RPMFILES ;; C) PREFC=$OPTARG set_restore_mode PREFC ;; v) VERBOSE="-v" ;; l) # Old scripts use obsolete option `-l logfile` echo "Redirecting output to $OPTARG" exec >>"$OPTARG" 2>&1 ;; M) BIND_MOUNT_FILESYSTEMS="-M" ;; F) FORCEFLAG="-F" ;; I) DIGESTFLAG="-I" ;; D) [ -z "$DIGESTFLAG" ] && DIGESTFLAG="-D" ;; f) fullFlag=1 ;; T) THREADS="-T $OPTARG" ;; *) usage exit 1 esac done # Move out processed options from arguments shift $(( OPTIND - 1 )) # Check for the command if [ $# -eq 0 ]; then usage exit 1 fi command="$1" # Move out command from arguments shift if [ $# -gt 0 ]; then set_restore_mode FILEPATH while [ $# -gt 0 ]; do FILEPATH="$1" process "$command" || exit $? shift done else process "$command" fi