---
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.4
Received 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