xref: /netbsd/etc/security (revision bf9ec67e)
1#!/bin/sh -
2#
3#	$NetBSD: security,v 1.75 2002/05/21 13:50:46 lukem Exp $
4#	from: @(#)security	8.1 (Berkeley) 6/9/93
5#
6
7PATH=/sbin:/usr/sbin:/bin:/usr/bin
8
9if [ -f /etc/rc.subr ]; then
10	. /etc/rc.subr
11else
12	echo "Can't read /etc/rc.subr; aborting."
13	exit 1;
14fi
15
16umask 077
17TZ=UTC; export TZ
18
19if [ -s /etc/security.conf ]; then
20	. /etc/security.conf
21fi
22
23# Set reasonable defaults (if they're not set in security.conf)
24#
25backup_dir=${backup_dir:-/var/backups}
26pkgdb_dir=${pkgdb_dir:-/var/db/pkg}
27max_loginlen=${max_loginlen:-8}
28max_grouplen=${max_grouplen:-8}
29
30# Other configurable variables
31#
32special_files="/etc/mtree/special /etc/mtree/special.local"
33MP=/etc/master.passwd
34CHANGELIST=""
35work_dir=$backup_dir/work
36
37if [ ! -d "$work_dir" ]; then
38	mkdir -p "$work_dir"
39fi
40
41SECUREDIR=`mktemp -d /tmp/_securedir.XXXXXX` || exit 1
42
43trap "/bin/rm -rf $SECUREDIR ; exit 0" EXIT INT QUIT PIPE
44
45if ! cd "$SECUREDIR"; then
46	echo "Can not cd to $SECUREDIR".
47	exit 1
48fi
49
50ERR=secure1.$$
51TMP1=secure2.$$
52TMP2=secure3.$$
53MPBYUID=secure4.$$
54MPBYPATH=secure5.$$
55LIST=secure6.$$
56OUTPUT=secure7.$$
57LABELS=secure8.$$
58PKGS=secure9.$$
59CHANGEFILES=secure10.$$
60
61
62# migrate_file old new
63#	Determine if the "${old}" path name needs to be migrated to the
64#	"${new}" path. Also checks if "${old}.current" needs migrating,
65#	and if so, migrate it and possibly "${old}.current,v" and
66#	"${old}.backup".
67#
68migrate_file()
69{
70	_old=$1
71	_new=$2
72	if [ -z "$_old" -o -z "$_new" ]; then
73		err 3 "USAGE: migrate_file old new"
74	fi
75	if [ ! -d "${_new%/*}" ]; then
76		mkdir -p "${_new%/*}"
77	fi
78	if [ -f "${_old}" -a ! -f "${_new}" ]; then
79		echo "==> migrating ${_old}"
80		echo "           to ${_new}"
81		mv "${_old}" "${_new}"
82	fi
83	if [ -f "${_old}.current" -a ! -f "${_new}.current" ]; then
84		echo "==> migrating ${_old}.current"
85		echo "           to ${_new}.current"
86		mv "${_old}.current" "${_new}.current"
87		if [ -f "${_old}.current,v" -a ! -f "${_new}.current,v" ]; then
88			echo "==> migrating ${_old}.current,v"
89			echo "           to ${_new}.current,v"
90			mv "${_old}.current,v" "${_new}.current,v"
91		fi
92		if [ -f "${_old}.backup" -a ! -f "${_new}.backup" ]; then
93			echo "==> migrating ${_old}.backup"
94			echo "           to ${_new}.backup"
95			mv "${_old}.backup" "${_new}.backup"
96		fi
97	fi
98}
99
100
101# backup_and_diff file printdiff
102#	Determine if file needs backing up, and if so, do it.
103#	If printdiff is yes, display the diffs, otherwise
104#	just print a message saying "[changes omitted]".
105#
106backup_and_diff()
107{
108	_file=$1
109	_printdiff=$2
110	if [ -z "$_file" -o -z "$_printdiff" ]; then
111		err 3 "USAGE: backup_and_diff file printdiff"
112	fi
113	! checkyesno _printdiff
114	_printdiff=$?
115
116	_old=$backup_dir/${_file##*/}
117	case "$_file" in
118	$work_dir/*)
119		_new=$_file
120		migrate_file "$backup_dir/$_old" "$_new"
121		migrate_file "$_old" "$_new"
122		;;
123	*)
124		_new=$backup_dir/$_file
125		migrate_file "$_old" "$_new"
126		;;
127	esac
128	CUR=${_new}.current
129	BACK=${_new}.backup
130	if [ -f $_file ]; then
131		if [ -f $CUR ] ; then
132			if [ "$_printdiff" -ne 0 ]; then
133				diff $CUR $_file > $OUTPUT
134			else
135				if ! cmp -s $CUR $_file; then
136					echo "[changes omitted]"
137				fi > $OUTPUT
138			fi
139			if [ -s $OUTPUT ] ; then
140				printf \
141			"\n======\n%s diffs (OLD < > NEW)\n======\n" $_file
142				cat $OUTPUT
143				backup_file update $_file $CUR $BACK
144			fi
145		else
146			printf "\n======\n%s added\n======\n" $_file
147			if [ "$_printdiff" -ne 0 ]; then
148				diff /dev/null $_file
149			else
150				echo "[changes omitted]"
151			fi
152			backup_file add $_file $CUR $BACK
153		fi
154	else
155		if [ -f $CUR ]; then
156			printf "\n======\n%s removed\n======\n" $_file
157			if [ "$_printdiff" -ne 0 ]; then
158				diff $CUR /dev/null
159			else
160				echo "[changes omitted]"
161			fi
162			backup_file remove $_file $CUR $BACK
163		fi
164	fi
165}
166
167
168# These are used several times.
169#
170awk -F: '!/^+/ { print $1 " " $3 }' $MP | sort -k2n > $MPBYUID
171awk -F: '{ print $1 " " $9 }' $MP | sort -k2 > $MPBYPATH
172
173
174# Check the master password file syntax.
175#
176if checkyesno check_passwd; then
177	awk -v "len=$max_loginlen" '
178	BEGIN {
179		while ( getline < "/etc/shells" > 0 ) {
180			if ($0 ~ /^\#/ || $0 ~ /^$/ )
181				continue;
182			shells[$1]++;
183		}
184		FS=":";
185	}
186
187	{
188		if ($0 ~ /^[	 ]*$/) {
189			printf "Line %d is a blank line.\n", NR;
190			next;
191		}
192		if (NF != 10 && ($1 != "+" || NF != 1))
193			printf "Line %d has the wrong number of fields.\n", NR;
194		if ($1 == "+" )  {
195			if (NF != 1 && $3 == 0)
196			    printf "Line %d includes entries with uid 0.\n", NR;
197			next;
198		}
199		if ($1 !~ /^[A-Za-z0-9]([-A-Za-z0-9]*[A-Za-z0-9])*$/)
200			printf "Login %s has non-alphanumeric characters.\n",
201			    $1;
202		if (length($1) > len)
203			printf "Login %s has more than "len" characters.\n", $1;
204		if ($2 == "")
205			printf "Login %s has no password.\n", $1;
206		if (length($2) != 13 &&
207		    length($2) != 20 &&
208		    length($2) != 34 &&
209		    $2 != "" &&
210		    $2 !~ /^\*[A-z-]+$/ &&
211		    $1 != "toor") {
212			if ($10 == "" || shells[$10])
213		    printf "Login %s is off but still has a valid shell (%s)\n",
214				    $1, $10;
215		} else if (! shells[$10])
216			printf "Login %s does not have a valid shell (%s)\n",
217			    $1, $10;
218		if ($3 == 0 && $1 != "root" && $1 != "toor")
219			printf "Login %s has a user id of 0.\n", $1;
220		if ($3 < 0)
221			printf "Login %s has a negative user id.\n", $1;
222		if ($4 < 0)
223			printf "Login %s has a negative group id.\n", $1;
224	}' < $MP > $OUTPUT
225	if [ -s $OUTPUT ] ; then
226		printf "\nChecking the $MP file:\n"
227		cat $OUTPUT
228	fi
229
230	awk -F: '{ print $1 }' $MP | sort | uniq -d > $OUTPUT
231	if [ -s $OUTPUT ] ; then
232		printf "\n$MP has duplicate user names.\n"
233		column $OUTPUT
234	fi
235
236# To not exclude 'toor', a standard duplicate root account, from the duplicate
237# account test, uncomment the line below (without egrep in it)and comment
238# out the line (with egrep in it) below it.
239#
240#	< $MPBYUID uniq -d -f 1 | awk '{ print $2 }' > $TMP2
241	< $MPBYUID egrep -v '^toor ' | uniq -d -f 1 | awk '{ print $2 }' > $TMP2
242	if [ -s $TMP2 ] ; then
243		printf "\n$MP has duplicate user id's.\n"
244		while read uid; do
245			grep -w $uid $MPBYUID
246		done < $TMP2 | column
247	fi
248fi
249
250# Check the group file syntax.
251#
252if checkyesno check_group; then
253	GRP=/etc/group
254	awk -F: -v "len=$max_grouplen" '{
255		if ($0 ~ /^[	 ]*$/) {
256			printf "Line %d is a blank line.\n", NR;
257			next;
258		}
259		if (NF != 4 && ($1 != "+" || NF != 1))
260			printf "Line %d has the wrong number of fields.\n", NR;
261		if ($1 == "+" )  {
262			next;
263		}
264		if ($1 !~ /^[A-Za-z0-9]([-A-Za-z0-9]*[A-Za-z0-9])*$/)
265			printf "Group %s has non-alphanumeric characters.\n",
266			    $1;
267		if (length($1) > len)
268			printf "Group %s has more than "len" characters.\n", $1;
269		if ($3 !~ /[0-9]*/)
270			printf "Login %s has a negative group id.\n", $1;
271	}' < $GRP > $OUTPUT
272	if [ -s $OUTPUT ] ; then
273		printf "\nChecking the $GRP file:\n"
274		cat $OUTPUT
275	fi
276
277	awk -F: '{ print $1 }' $GRP | sort | uniq -d > $OUTPUT
278	if [ -s $OUTPUT ] ; then
279		printf "\n$GRP has duplicate group names.\n"
280		column $OUTPUT
281	fi
282fi
283
284# Check for root paths, umask values in startup files.
285# The check for the root paths is problematical -- it's likely to fail
286# in other environments.  Once the shells have been modified to warn
287# of '.' in the path, the path tests should go away.
288#
289if checkyesno check_rootdotfiles; then
290	rhome=~root
291	umaskset=no
292	list="/etc/csh.cshrc /etc/csh.login ${rhome}/.cshrc ${rhome}/.login"
293	for i in $list ; do
294		if [ -f $i ] ; then
295			if egrep '^[ \t]*umask[ \t]+[0-7]+' $i > /dev/null ;
296			then
297				umaskset=yes
298			fi
299			# Double check the umask value itself; ensure that
300			# both the group and other write bits are set.
301			#
302			egrep '^[ \t]*umask[ \t]+[0-7]+' $i |
303			awk '{
304				if ($2 ~ /^.$/ || $2 ~! /[^2367].$/) {
305					print "\tRoot umask is group writeable"
306				}
307				if ($2 ~ /[^2367]$/) {
308					print "\tRoot umask is other writeable"
309			    	}
310			    }' | sort -u
311			SAVE_PATH=$PATH
312			unset PATH
313			/bin/csh -f -s << end-of-csh > /dev/null 2>&1
314				source $i
315				/bin/ls -ldgT \$path > $TMP1
316end-of-csh
317			PATH=$SAVE_PATH
318			awk '{
319				if ($10 ~ /^\.$/) {
320					print "\tThe root path includes .";
321					next;
322				}
323			     }
324			     $1 ~ /^d....w/ \
325		{ print "\tRoot path directory " $10 " is group writeable." } \
326			     $1 ~ /^d.......w/ \
327		{ print "\tRoot path directory " $10 " is other writeable." }' \
328			< $TMP1
329		fi
330	done > $OUTPUT
331	if [ $umaskset = "no" -o -s $OUTPUT ] ; then
332		printf "\nChecking root csh paths, umask values:\n$list\n\n"
333		if [ -s $OUTPUT ]; then
334			cat $OUTPUT
335		fi
336		if [ $umaskset = "no" ] ; then
337		    printf "\tRoot csh startup files do not set the umask.\n"
338		fi
339	fi
340
341	umaskset=no
342	list="/etc/profile ${rhome}/.profile"
343	for i in $list; do
344		if [ -f $i ] ; then
345			if egrep umask $i > /dev/null ; then
346				umaskset=yes
347			fi
348			egrep umask $i |
349			awk '$2 ~ /^.$/ || $2 ~ /[^2367].$/ \
350				{ print "\tRoot umask is group writeable" } \
351			     $2 ~ /[^2367]$/ \
352				{ print "\tRoot umask is other writeable" }'
353			SAVE_PATH=$PATH
354			unset PATH
355			/bin/sh << end-of-sh > /dev/null 2>&1
356				. $i
357				list=\`echo \$PATH | /usr/bin/sed -e \
358				    's/^:/.:/;s/:$/:./;s/::/:.:/g;s/:/ /g'\`
359				/bin/ls -ldgT \$list > $TMP1
360end-of-sh
361			PATH=$SAVE_PATH
362			awk '{
363				if ($10 ~ /^\.$/) {
364					print "\tThe root path includes .";
365					next;
366				}
367			     }
368			     $1 ~ /^d....w/ \
369		{ print "\tRoot path directory " $10 " is group writeable." } \
370			     $1 ~ /^d.......w/ \
371		{ print "\tRoot path directory " $10 " is other writeable." }' \
372			< $TMP1
373
374		fi
375	done > $OUTPUT
376	if [ $umaskset = "no" -o -s $OUTPUT ] ; then
377		printf "\nChecking root sh paths, umask values:\n$list\n"
378		if [ -s $OUTPUT ]; then
379			cat $OUTPUT
380		fi
381		if [ $umaskset = "no" ] ; then
382			printf "\tRoot sh startup files do not set the umask.\n"
383		fi
384	fi
385fi
386
387# Root and uucp should both be in /etc/ftpusers.
388#
389if checkyesno check_ftpusers; then
390	list="uucp "`awk '$2 == 0 { print $1 }' $MPBYUID`
391	for i in $list; do
392		if /usr/libexec/ftpd -C $i ; then
393			printf "\t$i is not denied\n"
394		fi
395	done > $OUTPUT
396	if [ -s $OUTPUT ]; then
397		printf "\nChecking the /etc/ftpusers configuration:\n"
398		cat $OUTPUT
399	fi
400fi
401
402# Uudecode should not be in the /etc/mail/aliases file.
403#
404if checkyesno check_aliases; then
405	for f in /etc/mail/aliases /etc/aliases; do
406		if [ -f $f ] && egrep '^[^#]*(uudecode|decode).*\|' $f; then
407			printf "\nEntry for uudecode in $f file.\n"
408		fi
409	done
410fi
411
412# Files that should not have + signs.
413#
414if checkyesno check_rhosts; then
415	list="/etc/hosts.equiv /etc/hosts.lpd"
416	for f in $list ; do
417		if [ -f $f ] && egrep '\+' $f > /dev/null ; then
418			printf "\nPlus sign in $f file.\n"
419		fi
420	done
421
422	# Check for special users with .rhosts files.  Only root and toor should
423	# have .rhosts files.  Also, .rhosts files should not have plus signs.
424	awk -F: '$1 != "root" && $1 != "toor" && \
425		($3 < 100 || $1 == "ftp" || $1 == "uucp") \
426			{ print $1 " " $9 }' $MP |
427	sort -k2 |
428	while read uid homedir; do
429		if [ -f ${homedir}/.rhosts ] ; then
430			rhost=`ls -ldgT ${homedir}/.rhosts`
431			printf -- "$uid: $rhost\n"
432		fi
433	done > $OUTPUT
434	if [ -s $OUTPUT ] ; then
435		printf "\nChecking for special users with .rhosts files.\n"
436		cat $OUTPUT
437	fi
438
439	while read uid homedir; do
440		if [ -f ${homedir}/.rhosts -a -r ${homedir}/.rhosts ] && \
441		    cat -f ${homedir}/.rhosts | egrep '\+' > /dev/null ; then
442			printf -- "$uid: + in .rhosts file.\n"
443		fi
444	done < $MPBYPATH > $OUTPUT
445	if [ -s $OUTPUT ] ; then
446		printf "\nChecking .rhosts files syntax.\n"
447		cat $OUTPUT
448	fi
449fi
450
451# Check home directories.  Directories should not be owned by someone else
452# or writeable.
453#
454if checkyesno check_homes; then
455	while read uid homedir; do
456		if [ -d ${homedir}/ ] ; then
457			file=`ls -ldgT ${homedir}`
458			printf -- "$uid $file\n"
459		fi
460	done < $MPBYPATH |
461	awk '$1 != $4 && $4 != "root" \
462		{ print "user " $1 " home directory is owned by " $4 }
463	     $2 ~ /^-....w/ \
464		{ print "user " $1 " home directory is group writeable" }
465	     $2 ~ /^-.......w/ \
466		{ print "user " $1 " home directory is other writeable" }' \
467	    > $OUTPUT
468	if [ -s $OUTPUT ] ; then
469		printf "\nChecking home directories.\n"
470		cat $OUTPUT
471	fi
472
473	# Files that should not be owned by someone else or readable.
474	list=".Xauthority .netrc .ssh/id_dsa .ssh/id_rsa .ssh/identity"
475	while read uid homedir; do
476		for f in $list ; do
477			file=${homedir}/${f}
478			if [ -f $file ] ; then
479				printf -- "$uid $f `ls -ldgT $file`\n"
480			fi
481		done
482	done < $MPBYPATH |
483	awk '$1 != $5 && $5 != "root" \
484		{ print "user " $1 " " $2 " file is owned by " $5 }
485	     $3 ~ /^-...r/ \
486		{ print "user " $1 " " $2 " file is group readable" }
487	     $3 ~ /^-......r/ \
488		{ print "user " $1 " " $2 " file is other readable" }
489	     $3 ~ /^-....w/ \
490		{ print "user " $1 " " $2 " file is group writeable" }
491	     $3 ~ /^-.......w/ \
492		{ print "user " $1 " " $2 " file is other writeable" }' \
493	    > $OUTPUT
494
495	# Files that should not be owned by someone else or writeable.
496	list=".bash_history .bash_login .bash_logout .bash_profile .bashrc \
497	      .cshrc .emacs .exrc .forward .history .klogin .login .logout \
498	      .profile .qmail .rc_history .rhosts .shosts ssh .tcshrc .twmrc \
499	      .xinitrc .xsession .ssh/authorized_keys .ssh/authorized_keys2 \
500	      .ssh/config .ssh/id_dsa.pub .ssh/id_rsa.pub .ssh/identity.pub \
501	      .ssh/known_hosts .ssh/known_hosts2"
502	while read uid homedir; do
503		for f in $list ; do
504			file=${homedir}/${f}
505			if [ -f $file ] ; then
506				printf -- "$uid $f `ls -ldgT $file`\n"
507			fi
508		done
509	done < $MPBYPATH |
510	awk '$1 != $5 && $5 != "root" \
511		{ print "user " $1 " " $2 " file is owned by " $5 }
512	     $3 ~ /^-....w/ \
513		{ print "user " $1 " " $2 " file is group writeable" }
514	     $3 ~ /^-.......w/ \
515		{ print "user " $1 " " $2 " file is other writeable" }' \
516	    >> $OUTPUT
517	if [ -s $OUTPUT ] ; then
518		printf "\nChecking dot files.\n"
519		cat $OUTPUT
520	fi
521fi
522
523# Mailboxes should be owned by user and unreadable.
524#
525if checkyesno check_varmail; then
526	ls -l /var/mail | \
527	awk '	NR == 1 { next; }
528	    	$3 != $9 {
529			print "user " $9 " mailbox is owned by " $3
530		}
531		$1 != "-rw-------" {
532			print "user " $9 " mailbox is " $1 ", group " $4
533		}' > $OUTPUT
534	if [ -s $OUTPUT ] ; then
535		printf "\nChecking mailbox ownership.\n"
536		cat $OUTPUT
537	fi
538fi
539
540# NFS exports shouldn't be globally exported
541#
542if checkyesno check_nfs && [ -f /etc/exports ]; then
543	awk '{
544		# ignore comments and blank lines
545		if ($0 ~ /^\#/ || $0 ~ /^$/ )
546			next;
547
548		readonly = 0;
549		for (i = 2; i <= NF; ++i) {
550			if ($i ~ /-ro/)
551				readonly = 1;
552			else if ($i !~ /^-/)
553				next;
554		}
555		if (readonly)
556			print "File system " $1 " globally exported, read-only."
557		else
558			print "File system " $1 " globally exported, read-write."
559	}' < /etc/exports > $OUTPUT
560	if [ -s $OUTPUT ] ; then
561		printf "\nChecking for globally exported file systems.\n"
562		cat $OUTPUT
563	fi
564fi
565
566# Display any changes in setuid files and devices.
567#
568if checkyesno check_devices; then
569	> $ERR
570	(find / \( ! -fstype local -o -fstype fdesc -o -fstype kernfs \
571			-o -fstype null \
572			-o -fstype procfs \) -a -prune -o \
573	    \( \( -perm -u+s -a ! -type d \) -o \
574	       \( -perm -g+s -a ! -type d \) -o \
575	       -type b -o -type c \) -print0 | \
576	xargs -0 ls -ldgTq | sort +9 > $LIST) 2> $OUTPUT
577
578	# Display any errors that occurred during system file walk.
579	if [ -s $OUTPUT ] ; then
580		printf "Setuid/device find errors:\n" >> $ERR
581		cat $OUTPUT >> $ERR
582		printf "\n" >> $ERR
583	fi
584
585	# Display any changes in the setuid file list.
586	egrep -v '^[bc]' $LIST > $TMP1
587	if [ -s $TMP1 ] ; then
588		# Check to make sure uudecode isn't setuid.
589		if grep -w uudecode $TMP1 > /dev/null ; then
590			printf "\nUudecode is setuid.\n" >> $ERR
591		fi
592
593		file=$work_dir/setuid
594		migrate_file "$backup_dir/setuid" "$file"
595		CUR=${file}.current
596		BACK=${file}.backup
597		if [ -s $CUR ] ; then
598			if cmp -s $CUR $TMP1 ; then
599				:
600			else
601				> $TMP2
602				join -110 -210 -v2 $CUR $TMP1 > $OUTPUT
603				if [ -s $OUTPUT ] ; then
604					printf "Setuid additions:\n" >> $ERR
605					tee -a $TMP2 < $OUTPUT >> $ERR
606					printf "\n" >> $ERR
607				fi
608
609				join -110 -210 -v1 $CUR $TMP1 > $OUTPUT
610				if [ -s $OUTPUT ] ; then
611					printf "Setuid deletions:\n" >> $ERR
612					tee -a $TMP2 < $OUTPUT >> $ERR
613					printf "\n" >> $ERR
614				fi
615
616				sort -k10 $TMP2 $CUR $TMP1 | \
617				    sed -e 's/[	 ][	 ]*/ /g' | \
618				    uniq -u > $OUTPUT
619				if [ -s $OUTPUT ] ; then
620					printf "Setuid changes:\n" >> $ERR
621					column -t $OUTPUT >> $ERR
622					printf "\n" >> $ERR
623				fi
624
625				backup_file update $TMP1 $CUR $BACK
626			fi
627		else
628			printf "Setuid additions:\n" >> $ERR
629			column -t $TMP1 >> $ERR
630			printf "\n" >> $ERR
631			backup_file add $TMP1 $CUR $BACK
632		fi
633	fi
634
635	# Check for block and character disk devices that are readable or
636	# writeable or not owned by root.operator.
637	>$TMP1
638	DISKLIST="ccd ch hk hp ld md ra raid rb rd rl rx \
639	    sd se ss uk up vnd wd xd xy"
640#	DISKLIST="$DISKLIST ct mt st wt"
641	for i in $DISKLIST; do
642		egrep "^b.*/${i}[0-9][0-9]*[a-p]$"  $LIST >> $TMP1
643		egrep "^c.*/r${i}[0-9][0-9]*[a-p]$"  $LIST >> $TMP1
644	done
645
646	awk '$3 != "root" || $4 != "operator" || $1 !~ /.rw-r-----/ \
647		{ printf "Disk %s is user %s, group %s, permissions %s.\n", \
648		    $11, $3, $4, $1; }' < $TMP1 > $OUTPUT
649	if [ -s $OUTPUT ] ; then
650		printf "\nChecking disk ownership and permissions.\n" >> $ERR
651		cat $OUTPUT >> $ERR
652		printf "\n" >> $ERR
653	fi
654
655	# Display any changes in the device file list.
656	egrep '^[bc]' $LIST | sort -k11 > $TMP1
657	if [ -s $TMP1 ] ; then
658		file=$work_dir/device
659		migrate_file "$backup_dir/device" "$file"
660		CUR=${file}.current
661		BACK=${file}.backup
662
663		if [ -s $CUR ] ; then
664			if cmp -s $CUR $TMP1 ; then
665				:
666			else
667				> $TMP2
668				join -111 -211 -v2 $CUR $TMP1 > $OUTPUT
669				if [ -s $OUTPUT ] ; then
670					printf "Device additions:\n" >> $ERR
671					tee -a $TMP2 < $OUTPUT >> $ERR
672					printf "\n" >> $ERR
673				fi
674
675				join -111 -211 -v1 $CUR $TMP1 > $OUTPUT
676				if [ -s $OUTPUT ] ; then
677					printf "Device deletions:\n" >> $ERR
678					tee -a $TMP2 < $OUTPUT >> $ERR
679					printf "\n" >> $ERR
680				fi
681
682				# Report any block device change. Ignore
683				# character devices, only the name is
684				# significant.
685				cat $TMP2 $CUR $TMP1 | \
686				    sed -e '/^c/d' | \
687				    sort -k11 | \
688				    sed -e 's/[	 ][	 ]*/ /g' | \
689				    uniq -u > $OUTPUT
690				if [ -s $OUTPUT ] ; then
691					printf "Block device changes:\n" >> $ERR
692					column -t $OUTPUT >> $ERR
693					printf "\n" >> $ERR
694				fi
695
696				backup_file update $TMP1 $CUR $BACK
697			fi
698		else
699			printf "Device additions:\n" >> $ERR
700			column -t $TMP1 >> $ERR
701			printf "\n" >> $ERR
702			backup_file add $TMP1 $CUR $BACK >> $ERR
703		fi
704	fi
705	if [ -s $ERR ] ; then
706		printf "\nChecking setuid files and devices:\n"
707		cat $ERR
708		printf "\n"
709	fi
710fi
711
712# Check special files.
713# Check system binaries.
714#
715# Create the mtree tree specifications using:
716#	mtree -cx -pDIR -kmd5,uid,gid,mode,nlink,size,link,time > DIR.secure
717#	chown root:wheel DIR.secure
718#	chmod u+r,go= DIR.secure
719#
720# Note, this is not complete protection against Trojan horsed binaries, as
721# the hacker can modify the tree specification to match the replaced binary.
722# For details on really protecting yourself against modified binaries, see
723# the mtree(8) manual page.
724#
725if checkyesno check_mtree; then
726	for file in $special_files; do
727		[ ! -s $file ] && continue
728		mtree -e -l -p / -f $file
729	done > $OUTPUT
730	if [ -s $OUTPUT ]; then
731		printf "\nChecking special files and directories.\n"
732		cat $OUTPUT
733	fi
734
735	for file in /etc/mtree/*.secure; do
736		[ $file = '/etc/mtree/*.secure' ] && continue
737		tree=`sed -n -e '3s/.* //p' -e 3q $file`
738		mtree -f $file -p $tree > $TMP1
739		if [ -s $TMP1 ]; then
740			printf "\nChecking $tree:\n"
741			cat $TMP1
742		fi
743	done > $OUTPUT
744	if [ -s $OUTPUT ]; then
745		printf "\nChecking system binaries:\n"
746		cat $OUTPUT
747	fi
748fi
749
750# Backup disklabels of available disks
751#
752if checkyesno check_disklabels; then
753		# migrate old disklabels
754	for file in `ls -1d $backup_dir/$backup_dir/disklabel.* \
755	    $backup_dir/disklabel.* 2>/dev/null`; do
756		migrate_file "$file" "$work_dir/${file##*/}"
757	done
758
759		# generate list of old disklabels & fdisks and remove them
760	ls -1d $work_dir/disklabel.* $work_dir/fdisk.* 2>/dev/null |
761	    egrep -v '\.(backup|current)(,v)?$' > $LABELS
762	xargs rm < $LABELS
763
764		# generate disklabels of all disks excluding:	cd fd md
765	disks=`iostat -x | awk 'NR > 1 && $1 !~ /^[cfm]d/ { print $1; }'`
766	for i in $disks; do
767		disklabel $i > "$work_dir/disklabel.$i" 2>/dev/null
768	done
769
770		# if fdisk is available, generate fdisks for:	ed ld sd wd
771	if [ -x /sbin/fdisk ]; then
772		disks=`iostat -x| awk 'NR > 1 && $1 ~ /^[elsw]d/ { print $1; }'`
773		for i in $disks; do
774			/sbin/fdisk $i > "$work_dir/fdisk.$i" 2>/dev/null
775		done
776	fi
777
778		# append list of new disklabels and fdisks
779	ls -1d $work_dir/disklabel.* $work_dir/fdisk.* 2>/dev/null |
780	    egrep -v '\.(backup|current)(,v)?$' >> $LABELS
781	CHANGELIST="$LABELS $CHANGELIST"
782fi
783
784# Check for changes in the list of installed pkgs
785#
786if checkyesno check_pkgs && [ -d $pkgdb_dir ]; then
787	pkgs=$work_dir/pkgs
788	migrate_file "$backup_dir/pkgs" "$pkgs"
789	(	cd $pkgdb_dir
790		pkg_info | sort
791		echo ""
792		find . \( -name +REQUIRED_BY -o -name +CONTENTS \) -print0 |
793			xargs -0 ls -ldgTq | sort -t. +1 | sed -e 's, \./, ,'
794	 ) > $pkgs
795	echo "$pkgs" > $PKGS
796	CHANGELIST="$PKGS $CHANGELIST"
797fi
798
799# List of files that get backed up and checked for any modifications.
800# Any changes cause the files to rotate.
801#
802if checkyesno check_changelist ; then
803	for file in $special_files; do
804		[ ! -s $file ] && continue
805		mtree -D -k type -f $file -E exclude |
806		    sed '/^type=file/!d ; s/type=file \.//'
807	done > $CHANGEFILES
808
809	(
810		# Add other files which might dynamically exist:
811		#	/etc/ifconfig.*
812		#	/etc/raid*.conf
813		#	/etc/rc.d/*
814		#	/etc/rc.conf.d/*
815		#
816		echo "/etc/ifconfig.*"
817		echo "/etc/raid*.conf"
818		echo "/etc/rc.d/*"
819		echo "/etc/rc.conf.d/*"
820
821		# Add /etc/changelist
822		#
823		if [ -s /etc/changelist ]; then
824			grep -v '^#' /etc/changelist
825		fi
826	) | while read file; do
827		case "$file" in
828		*[\*\?\[]*)	# If changelist line is a glob ...
829				# ... expand possible backup files
830				#
831			ls -1d $(echo $backup_dir/${file}.current) 2>/dev/null \
832			    | sed "s,^$backup_dir/,, ; s,\.current$,,"
833				
834				# ... expand possible files
835				#
836			ls -1d $(echo $file) 2>/dev/null
837			;;
838		*)
839				# Otherwise, just print the filename
840			echo $file
841			;;
842		esac
843	done >> $CHANGEFILES
844	CHANGELIST="$CHANGEFILES $CHANGELIST"
845fi
846
847# Special case backups, including the master password file and
848# ssh private host keys. The normal backup mechanisms for
849# $check_changelist (see below) also print out the actual file
850# differences and we don't want to do that for these files
851#
852echo $MP > $TMP1			# always add /etc/master.passwd
853for file in $special_files; do
854	[ ! -s $file ] && continue
855	mtree -D -k type -f $file -I nodiff |
856	    sed '/^type=file/!d ; s/type=file \.//'
857done >> $TMP1
858grep -v '^$' $TMP1 | sort -u > $TMP2
859
860while read file; do
861	backup_and_diff "$file" no
862done < $TMP2
863
864
865if [ -n "$CHANGELIST" ]; then
866	grep -h -v '^$' $CHANGELIST | sort -u > $TMP1
867	comm -23 $TMP1 $TMP2 | while read file; do
868		backup_and_diff "$file" yes
869	done
870fi
871
872if [ -f /etc/security.local ]; then
873	echo ""
874	echo "Running /etc/security.local:"
875	. /etc/security.local
876fi
877