Signed-off-by: Mattias Andrée <[email protected]>
---
 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
@@ -73,6 +73,7 @@ BIN = \
        readahead         \
        respawn           \
        rmmod             \
+       shred             \
        stat              \
        su                \
        swaplabel         \
@@ -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
@@ -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
@@ -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
@@ -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.4


Reply via email to