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