security revision 1.129
1#!/bin/sh -
2#
3#	$NetBSD: security,v 1.129 2021/11/04 12:40:00 nia Exp $
4#	from: @(#)security	8.1 (Berkeley) 6/9/93
5#
6
7PATH=/sbin:/usr/sbin:/bin:/usr/bin
8
9rcvar_manpage='security.conf(5)'
10
11if [ -f /etc/rc.subr ]; then
12	. /etc/rc.subr
13else
14	echo "Can't read /etc/rc.subr; aborting."
15	exit 1;
16fi
17
18umask 077
19TZ=UTC; export TZ
20
21if [ -s /etc/security.conf ]; then
22	. /etc/security.conf
23fi
24if [ -s /etc/pkgpath.conf ]; then
25	. /etc/pkgpath.conf
26fi
27
28# Set reasonable defaults (if they're not set in security.conf)
29#
30backup_dir=${backup_dir:-/var/backups}
31max_loginlen=${max_loginlen:-8}
32max_grouplen=${max_grouplen:-8}
33pkg_admin=${pkg_admin:-/usr/sbin/pkg_admin}
34pkg_info=${pkg_info:-/usr/sbin/pkg_info}
35
36# Other configurable variables
37#
38special_files="/etc/mtree/special /etc/mtree/special.local"
39MP=/etc/master.passwd
40CHANGELIST=""
41work_dir=$backup_dir/work
42
43if [ ! -d "$work_dir" ]; then
44	mkdir -p "$work_dir"
45fi
46
47SECUREDIR=$(mktemp -d -t _securedir) || exit 1
48
49trap "/bin/rm -rf $SECUREDIR ; exit 0" EXIT INT QUIT PIPE
50
51if ! cd "$SECUREDIR"; then
52	echo "Can not cd to $SECUREDIR".
53	exit 1
54fi
55
56ERR=err.$$
57TMP1=tmp1.$$
58TMP2=tmp2.$$
59MPBYUID=mpbyuid.$$
60MPBYPATH=mpbypath.$$
61LIST=list.$$
62OUTPUT=output.$$
63LABELS=labels.$$
64LVM_LABELS=lvm.$$
65PKGS=pkgs.$$
66CHANGEFILES=changefiles.$$
67SPECIALSPEC=specialspec.$$
68
69if [ -n "${pkgdb_dir}" ]; then
70	echo "WARNING: Setting pkgdb_dir in security.conf(5) is deprecated"
71	echo "WARNING: Please define PKG_DBDIR in pkg_install.conf(5) instead"
72	_compat_K_flag="-K ${pkgdb_dir}"
73fi
74
75have_pkgs() {
76	$pkg_info ${_compat_K_flag} -q -E '*'
77}
78
79# migrate_file old new
80#	Determine if the "${old}" path name needs to be migrated to the
81#	"${new}" path. Also checks if "${old}.current" needs migrating,
82#	and if so, migrate it and possibly "${old}.current,v" and
83#	"${old}.backup".
84#
85migrate_file()
86{
87	_old=$1
88	_new=$2
89	if [ -z "$_old" ] || [ -z "$_new" ]; then
90		err 3 "USAGE: migrate_file old new"
91	fi
92	if [ ! -d "${_new%/*}" ]; then
93		mkdir -p "${_new%/*}"
94	fi
95	if [ -f "${_old}" ] && ! [ -f "${_new}" ]; then
96		echo "==> migrating ${_old}"
97		echo "           to ${_new}"
98		mv "${_old}" "${_new}"
99	fi
100	if [ -f "${_old}.current" ] && ! [ -f "${_new}.current" ]; then
101		echo "==> migrating ${_old}.current"
102		echo "           to ${_new}.current"
103		mv "${_old}.current" "${_new}.current"
104		if [ -f "${_old}.current,v" ] &&
105		 ! [ -f "${_new}.current,v" ]; then
106			echo "==> migrating ${_old}.current,v"
107			echo "           to ${_new}.current,v"
108			mv "${_old}.current,v" "${_new}.current,v"
109		fi
110		if [ -f "${_old}.backup" ] && ! [ -f "${_new}.backup" ]; then
111			echo "==> migrating ${_old}.backup"
112			echo "           to ${_new}.backup"
113			mv "${_old}.backup" "${_new}.backup"
114		fi
115	fi
116}
117
118
119# backup_and_diff file printdiff
120#	Determine if file needs backing up, and if so, do it.
121#	If printdiff is yes, display the diffs, otherwise
122#	just print a message saying "[changes omitted]".
123#
124backup_and_diff()
125{
126	_file=$1
127	_printdiff=$2
128	if [ -z "$_file" ] || [ -z "$_printdiff" ]; then
129		err 3 "USAGE: backup_and_diff file printdiff"
130	fi
131	! checkyesno _printdiff
132	_printdiff=$?
133
134	_old=$backup_dir/${_file##*/}
135	case "$_file" in
136	$work_dir/*)
137		_new=$_file
138		migrate_file "$backup_dir/$_old" "$_new"
139		migrate_file "$_old" "$_new"
140		;;
141	*)
142		_new=$backup_dir/$_file
143		migrate_file "$_old" "$_new"
144		;;
145	esac
146	CUR=${_new}.current
147	BACK=${_new}.backup
148	if [ -f $_file ]; then
149		if [ -f $CUR ] ; then
150			if [ "$_printdiff" -ne 0 ]; then
151				diff ${diff_options} $CUR $_file > $OUTPUT
152			else
153				if ! cmp -s $CUR $_file; then
154					echo "[changes omitted]"
155				fi > $OUTPUT
156			fi
157			if [ -s $OUTPUT ] ; then
158				printf \
159			"\n======\n%s diffs (OLD < > NEW)\n======\n" $_file
160				cat $OUTPUT
161				backup_file update $_file $CUR $BACK
162			fi
163		else
164			printf "\n======\n%s added\n======\n" $_file
165			if [ "$_printdiff" -ne 0 ]; then
166				diff ${diff_options} /dev/null $_file
167			else
168				echo "[changes omitted]"
169			fi
170			backup_file add $_file $CUR $BACK
171		fi
172	else
173		if [ -f $CUR ]; then
174			printf "\n======\n%s removed\n======\n" $_file
175			if [ "$_printdiff" -ne 0 ]; then
176				diff ${diff_options} $CUR /dev/null
177			else
178				echo "[changes omitted]"
179			fi
180			backup_file remove $_file $CUR $BACK
181		fi
182	fi
183}
184
185
186# These are used several times.
187#
188awk -F: '!/^\+/ { print $1 " " $3 }' $MP | sort -k2n > $MPBYUID
189awk -F: '{ print $1 " " $9 }' $MP | sort -k2 > $MPBYPATH
190for file in $special_files; do
191	[ -s $file ] && cat $file
192done | mtree -CM -k all > $SPECIALSPEC || exit 1
193
194
195# Check for enough entropy.
196#
197if checkyesno check_entropy; then
198	if ! dd if=/dev/random iflag=nonblock of=/dev/null bs=1 count=1 \
199	    msgfmt=quiet 2>/dev/null; then
200		printf '\n'
201		printf 'Entropy:\n'
202		printf 'System may need more entropy for cryptography.\n'
203		printf 'See the entropy(7) man page for details.\n'
204	fi
205fi
206
207
208# Check the master password file syntax.
209#
210if checkyesno check_passwd; then
211	# XXX: the sense of permit_star is reversed; the code works as
212	# implemented, but usage needs to be negated.
213	checkyesno check_passwd_permit_star && permit_star=0 || permit_star=1
214	checkyesno check_passwd_permit_nonalpha \
215		 && permit_nonalpha=1 || permit_nonalpha=0
216
217	awk -v "len=$max_loginlen" \
218	    -v "nowarn_shells_list=$check_passwd_nowarn_shells" \
219	    -v "nowarn_users_list=$check_passwd_nowarn_users" \
220	    -v "permit_star=$permit_star" \
221	    -v "permit_nonalpha=$permit_nonalpha" \
222	'
223	BEGIN {
224		while ( getline < "/etc/shells" > 0 ) {
225			if ($0 ~ /^\#/ || $0 ~ /^$/ )
226				continue;
227			shells[$1]++;
228		}
229		split(nowarn_shells_list, a);
230		for (i in a) nowarn_shells[a[i]]++;
231		split(nowarn_users_list, a);
232		for (i in a) nowarn_users[a[i]]++;
233		uid0_users_list="root toor"
234		split(uid0_users_list, a);
235		for (i in a) uid0_users[a[i]]++;
236		FS=":";
237	}
238
239	{
240		if ($0 ~ /^[	 ]*$/) {
241			printf "Line %d is a blank line.\n", NR;
242			next;
243		}
244
245		# NIS compat entry?
246		compatline = $1 ~ "^[\\+-]";
247		if (compatline) {
248			if ($1 == "+" && NF == 1) {
249				next;
250			}
251			sub("^.", "", $1);
252		}
253		if (NF != 10)
254			printf "Line %d has the wrong number of fields.\n", NR;
255		if (compatline)  {
256			if ($3 == 0)
257			    printf "Line %d includes entries with uid 0.\n",
258			        NR;
259			if ($1 == "")
260			    next;
261		}
262		if (!permit_nonalpha &&
263		    $1 !~ /^[_A-Za-z0-9]([-A-Za-z0-9_.]*[A-Za-z0-9])*$/)
264			printf "Login %s has non-alphanumeric characters.\n",
265			    $1;
266		if (length($1) > len)
267			printf "Login %s has more than "len" characters.\n",
268			    $1;
269		if ($2 == "" && !compatline && !nowarn_users[$1])
270			    printf "Login %s has no password.\n", $1;
271		if (!nowarn_shells[$10] && !nowarn_users[$1]) {
272		    if (length($2) != 13 &&
273		    	length($2) != 20 &&
274		    	$2 !~ /^\$1/ &&
275		    	$2 !~ /^\$2/ &&
276			$2 !~ /^\$sha1/ &&
277			$2 !~ /^\$argon2(i|d|id)/ &&
278		    	$2 != "" &&
279			(permit_star || $2 != "*") &&
280		    	$2 !~ /^\*[A-z-]+$/ &&
281			$1 != "toor") {
282		    	    if ($10 == "" || shells[$10])
283				printf "Login %s is off but still has "\
284				  "a valid shell (%s)\n", $1, $10;
285		    } else if (compatline && $10 == "") {
286			    # nothing
287		    } else if (! shells[$10])
288		    	    printf "Login %s does not have a valid "\
289			    "shell (%s)\n", $1, $10;
290		}
291		if ($3 == 0 && !uid0_users[$1] && !nowarn_users[$1])
292			printf "Login %s has a user id of 0.\n", $1;
293		if ($3 != "" && $3 < 0)
294			printf "Login %s has a negative user id.\n", $1;
295		if ($4 != "" && $4 < 0)
296			printf "Login %s has a negative group id.\n", $1;
297	}' < $MP > $OUTPUT
298	if [ -s $OUTPUT ] ; then
299		printf "\nChecking the $MP file:\n"
300		cat $OUTPUT
301	fi
302
303	awk -F: '{ print $1 }' $MP | sort | uniq -d > $OUTPUT
304	if [ -s $OUTPUT ] ; then
305		printf "\n$MP has duplicate user names.\n"
306		column $OUTPUT
307	fi
308
309	awk -v "permit_dups_list=$check_passwd_permit_dups" \
310	'
311	BEGIN {
312		split(permit_dups_list, a);
313		for (i in a) permit_dups[a[i]]++;
314	}
315	{
316		if (!permit_dups[$1])
317			print $2;
318	}' < $MPBYUID | uniq -d > $TMP2
319	if [ -s $TMP2 ] ; then
320		printf "\n$MP has duplicate user ids.\n"
321		while read uid; do
322			grep -w $uid $MPBYUID
323		done < $TMP2 | column
324	fi
325fi
326
327# Check the group file syntax.
328#
329if checkyesno check_group; then
330	GRP=/etc/group
331	awk -F: -v "len=$max_grouplen" '{
332		if ($0 ~ /^[	 ]*$/) {
333			printf "Line %d is a blank line.\n", NR;
334			next;
335		}
336		if (NF != 4 && ($1 != "+" || NF != 1))
337			printf "Line %d has the wrong number of fields.\n", NR;
338		if ($1 == "+" )  {
339			next;
340		}
341		if ($1 !~ /^[_A-Za-z0-9]([-A-Za-z0-9_.]*[A-Za-z0-9])*$/)
342			printf "Group %s has non-alphanumeric characters.\n",
343			    $1;
344		if (length($1) > len)
345			printf "Group %s has more than "len" characters.\n", $1;
346		if ($3 !~ /[0-9]*/)
347			printf "Login %s has a negative group id.\n", $1;
348	}' < $GRP > $OUTPUT
349	if [ -s $OUTPUT ] ; then
350		printf "\nChecking the $GRP file:\n"
351		cat $OUTPUT
352	fi
353
354	awk -F: '{ print $1 }' $GRP | sort | uniq -d > $OUTPUT
355	dupgroups=""
356	for group in $(cat $OUTPUT) ; do
357		gcount=$(awk -F: "/$group/ { print \$1,\$3 }" $GRP |
358			sort -u | wc -l)
359		if [ $gcount -gt 1 ]; then
360			dupgroups="$dupgroups $group"
361		fi
362	done
363	if [ ! -z $dupgroups ] ; then
364		printf "\n$GRP has duplicate group names.\n"
365		printf "$dupgroups\n"
366	fi
367fi
368
369# Check for root paths, umask values in startup files.
370# The check for the root paths is problematical -- it's likely to fail
371# in other environments.  Once the shells have been modified to warn
372# of '.' in the path, the path tests should go away.
373#
374if checkyesno check_rootdotfiles; then
375	rhome=~root
376	umaskset=no
377	list="/etc/csh.cshrc /etc/csh.login ${rhome}/.cshrc ${rhome}/.login"
378	for i in $list ; do
379		if [ -f $i ] ; then
380			if egrep '^[ \t]*umask[ \t]+[0-7]+' $i > /dev/null ;
381			then
382				umaskset=yes
383			fi
384			# Double check the umask value itself; ensure that
385			# both the group and other write bits are set.
386			#
387			egrep '^[ \t]*umask[ \t]+[0-7]+' $i |
388			awk '{
389				if ($2 ~ /^.$/ || $2 ~! /[^2367].$/) {
390					print "\tRoot umask is group writable"
391				}
392				if ($2 ~ /[^2367]$/) {
393					print "\tRoot umask is other writable"
394			    	}
395			    }' | sort -u
396			SAVE_PATH=$PATH
397			unset PATH
398			/bin/csh -f -s << end-of-csh > /dev/null 2>&1
399				source $i
400				/bin/ls -ldgT \$path > $TMP1
401end-of-csh
402			export PATH=$SAVE_PATH
403			awk '{
404				if ($10 ~ /^\.$/) {
405					print "\tThe root path includes .";
406					next;
407				}
408			     }
409			     $1 ~ /^d....w/ \
410		{ print "\tRoot path directory " $10 " is group writable." } \
411			     $1 ~ /^d.......w/ \
412		{ print "\tRoot path directory " $10 " is other writable." }' \
413			< $TMP1
414		fi
415	done > $OUTPUT
416	if [ $umaskset = no ] || [ -s $OUTPUT ] ; then
417		printf "\nChecking root csh paths, umask values:\n$list\n\n"
418		if [ -s $OUTPUT ]; then
419			cat $OUTPUT
420		fi
421		if [ $umaskset = no ] ; then
422		    printf "\tRoot csh startup files do not set the umask.\n"
423		fi
424	fi
425
426	umaskset=no
427	list="/etc/profile ${rhome}/.profile"
428	for i in $list; do
429		if [ -f $i ] ; then
430			if egrep umask $i > /dev/null ; then
431				umaskset=yes
432			fi
433			egrep umask $i |
434			awk '$2 ~ /^.$/ || $2 ~ /[^2367].$/ \
435				{ print "\tRoot umask is group writable" } \
436			     $2 ~ /[^2367]$/ \
437				{ print "\tRoot umask is other writable" }'
438			SAVE_PATH=$PATH
439			unset PATH
440			/bin/sh << end-of-sh > /dev/null 2>&1
441				. $i
442				list=\$(echo \$PATH | /usr/bin/sed -e \
443				    's/^:/.:/;s/:$/:./;s/::/:.:/g;s/:/ /g')
444				/bin/ls -ldgT \$list > $TMP1
445end-of-sh
446			export PATH=$SAVE_PATH
447			awk '{
448				if ($10 ~ /^\.$/) {
449					print "\tThe root path includes .";
450					next;
451				}
452			     }
453			     $1 ~ /^d....w/ \
454		{ print "\tRoot path directory " $10 " is group writable." } \
455			     $1 ~ /^d.......w/ \
456		{ print "\tRoot path directory " $10 " is other writable." }' \
457			< $TMP1
458
459		fi
460	done > $OUTPUT
461	if [ $umaskset = no ] || [ -s $OUTPUT ] ; then
462		printf "\nChecking root sh paths, umask values:\n$list\n"
463		if [ -s $OUTPUT ]; then
464			cat $OUTPUT
465		fi
466		if [ $umaskset = no ] ; then
467			printf "\tRoot sh startup files do not set the umask.\n"
468		fi
469	fi
470fi
471
472# Root and uucp should both be in /etc/ftpusers.
473#
474if checkyesno check_ftpusers; then
475	list="uucp "$(awk '$2 == 0 { print $1 }' $MPBYUID)
476	for i in $list; do
477		if /usr/libexec/ftpd -C $i ; then
478			printf "\t$i is not denied\n"
479		fi
480	done > $OUTPUT
481	if [ -s $OUTPUT ]; then
482		printf "\nChecking the /etc/ftpusers configuration:\n"
483		cat $OUTPUT
484	fi
485fi
486
487# Uudecode should not be in the /etc/mail/aliases file.
488#
489if checkyesno check_aliases; then
490	for f in /etc/mail/aliases /etc/aliases; do
491		if [ -f $f ] && egrep '^[^#]*(uudecode|decode).*\|' $f; then
492			printf "\nEntry for uudecode in $f file.\n"
493		fi
494	done
495fi
496
497# Files that should not have + signs.
498#
499if checkyesno check_rhosts; then
500	list="/etc/hosts.equiv /etc/hosts.lpd"
501	for f in $list ; do
502		if [ -f $f ] && egrep '\+' $f > /dev/null ; then
503			printf "\nPlus sign in $f file.\n"
504		fi
505	done
506
507	# Check for special users with .rhosts files.  Only root and toor should
508	# have .rhosts files.  Also, .rhosts files should not have plus signs.
509	awk -F: '$1 != "root" && $1 != "toor" && \
510		($3 < 100 || $1 == "ftp" || $1 == "uucp") \
511			{ print $1 " " $9 }' $MP |
512	sort -k2 |
513	while read uid homedir; do
514		if [ -f ${homedir}/.rhosts ] ; then
515			rhost=$(ls -ldgT ${homedir}/.rhosts)
516			printf -- "$uid: $rhost\n"
517		fi
518	done > $OUTPUT
519	if [ -s $OUTPUT ] ; then
520		printf "\nChecking for special users with .rhosts files.\n"
521		cat $OUTPUT
522	fi
523
524	while read uid homedir; do
525		if [ -f ${homedir}/.rhosts ] &&
526		   [ -r ${homedir}/.rhosts ] &&
527		   cat -f ${homedir}/.rhosts | egrep '\+' > /dev/null
528		then
529			printf -- "$uid: + in .rhosts file.\n"
530		fi
531	done < $MPBYPATH > $OUTPUT
532	if [ -s $OUTPUT ] ; then
533		printf "\nChecking .rhosts files syntax.\n"
534		cat $OUTPUT
535	fi
536fi
537
538# Check home directories.  Directories should not be owned by someone else
539# or writable.
540#
541if checkyesno check_homes; then
542	checkyesno check_homes_permit_usergroups && \
543		permit_usergroups=1 || permit_usergroups=0
544	while read uid homedir; do
545		if [ -d ${homedir}/ ] ; then
546			file=$(ls -ldgT ${homedir})
547			printf -- "$uid $file\n"
548		fi
549	done < $MPBYPATH |
550	awk -v "usergroups=$permit_usergroups" \
551	    -v "permit_owners_list=$check_homes_permit_other_owner"  '
552	     BEGIN {
553		split(permit_owners_list, a);
554		for (i in a) permit_owners[a[i]]++;
555	     }
556	     $1 != $4 && $4 != "root" && !permit_owners[$1] \
557		{ print "user " $1 " home directory is owned by " $4 }
558	     $2 ~ /^d....w/ && (!usergroups || $5 != $1) \
559		{ print "user " $1 " home directory is group writable" }
560	     $2 ~ /^d.......w/ \
561		{ print "user " $1 " home directory is other writable" }' \
562	    > $OUTPUT
563	if [ -s $OUTPUT ] ; then
564		printf "\nChecking home directories.\n"
565		cat $OUTPUT
566	fi
567
568	# Files that should not be owned by someone else or readable.
569	list=".Xauthority .netrc .ssh/id_dsa .ssh/id_rsa .ssh/identity"
570	while read uid homedir; do
571		for f in $list ; do
572			file=${homedir}/${f}
573			if [ -f $file ] ; then
574				printf -- "$uid $f $(ls -ldgT $file)\n"
575			fi
576		done
577	done < $MPBYPATH |
578	awk -v "usergroups=$permit_usergroups" \
579	    -v "permit_owners_list=$check_homes_permit_other_owner"  '
580	     BEGIN {
581		split(permit_owners_list, a);
582		for (i in a) permit_owners[a[i]]++;
583	     }
584	     $1 != $5 && $5 != "root" && !permit_owners[$1] \
585		{ print "user " $1 " " $2 " file is owned by " $5 }
586	     $3 ~ /^-...r/ && (!usergroups || $6 != $1) \
587		{ print "user " $1 " " $2 " file is group readable" }
588	     $3 ~ /^-......r/ \
589		{ print "user " $1 " " $2 " file is other readable" }
590	     $3 ~ /^-....w/ && (!usergroups || $6 != $1) \
591		{ print "user " $1 " " $2 " file is group writable" }
592	     $3 ~ /^-.......w/ \
593		{ print "user " $1 " " $2 " file is other writable" }' \
594	    > $OUTPUT
595
596	# Files that should not be owned by someone else or writable.
597	list=".bash_history .bash_login .bash_logout .bash_profile .bashrc \
598	      .cshrc .emacs .exrc .forward .history .k5login .klogin .login \
599	      .logout .profile .qmail .rc_history .rhosts .shosts ssh .tcshrc \
600	      .twmrc .xinitrc .xsession .ssh/authorized_keys \
601	      .ssh/authorized_keys2 .ssh/config .ssh/id_dsa.pub \
602	      .ssh/id_rsa.pub .ssh/identity.pub .ssh/known_hosts \
603	      .ssh/known_hosts2"
604	while read uid homedir; do
605		for f in $list ; do
606			file=${homedir}/${f}
607			if [ -f $file ] ; then
608				printf -- "$uid $f $(ls -ldgT $file)\n"
609			fi
610		done
611	done < $MPBYPATH |
612	awk -v "usergroups=$permit_usergroups" \
613	    -v "permit_owners_list=$check_homes_permit_other_owner"  '
614	     BEGIN {
615		split(permit_owners_list, a);
616		for (i in a) permit_owners[a[i]]++;
617	     }
618	     $1 != $5 && $5 != "root" && !permit_owners[$1] \
619		{ print "user " $1 " " $2 " file is owned by " $5 }
620	     $3 ~ /^-....w/ && (!usergroups || $6 != $1) \
621		{ print "user " $1 " " $2 " file is group writable" }
622	     $3 ~ /^-.......w/ \
623		{ print "user " $1 " " $2 " file is other writable" }' \
624	    >> $OUTPUT
625	if [ -s $OUTPUT ] ; then
626		printf "\nChecking dot files.\n"
627		cat $OUTPUT
628	fi
629fi
630
631# Mailboxes should be owned by user and unreadable.
632#
633if checkyesno check_varmail; then
634	ls -lA /var/mail | \
635	awk '	NR == 1 { next; }
636		$9 ~ /^\./ {next; }
637	    	$3 != $9 {
638			print "user " $9 " mailbox is owned by " $3
639		}
640		$1 != "-rw-------" {
641			print "user " $9 " mailbox is " $1 ", group " $4
642		}' > $OUTPUT
643	if [ -s $OUTPUT ] ; then
644		printf "\nChecking mailbox ownership.\n"
645		cat $OUTPUT
646	fi
647fi
648
649# NFS exports shouldn't be globally exported
650#
651if checkyesno check_nfs && [ -f /etc/exports ]; then
652	awk '{
653		# ignore comments and blank lines
654		if ($0 ~ /^\#/ || $0 ~ /^$/ )
655			next;
656		# manage line continuation
657		while ($NF ~ /^\\$/) {
658			$NF = "";
659			line = $0 "";
660			getline;
661			$0 = line $0 "";
662		}
663
664		delete dir;
665		readonly = ndir = 0;
666		for (i = 1; i <= NF; ++i) {
667			if ($i ~ /^\//) dir[ndir++] = $i;
668			else if ($i ~ /^-/) {
669				if ($i ~ /^-(ro|o)$/) readonly = 1;
670				if ($i ~ /^-network/) next;
671			}
672			else next;
673		}
674		if (readonly)
675			for (item in dir)
676				rodir[nrodir++] = dir[item];
677		else
678			for (item in dir)
679				rwdir[nrwdir++] = dir[item];
680
681	}
682
683	END {
684		if (nrodir) {
685			printf("Globally exported file system%s, read-only:\n",
686				nrodir > 1 ? "s" : "");
687			for (item in rodir)
688				printf("\t%s\n", rodir[item]);
689		}
690		if (nrwdir) {
691			printf("Globally exported file system%s, read-write:\n",
692				nrwdir > 1 ? "s" : "");
693			for (item in rwdir)
694				printf("\t%s\n", rwdir[item]);
695		}
696	}' < /etc/exports > $OUTPUT
697	if [ -s $OUTPUT ] ; then
698		printf "\nChecking for globally exported file systems.\n"
699		cat $OUTPUT
700	fi
701fi
702
703# Display any changes in setuid files and devices.
704#
705if checkyesno check_devices; then
706	> $ERR
707	(
708
709	# Convert check_devices_ignore_fstypes="foo !bar bax"
710	#    into "-fstype foo -o ! -fstype bar -o -fstype bax"
711	# and check_devices_ignore_paths="/foo !/bar /bax"
712	#    into " -path /foo -o ! -path /bar -o -path /bax"
713	#
714	ignexpr=$(\
715	    echo $check_devices_ignore_fstypes | \
716		sed -e's/\(!*\)\([^[:space:]]\{1,\}\)/-o \1 -fstype \2/g' ; \
717	    echo $check_devices_ignore_paths | \
718		sed -e's/\(!*\)\([^[:space:]]\{1,\}\)/-o \1 -path \2/g' \
719	)
720
721	# Massage the expression into ( $ignexpr ) -a -prune -o
722	if [ -n "${ignexpr}" ]; then
723		ignexpr=$(\
724			echo $ignexpr | \
725			    sed -e 's/^-o /( /' \
726				-e 's/$/ ) -a -prune -o/' \
727		)
728	fi
729
730	find / $ignexpr \
731	    \( \( -perm -u+s -a ! -type d \) -o \
732	       \( -perm -g+s -a ! -type d \) -o \
733	       -type b -o -type c \) -print0 | \
734	xargs -0 ls -ldgTq | sort +9 > $LIST
735
736	) 2> $OUTPUT
737
738	# Display any errors that occurred during system file walk.
739	if [ -s $OUTPUT ] ; then
740		printf "Setuid/device find errors:\n" >> $ERR
741		cat $OUTPUT >> $ERR
742		printf "\n" >> $ERR
743	fi
744
745	# Display any changes in the setuid file list.
746	egrep -v '^[bc]' $LIST > $TMP1
747	if [ -s $TMP1 ] ; then
748		# Check to make sure uudecode isn't setuid.
749		if grep -w uudecode $TMP1 > /dev/null ; then
750			printf "\nUudecode is setuid.\n" >> $ERR
751		fi
752
753		file=$work_dir/setuid
754		migrate_file "$backup_dir/setuid" "$file"
755		CUR=${file}.current
756		BACK=${file}.backup
757		if [ -s $CUR ] ; then
758			if cmp -s $CUR $TMP1 ; then
759				:
760			else
761				> $TMP2
762				join -110 -210 -v2 $CUR $TMP1 > $OUTPUT
763				if [ -s $OUTPUT ] ; then
764					printf "Setuid additions:\n" >> $ERR
765					tee -a $TMP2 < $OUTPUT >> $ERR
766					printf "\n" >> $ERR
767				fi
768
769				join -110 -210 -v1 $CUR $TMP1 > $OUTPUT
770				if [ -s $OUTPUT ] ; then
771					printf "Setuid deletions:\n" >> $ERR
772					tee -a $TMP2 < $OUTPUT >> $ERR
773					printf "\n" >> $ERR
774				fi
775
776				sort -k10 $TMP2 $CUR $TMP1 | \
777				    sed -e 's/[	 ][	 ]*/ /g' | \
778				    uniq -u > $OUTPUT
779				if [ -s $OUTPUT ] ; then
780					printf "Setuid changes:\n" >> $ERR
781					column -t $OUTPUT >> $ERR
782					printf "\n" >> $ERR
783				fi
784
785				backup_file update $TMP1 $CUR $BACK
786			fi
787		else
788			printf "Setuid additions:\n" >> $ERR
789			column -t $TMP1 >> $ERR
790			printf "\n" >> $ERR
791			backup_file add $TMP1 $CUR $BACK
792		fi
793	fi
794
795	# Check for block and character disk devices that are readable or
796	# writable or not owned by root.operator.
797	>$TMP1
798	DISKLIST="ccd ch hk hp ld md ra raid rb rd rl rx \
799	    sd se ss uk up vnd wd xd xy"
800#	DISKLIST="$DISKLIST ct mt st wt"
801	for i in $DISKLIST; do
802		egrep "^b.*/${i}[0-9][0-9]*[a-p]$"  $LIST >> $TMP1
803		egrep "^c.*/r${i}[0-9][0-9]*[a-p]$"  $LIST >> $TMP1
804	done
805
806	awk '$3 != "root" || $4 != "operator" || $1 !~ /.rw-r-----/ \
807		{ printf "Disk %s is user %s, group %s, permissions %s.\n", \
808		    $11, $3, $4, $1; }' < $TMP1 > $OUTPUT
809	if [ -s $OUTPUT ] ; then
810		printf "\nChecking disk ownership and permissions.\n" >> $ERR
811		cat $OUTPUT >> $ERR
812		printf "\n" >> $ERR
813	fi
814
815	# Display any changes in the device file list.
816	egrep '^[bc]' $LIST | sort -k11 > $TMP1
817	if [ -s $TMP1 ] ; then
818		file=$work_dir/device
819		migrate_file "$backup_dir/device" "$file"
820		CUR=${file}.current
821		BACK=${file}.backup
822
823		if [ -s $CUR ] ; then
824			if cmp -s $CUR $TMP1 ; then
825				:
826			else
827				> $TMP2
828				join -111 -211 -v2 $CUR $TMP1 > $OUTPUT
829				if [ -s $OUTPUT ] ; then
830					printf "Device additions:\n" >> $ERR
831					tee -a $TMP2 < $OUTPUT >> $ERR
832					printf "\n" >> $ERR
833				fi
834
835				join -111 -211 -v1 $CUR $TMP1 > $OUTPUT
836				if [ -s $OUTPUT ] ; then
837					printf "Device deletions:\n" >> $ERR
838					tee -a $TMP2 < $OUTPUT >> $ERR
839					printf "\n" >> $ERR
840				fi
841
842				# Report any block device change. Ignore
843				# character devices, only the name is
844				# significant.
845				cat $TMP2 $CUR $TMP1 | \
846				    sed -e '/^c/d' | \
847				    sort -k11 | \
848				    sed -e 's/[	 ][	 ]*/ /g' | \
849				    uniq -u > $OUTPUT
850				if [ -s $OUTPUT ] ; then
851					printf "Block device changes:\n" >> $ERR
852					column -t $OUTPUT >> $ERR
853					printf "\n" >> $ERR
854				fi
855
856				backup_file update $TMP1 $CUR $BACK
857			fi
858		else
859			printf "Device additions:\n" >> $ERR
860			column -t $TMP1 >> $ERR
861			printf "\n" >> $ERR
862			backup_file add $TMP1 $CUR $BACK >> $ERR
863		fi
864	fi
865	if [ -s $ERR ] ; then
866		printf "\nChecking setuid files and devices:\n"
867		cat $ERR
868		printf "\n"
869	fi
870fi
871
872# Check special files.
873# Check system binaries.
874#
875# Create the mtree tree specifications using:
876#	mtree -cx -pDIR -kmd5,uid,gid,mode,nlink,size,link,time > DIR.secure
877#	chown root:wheel DIR.secure
878#	chmod u+r,go= DIR.secure
879#
880# Note, this is not complete protection against Trojan horsed binaries, as
881# the hacker can modify the tree specification to match the replaced binary.
882# For details on really protecting yourself against modified binaries, see
883# the mtree(8) manual page.
884#
885if checkyesno check_mtree; then
886	if checkyesno check_mtree_follow_symlinks; then
887		check_mtree_flags="-L"
888	else
889		check_mtree_flags=""
890	fi
891	mtree -e -l -p / $check_mtree_flags -f $SPECIALSPEC 3>&1 >$OUTPUT 2>&3 |
892		grep -v '^mtree: dev/tty: Device not configured$' >&2
893	if [ -s $OUTPUT ]; then
894		printf "\nChecking special files and directories.\n"
895		cat $OUTPUT
896	fi
897
898	for file in /etc/mtree/*.secure; do
899		[ $file = '/etc/mtree/*.secure' ] && continue
900		tree=$(sed -n -e '3s/.* //p' -e 3q $file)
901		mtree $check_mtree_flags -f $file -p $tree > $TMP1
902		if [ -s $TMP1 ]; then
903			printf "\nChecking $tree:\n"
904			cat $TMP1
905		fi
906	done > $OUTPUT
907	if [ -s $OUTPUT ]; then
908		printf "\nChecking system binaries:\n"
909		cat $OUTPUT
910	fi
911fi
912
913# Backup disklabels of available disks
914#
915if checkyesno check_disklabels; then
916		# migrate old disklabels
917	for file in $(ls -1d $backup_dir/$backup_dir/disklabel.* \
918	    $backup_dir/disklabel.* 2>/dev/null); do
919		migrate_file "$file" "$work_dir/${file##*/}"
920	done
921
922		# generate list of old disklabels, fdisks & wedges,
923		# and remove them
924	ls -1d $work_dir/disklabel.* $work_dir/fdisk.* $work_dir/wedges.* \
925	    2>/dev/null |
926	    egrep -v '\.(backup|current)(,v)?$' > $LABELS
927	xargs rm < $LABELS
928
929	disks="$(/sbin/sysctl -n hw.iostatnames)"
930
931		# generate disklabels of all disks excluding:	cd fd md dk st
932		# nfs and "device" (the header of iostat)
933	for i in $disks; do
934		case $i in
935		[cfm]d[0-9]*|dk[0-9]*|st[0-9]*|nfs[0-9]*)
936			;;
937		*)
938			if disklabel $i > /dev/null 2>&1; then
939				disklabel $i > "$work_dir/disklabel.$i"
940			fi
941			;;
942		esac
943	done
944
945		# if fdisk is available, generate fdisks for:	ed ld sd wd
946	if [ -x /sbin/fdisk ]; then
947		for i in $disks; do
948			case $i in
949			[elsw]d[0-9]*)
950				/sbin/fdisk $i > "$work_dir/fdisk.$i" \
951				    2>/dev/null
952				;;
953			esac
954		done
955	fi
956
957		# if dkctl is available, generate dkctl listwedges
958		# for:	ed ld sd wd cgd ofdisk ra rl raid
959	if [ -x /sbin/dkctl ]; then
960		for i in $disks; do
961			case $i in
962			[elsw]d[0-9]*|cgd[0-9]*|ofdisk[0-9]*|r[al][0-9]*|raid[0-9]*)
963				if /sbin/dkctl $i listwedges |
964				     grep -qe '[0-9] wedges:'; then
965					/sbin/dkctl $i listwedges \
966					    > "$work_dir/wedges.$i" 2>/dev/null
967				fi
968				;;
969			esac
970		done
971	fi
972
973		# if raidctl is available, generate raidctls for:	raid
974	if [ -x /sbin/raidctl ]; then
975		disks=$(iostat -x | awk 'NR > 1 && $1 ~ /^raid/ { print $1; }')
976		for i in $disks; do
977			/sbin/raidctl -G $i > "$work_dir/raidconf.$i" \
978				2>/dev/null
979		done
980	fi
981
982		# append list of new disklabels, fdisks and wedges
983	ls -1d $work_dir/disklabel.* $work_dir/fdisk.* $work_dir/wedges.* \
984	    $work_dir/raidconf.* 2>/dev/null |
985	    egrep -v '\.(backup|current)(,v)?$' >> $LABELS
986	CHANGELIST="$LABELS $CHANGELIST"
987fi
988
989if checkyesno check_lvm; then
990		# generate list of existing LVM elements Physical Volumes,
991		# Volume Groups and Logical Volumes.
992	if [ -x /sbin/lvm ]; then
993		lvm pvdisplay -m >"$work_dir/lvm.pv" 2>/dev/null
994		lvm vgdisplay -m >"$work_dir/lvm.vg" 2>/dev/null
995		lvm lvdisplay -m >"$work_dir/lvm.lv" 2>/dev/null
996	fi
997	ls -1d $work_dir/lvm.* 2>/dev/null |
998	    egrep -v '\.(backup|current)(,v)?$'>> $LVM_LABELS
999	CHANGELIST="$CHANGELIST $LVM_LABELS"
1000fi
1001
1002# Check for changes in the list of installed pkgs
1003#
1004if checkyesno check_pkgs && have_pkgs; then
1005	pkgs=$work_dir/pkgs
1006	migrate_file "$backup_dir/pkgs" "$pkgs"
1007	pkg_dbdir=$(${pkg_admin} config-var PKG_DBDIR)
1008	: ${pkg_dbdir:=/usr/pkg/pkgdb}
1009	(	cd $pkg_dbdir
1010		$pkg_info | sort
1011		echo ""
1012		find . \( -name +REQUIRED_BY -o -name +CONTENTS \) -print0 |
1013			xargs -0 ls -ldgTq | sort -t. +1 | sed -e 's, \./, ,'
1014	 ) > $pkgs
1015	echo "$pkgs" > $PKGS
1016	CHANGELIST="$PKGS $CHANGELIST"
1017fi
1018
1019# List of files that get backed up and checked for any modifications.
1020# Any changes cause the files to rotate.
1021#
1022if checkyesno check_changelist ; then
1023	mtree -D -k type -f $SPECIALSPEC -E exclude |
1024	    sed '/^type=file/!d ; s/type=file \.//' | unvis > $CHANGEFILES
1025
1026	(
1027		# Add other files which might dynamically exist:
1028		#	/etc/ifconfig.*
1029		#	/etc/raid*.conf
1030		#	/etc/rc.d/*
1031		#	/etc/rc.conf.d/*
1032		#
1033		echo "/etc/ifconfig.*"
1034		echo "/etc/raid*.conf"
1035		echo "/etc/rc.d/*"
1036		echo "/etc/rc.conf.d/*"
1037		echo "/etc/lvm/backup/*"
1038		echo "/etc/lvm/archive/*"
1039
1040		# Add /etc/changelist
1041		#
1042		if [ -s /etc/changelist ]; then
1043			grep -v '^#' /etc/changelist
1044		fi
1045	) | while read file; do
1046		case "$file" in
1047		*[\*\?\[]*)	# If changelist line is a glob ...
1048				# ... expand possible backup files
1049				#
1050			ls -1d $backup_dir/${file}.current 2>/dev/null \
1051			    | sed "s,^$backup_dir/,, ; s,\.current$,,"
1052
1053				# ... expand possible files
1054				#
1055			ls -1d $file 2>/dev/null
1056			;;
1057		*)
1058				# Otherwise, just print the filename
1059			echo $file
1060			;;
1061		esac
1062	done >> $CHANGEFILES
1063	CHANGELIST="$CHANGEFILES $CHANGELIST"
1064fi
1065
1066# Save entropy to ${random_file} if defined, like
1067# /etc/rc.d/random_seed.
1068#
1069if [ -n "${random_file:-}" ]; then
1070	rndctl -S "$random_file"
1071fi
1072
1073# Special case backups, including the master password file and
1074# ssh private host keys. The normal backup mechanisms for
1075# $check_changelist (see below) also print out the actual file
1076# differences and we don't want to do that for these files
1077#
1078echo $MP > $TMP1			# always add /etc/master.passwd
1079mtree -D -k type -f $SPECIALSPEC -I nodiff |
1080    sed '/^type=file/!d ; s/type=file \.//' | unvis >> $TMP1
1081grep -v '^$' $TMP1 | sort -u > $TMP2
1082
1083while read file; do
1084	backup_and_diff "$file" no
1085done < $TMP2
1086
1087
1088if [ -n "$CHANGELIST" ]; then
1089	grep -h -v '^$' $CHANGELIST | sort -u > $TMP1
1090	comm -23 $TMP1 $TMP2 | while read file; do
1091		backup_and_diff "$file" yes
1092	done
1093fi
1094
1095if have_pkgs; then
1096	if checkyesno check_pkg_vulnerabilities; then
1097		${pkg_admin} ${_compat_K_flag} audit >${OUTPUT} 2>&1
1098		if [ -s ${OUTPUT} ]; then
1099			printf "\nInstalled vulnerable packages:\n"
1100			cat ${OUTPUT}
1101		fi
1102	fi
1103
1104	if checkyesno check_pkg_signatures; then
1105		${pkg_admin} ${_compat_K_flag} check >${OUTPUT} 2>&1
1106		if [ $? -ne 0 ]; then
1107			printf "\nFiles with invalid signatures:\n"
1108			cat ${OUTPUT}
1109		fi
1110	fi
1111fi
1112
1113if [ -f /etc/security.local ]; then
1114	. /etc/security.local > $OUTPUT 2>&1
1115	if [ -s $OUTPUT ] ; then
1116		printf "\nRunning /etc/security.local:\n"
1117		cat $OUTPUT
1118	fi
1119fi
1120