Initial git import
[pkgusr] / usr / bin / list_suspicious_files
1 #!/bin/bash
2 # Copyright (c) 2004 Matthias S. Benkmann <article AT winterdrache DOT de>
3 # You may do everything with this code except misrepresent its origin.
4 # PROVIDED `AS IS' WITH ABSOLUTELY NO WARRANTY OF ANY KIND!
5
6 #The following list should contain the mount points of all filesystems
7 #that are to be scanned as a space-separated list within parentheses. 
8 #/ will usually be in this list and if you have /usr
9 #on a separate partition, it will also be in this list. Other non-special
10 #filesystems where suspicious files could be located should also be put in 
11 #this list.
12 #Mount points whose filesystems are special, such as procfs or sysfs should
13 #not be in this list. 
14
15 ## Bastard settings
16 # fs_to_scan=(/ /opt /usr /usr/local /var)
17
18 fs_to_scan=(/)
19
20 #Files with a path prefix found in the following list are ignored.
21 #DO !!!!NOT!!! PUT /usr/src OR WHATEVER THE HOME DIRECTORY prefix is for your
22 #package users into this list!!! You DO want to scan those directories in
23 #order to spot e.g. world-writable tarballs and other abominations that
24 #may have crept in.
25 #Ideally, this list should be empty.
26
27 ## Bastard settings
28 # prune_prefixes=(/*/\{arch\}) #NO TRAILING SLASHES!!!
29
30 prune_prefixes=()
31
32 #If the following variable is set to "yes", then files that contain
33 #control characters or other non-printable characters (except for space)
34 #will be reported as suspicious. 
35 #This test slows down the search considerably!
36 enable_illchars=yes
37
38
39 #suppress ugly debug output from shell
40 trap ':' SIGPIPE
41
42 #"-false" as 1st argument is used when called by list_suspicious_files_from
43 if [ $# -ge 1 -a "$1" != "-false" ]; then
44     echo 1>&2 
45     echo 1>&2 "USAGE: ${0##*/}"
46     echo 1>&2 
47     echo 1>&2 '  Outputs a categorized list of files and directories with properties'
48     echo 1>&2 '  that could mean trouble and should be investigated.'
49     echo 1>&2
50     exit 1
51 fi
52
53
54 usergroupmatch=(-true)
55 if [ "$1" = "-false" ]; then
56     usergroupmatch=(\( "$@" \))
57 fi
58
59 #construct find commands that match the prune_prefixes. Each prefix will be
60 #matched as -path <prefix> -or -path <prefix>/*
61 #so that the directory itself and all subdirectories are matched.
62 y=(\( -false)
63 for ((i=0; $i<${#prune_prefixes[@]}; i=$i+1)) 
64 do
65     y[${#y[@]}]='-or'
66     y[${#y[@]}]=-path
67     y[${#y[@]}]="${prune_prefixes[$i]}"
68     y[${#y[@]}]='-or'
69     y[${#y[@]}]=-path
70     y[${#y[@]}]="${prune_prefixes[$i]}/*"
71 done
72 y[${#y[@]}]=')'
73
74 illchars=( $'\x1'  $'\x2'  $'\x3'  $'\x4'  $'\x5'  $'\x6'  $'\x7'  $'\x8'  
75   $'\x9'  $'\xA'  $'\xB'  $'\xC'  $'\xD'  $'\xE'  $'\xF'  $'\x10'  $'\x11' 
76   $'\x12'  $'\x13'  $'\x14'  $'\x15'  $'\x16'  $'\x17'  $'\x18'  $'\x19'  
77   $'\x1A'  $'\x1B'  $'\x1C'  $'\x1D'  $'\x1E'  $'\x1F'  $'\x7f' $'\x80' 
78   $'\x81'  $'\x82'  $'\x83'  $'\x84'  $'\x85'  $'\x86'  $'\x87'  $'\x88'  
79   $'\x89'  $'\x8A'  $'\x8B'  $'\x8C'  $'\x8D'  $'\x8E'  $'\x8F'  $'\x90'  
80   $'\x91'  $'\x92'  $'\x93'  $'\x94'  $'\x95'  $'\x96'  $'\x97'  $'\x98'  
81   $'\x99'  $'\x9A'  $'\x9B'  $'\x9C'  $'\x9D'  $'\x9E'  $'\x9F' ) 
82
83
84 if [ "$enable_illchars" = yes ]; then
85
86     illname=(\( -false)
87     for ((i=0; $i<${#illchars[@]}; i=$i+1)) 
88     do
89         #handle bash \x7f error
90         if [ "*${illchars[$i]}*" = "**" ]; then
91             illchars[$i]=$'\x80'  #'
92         fi
93         illname[${#illname[@]}]='-or'
94         illname[${#illname[@]}]=-name
95         illname[${#illname[@]}]="*${illchars[$i]}*"
96     done
97     illname[${#illname[@]}]=')'
98
99     illlink=(\( -false)
100     for ((i=0; $i<${#illchars[@]}; i=$i+1)) 
101     do
102         illlink[${#illlink[@]}]='-or'
103         illlink[${#illlink[@]}]=-lname
104         illlink[${#illlink[@]}]="*${illchars[$i]}*"
105     done
106     illlink[${#illlink[@]}]=')'
107 else #if [ "$enable_illchars" = no ]
108     illlink=(-false)
109     illname=(-false)
110 fi
111
112 # $1=section heading
113 # $2=inode message
114 report()
115 {
116     echo -printf "increment_code_here"
117     echo -printf 
118     echo "1 ${1}\\n" | sed 's/ /\\040/g'
119     echo -printf "insert_code_here"
120   
121     if [ -n "$2" ]; then
122         echo -printf 
123         echo "2 %i 1 ${2}\\n" | sed 's/ /\\040/g'
124         echo -printf "insert_code_here"
125         echo -printf 
126         echo "2 %i 2 " | sed 's/ /\\040/g'
127     else
128         echo -printf "2\\040" 
129     fi
130   
131     echo -exec ls -T 0 -ladQ {} \;
132 }
133
134
135 filegoodperm=(\( -perm 644 -or -perm 755 -or -perm 555 -or -perm 444 -or -perm 600 -or -perm 700 -or -perm 640 \))
136 dirgoodperm=(\( -perm 755 -or -perm 555 -or -perm 700 -or -perm 750 \))
137
138 good=( \( 
139           -not \( -not -type d -links +1 \)
140           -not -nouser -not -nogroup 
141           -not \( "${illname[@]}" \) 
142           -not \( "${illlink[@]}" \) 
143        \) 
144        -and
145 \(
146       \( -type f -not -group install "${filegoodperm[@]}" \) 
147   -or \( -type d -not -group install "${dirgoodperm[@]}" \)
148   -or \( -type d -group install \( -perm 1775 \) \)
149   -or \( -type d -group root -user root -path "/tmp" \( -perm 1777 \) \)
150   -or \( -type d -group root -user root -path "/var/tmp" \( -perm 1777 \) \)
151   -or \( -not -type d -not -type f -not -type l -path "/dev/*" \)
152   -or \( -type l \( -xtype b -or -xtype c -or -xtype d -or -xtype p -or -xtype f \) \)
153 \)
154 )
155
156 bad=(
157     \( "${illname[@]}" $(report  "NON-PRINTABLE CHAR IN NAME")  \)
158  OP \( "${illlink[@]}" $(report  "NON-PRINTABLE-CHAR IN LINK-TARGET")  \)
159  OP \( -type f -perm -4000 $(report  "SETUID FILES")  \)
160  OP \( -type f -perm -2000 $(report  "SETGID FILES")  \)
161  OP \( -type f -perm -1000 $(report  "STICKY FILES")  \)
162  OP \( -type d -perm -2000 $(report  "GROUP-KEEPING DIRECTORIES")  \)
163  OP \( -type d -not -group install -perm -1000  $(report  "STICKY DIRECTORIES")  \)
164  OP \( -type f -perm -g+w  $(report  "GROUP-WRITABLE FILES")  \)
165  OP \( -type f -perm -o+w  $(report  "WORLD-WRITABLE FILES")  \)
166  OP \( -type d -perm -g+w  $(report  "GROUP-WRITABLE DIRECTORIES")  \)
167  OP \( -type d -perm -o+w  $(report  "WORLD-WRITABLE DIRECTORIES")  \)
168  OP \( -not \( -type f -or -type l -or -type d \) -not -path "/dev/*"  $(report  "SPECIAL FILES OUTSIDE /dev")  \)
169  OP \( -type d -group install -not -perm 1755  $(report  "INSTALL DIRECTORIES WITH UNUSUAL PERMISSIONS")  \)
170  OP \( -type f -group install $(report  "FILES ASSIGNED TO GROUP INSTALL")  \)
171  OP \( -type l -not \( -xtype b -or -xtype c -or -xtype d -or -xtype p -or -xtype f \) $(report  "SYMLINKS POSSIBLY BROKEN OR LOOP")  \)
172  OP \( -not -type d -links +1 $(report "HARDLINKED FILES" "Inode %i is shared by %n files, including") \)
173  OP \( -nouser  $(report  "THINGS HAVING UID WITH NO ASSIGNED USER NAME")  \)
174  OP \( -nogroup  $(report  "THINGS HAVING GID WITH NO ASSIGNED GROUP NAME")  \)
175  OP \( -type f -not -group install -not "${filegoodperm[@]}"  $(report  "FILES WITH UNUSUAL PERMISSIONS")  \)
176  OP \( -type d -not -group install -not "${dirgoodperm[@]}"  $(report  "DIRECTORIES WITH UNUSUAL PERMISSIONS")  \)
177 )
178
179 #insert unique codes for the messages
180 code=100
181 for ((i=0; $i<${#bad[@]}; i=$i+1)) 
182 do
183     if [ "${bad[$i]}" = "increment_code_here" ]; then
184         code=$(($code + 1))
185         bad[$i]=$code
186     elif [ "${bad[$i]}" = "insert_code_here" ]; then
187         bad[$i]=$code
188     fi
189 done
190
191 allbad=()  #all bad matches are reported
192 onebad=()  #only the first bad match is reported
193 for ((i=0; $i<${#bad[@]}; i=$i+1)) 
194 do
195     if [ "${bad[$i]}" = "OP" ]; then
196         allbad[$i]=","
197         onebad[$i]="-or"
198     else
199         allbad[$i]="${bad[$i]}"
200         onebad[$i]="${bad[$i]}"
201     fi
202 done
203
204 #Add a default case to onebad.
205 #This should never be hit, because the explicit cases should catch all
206 #files, but just in case I've missed something, this will catch it.
207 onebad=("${onebad[@]}" -or  $(report  "WEIRD SHIT") )
208
209 #make allbad always return false
210 allbad=("${allbad[@]}" , -false)
211
212 cmd=( "${usergroupmatch[@]}" -and
213      \( \( "${good[@]}" \) -or \( "${allbad[@]}" \) -or \( "${onebad[@]}" \) \)
214     )
215
216 #In the following find command, the part
217 # -not ( ( "${y[@]}" -prune ) -or "${y[@]}" )
218 #is responsible for preventing the files that match prune_prefixes from
219 #being processed. The 2nd "${y[@]}" may seem redundant, but it isn't, because
220 #-prune has no effect and is always false when -depth is used.
221 find "${fs_to_scan[@]}" -xdev -noleaf \
222     -not \( \( "${y[@]}" -prune \) -or "${y[@]}" \) \
223     -and \( "${cmd[@]}" \) 2>/dev/null | 
224 sed 's/^\(...2\) \([0-9]\+ 2 \)\?\([^ ]\+\) \+[^ ]\+ \+\([^ ]\+\) \+\([^ ]\+\) \+[^"]\+\(".\+\)/\1 \2\3 \6  \4:\5/' |
225 sort -u | 
226 sed 's/^...1 /\'$'\n''/;s/^...2 [0-9]\+ 1 /\'$'\n''  /;s/^...2 [0-9]\+ 2 /    /;s/^...2 /  /'