On Wed, Aug 05, 2015 at 05:49:59PM +0200, Julian Andres Klode wrote: > On Wed, Aug 05, 2015 at 05:23:48PM +0200, Julian Andres Klode wrote: > > On Wed, Aug 05, 2015 at 05:09:50PM +0200, Julian Andres Klode wrote: > > > On Tue, Aug 26, 2014 at 08:07:00PM -0700, Josh Triplett wrote: > > > > Since safe-rm currently uses Perl, this change will also require one of > > > > two approaches: either provide a small wrapper C program that attempts > > > > to run /usr/bin/safe-rm and falls back to /bin/rm if that fails, or > > > > rewrite safe-rm in C. > > > > > > Here's a rewrite in C (currently modified to prepend /bin/echo to > > > the program being run, for testing purposes). > > > > > > I did not understand the "Prepare for actually deleting the file" > > > part of safe-rm, so I did not translate it (it was resetting IFS > > > and PATH and stuff, that's not really needed here). > > > > > > Otherwise it should be an exact match. > > >
I noticed a bug in v3, namely symbolic links. Now it should work correctly. Changes since v3: - Only call realpath() for files that are not symbolic links, so symbolic links can be blacklisted. Changes since v2: read_config_file: - Rework existence check to check for ENOENT from fopen() main: - reorder - use an lstat() for the S_ISLNK check and remove the previous stat(), as it was unneeded -- Julian Andres Klode - Debian Developer, Ubuntu Member See http://wiki.debian.org/JulianAndresKlode and http://jak-linux.org/. Be friendly, do not top-post, and follow RFC 1855 "Netiquette". - If you don't I might ignore you.
/* safe-rm.c - wrapper around the rm command to prevent accidental deletions * * Copyright (C) 2008-2014 Francois Marier * Copyright (C) 2015 Julian Andres Klode <j...@debian.org> (C translation) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #define _POSIX_C_SOURCE 200809L #define _XOPEN_SOURCE 700 #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <search.h> #include <string.h> #include <errno.h> #include <stdlib.h> #include <glob.h> #include <stdio.h> /** DEFAULT_PROTECTED_DIRS - Directories protected by default */ static const char *DEFAULT_PROTECTED_DIRS[] = { "/bin", "/boot", "/dev", "/etc", "/home", "/initrd", "/lib", "/lib32", "/lib64", "/proc", "/root", "/sbin", "/sys", "/usr", "/usr/bin", "/usr/include", "/usr/lib", "/usr/local", "/usr/local/bin", "/usr/local/include", "/usr/local/sbin", "/usr/local/share", "/usr/sbin", "/usr/share", "/usr/src", "/var", NULL, }; /** * protected_dirs - A binary search tree of protected directories * * This is used with tsearch() and friends */ void *protected_dirs; /** * strcmpvoid - Check whether string @a matches string @b * @a: the first string * @b: the second string * * Returns: strcmp(a, b). */ int strcmpvoid(const void *a, const void *b) { return strcmp(a, b); } /** * strany - Check if any string is not NULL. * @a: the first string * @b: the second string * * This can be used with tfind to check for emptyness: * tfind(NULL, &rootp, strany) * will return the first element in the tree. * * Returns: 0 if a or b is not NULL, 1 otherwise. */ int strany(const void *a, const void *b) { return (a != NULL || b != NULL) ? 0 : 1; } /** * rtrim - Trim @value * @value: The string that shall be trimmed. * @totrim: Characters that shall be trimmed * * Replaces all characters from @totrim on the right hand side of @value. */ void rtrim(char *value, char *totrim) { size_t len = strlen(value); while (--len > 0 && strchr(totrim, value[len]) != NULL) { value[len] = 0; } } /** * read_config_file - Read a configuration file * @path: The path to the configuration file * */ void read_config_file(const char *path) { FILE *infile; char *line = NULL; size_t line_alloc = 0; glob_t globbed; infile = fopen(path, "r"); if (infile == NULL) { if (errno != ENOENT) { fprintf(stderr, "Could not open configuration file %s: %s\n", path, strerror(errno)); } return; } while (getline(&line, &line_alloc, infile) != -1) { rtrim(line, "\n\r\t "); switch (glob(line, 0, NULL, &globbed)) { case GLOB_NOMATCH: case 0: break; default: fprintf(stderr, "Cannot glob() for line %s\n", line); exit(1); } /* Insert the matches into our tree of paths */ for (size_t i = 0; i < globbed.gl_pathc; i++) tsearch(globbed.gl_pathv[i], &protected_dirs, strcmpvoid); } } /** * join - Join two paths * @a: First path * @b: Second path * * Returns: @a/@b */ char *join(const char *a, const char *b) { size_t lena; size_t lenb; char *out; lena = strlen(a); lenb = strlen(b); out = malloc(lena + 1 /* separator */ + lenb + 1 /* 0 byte */ ); strncpy(out, a, lena); strncpy(out + lena, "/", 1); strncpy(out + lena + 1, b, lenb); out[lena + 1 + lenb] = '\0'; return out; } int main(int argc, char *argv[]) { const char *HOME = getenv("HOME") ? : ""; const char *XDG_CONFIG_HOME = getenv("XDG_CONFIG_HOME") ? : join(HOME, ".config"); const char *LEGACY_CONFIG_FILE = join(HOME, ".safe-rm"); const char *USER_CONFIG_FILE = join(XDG_CONFIG_HOME, "safe-rm"); const char *GLOBAL_CONFIG_FILE = "/etc/safe-rm.conf"; /* Allocate one more parameter than necessary, so we can quickly * prepend something for testing. */ char **allowed_args = calloc(argc + 2, sizeof(*allowed_args)); int allowed = 0; read_config_file(GLOBAL_CONFIG_FILE); read_config_file(LEGACY_CONFIG_FILE); read_config_file(USER_CONFIG_FILE); /* Insert defaults if no configuration option was read. */ if (tfind(NULL, &protected_dirs, strany) == NULL) { for (int i = 0; DEFAULT_PROTECTED_DIRS[i] != NULL; i++) tsearch(DEFAULT_PROTECTED_DIRS[i], &protected_dirs, strcmpvoid); } /* Build the array of allowed arguments (TODO: Disable the echo test.) */ allowed_args[allowed++] = "/bin/echo"; allowed_args[allowed++] = "/bin/rm"; for (int i = 1; i < argc; i++) { struct stat buf; char *pathname = argv[i]; /* Normalize the pathname */ char *normalized_pathname = pathname; /* stat the file */ buf.st_mode = 0; lstat(pathname, &buf); /* Try looking up the real path */ if (!S_ISLNK(buf.st_mode)) { char *cpath = realpath(pathname, NULL); if (cpath != NULL) normalized_pathname = cpath; } /* Trim trailing slashes */ rtrim(normalized_pathname, "/"); /* Check against the blacklist */ if (tfind(normalized_pathname, &protected_dirs, strcmpvoid) != NULL) { fprintf(stderr, "safe-rm: skipping %s\n", pathname); } else { allowed_args[allowed++] = normalized_pathname; } } allowed_args[allowed] = NULL; /* Make sure we're not calling ourselves recursively */ if (strcmp(realpath(allowed_args[0], NULL), realpath(argv[0], NULL)) == 0) { fprintf(stderr, "safe-rm cannot find the real \"rm\" binary\n"); exit(1); } /* Run the real rm command, returning with the same error code */ if (execv(allowed_args[0], allowed_args) != 0) { fprintf(stderr, "safe-rm: Cannot execute the real \"rm\" binary: %s\n", strerror(errno)); exit(2); } }