1#!/bin/mksh
2# $MirSecuCron$
3# $MirOS: src/etc/security,v 1.22 2009/10/17 16:20:04 tg Exp $
4# $OpenBSD: security,v 1.71 2005/02/22 10:50:55 otto Exp $
5# from: @(#)security	8.1 (Berkeley) 6/9/93
6
7export TZ=UTC PATH=/bin:/usr/bin:/sbin:/usr/sbin RCSLOCALID='!MirSecuCron'
8cd /
9nl='
10'
11
12umask 077
13
14function _diffhdr {
15	print
16	print ======
17	print -r -- "$1 diffs (-OLD  +NEW)"
18	print ======
19}
20function _stripcom {
21	cat "$@" | { set -o noglob; while read _line; do
22		_line=${_line%%#*}
23		[[ -n $_line ]] && print -r -- $_line
24	done; }
25}
26rm -f /var/backups/rcs.log
27
28# fback [-m|-n] <file> [<backupfile>]
29function fback {
30	local fn bf cf
31	local wd o fd fo ft fa fp
32
33	if [[ $1 = -m ]]; then
34		o=md5
35		shift
36	elif [[ $1 = -n ]]; then
37		o=nodiff
38		shift
39	elif [[ $1 = -- ]]; then
40		shift
41	fi
42	fn=$1
43	cf=$fn
44	bf=$2
45	ft=$3
46	[[ -s $fn ]] || return
47	[[ -n $bf ]] || bf=$(sed 's/^\///;s/\//_/g' <<<"$fn")
48	[[ -n $ft ]] || ft=$fn
49
50	wd=$(pwd)
51	cd /var/backups
52
53	# migration for special cases
54	if [[ $o = md5 ]]; then
55		cf=$DIR/_md5
56		md5 <"$fn" >$cf
57		[[ -e "$bf".current.md5 ]] && \
58		    mv "$bf".current.md5 "$bf".md5.current
59		[[ -e "$bf".backup.md5 ]] && \
60		    mv "$bf".backup.md5 "$bf".md5.backup
61		bf=$bf.md5
62	fi
63
64	if [[ ! -x /usr/bin/co ]]; then
65		# back things up without RCS tools
66		if [[ ! -e $bf.current ]] || ! cmp -s "$cf" "$bf.current"; then
67			[[ -e $bf.current ]] && \
68			    cp -p "$bf.current" "$bf.backup"
69			cp -p "$cf" "$bf.current"
70			[[ $o = nodiff ]] || _diffhdr "$ft"
71			[[ $o = nodiff ]] || if [[ -e $bf.backup ]]; then
72				diff -up /var/backups/"$bf.backup" \
73				    /var/backups/"$bf.current"
74			else
75				diff -u /dev/null /var/backups/"$bf.current"
76			fi
77			chown 0:0 "$bf.current"
78			chmod 0600 "$bf.current"
79		fi
80		cd "$wd"
81		return
82	fi
83
84	# migration for RCS/foo,x (-x,v/) to RCS/foo (-x)
85	[[ -e RCS/$bf,v ]] && mv -f RCS/$bf,v RCS/$bf
86
87	# migration for non-RCS to RCS schema
88	if [[ -e $bf.backup ]]; then
89		[[ -e RCS/$bf ]] && co -x -M -T -l "$bf" >>rcs.log 2>&1
90		fd=$(stat -f '%Sm' -t '%Y/%m/%d %H:%M:%S' "$bf.backup")
91		fo=$(stat -f '%Su' "$bf.backup")
92		mv -f "$bf.backup" "$bf"
93		ci -x -M -T -u -d"$fd" -t-"automatic backup for $fn" -w"$fo" \
94		    -sAncient -f -m"legacy backup $fn" "$bf" >>rcs.log 2>&1
95	fi
96	if [[ -e $bf.current ]]; then
97		[[ -e RCS/$bf ]] && co -x -M -T -l "$bf" >>rcs.log 2>&1
98		fd=$(stat -f '%Sm' -t '%Y/%m/%d %H:%M:%S' "$bf.current")
99		fo=$(stat -f '%Su' "$bf.current")
100		mv -f "$bf.current" "$bf"
101		ci -x -M -T -u -d"$fd" -t-"automatic backup for $fn" -w"$fo" \
102		    -sLegacy -f -m"legacy current $fn" "$bf" >>rcs.log 2>&1
103	fi
104	rm -f "$bf.backup" "$bf.current"
105
106	# back things up using RCS tools
107	if [[ ! -e $bf ]] || ! cmp -s "$cf" "$bf"; then
108		[[ -e RCS/$bf ]] && co -x -M -T -l "$bf" >>rcs.log 2>&1
109		rm -f "$bf"
110		cp -p "$cf" "$bf"
111		fd=$(stat -f '%Sm' -t '%Y/%m/%d %H:%M:%S' "$fn")
112		fo=$(stat -f '%Su' "$fn")
113		fa=$(stat -f '%u:%g' "$fn")
114		fp=$(stat -f '%Mp%Lp' "$fn")
115		[[ $o = nodiff ]] || _diffhdr "$ft"
116		[[ $o = nodiff ]] || if [[ -e RCS/$bf ]]; then
117			rcsdiff -x -q -u -p /var/backups/"$bf"
118		else
119			diff -u /dev/null /var/backups/"$bf"
120		fi
121		ci -x -M -T -u -d"$fd" -t-"automatic backup for $fn" -w"$fo" \
122		    -sBackup -m"$(date) backup for $fn" "$bf" >>rcs.log 2>&1
123		cp -p "$bf" "$cf"
124		chown 0:0 "$bf" "RCS/$bf"
125		chmod 0400 "$bf" "RCS/$bf"
126		chown $fa "$cf"
127		chmod $fp "$cf"
128	fi
129	cd "$wd"
130}
131
132DIR=$(mktemp -d /tmp/_secure.XXXXXXXXXX) || exit 1
133ERR=$DIR/_secure1
134TMP1=$DIR/_secure2
135TMP2=$DIR/_secure3
136TMP3=$DIR/_secure4
137LIST=$DIR/_secure5
138OUTPUT=$DIR/_secure6
139
140trap 'rm -rf $DIR; exit 1' 0 1 2 3 13 15
141
142# Check the master password file syntax.
143MP=/etc/master.passwd
144awk -F: '{
145	if ($0 ~ /^[	 ]*$/) {
146		printf("Line %d is a blank line.\n", NR);
147		next;
148	}
149	if (NF != 10)
150		printf("Line %d has the wrong number of fields:\n%s\n", NR, $0);
151	if ($1 ~ /^[+-]/)
152		next;
153	if ($1 == "")
154		printf("Line %d has an empty login field:\n%s\n", NR, $0);
155	else if ($1 !~ /^[A-Za-z0-9_][A-Za-z0-9_\-\.]*\$?$/)
156		printf("Login %s has non-alphanumeric characters.\n", $1);
157	if (length($1) > 31)
158		printf("Login %s has more than 31 characters.\n", $1);
159	if ($2 == "" && $1 !~ /^_*anoncvs$/ && $1 !~ /^_*rsync$/)
160		printf("Login %s has no password.\n", $1);
161	if ($2 != "" && length($2) != 13 && ($10 ~ /.*sh$/ || $10 == "") &&
162	   ($2 !~ /^\$[0-9a-f]+\$/) && ($2 != "skey")) {
163		if (system("test -s /etc/skey/"$1"") == 0)
164			printf("Login %s is off but still has a valid shell and an entry in /etc/skey.\n", $1);
165		if (system("test -d "$9" -a ! -r "$9"") == 0)
166			printf("Login %s is off but still has valid shell and home directory is unreadable\n\t by root; cannot check for existence of alternate access files.\n", $1);
167		else if (system("for file in .etc/ssh .rhosts .shosts .klogin; do if test -e "$9"/$file; then if ( (ls -ld "$9"/$file | cut -b 2-10 | grep -q r) && (test ! -O "$9"/$file)); then exit 1; fi; fi; done"))
168			 printf("Login %s is off but still has a valid shell and alternate access files in\n\t home directory are still readable.\n",$1);
169	}
170	if ($3 == 0 && $1 != "root")
171		printf("Login %s has a user ID of 0.\n", $1);
172	if ($3 < 0)
173		printf("Login %s has a negative user ID.\n", $1);
174	if ($4 < 0)
175		printf("Login %s has a negative group ID.\n", $1);
176	if (int($7) != 0 && system("test "$7" -lt $(date +%s)") == 0)
177		printf("Login %s has expired.\n", $1);
178}' < $MP > $OUTPUT
179if [ -s $OUTPUT ] ; then
180	echo "\nChecking the ${MP} file:"
181	cat $OUTPUT
182fi
183
184awk -F: '{ print $1 }' $MP | sort | uniq -d > $OUTPUT
185if [ -s $OUTPUT ] ; then
186	echo "\n${MP} has duplicate user names."
187	column $OUTPUT
188fi
189
190awk -F: '/^[^\+]/ { print $1 " " $3 }' $MP | sort -n +1 | tee $TMP1 |
191uniq -d -f 1 | awk '{ print $2 }' > $TMP2
192set -A dupusers
193if [ -s $TMP2 ] ; then
194	echo "\n${MP} has duplicate user IDs."
195	duptxt=
196	while read uid; do
197		grep " ${uid}\$" $TMP1 |&
198		i=0
199		while read -p uline; do
200			duptxt=$duptxt$uline$nl
201			if (( i++ )); then
202				dupusers[${#dupusers[*]}]=-e
203				dupusers[${#dupusers[*]}]='^'${uline%% *}' '
204			fi
205		done
206	done <$TMP2
207	print -nr -- "$duptxt" | column
208fi
209(( ${#dupusers[*]} )) || set -A dupusers -- -e '^$'
210
211if [[ ! -d /var/backups/RCS/. ]]; then
212	mkdir -p /var/backups/RCS
213	chmod 700 /var/backups /var/backups/RCS
214fi
215for i in current backup; do
216	f=/var/backups/master.passwd.$i
217	[[ -e $f ]] && mv $f /var/backups/etc_master.passwd.$i
218done
219
220# Check the group file syntax.
221GRP=/etc/group
222awk -F: '{
223	if ($0 ~ /^[	 ]*$/) {
224		printf("Line %d is a blank line.\n", NR);
225		next;
226	}
227	if ($1 ~ /^[+-].*$/)
228		next;
229	if (NF != 4)
230		printf("Line %d has the wrong number of fields:\n%s\n", NR, $0);
231	if ($1 !~ /^[A-Za-z0-9_][A-Za-z0-9_\-\.]*$/)
232		printf("Group %s has non-alphanumeric characters.\n", $1);
233	if (length($1) > 31)
234		printf("Group %s has more than 31 characters.\n", $1);
235	if ($3 !~ /[0-9]*/)
236		printf("Login %s has a negative group ID.\n", $1);
237}' < $GRP > $OUTPUT
238if [ -s $OUTPUT ] ; then
239	echo "\nChecking the ${GRP} file:"
240	cat $OUTPUT
241fi
242
243awk -F: '{ print $1 }' $GRP | sort | uniq -d > $OUTPUT
244if [ -s $OUTPUT ] ; then
245	echo "\n${GRP} has duplicate group names."
246	column $OUTPUT
247fi
248
249# Check for root paths, umask values in startup files.
250# The check for the root paths is problematical -- it's likely to fail
251# in other environments.  Once the shells have been modified to warn
252# of '.' in the path, the path tests should go away.
253> $OUTPUT
254rhome=/
255umaskset=no
256list="/etc/profile ${rhome}/.profile"
257for i in $list; do
258	if [ -s $i ] ; then
259		if egrep umask $i > /dev/null ; then
260			umaskset=yes
261		fi
262		egrep umask $i |
263		awk '$2 % 100 < 20 \
264			{ print "Root umask is group writable" } \
265		     $2 % 10 < 2 \
266			{ print "Root umask is other writable" }' >> $OUTPUT
267		SAVE_PATH=$PATH
268		SAVE_ENV=$ENV
269		unset PATH ENV
270		mksh << end-of-sh > /dev/null 2>&1
271			. $i
272			if [ X"\$PATH" != "X" ]; then
273				list=\`echo \$PATH | /usr/bin/sed -e 's/:/ /g'\`
274				/bin/ls -ldgT \$list > $TMP1
275			else
276				> $TMP1
277			fi
278			echo \$ENV >> $TMP2
279end-of-sh
280		PATH=$SAVE_PATH
281		ENV=$SAVE_ENV
282		awk '{
283			if ($10 ~ /^\.$/) {
284				print "The root path includes .";
285				next;
286			}
287		     }
288		     $1 ~ /^d....w/ \
289	{ print "Root path directory " $10 " is group writable." } \
290		     $1 ~ /^d.......w/ \
291	{ print "Root path directory " $10 " is other writable." }' \
292		< $TMP1 >> $OUTPUT
293
294	fi
295done
296if [ $umaskset = "no" -o -s $OUTPUT ] ; then
297	echo "\nChecking root sh paths, umask values:\n${list}"
298	if [ -s $OUTPUT ] ; then
299		cat $OUTPUT
300	fi
301	if [ $umaskset = "no" ] ; then
302		echo "\nRoot sh startup files do not set the umask."
303	fi
304fi
305
306# Root and uucp should both be in /etc/ftpusers.
307if egrep root /etc/ftpusers > /dev/null ; then
308	:
309else
310	echo "\nRoot not listed in /etc/ftpusers file."
311fi
312if egrep uucp /etc/ftpusers > /dev/null ; then
313	:
314else
315	echo "\nUucp not listed in /etc/ftpusers file."
316fi
317
318# Uudecode should not be in the /etc/mail/aliases file.
319if egrep 'uudecode|decode' /etc/mail/aliases; then
320	echo "\nThere is an entry for uudecode in the /etc/mail/aliases file."
321fi
322
323# Files that should not have + signs.
324list="/etc/hosts.equiv /etc/shosts.equiv /etc/hosts.lpd"
325for f in $list ; do
326	if [ -s $f ] ; then
327		awk '{
328			if ($0 ~ /^\+@.*$/)
329				next;
330			if ($0 ~ /^\+.*$/)
331				printf("\nPlus sign in %s file.\n", FILENAME);
332		}' $f
333	fi
334done
335
336# Check for special users with .rhosts/.shosts files.  Only root
337# should have .rhosts/.shosts files.  Also, .rhosts/.shosts
338# files should not have plus signs.
339awk -F: '$1 != "root" && $1 !~ /^[+-]/ && \
340	($3 < 100 || $1 == "ftp" || $1 == "uucp") \
341		{ print $1 " " $6 }' /etc/passwd |
342grep -v "${dupusers[@]}" |
343while read uid homedir; do
344	for j in .rhosts .shosts; do
345		# Root owned .rhosts/.shosts files are ok.
346		if [ -s ${homedir}/$j -a ! -O ${homedir}/$j ] ; then
347			rhost=$(ls -ldgT ${homedir}/$j)
348			echo "${uid}: ${rhost}"
349		fi
350	done
351done > $OUTPUT
352if [ -s $OUTPUT ] ; then
353	echo "\nChecking for special users with .rhosts/.shosts files."
354	cat $OUTPUT
355fi
356
357awk -F: '/^[^+-]/ { print $1 " " $6 }' /etc/passwd | \
358grep -v "${dupusers[@]}" |
359while read uid homedir; do
360	for j in .rhosts .shosts; do
361		if [ -s ${homedir}/$j ] ; then
362			awk '{
363				if ($0 ~ /^+@.*$/ )
364					next;
365				if ($0 ~ /^\+[ 	]*$/ )
366					printf("%s has + sign in it.\n",
367						FILENAME);
368			}' ${homedir}/$j
369		fi
370	done
371done > $OUTPUT
372if [ -s $OUTPUT ] ; then
373	echo "\nChecking .rhosts/.shosts files syntax."
374	cat $OUTPUT
375fi
376
377# Check home directories.  Directories should not be owned by someone else
378# or writeable.
379awk -F: '/^[^+-]/ { print $1 " " $6 }' /etc/passwd | \
380grep -v "${dupusers[@]}" |
381while read uid homedir; do
382	if [ -d ${homedir}/ ] ; then
383		file=$(ls -ldgT ${homedir})
384		echo "${uid} ${file}"
385	fi
386done |
387awk '$1 != $4 && $4 != "root" \
388	{ print "user " $1 " home directory is owned by " $4 }
389     $2 ~ /^-....w/ \
390	{ print "user " $1 " home directory is group writable" }
391     $2 ~ /^-.......w/ \
392	{ print "user " $1 " home directory is other writable" }' > $OUTPUT
393if [ -s $OUTPUT ] ; then
394	echo "\nChecking home directories."
395	cat $OUTPUT
396fi
397
398# Files that should not be owned by someone else or readable.
399list=".netrc .rhosts .gnupg/secring.gpg .gnupg/random_seed \
400	.pgp/secring.pgp .shosts .etc/ssh/identity .etc/ssh/id_dsa .etc/ssh/id_rsa"
401awk -F: '/^[^+-]/ { print $1 " " $6 }' /etc/passwd | \
402grep -v "${dupusers[@]}" |
403while read uid homedir; do
404	for f in $list ; do
405		file=${homedir}/${f}
406		if [ -f $file ] ; then
407			echo "${uid} ${f} $(ls -ldgT ${file})"
408		fi
409	done
410done |
411awk '$1 != $5 && $5 != "root" \
412	{ print "user " $1 " " $2 " file is owned by " $5 }
413     $3 ~ /^-...r/ \
414	{ print "user " $1 " " $2 " file is group readable" }
415     $3 ~ /^-......r/ \
416	{ print "user " $1 " " $2 " file is other readable" }
417     $3 ~ /^-....w/ \
418	{ print "user " $1 " " $2 " file is group writable" }
419     $3 ~ /^-.......w/ \
420	{ print "user " $1 " " $2 " file is other writable" }' > $OUTPUT
421
422# Files that should not be owned by someone else or writeable.
423list=".bashrc .bash_profile .bash_login .bash_logout .cshrc \
424      .emacs .exrc .forward .fvwmrc .inputrc .klogin .kshrc .login \
425      .logout .nexrc .profile .screenrc .etc/ssh .etc/ssh/config \
426      .etc/ssh/authorized_keys .etc/ssh/authorized_keys2 .etc/ssh/environment \
427      .etc/ssh/known_hosts .etc/ssh/rc .tcshrc .twmrc .xsession .xinitrc \
428      .Xdefaults .Xauthority mail"
429awk -F: '/^[^+-]/ { print $1 " " $6 }' /etc/passwd | \
430grep -v "${dupusers[@]}" |
431while read uid homedir; do
432	for f in $list ; do
433		file=${homedir}/${f}
434		if [ -f $file ] ; then
435			echo "${uid} ${f} $(ls -ldgT ${file})"
436		fi
437	done
438done |
439awk '$1 != $5 && $5 != "root" \
440	{ print "user " $1 " " $2 " file is owned by " $5 }
441     $3 ~ /^-....w/ \
442	{ print "user " $1 " " $2 " file is group writable" }
443     $3 ~ /^-.......w/ \
444	{ print "user " $1 " " $2 " file is other writable" }' >> $OUTPUT
445if [ -s $OUTPUT ] ; then
446	echo "\nChecking dot files."
447	cat $OUTPUT
448fi
449
450# Mailboxes should be owned by user and unreadable.
451ls -l /var/mail | sed 1d | \
452awk '$3 != $9 \
453	{ print "user " $9 " mailbox is owned by " $3 }
454     $1 != "-rw-------" \
455	{ print "user " $9 " mailbox is " $1 ", group " $4 }' > $OUTPUT
456if [ -s $OUTPUT ] ; then
457	echo "\nChecking mailbox ownership."
458	cat $OUTPUT
459fi
460
461# File systems should not be globally exported.
462if [ -s /etc/exports ] ; then
463	awk '{
464		if (($1 ~ /^#/) || ($1 ~ /^$/))
465			next;
466		readonly = 0;
467		for (i = 2; i <= NF; ++i) {
468			if ($i ~ /^-ro$/)
469				readonly = 1;
470			else if ($i !~ /^-/ || $i ~ /^-network/)
471				next;
472		}
473		if (readonly)
474			print "File system " $1 " globally exported, read-only."
475		else
476			print "File system " $1 " globally exported, read-write."
477	}' < /etc/exports > $OUTPUT
478	if [ -s $OUTPUT ] ; then
479		echo "\nChecking for globally exported file systems."
480		cat $OUTPUT
481	fi
482fi
483
484# Display any changes in setuid/setgid files and devices.
485pending="\nChecking setuid/setgid files and devices:\n"
486(find / \( ! -fstype local -o -fstype fdesc -o -fstype kernfs \
487	-o -fstype procfs \) -a -prune -o \
488	-type f -a \( -perm -u+s -o -perm -g+s \) -print0 -o \
489	! -type d -a ! -type f -a ! -type l -a ! -type s -a ! -type p \
490	-print0 | xargs -0 ls -ldgT | sort +9 > $LIST) 2> $OUTPUT
491
492# Display any errors that occurred during system file walk.
493if [ -s $OUTPUT ] ; then
494	echo "${pending}Setuid/device find errors:"
495	pending=
496	cat $OUTPUT
497	echo ""
498fi
499
500# Display any changes in the setuid/setgid file list.
501FIELDS1=1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,0
502FIELDS2=2.1,2.2,2.3,2.4,2.5,2.6,2.7,2.8,2.9,0
503egrep -av '^[bc]' $LIST | join -o $FIELDS2 -110 -210 -v2 /dev/null - > $TMP1
504if [ -s $TMP1 ] ; then
505	# Check to make sure uudecode isn't setuid.
506	if grep -aw uudecode $TMP1 > /dev/null ; then
507		echo "${pending}\nUudecode is setuid."
508		pending=
509	fi
510
511	CUR=/var/backups/setuid.current
512	BACK=/var/backups/setuid.backup
513
514	if [ -s $CUR ] ; then
515		if cmp -s $CUR $TMP1 ; then
516			:
517		else
518			> $TMP2
519			join -o $FIELDS2 -110 -210 -v2 $CUR $TMP1 > $OUTPUT
520			if [ -s $OUTPUT ] ; then
521				echo "${pending}Setuid additions:"
522				pending=
523				tee -a $TMP2 < $OUTPUT | column -t
524				echo ""
525			fi
526
527			join -o $FIELDS1 -110 -210 -v1 $CUR $TMP1 > $OUTPUT
528			if [ -s $OUTPUT ] ; then
529				echo "${pending}Setuid deletions:"
530				pending=
531				tee -a $TMP2 < $OUTPUT | column -t
532				echo ""
533			fi
534
535			sort +9 $TMP2 $CUR $TMP1 | \
536			    sed -e 's/[	 ][	 ]*/ /g' | uniq -u > $OUTPUT
537			if [ -s $OUTPUT ] ; then
538				echo "${pending}Setuid changes:"
539				pending=
540				column -t $OUTPUT
541				echo ""
542			fi
543
544			cp $CUR $BACK
545			cp $TMP1 $CUR
546		fi
547	else
548		echo "${pending}Setuid additions:"
549		pending=
550		column -t $TMP1
551		echo ""
552		cp $TMP1 $CUR
553	fi
554fi
555
556# Check for block and character disk devices that are readable or writeable
557# or not owned by root.operator.
558>$TMP1
559DISKLIST="ccd dk fd hd hk hp jb kra ra rb rd rl rx rz sd up vnd wd xd"
560for i in $DISKLIST; do
561	egrep "^b.*/${i}[0-9][0-9]*[B-H]?[a-p]$"  $LIST >> $TMP1
562	egrep "^c.*/r${i}[0-9][0-9]*[B-H]?[a-p]$"  $LIST >> $TMP1
563done
564
565awk '$3 != "root" || $4 != "operator" || $1 !~ /.rw-r-----/ \
566	{ printf("Disk %s is user %s, group %s, permissions %s.\n", \
567	    $11, $3, $4, $1); }' < $TMP1 > $OUTPUT
568if [ -s $OUTPUT ] ; then
569	echo "\nChecking disk ownership and permissions."
570	cat $OUTPUT
571	echo ""
572fi
573
574FIELDS1=1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,1.10,0
575FIELDS2=2.1,2.2,2.3,2.4,2.5,2.6,2.7,2.8,2.9,2.10,0
576# Display any changes in the device file list.
577egrep -a '^[bc]' $LIST | sort +10 | \
578    join -o $FIELDS2 -111 -211 -v2 /dev/null - > $TMP1
579if [ -s $TMP1 ] ; then
580	CUR=/var/backups/device.current
581	BACK=/var/backups/device.backup
582
583	if [ -s $CUR ] ; then
584		if cmp -s $CUR $TMP1 ; then
585			:
586		else
587			> $TMP2
588			join -o $FIELDS2 -111 -211 -v2 $CUR $TMP1 > $OUTPUT
589			if [ -s $OUTPUT ] ; then
590				echo "Device additions:"
591				tee -a $TMP2 < $OUTPUT | column -t
592				echo ""
593			fi
594
595			join -o $FIELDS1 -111 -211 -v1 $CUR $TMP1 > $OUTPUT
596			if [ -s $OUTPUT ] ; then
597				echo "Device deletions:"
598				tee -a $TMP2 < $OUTPUT | column -t
599				echo ""
600			fi
601
602			# Report any block device change.  Ignore character
603			# devices, only the name is significant.
604			cat $TMP2 $CUR $TMP1 | \
605			sed -e '/^c/d' | \
606			sort +10 | \
607			sed -e 's/[	 ][	 ]*/ /g' | \
608			uniq -u > $OUTPUT
609			if [ -s $OUTPUT ] ; then
610				echo "Block device changes:"
611				column -t $OUTPUT
612				echo ""
613			fi
614
615			cp $CUR $BACK
616			cp $TMP1 $CUR
617		fi
618	else
619		echo "Device additions:"
620		column -t $TMP1
621		echo ""
622		cp $TMP1 $CUR
623	fi
624fi
625
626# Check special files.
627# Check system binaries.
628#
629# Create the mtree tree specifications using:
630#
631#	mtree -cx -p DIR -K md5digest,type >/etc/mtree/DIR.secure
632#	chown root:wheel /etc/mtree/DIR.secure
633#	chmod 600 /etc/mtree/DIR.secure
634#
635# Note, this is not complete protection against Trojan horsed binaries, as
636# the hacker can modify the tree specification to match the replaced binary.
637# For details on really protecting yourself against modified binaries, see
638# the mtree(8) manual page.
639if [ -d /etc/mtree ] ; then
640	cd /etc/mtree
641	mtree -e -l -p / -f /etc/mtree/special > $OUTPUT
642	if [ -s $OUTPUT ] ; then
643		echo "\nChecking special files and directories."
644		echo "Output format is:\n\tfilename:"
645		echo "\t\tcriteria (shouldbe, reallyis)"
646		cat $OUTPUT
647		echo "\nIf this message bugs you, use:"
648		echo "\tmtree -e -p / -f /etc/mtree/special -U"
649	fi
650
651	> $OUTPUT
652	for file in *.secure; do
653		[ $file = '*.secure' ] && continue
654		tree=$(sed -n -e '3s/.* //p' -e 3q $file)
655		mtree -f $file -p $tree > $TMP1
656		if [ -s $TMP1 ] ; then
657			echo "\nChecking ${tree}:" >> $OUTPUT
658			cat $TMP1 >> $OUTPUT
659		fi
660	done
661	if [ -s $OUTPUT ] ; then
662		echo "\nChecking system binaries:"
663		cat $OUTPUT
664	fi
665else
666	echo /etc/mtree is missing
667fi
668
669# Record a list of installed packages too; be sure to record
670# the change to emptiness if previously recorded, but to not
671# complain if no MirPorts Framework package tools installed.
672if [[ -s /var/backups/pkglist || -x /usr/mpkg/sbin/pkg_info ]]; then
673	if [[ -x /usr/mpkg/sbin/pkg_info ]]; then
674		/usr/mpkg/sbin/pkg_info | sort
675	else
676		:
677	fi >/var/backups/pkglist
678fi
679
680# List of files that get backed up and checked for any modifications.
681# We try to use GNU RCS for versioning backups, otherwise, rotate: Each
682# file is expected to have two backups, /var/backups/file.{current,backup}.
683# Any changes cause the files to rotate.
684if [ -s /etc/changelist ] ; then
685	# Handle /etc/changelist.local
686	if R=$(mktemp); then
687		cat /etc/changelist >$R
688		[ -e /etc/changelist.local ] && \
689		    cat /etc/changelist.local >>$R
690	else
691		R=/etc/changelist
692	fi
693	# Now check
694	_stripcom $R | while read file; do
695		case $file {
696		(+*)	fback -m "${file#+}" ;;
697		(-*)	fback -n "${file#-}" ;;
698		(/*)	fback -- "${file}" ;;
699		}
700	done
701
702	for file in $(egrep -v "^(#|\+|$MP)" $R); do
703		fback -- "$file"
704	done
705	for file in $(egrep "^\+" $R); do
706		fback -m "${file#+}"
707	done
708	[ /etc/changelist = $R ] || rm -f $R
709fi
710
711# Make backups of the labels for any mounted disks and produce diffs
712# when they change.
713for d in `df -ln | sed -n 's:^/dev/\([a-z]*[0-9]*\)[a-p].*$:\1:p' | sort -u`; do
714	if disklabel $d >$DIR/_disklabel 2>&1; then
715		fback -- $DIR/_disklabel disklabel.$d
716	fi
717done
718