1#!/bin/bash
2# fixfiles
3#
4# Script to restore labels on a SELinux box
5#
6# Copyright (C) 2004-2013 Red Hat, Inc.
7# Authors: Dan Walsh <dwalsh@redhat.com>
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
23#
24# seclabel support was added in 2.6.30.  This function will return a positive
25# number if the current kernel version is greater than 2.6.30, a negative
26# number if the current is less than 2.6.30 and 0 if they are the same.
27#
28function useseclabel {
29	VER=`uname -r`
30	SUP=2.6.30
31	expr '(' "$VER" : '\([^.]*\)' ')' '-' '(' "$SUP" : '\([^.]*\)' ')' '|' \
32	     '(' "$VER.0" : '[^.]*[.]\([^.]*\)' ')' '-' '(' "$SUP.0" : '[^.]*[.]\([^.]*\)' ')' '|' \
33	     '(' "$VER.0.0" : '[^.]*[.][^.]*[.]\([^.]*\)' ')' '-' '(' "$SUP.0.0" : '[^.]*[.][^.]*[.]\([^.]*\)' ')'
34}
35
36#
37# Get all mount points that support labeling.  Use the 'seclabel' field if it
38# is available.  Else fall back to known fs types which likely support xattrs
39# and we know were not context mounted.
40#
41get_all_labeled_mounts() {
42FS="`cat /proc/self/mounts | sort | uniq | awk '{print $2}'`"
43for i in $FS; do
44	if [ `useseclabel` -ge 0 ]
45	then
46		grep " $i " /proc/self/mounts | awk '{print $4}' | egrep --silent '(^|,)seclabel(,|$)' && echo $i
47	else
48		grep " $i " /proc/self/mounts | grep -v "context=" | egrep --silent '(ext[234]| ext4dev | gfs2 | xfs | jfs | btrfs )' && echo $i
49	fi
50done
51}
52
53get_rw_labeled_mounts() {
54FS=`get_all_labeled_mounts | sort | uniq`
55for i in $FS; do
56	grep " $i " /proc/self/mounts | awk '{print $4}' | egrep --silent '(^|,)rw(,|$)' && echo $i
57done
58}
59
60get_ro_labeled_mounts() {
61FS=`get_all_labeled_mounts | sort | uniq`
62for i in $FS; do
63	grep " $i " /proc/self/mounts | awk '{print $4}' | egrep --silent '(^|,)ro(,|$)' && echo $i
64done
65}
66
67#
68# Get the default label returned from the kernel for a file with a lable the
69# kernel does not understand
70#
71get_undefined_type() {
72	SELINUXMNT=`grep selinuxfs /proc/self/mountinfo | head -1 | awk '{ print $5 }'`
73	cat ${SELINUXMNT}/initial_contexts/unlabeled | secon -t
74}
75
76#
77# Get the default label for a file without a label
78#
79get_unlabeled_type() {
80	SELINUXMNT=`grep selinuxfs /proc/self/mountinfo | head -1 | awk '{ print $5 }'`
81	cat $SELINUXMNT/initial_contexts/file | secon -t
82}
83
84exclude_dirs_from_relabelling() {
85    exclude_from_relabelling=
86    if [ -e /etc/selinux/fixfiles_exclude_dirs ]
87    then
88	while read i
89	do
90	  # skip blank line and comment
91	  # skip not absolute path
92	  # skip not directory
93	  [ -z "${i}" ] && continue
94	  [[ "${i}" =~ "^[[:blank:]]*#" ]] && continue
95	  [[ ! "${i}" =~ ^/.* ]] && continue
96	  [[ ! -d "${i}" ]] && continue
97	  exclude_from_relabelling="$exclude_from_relabelling -e $i"
98	  logit "skipping the directory $i"
99	done < /etc/selinux/fixfiles_exclude_dirs
100    fi
101    echo "$exclude_from_relabelling"
102}
103
104exclude_dirs() {
105    exclude=
106    for i in /sys /proc /dev /run /mnt /var/tmp /var/lib/BackupPC /home /tmp /dev; do
107	[ -e $i ]  && exclude="$exclude -e $i";
108    done
109    exclude="$exclude `exclude_dirs_from_relabelling`"
110    echo "$exclude"
111}
112
113#
114# Set global Variables
115#
116fullFlag=0
117BOOTTIME=""
118VERBOSE="-p"
119FORCEFLAG=""
120DIRS=""
121RPMILES=""
122LOGFILE=`tty`
123if [ $? != 0 ]; then
124    LOGFILE="/dev/null"
125fi
126LOGGER=/usr/sbin/logger
127SETFILES=/sbin/setfiles
128RESTORECON=/sbin/restorecon
129FILESYSTEMSRW=`get_rw_labeled_mounts`
130FILESYSTEMSRO=`get_ro_labeled_mounts`
131FILESYSTEMS="$FILESYSTEMSRW $FILESYSTEMSRO"
132SELINUXTYPE="targeted"
133if [ -e /etc/selinux/config ]; then
134    . /etc/selinux/config
135    FC=/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts
136else
137    FC=/etc/security/selinux/file_contexts
138fi
139
140#
141# Log to either syslog or a LOGFILE
142#
143logit () {
144if [ -n $LOGFILE ]; then
145    echo $1 >> $LOGFILE
146fi
147}
148#
149# Find files newer then the passed in date and fix the label
150#
151newer() {
152    DATE=$1
153    for m in `echo $FILESYSTEMSRW`; do
154	find $m -mount -newermt $DATE -print0 2>/dev/null | ${RESTORECON} ${FORCEFLAG} ${VERBOSE} -i -0 -f -
155    done;
156
157}
158
159#
160# Compare PREVious File Context to currently installed File Context and
161# run restorecon on all files affected by the differences.
162#
163diff_filecontext() {
164if [ -f ${PREFC} -a -x /usr/bin/diff ]; then
165	TEMPFILE=`mktemp ${FC}.XXXXXXXXXX`
166	test -z "$TEMPFILE" && exit
167	PREFCTEMPFILE=`mktemp ${PREFC}.XXXXXXXXXX`
168	sed -r -e 's,:s0, ,g' $PREFC | sort -u > ${PREFCTEMPFILE}
169	sed -r -e 's,:s0, ,g' $FC | sort -u | \
170	/usr/bin/diff -b ${PREFCTEMPFILE} - | \
171	    grep '^[<>]'|cut -c3-| grep ^/ | \
172	    egrep -v '(^/home|^/root|^/tmp|^/dev)' |\
173	sed -r -e 's,[[:blank:]].*,,g' \
174	       -e 's|\(([/[:alnum:]]+)\)\?|{\1,}|g' \
175	       -e 's|([/[:alnum:]])\?|{\1,}|g' \
176	       -e 's|\?.*|*|g' \
177	       -e 's|\{.*|*|g' \
178	       -e 's|\(.*|*|g' \
179	       -e 's|\[.*|*|g' \
180	       -e 's|\.\*.*|*|g' \
181	       -e 's|\.\+.*|*|g' | \
182	    # These two sorts need to be separate commands \
183	sort -u | \
184	sort -d | \
185	while read pattern ; \
186	    do if ! echo "$pattern" | grep -q -f ${TEMPFILE} 2>/dev/null; then \
187		  echo "$pattern"; \
188		  case "$pattern" in *"*") \
189		       echo "$pattern" | sed -e 's,^,^,' -e 's,\*$,,g' >> ${TEMPFILE};;
190		  esac; \
191	       fi; \
192	    done | \
193	${RESTORECON} ${VERBOSE} -i -f - -R `exclude_dirs`; \
194	rm -f ${TEMPFILE} ${PREFCTEMPFILE}
195fi
196}
197#
198# Log all Read Only file systems
199#
200LogReadOnly() {
201if [ ! -z "$FILESYSTEMSRO" ]; then
202    logit "Warning: Skipping the following R/O filesystems:"
203    logit "$FILESYSTEMSRO"
204fi
205}
206
207rpmlist() {
208rpm -q --qf '[%{FILESTATES} %{FILENAMES}\n]' "$1" | grep '^0 ' | cut -f2- -d ' '
209[ ${PIPESTATUS[0]} != 0 ] && echo "$1 not found" >/dev/stderr
210}
211
212#
213# restore
214# if called with -n will only check file context
215#
216restore () {
217OPTION=$1
218shift
219
220if [ ! -z "$PREFC" ]; then
221    diff_filecontext $*
222    exit $?
223fi
224if [ ! -z "$BOOTTIME" ]; then
225    newer $BOOTTIME
226    exit $?
227fi
228[ -x /usr/sbin/genhomedircon ] && /usr/sbin/genhomedircon
229LogReadOnly
230#
231exclude_dirs="`exclude_dirs_from_relabelling $OPTION`"
232if [ -n "${exclude_dirs}" ]
233then
234	TEMPFCFILE=`mktemp ${FC}.XXXXXXXXXX`
235	test -z "$TEMPFCFILE" && exit
236	/bin/cp -p ${FC} ${TEMPFCFILE} &>/dev/null || exit
237	tmpdirs=${tempdirs//-e/}
238	for p in ${tmpdirs}
239	do
240		p="${p%/}"
241		p1="${p}(/.*)? -- <<none>>"
242		echo "${p1}" >> $TEMPFCFILE
243		logit "skipping the directory ${p}"
244	done
245FC=$TEMPFCFILE
246fi
247if [ ! -z "$RPMFILES" ]; then
248    for i in `echo "$RPMFILES" | sed 's/,/ /g'`; do
249	rpmlist $i | ${RESTORECON} $exclude_dirs ${FORCEFLAG} ${VERBOSE} $* -R -i -f - 2>&1 | cat >> $LOGFILE
250    done
251    exit $?
252fi
253if [ ! -z "$FILEPATH" ]; then
254    ${RESTORECON} $exclude_dirs ${FORCEFLAG} ${VERBOSE} -R $* $FILEPATH 2>&1 | cat >> $LOGFILE
255    return
256fi
257if [  -n "${FILESYSTEMSRW}" ]; then
258    echo "${OPTION}ing `echo ${FILESYSTEMSRW}`"
259    ${SETFILES} ${VERBOSE} $exclude_dirs -q ${FORCEFLAG} $* ${FC} ${FILESYSTEMSRW} 2>&1 | cat >> $LOGFILE
260else
261    echo >&2 "fixfiles: No suitable file systems found"
262fi
263if [ ${OPTION} != "Relabel" ]; then
264    return
265fi
266echo "Cleaning up labels on /tmp"
267rm -rf /tmp/gconfd-* /tmp/pulse-* /tmp/orbit-* $TEMPFCFILE
268
269UNDEFINED=`get_undefined_type` || exit $?
270UNLABELED=`get_unlabeled_type` || exit $?
271find /tmp \( -context "*:${UNLABELED}*" -o -context "*:${UNDEFINED}*" \) \( -type s -o -type p \) -delete
272find /tmp \( -context "*:${UNLABELED}*" -o -context "*:${UNDEFINED}*" \) -exec chcon --reference /tmp {} \;
273find /var/tmp \( -context "*:${UNLABELED}*" -o -context "*:${UNDEFINED}*" \) -exec chcon --reference /var/tmp {} \;
274find /var/run \( -context "*:${UNLABELED}*" -o -context "*:${UNDEFINED}*" \) -exec chcon --reference /var/run {} \;
275[ ! -e /var/lib/debug ] || find /var/lib/debug \( -context "*:${UNLABELED}*" -o -context "*:${UNDEFINED}*" \) -exec chcon --reference /lib {} \;
276exit 0
277}
278
279fullrelabel() {
280    logit "Cleaning out /tmp"
281    find /tmp/ -mindepth 1 -delete
282    LogReadOnly
283    restore Relabel
284}
285
286relabel() {
287    if [ ! -z "$RPMFILES" ]; then
288	restore Relabel
289    fi
290
291    if [ $fullFlag == 1  ]; then
292	fullrelabel
293    fi
294
295    echo -n "
296    Files in the /tmp directory may be labeled incorrectly, this command
297    can remove all files in /tmp.  If you choose to remove files from /tmp,
298    a reboot will be required after completion.
299
300    Do you wish to clean out the /tmp directory [N]? "
301    read answer
302    if [ "$answer" = y -o  "$answer" = Y ]; then
303	fullrelabel
304    else
305	restore Relabel
306    fi
307}
308
309process() {
310#
311# Make sure they specified one of the three valid commands
312#
313case "$1" in
314    restore) restore Relabel;;
315    check) VERBOSE="-v"; restore Check -n;;
316    verify) restore Verify -n -o -;;
317    relabel) relabel;;
318    onboot)
319	> /.autorelabel
320	[ -z "$FORCEFLAG" ] || echo -n "$FORCEFLAG " >> /.autorelabel
321	[ -z "$BOOTTIME" ] || echo -N $BOOTTIME >> /.autorelabel
322	# Force full relabel if / does not have a label on it
323	getfilecon / > /dev/null 2>&1  || echo -F >/.autorelabel
324	echo "System will relabel on next boot"
325	;;
326    *)
327    usage
328    exit 1
329esac
330}
331usage() {
332	echo $"""
333Usage: $0 [-v] [-F]  [-N time ] [-l logfile ] { check | restore| [-f] relabel | verify } [[dir/file] ... ]
334or
335Usage: $0 [-v] [-F] -R rpmpackage[,rpmpackage...] [-l logfile ] { check | restore | verify }
336or
337Usage: $0 [-v] [-F] -C PREVIOUS_FILECONTEXT { check | restore | verify }
338or
339Usage: $0 [-F] [-B] onboot
340"""
341}
342
343if [ $# = 0 ]; then
344	usage
345	exit 1
346fi
347
348# See how we were called.
349while getopts "N:BC:FfR:l:v" i; do
350    case "$i" in
351	B)
352		BOOTTIME=`/bin/who -b | awk '{print $3}'`
353		;;
354	f)
355		fullFlag=1
356		;;
357	v)
358		VERBOSE="-v"
359		;;
360	R)
361		RPMFILES=$OPTARG
362		;;
363	l)
364		LOGFILE=$OPTARG
365		;;
366	C)
367		PREFC=$OPTARG
368		;;
369	F)
370		FORCEFLAG="-F"
371		;;
372	N)
373		BOOTTIME=$OPTARG
374		;;
375	*)
376	    usage
377	    exit 1
378esac
379done
380# Move out processed options from arguments
381shift $(( OPTIND - 1 ))
382
383# Check for the command
384command=$1
385if [ -z $command ]; then
386    usage
387fi
388
389# Move out command from arguments
390shift
391
392#
393# check if they specified both DIRS and RPMFILES
394#
395
396if [ ! -z "$RPMFILES" ]; then
397    process $command
398    if [ $# -gt 0 ]; then
399	    usage
400    fi
401else
402    if [ -z "$1" ]; then
403	process $command
404    else
405	while [ -n "$1" ]; do
406	    FILEPATH=$1
407	    process $command
408	    shift
409	done
410    fi
411fi
412exit $?
413