view rm-limit.pl @ 1:1217ea1da6d7

Ready for testing.
author Eris Caffee <discordia@eldalin.com>
date Tue, 17 May 2011 05:00:54 -0500
parents c1b3644bfc04
children 58e218e2b4ac
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.
27 # A whitelist of directories from which deletions are always allowed.
28 # A whitelist of directories from which deletions are always allowed only if
29 # they occur in subdirectories of the listed main directory.
30 #
31 # Any file not specified as whitelisted or blacklisted will generate a
32 # warning prompt and offer the user a chance to cancel the deletion.
33 #
34 # The purpose is to help prevent accidental deletion of important system files.
35 #
36 # To use this, install this script somewher ein your path and add something
37 # like the following to your default login scripts, such as the .bash_profile
38 # file of the root user.
39 #
40 # alias | grep -q "alias rm="
41 # if [ $? -eq 0 ] ; then
42 # RM_Opts=$(alias | awk '/alias rm=/ { sub(/^rm /, "", $2); print $2}' FS="'")
43 # fi
44 #
45 # unalias rm 2> /dev/null
46 # alias rm="rm-limit ${RM_Opts}"
47 #
48 # By installing this as an alias for rm that is set up in .bash_profile, it will
49 # only be active during interactive logins, and not when scripts are running.
50 #
52 use strict;
53 use warnings;
55 use Cwd 'realpath';
56 use File::Basename;
59 ################################################################################
60 #
61 # Note: / itself is protected by default. You are not allowed to delete the
62 # entire filesystem using this script no matter what.
63 #
64 # Protecting or exposing a directory affects all subdirectories underneath it.
65 #
66 # The whitelist consists of directories from which we may always delete.
68 my @whitelist = (
69 '/tmp',
70 );
72 # whitelist_subdirs lists directories from which it is safe to delete only if
73 # we are deleting from a subdirectory of the listed directory.
74 # The purpose of this is to let us delete with impunity from user home directories
75 # but guard against accidental deletion of multiple home directories in their
76 # entirety.
78 my @whitelist_subdirs = (
79 '/home',
80 );
82 # The blacklist consists of directories that we must never delete from
83 # under any circumstances. To delete from these directories the user must
84 # invoke the /bin/rm command directly.
86 my @blacklist = (
87 '/bin/',
88 '/boot',
89 '/etc',
90 '/lib/',
91 '/lib64',
92 '/sbin',
93 );
95 ################################################################################
97 my $debug = 0;
99 my $rm="/bin/rm";
100 my $echo="/bin/echo";
103 my $proceed = "yes";
104 my $fail = 0;
105 my $file = 0;
106 my $opts = "";
109 setup_lists();
112 my $path = "";
113 foreach (@ARGV) {
114 $path = $_;
115 if ($path !~ /^-/) {
116 if (check_whitelist($path) ) {
117 next;
118 }
120 if (check_whitelist_subdirs($path) ) {
121 next;
122 }
124 $proceed = "no";
126 if (check_blacklist($path)) {
127 $fail = 1;
128 }
129 }
130 }
132 if ($fail) {
133 print("File deletion aborted because blacklisted files were detected in the file list.
134 If you truly need to delete the files, please call /bin/rm directly.
136 ");
137 exit 1;
138 }
142 if ($proceed ne "yes") {
143 print("
145 ============ WARNING! ========== WARNING! ========== WARNING! =================
147 You are about to delete files or directories that are not in the whitelist of
148 safe locations from which to delete. Please review the rm command for any
149 typos before proceeding.
151 Type \"yes\" to continue.
154 ");
155 $proceed = <STDIN>;
156 }
158 chomp($proceed);
159 if ($proceed eq "yes"){
160 if ($debug) {
161 exec($echo, ($rm, @ARGV));
162 } else {
163 exec($rm, @ARGV);
164 }
165 }
167 ################################################################################
168 # Expand to full paths, append / to ends of directories.
169 # Returns empty string if the specified file does not exist.
171 sub normalize_name {
172 my $path = $_;
173 my $normal_path = "";
175 if (-l $path) {
176 $path = readlink($_);
177 }
179 $normal_path = realpath($path);
180 if (!defined $normal_path) {
181 return "";
182 }
184 if ((-d $normal_path) and ($normal_path !~ m{/$} )) {
185 $normal_path = $normal_path."/";
186 }
188 return $normal_path;
189 }
191 ################################################################################
192 # Return true if on whitelist
194 sub check_whitelist {
195 my $regex = undef;
196 my $path = normalize_name($_);
197 for (my $i = 0; $i <= $#whitelist; $i += 1) {
198 $regex = "^".quotemeta($whitelist[$i]);
199 if ($whitelist[$i] !~ m{/$}) {
200 $regex = $regex."$$";
201 }
202 ($debug) and print("regex is $regex\n");
203 if (($path =~ $regex) and ($path ne $whitelist[$i])) {
204 $debug and print("Whitelisted for being in $whitelist[$i]: $_\n");
205 return 1;
206 }
207 }
209 return 0;
210 }
212 ################################################################################
213 # Return true if on whitelist_subdirs
215 sub check_whitelist_subdirs {
216 my $regex = undef;
217 my $path = normalize_name($_);
218 for (my $i = 0; $i <= $#whitelist_subdirs; $i += 1) {
219 $regex = "^".quotemeta($whitelist_subdirs[$i]);
220 ($debug) and print("regex is $regex\n");
221 if (($path =~ $regex) and ($path ne $whitelist_subdirs[$i])) {
222 $regex = $regex.".*/.+";
223 ($debug) and print("new regex is $regex\n");
224 if ($path =~ $regex) {
225 $debug and print("Whitelisted for being subdir of $whitelist_subdirs[$i]: $_\n");
226 return 1;
227 }
228 }
229 }
231 return 0;
232 }
234 ################################################################################
235 # Return true if on blacklist
237 sub check_blacklist {
238 my $regex = undef;
239 my $path = normalize_name($_);
241 # Always blacklist /
242 if ($path eq "/\n") {
243 print("Blacklisted for being the entire system: /");
244 return 1;
245 }
247 for (my $i = 0; $i <= $#blacklist; $i += 1) {
248 $regex = "^".quotemeta($blacklist[$i]);
249 if ($blacklist[$i] !~ m{/$}) {
250 $regex = $regex."$$";
251 }
252 ($debug) and print("regex is $regex\n");
253 if ($path =~ $regex) {
254 print("Blacklisted for being in $blacklist[$i]: $_\n");
255 return 1;
256 }
257 }
259 return 0;
260 }
262 ################################################################################
263 # At the moment all this does is make sure that directories listed in the
264 # lists all have a / at the end.
266 sub setup_lists {
267 for (my $i = 0; $i <= $#whitelist; $i += 1) {
268 if (-d $whitelist[$i] and $whitelist[$i] !~ m{/$}) {
269 $whitelist[$i] = $whitelist[$i]."/";
270 }
271 }
273 # All entries on the whitelist_subdirs list _must_ be directories.
274 for (my $i = 0; $i <= $#whitelist_subdirs; $i += 1) {
275 if ($whitelist_subdirs[$i] !~ m{/$}) {
276 $whitelist_subdirs[$i] = $whitelist_subdirs[$i]."/";
277 }
278 }
279 for (my $i = 0; $i <= $#blacklist; $i += 1) {
280 if (-d $blacklist[$i] and $blacklist[$i] !~ m{/$}) {
281 $blacklist[$i] = $blacklist[$i]."/";
282 }
283 }
285 if ($debug) {
286 print("whitelist:\n");
287 for (my $i = 0; $i <= $#whitelist; $i += 1) {
288 print($whitelist[$i]."\n");
289 }
290 print("whitelist_subdirs:\n");
291 for (my $i = 0; $i <= $#whitelist_subdirs; $i += 1) {
292 print($whitelist_subdirs[$i]."\n");
293 }
294 print("blacklist:\n");
295 for (my $i = 0; $i <= $#blacklist; $i += 1) {
296 print($blacklist[$i]."\n");
297 }
298 }
299 }