--- Makefile | 2 + config.def.h | 1 + shred.1 | 66 ++++++++++++ shred.c | 332 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 401 insertions(+) create mode 100644 shred.1 create mode 100644 shred.c diff --git a/Makefile b/Makefile index 59616a4..ee348bb 100644 --- a/Makefile +++ b/Makefile _AT_@ -73,6 +73,7 @@ BIN = \ readahead \ respawn \ rmmod \ + shred \ stat \ su \ swaplabel \ _AT_@ -107,6 +108,7 @@ MAN1 = \ pidof.1 \ ps.1 \ respawn.1 \ + shred.1 \ stat.1 \ su.1 \ truncate.1 \ diff --git a/config.def.h b/config.def.h index 577833e..8651cd5 100644 --- a/config.def.h +++ b/config.def.h _AT_@ -9,3 +9,4 @@ #define BTMP_PATH "/var/log/btmp" #undef WTMP_PATH #define WTMP_PATH "/var/log/wtmp" +#define URANDOM_PATH "/dev/urandom" diff --git a/shred.1 b/shred.1 new file mode 100644 index 0000000..18add0d --- /dev/null +++ b/shred.1 _AT_@ -0,0 +1,66 @@ +.Dd March 26, 2016 +.Dt SHRED 1 +.Os ubase +.Sh NAME +.Nm shred +.Nd securely erase the content of a file +.Sh SYNOPSIS +.Nm +.Op Fl n Ar num +.Op Fl r Ar source +.Op Fl fuvxz +.Ar file ... +.Sh DESCRIPTION +.Nm +overrides each specified +.Ar file +with random data to hide its original content. +If +.Ar file +is -, erase standard output. +.Pp +This program is useless on solid state drives +and modern filesystems that have not been +especially configured. If someone care to write +an essay on all assumptions +.Nm +makes, that would be great! +.Pp +.Nm +never writes to directories or symbolic links, +only the flags +.Fl fur +applies to directories. For symbolic links, this +is the case because it impossible to rewrite +a symbolic link without removing it first. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl f +Erase the file even if it is write-protected. +.It Fl n Ar num +Override the file with random data. If not +specified, the file will be overridden 3 times. +.Ar num +times. +.It Fl r Ar source +Read random data from the file +.Ar source . +.It Fl u +After erasing, truncate the file, obfuscate the +filename, obfuscate the file's atime and mtime, +and unlink it. The filename and timestamps will +be obfuscated 20 times. +.It Fl v +Show progress. +.It Fl x +Do not round up file sizes to the full blocks. +This option is always applied on non-regular files. +.It Fl z +After the last erasure, hide the shredding by +overridding the file once more but with zeroes. +.El +.Sh NOTES +.Nm +will not warn you about existence unlisted hard links. +.Sh SEE ALSO +.Xr rm 1 diff --git a/shred.c b/shred.c new file mode 100644 index 0000000..b2375f6 --- /dev/null +++ b/shred.c _AT_@ -0,0 +1,332 @@ +/* See LICENSE file for copyright and license details. */ +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "config.h" +#include "util.h" + +static int fflag = 0; +static long long nflag = 3; +static char *rflag = URANDOM_PATH; +static int uflag = 0; +static int vflag = 0; +static int xflag = 0; +static int zflag = 0; +static int source; +static int zsource; +static FILE *logfp; + +static void +usage(void) +{ + eprintf("usage: %s [-n num] [-r source] [-fuvxz] file ...\n", argv0); +} + +static int +check_dir_empty(char *path) +{ + DIR *dir; + struct dirent *file; + + if (!(dir = opendir(path))) { + weprintf("opendir %s:", path); + return 1; + } + + while (errno = 0, (file = readdir(dir))) { + if (strcmp(file->d_name, ".") && strcmp(file->d_name, "..")) { + weprintf("%s: directory is not empty\n", path); + return 1; + } + } + + if (errno) { + weprintf("readdir %s:", path); + return 1; + } + + closedir(dir); + return 0; +} + +static void +fill_random(int fd, const char *path, void *buffer, size_t n) +{ + ssize_t m; + size_t ptr; + + for (ptr = 0; ptr < n; ptr += m) { + m = read(fd, (char *)buffer + ptr, n - ptr); + if (m < 0) + eprintf("read %s:", path); + if (!m) + eprintf("read %s: end of file reached\n", path); + } +} + +static void +do_shred_content(int dest, char *destpath, int src, const char *srcpath, off_t size, blksize_t blksize) +{ + static char *buffer = 0; + static blksize_t buffer_size = 0; + off_t n, ptr, size_saved = size; + ssize_t m; + double progress; + + if (blksize > buffer_size) { + buffer_size = blksize; + buffer = erealloc(buffer, blksize); + } + + for (; size; size -= n) { + progress = (double)(size_saved - size) / size_saved; + fprintf(logfp, " %i %%\n", (int)(progress * 100)); + + n = size < (off_t)blksize ? size : (off_t)blksize; + + fill_random(src, srcpath, buffer, n); + + for (ptr = 0; ptr < n; ptr += m) + if ((m = write(dest, buffer + ptr, n - ptr)) < 0) + eprintf("write %s:", destpath); + fprintf(logfp, "\033[A\033[K"); + } + + lseek(dest, 0, SEEK_SET); +} + +static void +shred_content(int fd, char *path, off_t size, blksize_t blksize) +{ + long long i; + + if (blksize < 512) + blksize = 8 << 10; + + for (i = 0; i < nflag; i++) { + fprintf(logfp, " Shredding content, pass %llu of %llu\n", i + 1, nflag); + do_shred_content(fd, path, source, rflag, size, blksize); + } + + if (zflag) { + fprintf(logfp, " Zeroing content\n"); + do_shred_content(fd, path, zsource, "/dev/null", size, blksize); + } +} + +static int +shred_times(char *path) +{ + struct timespec times[2]; + int i; + + fill_random(source, rflag, times, sizeof(times)); + + for (i = 0; i < 2; i++) { + if (times[i].tv_nsec < 0) + times[i].tv_nsec = ~times[i].tv_nsec; + times[i].tv_nsec %= 1000000000L; + } + + if (utimensat(AT_FDCWD, path, times, AT_SYMLINK_NOFOLLOW)) { + weprintf("utime %s:", path); + return 1; + } + + return 0; +} + +static int +shred_filename(char *path, char *name, char *orig) +{ + char old[PATH_MAX]; + size_t r, w, n; + + strcpy(old, path); + + n = PATH_MAX - 1 - (size_t)(name - path); + if (n > NAME_MAX) + n = NAME_MAX; + + do { + fill_random(source, rflag, name, n); + for (r = w = 0; r < n; r++) + if (name[r] && name[r] != '/') + name[w++] = name[r]; + name[w] = 0; + } while (!w || !access(path, F_OK)); + + if (rename(old, path)) { + weprintf("rename %s:", orig); + strcpy(path, old); + return 1; + } + + return 0; +} + +static int +shred(char *path) +{ + struct stat attr; + int fd; + size_t n; + char *p; + char tmppath[PATH_MAX]; + + fprintf(logfp, "Shredding %s\n", path); + + if (lstat(path, &attr)) { + weprintf("stat %s:", path); + return 1; + } + + if (fflag && access(path, W_OK) && chmod(path, attr.st_mode | S_IWUSR)) { + weprintf("chmod %s:", path); + return 1; + } + + switch (attr.st_mode & S_IFMT) { + case S_IFLNK: + /* Cannot do anything because of missing system call. */ + goto no_content_shred; + case S_IFDIR: + if (check_dir_empty(path)) + return 1; + goto no_content_shred; + default: + fd = open(path, O_WRONLY | O_NOCTTY | O_NOFOLLOW | O_NOATIME | O_LARGEFILE); + if (fd < 0) { + weprintf("open %s:", path); + return 1; + } + } + + switch (attr.st_mode & S_IFMT) { + case S_IFREG: + if (!xflag && attr.st_size % attr.st_blksize) { + attr.st_size -= attr.st_size % attr.st_blksize; + attr.st_size += attr.st_blksize; + } + break; + case S_IFBLK: + if (ioctl(fd, BLKGETSIZE64, &attr.st_size) < 0) { + weprintf("BLKGETSIZE64 %s:", path); + close(fd); + return 1; + } + break; + default: + attr.st_size = (off_t)~0; + attr.st_size ^= (off_t)1 << (8 * sizeof(off_t) - 1); + break; + } + + shred_content(fd, path, attr.st_size, attr.st_blksize); + + if (uflag && ftruncate(fd, 0)) { + fprintf(logfp, " Truncating\n"); + weprintf("ftruncate %s:", path); + close(fd); + return 1; + } + + if (close(fd) && errno != EINTR) { + weprintf("close %s:", path); + return 1; + } + +no_content_shred: + + if (!uflag) + return 0; + + for (n = 0; n < 20; n++) { + fprintf(logfp, " Shredding timestamps, pass %zu of 20\n", n + 1); + if (shred_times(path)) + break; + } + fprintf(logfp, " Shredding timestamps, done\n"); + + strcpy(tmppath, path); + for (n = strlen(path); n > 1 && tmppath[n - 1] == '/';) + tmppath[--n] = '\0'; + p = strrchr(tmppath, '/'); + p = p ? p + 1 : tmppath; + for (n = 0; n < 20; n++) { + fprintf(logfp, " Shredding filename, pass %zu of 20\n", n + 1); + if (shred_filename(tmppath, p, path)) + break; + } + fprintf(logfp, " Shredding filename, done\n"); + + fprintf(logfp, " Unlinking\n"); + if (remove(tmppath)) { + weprintf("remove %s:", path); + return 1; + } + + return 0; +} + +int +main(int argc, char *argv[]) +{ + int ret = 0; + + ARGBEGIN { + case 'f': + fflag = 1; + break; + case 'n': + nflag = estrtonum(EARGF(usage()), 0, LLONG_MAX); + break; + case 'r': + rflag = EARGF(usage()); + break; + case 'u': + uflag = 1; + break; + case 'v': + vflag = 1; + break; + case 'x': + xflag = 1; + break; + case 'z': + zflag = 1; + break; + default: + usage(); + } ARGEND; + + if (!argc) + usage(); + + logfp = stderr; + if ((source = open(rflag, O_RDONLY | O_NOCTTY | O_LARGEFILE)) < 0) + eprintf("open %s:", rflag); + if (zflag && (zsource = open("/dev/zero", O_RDONLY | O_NOCTTY | O_LARGEFILE)) < 0) + eprintf("open /dev/zero:"); + if (!vflag && !(logfp = fopen("/dev/null", "r"))) + eprintf("fopen /dev/null:"); + + for (; *argv; argv++) { + if (!strcmp(*argv, "-")) + *argv = "/dev/stdout"; + ret |= shred(*argv); + } + + close(source); + if (zsource >= 0) + close(zsource); + return ret; +} -- 2.7.4Received on Sun Mar 27 2016 - 15:00:43 CEST
This archive was generated by hypermail 2.3.0 : Sun Mar 27 2016 - 15:12:15 CEST