rlm@1: #!/bin/bash rlm@1: # Copyright (c) 2004 Matthias S. Benkmann
rlm@1: # You may do everything with this code except misrepresent its origin. rlm@1: # PROVIDED `AS IS' WITH ABSOLUTELY NO WARRANTY OF ANY KIND! rlm@1: rlm@1: #The following list should contain the mount points of all filesystems rlm@1: #that are to be scanned as a space-separated list within parentheses. rlm@1: #/ will usually be in this list and if you have /usr rlm@1: #on a separate partition, it will also be in this list. Other non-special rlm@1: #filesystems where suspicious files could be located should also be put in rlm@1: #this list. rlm@1: #Mount points whose filesystems are special, such as procfs or sysfs should rlm@1: #not be in this list. rlm@1: fs_to_scan=(/) rlm@1: rlm@1: #Files with a path prefix found in the following list are ignored. rlm@1: #DO !!!!NOT!!! PUT /usr/src OR WHATEVER THE HOME DIRECTORY prefix is for your rlm@1: #package users into this list!!! You DO want to scan those directories in rlm@1: #order to spot e.g. world-writable tarballs and other abominations that rlm@1: #may have crept in. rlm@1: #Ideally, this list should be empty. rlm@1: prune_prefixes=() #NO TRAILING SLASHES!!! rlm@1: rlm@1: #If the following variable is set to "yes", then files that contain rlm@1: #control characters or other non-printable characters (except for space) rlm@1: #will be reported as suspicious. rlm@1: #This test slows down the search considerably! rlm@1: enable_illchars=yes rlm@1: rlm@1: rlm@1: #suppress ugly debug output from shell rlm@1: trap ':' SIGPIPE rlm@1: rlm@1: #"-false" as 1st argument is used when called by list_suspicious_files_from rlm@1: if [ $# -ge 1 -a "$1" != "-false" ]; then rlm@1: echo 1>&2 rlm@1: echo 1>&2 "USAGE: ${0##*/}" rlm@1: echo 1>&2 rlm@1: echo 1>&2 ' Outputs a categorized list of files and directories with properties' rlm@1: echo 1>&2 ' that could mean trouble and should be investigated.' rlm@1: echo 1>&2 rlm@1: exit 1 rlm@1: fi rlm@1: rlm@1: rlm@1: usergroupmatch=(-true) rlm@1: if [ "$1" = "-false" ]; then rlm@1: usergroupmatch=(\( "$@" \)) rlm@1: fi rlm@1: rlm@1: #construct find commands that match the prune_prefixes. Each prefix will be rlm@1: #matched as -path -or -path /* rlm@1: #so that the directory itself and all subdirectories are matched. rlm@1: y=(\( -false) rlm@1: for ((i=0; $i<${#prune_prefixes[@]}; i=$i+1)) rlm@1: do rlm@1: y[${#y[@]}]='-or' rlm@1: y[${#y[@]}]=-path rlm@1: y[${#y[@]}]="${prune_prefixes[$i]}" rlm@1: y[${#y[@]}]='-or' rlm@1: y[${#y[@]}]=-path rlm@1: y[${#y[@]}]="${prune_prefixes[$i]}/*" rlm@1: done rlm@1: y[${#y[@]}]=')' rlm@1: rlm@1: illchars=( $'\x1' $'\x2' $'\x3' $'\x4' $'\x5' $'\x6' $'\x7' $'\x8' rlm@1: $'\x9' $'\xA' $'\xB' $'\xC' $'\xD' $'\xE' $'\xF' $'\x10' $'\x11' rlm@1: $'\x12' $'\x13' $'\x14' $'\x15' $'\x16' $'\x17' $'\x18' $'\x19' rlm@1: $'\x1A' $'\x1B' $'\x1C' $'\x1D' $'\x1E' $'\x1F' $'\x7f' $'\x80' rlm@1: $'\x81' $'\x82' $'\x83' $'\x84' $'\x85' $'\x86' $'\x87' $'\x88' rlm@1: $'\x89' $'\x8A' $'\x8B' $'\x8C' $'\x8D' $'\x8E' $'\x8F' $'\x90' rlm@1: $'\x91' $'\x92' $'\x93' $'\x94' $'\x95' $'\x96' $'\x97' $'\x98' rlm@1: $'\x99' $'\x9A' $'\x9B' $'\x9C' $'\x9D' $'\x9E' $'\x9F' ) rlm@1: rlm@1: rlm@1: if [ "$enable_illchars" = yes ]; then rlm@1: rlm@1: illname=(\( -false) rlm@1: for ((i=0; $i<${#illchars[@]}; i=$i+1)) rlm@1: do rlm@1: #handle bash \x7f error rlm@1: if [ "*${illchars[$i]}*" = "**" ]; then rlm@1: illchars[$i]=$'\x80' #' rlm@1: fi rlm@1: illname[${#illname[@]}]='-or' rlm@1: illname[${#illname[@]}]=-name rlm@1: illname[${#illname[@]}]="*${illchars[$i]}*" rlm@1: done rlm@1: illname[${#illname[@]}]=')' rlm@1: rlm@1: illlink=(\( -false) rlm@1: for ((i=0; $i<${#illchars[@]}; i=$i+1)) rlm@1: do rlm@1: illlink[${#illlink[@]}]='-or' rlm@1: illlink[${#illlink[@]}]=-lname rlm@1: illlink[${#illlink[@]}]="*${illchars[$i]}*" rlm@1: done rlm@1: illlink[${#illlink[@]}]=')' rlm@1: else #if [ "$enable_illchars" = no ] rlm@1: illlink=(-false) rlm@1: illname=(-false) rlm@1: fi rlm@1: rlm@1: # $1=section heading rlm@1: # $2=inode message rlm@1: report() rlm@1: { rlm@1: echo -printf "increment_code_here" rlm@1: echo -printf rlm@1: echo "1 ${1}\\n" | sed 's/ /\\040/g' rlm@1: echo -printf "insert_code_here" rlm@1: rlm@1: if [ -n "$2" ]; then rlm@1: echo -printf rlm@1: echo "2 %i 1 ${2}\\n" | sed 's/ /\\040/g' rlm@1: echo -printf "insert_code_here" rlm@1: echo -printf rlm@1: echo "2 %i 2 " | sed 's/ /\\040/g' rlm@1: else rlm@1: echo -printf "2\\040" rlm@1: fi rlm@1: rlm@1: echo -exec ls -T 0 -ladQ {} \; rlm@1: } rlm@1: rlm@1: rlm@1: filegoodperm=(\( -perm 644 -or -perm 755 -or -perm 555 -or -perm 444 -or -perm 600 -or -perm 700 -or -perm 640 \)) rlm@1: dirgoodperm=(\( -perm 755 -or -perm 555 -or -perm 700 -or -perm 750 \)) rlm@1: rlm@1: good=( \( rlm@1: -not \( -not -type d -links +1 \) rlm@1: -not -nouser -not -nogroup rlm@1: -not \( "${illname[@]}" \) rlm@1: -not \( "${illlink[@]}" \) rlm@1: \) rlm@1: -and rlm@1: \( rlm@1: \( -type f -not -group install "${filegoodperm[@]}" \) rlm@1: -or \( -type d -not -group install "${dirgoodperm[@]}" \) rlm@1: -or \( -type d -group install \( -perm 1775 \) \) rlm@1: -or \( -type d -group root -user root -path "/tmp" \( -perm 1777 \) \) rlm@1: -or \( -type d -group root -user root -path "/var/tmp" \( -perm 1777 \) \) rlm@1: -or \( -not -type d -not -type f -not -type l -path "/dev/*" \) rlm@1: -or \( -type l \( -xtype b -or -xtype c -or -xtype d -or -xtype p -or -xtype f \) \) rlm@1: \) rlm@1: ) rlm@1: rlm@1: bad=( rlm@1: \( "${illname[@]}" $(report "NON-PRINTABLE CHAR IN NAME") \) rlm@1: OP \( "${illlink[@]}" $(report "NON-PRINTABLE-CHAR IN LINK-TARGET") \) rlm@1: OP \( -type f -perm -4000 $(report "SETUID FILES") \) rlm@1: OP \( -type f -perm -2000 $(report "SETGID FILES") \) rlm@1: OP \( -type f -perm -1000 $(report "STICKY FILES") \) rlm@1: OP \( -type d -perm -2000 $(report "GROUP-KEEPING DIRECTORIES") \) rlm@1: OP \( -type d -not -group install -perm -1000 $(report "STICKY DIRECTORIES") \) rlm@1: OP \( -type f -perm -g+w $(report "GROUP-WRITABLE FILES") \) rlm@1: OP \( -type f -perm -o+w $(report "WORLD-WRITABLE FILES") \) rlm@1: OP \( -type d -perm -g+w $(report "GROUP-WRITABLE DIRECTORIES") \) rlm@1: OP \( -type d -perm -o+w $(report "WORLD-WRITABLE DIRECTORIES") \) rlm@1: OP \( -not \( -type f -or -type l -or -type d \) -not -path "/dev/*" $(report "SPECIAL FILES OUTSIDE /dev") \) rlm@1: OP \( -type d -group install -not -perm 1755 $(report "INSTALL DIRECTORIES WITH UNUSUAL PERMISSIONS") \) rlm@1: OP \( -type f -group install $(report "FILES ASSIGNED TO GROUP INSTALL") \) rlm@1: OP \( -type l -not \( -xtype b -or -xtype c -or -xtype d -or -xtype p -or -xtype f \) $(report "SYMLINKS POSSIBLY BROKEN OR LOOP") \) rlm@1: OP \( -not -type d -links +1 $(report "HARDLINKED FILES" "Inode %i is shared by %n files, including") \) rlm@1: OP \( -nouser $(report "THINGS HAVING UID WITH NO ASSIGNED USER NAME") \) rlm@1: OP \( -nogroup $(report "THINGS HAVING GID WITH NO ASSIGNED GROUP NAME") \) rlm@1: OP \( -type f -not -group install -not "${filegoodperm[@]}" $(report "FILES WITH UNUSUAL PERMISSIONS") \) rlm@1: OP \( -type d -not -group install -not "${dirgoodperm[@]}" $(report "DIRECTORIES WITH UNUSUAL PERMISSIONS") \) rlm@1: ) rlm@1: rlm@1: #insert unique codes for the messages rlm@1: code=100 rlm@1: for ((i=0; $i<${#bad[@]}; i=$i+1)) rlm@1: do rlm@1: if [ "${bad[$i]}" = "increment_code_here" ]; then rlm@1: code=$(($code + 1)) rlm@1: bad[$i]=$code rlm@1: elif [ "${bad[$i]}" = "insert_code_here" ]; then rlm@1: bad[$i]=$code rlm@1: fi rlm@1: done rlm@1: rlm@1: allbad=() #all bad matches are reported rlm@1: onebad=() #only the first bad match is reported rlm@1: for ((i=0; $i<${#bad[@]}; i=$i+1)) rlm@1: do rlm@1: if [ "${bad[$i]}" = "OP" ]; then rlm@1: allbad[$i]="," rlm@1: onebad[$i]="-or" rlm@1: else rlm@1: allbad[$i]="${bad[$i]}" rlm@1: onebad[$i]="${bad[$i]}" rlm@1: fi rlm@1: done rlm@1: rlm@1: #Add a default case to onebad. rlm@1: #This should never be hit, because the explicit cases should catch all rlm@1: #files, but just in case I've missed something, this will catch it. rlm@1: onebad=("${onebad[@]}" -or $(report "WEIRD SHIT") ) rlm@1: rlm@1: #make allbad always return false rlm@1: allbad=("${allbad[@]}" , -false) rlm@1: rlm@1: cmd=( "${usergroupmatch[@]}" -and rlm@1: \( \( "${good[@]}" \) -or \( "${allbad[@]}" \) -or \( "${onebad[@]}" \) \) rlm@1: ) rlm@1: rlm@1: #In the following find command, the part rlm@1: # -not ( ( "${y[@]}" -prune ) -or "${y[@]}" ) rlm@1: #is responsible for preventing the files that match prune_prefixes from rlm@1: #being processed. The 2nd "${y[@]}" may seem redundant, but it isn't, because rlm@1: #-prune has no effect and is always false when -depth is used. rlm@1: find "${fs_to_scan[@]}" -xdev -noleaf \ rlm@1: -not \( \( "${y[@]}" -prune \) -or "${y[@]}" \) \ rlm@1: -and \( "${cmd[@]}" \) | rlm@1: sed 's/^\(...2\) \([0-9]\+ 2 \)\?\([^ ]\+\) \+[^ ]\+ \+\([^ ]\+\) \+\([^ ]\+\) \+[^"]\+\(".\+\)/\1 \2\3 \6 \4:\5/' | rlm@1: sort -u | rlm@1: sed 's/^...1 /\'$'\n''/;s/^...2 [0-9]\+ 1 /\'$'\n'' /;s/^...2 [0-9]\+ 2 / /;s/^...2 / /'