view rm-limit.pl @ 8:10937b0bb5f4

Added /home2 to the default whitelist_subdirs, since that is another common home directory location.
author Eris Caffee <discordia@eldalin.com>
date Sun, 14 Aug 2011 01:56:19 -0500
parents 26257f7532ac
children f2322f4de2a3
line source
1 #!/usr/bin/env perl
3 ################################################################################
4 #
5 # Copyright (C) 2011 Sarah Eris Horsley Caffee
6 #
7 # This is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #
20 ################################################################################
21 #
22 # A limited rm wrapper.
23 #
24 # This scripts has 3 lists:
25 # A blacklist of directories from which this script will absolutely refuse
26 # to delete anything. Individual files may be listed too.
27 # A whitelist of directories from which deletions are always allowed.
28 # Individual files may be listed too.
29 # A whitelist of directories from which deletions are always allowed only if
30 # they occur in subdirectories of the listed main directory. Only
31 # directories may be listed. No individual files allowed here.
32 #
33 # Any file not specified as whitelisted or blacklisted will generate a
34 # warning prompt and offer the user a chance to cancel the deletion.
35 #
36 # The purpose is to help prevent accidental deletion of important system files.
37 #
38 # To use this, install this script somewhere in your path and add something
39 # like the following to your default login scripts, such as the .bash_profile
40 # file of the root user.
41 #
42 # alias | grep -q "alias rm="
43 # if [ $? -eq 0 ] ; then
44 # RM_Opts=$(alias | awk '/alias rm=/ { sub(/^rm /, "", $2); print $2}' FS="'")
45 # fi
46 #
47 # unalias rm 2> /dev/null
48 # alias rm="rm-limit.pl ${RM_Opts}"
49 #
50 # By installing this as an alias for rm that is set up in .bash_profile, it will
51 # only be active during interactive logins, and not when scripts are running.
52 #
54 # Last update: 2011/06/15 04:12:49
56 use strict;
57 use warnings;
59 use Cwd 'realpath';
60 use File::Basename;
63 ################################################################################
64 #
65 # Note: / itself is protected by default. You are not allowed to delete the
66 # entire filesystem using this script no matter what.
67 #
68 # Protecting or exposing a directory affects all subdirectories underneath it.
69 #
70 # The whitelist consists of directories from which we may always delete.
72 my @whitelist = (
73 '/tmp',
74 );
76 # whitelist_subdirs lists directories from which it is safe to delete only if
77 # we are deleting from a subdirectory of the listed directory.
78 # The purpose of this is to let us delete with impunity from user home directories
79 # but guard against accidental deletion of multiple home directories in their
80 # entirety.
82 my @whitelist_subdirs = (
83 '/home',
84 '/home2',
85 '/var/www/vhosts',
86 );
88 # The blacklist consists of directories that we must never delete from
89 # under any circumstances. To delete from these directories the user must
90 # invoke the /bin/rm command directly.
92 my @blacklist = (
93 '/bin/',
94 '/boot',
95 '/etc',
96 '/lib/',
97 '/lib64',
98 '/sbin',
99 );
101 ################################################################################
103 my $debug = 0;
105 my $rm="/bin/rm";
106 my $echo="/bin/echo";
109 my $proceed = "yes";
110 my $fail = 0;
111 my $file = 0;
112 my $opts = "";
115 setup_lists();
118 my $path = "";
119 foreach (@ARGV) {
120 $path = $_;
121 if ($path !~ /^-/) {
122 if (check_whitelist($path) ) {
123 next;
124 }
126 if (check_whitelist_subdirs($path) ) {
127 next;
128 }
130 $proceed = "no";
132 if (check_blacklist($path)) {
133 $fail = 1;
134 }
135 }
136 }
138 if ($fail) {
139 print("File deletion aborted because blacklisted files were detected in the file list.
140 If you truly need to delete the files, please call /bin/rm directly.
142 ");
143 exit 1;
144 }
148 if ($proceed ne "yes") {
149 print("
151 ============ WARNING! ========== WARNING! ========== WARNING! =================
153 You are about to delete files or directories that are not in the whitelist of
154 safe locations from which to delete. Please review the rm command for any
155 typos before proceeding.
157 Type \"yes\" to continue.
160 ");
161 $proceed = <STDIN>;
162 }
164 chomp($proceed);
165 if ($proceed eq "yes"){
166 if ($debug) {
167 exec($echo, ($rm, @ARGV));
168 } else {
169 exec($rm, @ARGV);
170 }
171 }
173 ################################################################################
174 # Expand to full paths, append / to ends of directories.
175 # Returns empty string if the specified file does not exist.
177 sub normalize_name {
178 my $path = $_;
179 my $normal_path = "";
181 if (-l $path) {
182 $path = readlink($_);
183 }
185 $normal_path = realpath($path);
186 if (!defined $normal_path) {
187 return "";
188 }
190 if ((-d $normal_path) and ($normal_path !~ m{/$} )) {
191 $normal_path = $normal_path."/";
192 }
194 return $normal_path;
195 }
197 ################################################################################
198 # Return true if on whitelist
200 sub check_whitelist {
201 my $regex = undef;
202 my $path = normalize_name($_);
203 for (my $i = 0; $i <= $#whitelist; $i += 1) {
204 $regex = "^".quotemeta($whitelist[$i]);
205 if ($whitelist[$i] !~ m{/$}) {
206 $regex = $regex."$$";
207 }
208 ($debug) and print("regex is $regex\n");
209 if (($path =~ $regex) and ($path ne $whitelist[$i])) {
210 $debug and print("Whitelisted for being in $whitelist[$i]: $_\n");
211 return 1;
212 }
213 }
215 return 0;
216 }
218 ################################################################################
219 # Return true if on whitelist_subdirs
221 sub check_whitelist_subdirs {
222 my $regex = undef;
223 my $path = normalize_name($_);
224 for (my $i = 0; $i <= $#whitelist_subdirs; $i += 1) {
225 $regex = "^".quotemeta($whitelist_subdirs[$i]);
226 ($debug) and print("regex is $regex\n");
227 if (($path =~ $regex) and ($path ne $whitelist_subdirs[$i])) {
228 $regex = $regex.".*/.+";
229 ($debug) and print("new regex is $regex\n");
230 if ($path =~ $regex) {
231 $debug and print("Whitelisted for being subdir of $whitelist_subdirs[$i]: $_\n");
232 return 1;
233 }
234 }
235 }
237 return 0;
238 }
240 ################################################################################
241 # Return true if on blacklist
243 sub check_blacklist {
244 my $regex = undef;
245 my $path = normalize_name($_);
247 # Always blacklist /
248 if ($path eq "/") {
249 print("Blacklisted for being the entire system: /\n");
250 return 1;
251 }
253 for (my $i = 0; $i <= $#blacklist; $i += 1) {
254 $regex = "^".quotemeta($blacklist[$i]);
255 if ($blacklist[$i] !~ m{/$}) {
256 $regex = $regex."$$";
257 }
258 ($debug) and print("regex is $regex\n");
259 if ($path =~ $regex) {
260 print("Blacklisted for being in $blacklist[$i]: $_\n");
261 return 1;
262 }
263 }
265 return 0;
266 }
268 ################################################################################
269 # At the moment all this does is make sure that directories listed in the
270 # lists all have a / at the end.
272 sub setup_lists {
273 for (my $i = 0; $i <= $#whitelist; $i += 1) {
274 if (-d $whitelist[$i] and $whitelist[$i] !~ m{/$}) {
275 $whitelist[$i] = $whitelist[$i]."/";
276 }
277 }
279 # All entries on the whitelist_subdirs list _must_ be directories.
280 for (my $i = 0; $i <= $#whitelist_subdirs; $i += 1) {
281 if ($whitelist_subdirs[$i] !~ m{/$}) {
282 $whitelist_subdirs[$i] = $whitelist_subdirs[$i]."/";
283 }
284 }
285 for (my $i = 0; $i <= $#blacklist; $i += 1) {
286 if (-d $blacklist[$i] and $blacklist[$i] !~ m{/$}) {
287 $blacklist[$i] = $blacklist[$i]."/";
288 }
289 }
291 if ($debug) {
292 print("whitelist:\n");
293 for (my $i = 0; $i <= $#whitelist; $i += 1) {
294 print($whitelist[$i]."\n");
295 }
296 print("whitelist_subdirs:\n");
297 for (my $i = 0; $i <= $#whitelist_subdirs; $i += 1) {
298 print($whitelist_subdirs[$i]."\n");
299 }
300 print("blacklist:\n");
301 for (my $i = 0; $i <= $#blacklist; $i += 1) {
302 print($blacklist[$i]."\n");
303 }
304 }
305 }