Secure your rm command

May 1996

 

This is a shell script for Bash, called ‘rm_secure’. I use it as frontal for the rm command. It stores the deleted files in an archive in the user’s directory. A command-line option allows the user to view the content of this archive, and another option permits the restoration of the deleted files.

For example :

$ ls -l
-rw-r--r-- 1 ccb users 22 May 26 10:33 important_file
-rw-r--r-- 1 ccb users 23 May 26 10:34 not_important
$ rm * (OOPS!)
$ ls -l
$ rm --viewtrash
-rw-r--r-- 1 ccb users 22 May 26 10:35 1996 important_file
-rw-r--r-- 1 ccb users 23 May 26 10:35 1996 not_important
$ rm --restore important_file
$ ls -l
-rw-r--r-- 1 ccb users 22 May 26 10:35 important_file
$ rm --viewtrash
-rw-r--r-- 1 ccb users 23 May 26 10:35 1996 not_important
$ rm --emptytrash
$ rm --viewtrash
$

Okay, it slows down a few the rm command. But it may also save hours of work lost due to a keystroke error…

There is the script ‘rm_secure’ :

#!/bin/bash

# Configuration
# the real 'rm' command
bin_rm=/bin/rm
# where archiving the files
Archive=~/.rm_saved.tar
# you may prefer something like :
# Archive=/var/trash/$USER/saved_file.tar
# (with write access permission on the directory)

# global variables for the options
Opt_recursive=0
Opt_no_secure=0
Opt_restore=0
Opt_rm=""

# function for archiving a file or a directory
save_file() {
  if [ $Opt_no_secure -ne 1 ] ; then
  # set date/time of deletion
  touch "$1" > /dev/null 2>&1
    if [ -f $Archive ] ; then
      tar --delete -f "$Archive" "$1" > /dev/null 2>&1
      tar -rf "$Archive" "$1" > /dev/null 2>&1
    else
      tar -cf "$Archive" "$1" > /dev/null 2>&1
      # r/w access only for the user
      chmod 600 "$Archive"
    fi
  fi
}

# function for restoring file or directory
restore_file () {
  if [ -f $Archive ] ; then
    tar -xf "$Archive" "$1" > /dev/null 2>&1
    tar --delete -f "$Archive" "$1" > /dev/null 2>&1
  fi
}

# reading the command-line args
while getopts "dfirRvns-:" opt ; do
  case $opt in
    d ) Opt_rm="$Opt_rm -d" ;;
    f ) Opt_rm="$Opt_rm -f" ;;
    i ) Opt_rm="$Opt_rm -i" ;;
    r | R ) Opt_recursive=1
            Opt_rm="$Opt_rm -r" ;;
    v ) Opt_rm="$Opt_rm -v" ;;
    n ) Opt_no_secure=1 ;;
    s ) Option_restore=1 ;;
    - ) case $OPTARG in
      directory ) Opt_rm="$Opt_rm -d" ;;
      force ) Opt_rm="$Opt_rm -f" ;;
      interactive ) Opt_rm="$Opt_rm -i" ;;
      recursive ) Opt_recursive=1
                  Opt_rm="$Opt_rm -r" ;;
     help ) $bin_rm --help
             echo "(rm_secure)"
             echo " -n, --nosecure delete without backup"
             echo " --viewtrash list the saved files"
             echo " --emptytrash erase the saved files"
             echo " -s, --restore restore the specified files"
             exit 0 ;;
      version ) $bin_rm --version
                 echo "(rm_secure 1.0)"
                 exit 0 ;;
      verbose ) Opt_rm="$Opt_rm -v" ;;
      viewtrash ) if [ -f $Archive.gz ] ; then
                    tar -tvzf $Archive.gz
                  fi
                  exit 0 ;;
      nosecure ) Opt_no_secure=1 ;;
      emptytrash ) if [ -f $Archive.gz ] ; then
                     $bin_rm $Archive.gz
                   fi
                   exit 0 ;;
      restore ) Opt_restore=1 ;;
      * )  ;;
    esac ;;
    ? )  ;;
  esac
done

shift $(($OPTIND - 1))
gunzip $Archive.gz >; /dev/null 2>&1

# restoration ?
if [ $Opt_restore -ne 0 ] ; then
  while [ -n "$1" ] ; do
    restore_file "$1"
    shift
  done
  exit 0
else
  while [ -n "$1" ] ; do
    if [ -d "$1" ] ; then
      # the directories are archived only with
      # the -r option
      if [ $Opt_recursive -ne 0 ] ; then
        save_file "$1"
      fi
      $bin_rm $Opt_rm $1
    elif [ -e "$1" ] ; then
      # existing file
      save_file "$1"
      $bin_rm $Opt_rm $1
    else
      # let 'rm' give his error message
      $bin_rm $1
    fi
  shift
done
fi

nice gzip $Archive > /dev/null 2>&1 &
# -- end of script --

Place it in /usr/bin or /usr/local/bin then insert a ligne :

alias rm='/usr/local/bin/rm_secure'

in /etc/profile, so this script will be called by Bash in the place of the true rm command.

You can use the ‘–nosecure’ or ‘-n’ option to delete a file without archiving it. This is useful when you decide to erase huge amount of files in recursive directories (for example a package you have tested but find uninteresting).

I use a cron job to deleted the archived files every day (running as root job).

# crontab -l
[ …]
00 04 * * * /usr/local/bin/empty_trash
#

Here is the ’empty_trash’ script :

#! /bin/bash
  for user in /home/* ; do
    /bin/rm $user/.rm_saved.tar.gz
  done
  /bin/rm /root/.rm_saved.tar.gz
# -- end of script --

Maybe you can prefer something like :

trap '/bin/rm ~/.rm_saved.tar.gz EXIT

in /etc/profile, which erase the archive each time the user exits the shell. (I’ve not fully tested this)

Obviously this tips doesn’t secure the deletion of files or directories by a file-manager, but I find it quite usefull, especially when doing administrative jobs as root (‘rm tmp/ *’ in place of ‘rm tmp/*’ …)

2 Réponses

  1. Ardo dit :

    It seems cool but i think there is a mistake on the definition of this variable.

    OPTIND is not declared.

    shift $(($OPTIND – 1))

    • cpb dit :

      OPTIND is a special variable for Bash. Here’s an excerpt of the bash man page :

        OPTIND The  index  of the next argument to be processed by the getopts
          builtin command (see SHELL BUILTIN COMMANDS below).
      [...]
        getopts optstring name [args]
          getopts is used by shell procedures to parse positional parame‐
          ters.   optstring  contains  the option characters to be recog‐
          nized; if a character is followed by a  colon,  the  option  is
          expected to have an argument, which should be separated from it
          by white space.  The colon and question mark characters may not
          be used as option characters.  Each time it is invoked, getopts
          places the next option in the shell variable name, initializing
          name  if  it does not exist, and the index of the next argument
          to be processed into the variable OPTIND.  OPTIND  is  initial‐
          ized  to  1  each  time the shell or a shell script is invoked.
          When an option requires an argument, getopts places that  argu‐
          ment into the variable OPTARG.  The shell does not reset OPTIND
          automatically; it must be manually reset between multiple calls
          to  getopts  within  the  same shell invocation if a new set of
          parameters is to be used.
      
          When the end of options is encountered, getopts  exits  with  a
          return  value greater than zero.  OPTIND is set to the index of
          the first non-option argument, and name is set to ?.
          getopts normally parses the positional parameters, but if  more
          arguments are given in args, getopts parses those instead.
      [...]

URL de trackback pour cette page