371 lines
10 KiB
C
371 lines
10 KiB
C
|
/*
|
||
|
* Copyright (c) 2011, 2012 Kyle Isom <kyle@tyrfingr.is>
|
||
|
*
|
||
|
* Permission to use, copy, modify, and distribute this software for any
|
||
|
* purpose with or without fee is hereby granted, provided that the above
|
||
|
* copyright notice and this permission notice appear in all copies.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
|
*/
|
||
|
|
||
|
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/stat.h>
|
||
|
|
||
|
#include <dirent.h>
|
||
|
#include <err.h>
|
||
|
#include <libgen.h>
|
||
|
#include <limits.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <sysexits.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
|
||
|
#define DEFAULT_PASSES 3
|
||
|
#define DEV_RANDOM "/dev/urandom"
|
||
|
#define MAX_CHUNK 4096
|
||
|
#define SRM_VERSION "1.3.1"
|
||
|
|
||
|
|
||
|
static int do_wipe(char *, size_t, int);
|
||
|
static int wipe(char *);
|
||
|
static int rmdirs(const char *, size_t);
|
||
|
static void usage(void);
|
||
|
static void version(void);
|
||
|
|
||
|
extern char *__progname;
|
||
|
|
||
|
|
||
|
/*
|
||
|
* overwrite a file with one or more passes of random data, then unlink it.
|
||
|
*/
|
||
|
int
|
||
|
main(int argc, char **argv)
|
||
|
{
|
||
|
size_t passes, tmp_passes, wiped, failed, i;
|
||
|
char **file_ptr, **wipe_success, **wipe_fail;
|
||
|
int retval, opt, verbose, recur;
|
||
|
|
||
|
passes = DEFAULT_PASSES;
|
||
|
retval = EX_DATAERR;
|
||
|
wiped = 0;
|
||
|
failed = 0;
|
||
|
verbose = 0;
|
||
|
recur = 0;
|
||
|
|
||
|
if (argc == 1)
|
||
|
usage();
|
||
|
|
||
|
while (-1 != (opt = getopt(argc, argv, "hn:rvV"))) {
|
||
|
switch( opt ) {
|
||
|
case 'h':
|
||
|
usage();
|
||
|
break; /* don't technically need it but meh */
|
||
|
case 'n':
|
||
|
tmp_passes = atoi(optarg);
|
||
|
passes = tmp_passes > 0 ? tmp_passes : passes;
|
||
|
break;
|
||
|
case 'r':
|
||
|
recur = 1;
|
||
|
break;
|
||
|
case 'v':
|
||
|
verbose = 1;
|
||
|
break;
|
||
|
case 'V':
|
||
|
version();
|
||
|
exit(EX_OK);
|
||
|
default:
|
||
|
usage();
|
||
|
/* NOTREACHED */
|
||
|
}
|
||
|
}
|
||
|
|
||
|
argc -= optind;
|
||
|
argv += optind;
|
||
|
|
||
|
file_ptr = argv;
|
||
|
wipe_success = calloc(argc, sizeof(char *));
|
||
|
wipe_fail = calloc(argc, sizeof(char *));
|
||
|
|
||
|
|
||
|
while (NULL != *file_ptr) {
|
||
|
printf("%s: ", *file_ptr);
|
||
|
fflush(stdout);
|
||
|
|
||
|
if (EX_OK != do_wipe(*file_ptr, passes, recur)) {
|
||
|
wipe_fail[failed] = strdup(*file_ptr);
|
||
|
failed++;
|
||
|
} else {
|
||
|
wipe_success[wiped] = strdup(*file_ptr);
|
||
|
wiped++;
|
||
|
printf(" OK!");
|
||
|
}
|
||
|
file_ptr++;
|
||
|
printf("\n");
|
||
|
}
|
||
|
|
||
|
if (verbose) {
|
||
|
if (0 < wiped) {
|
||
|
printf("success: ");
|
||
|
for( i = 0; i < wiped; ++i ) {
|
||
|
printf("%s ", wipe_success[i]);
|
||
|
}
|
||
|
printf("\n");
|
||
|
}
|
||
|
|
||
|
if (0 < failed) {
|
||
|
printf("failures: ");
|
||
|
for( i = 0; i < failed; ++i ) {
|
||
|
printf("%s ", wipe_fail[i]);
|
||
|
}
|
||
|
printf("\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* free allocated memory */
|
||
|
for (i = 0; i < failed; ++i) {
|
||
|
free(wipe_fail[i]);
|
||
|
wipe_fail[i] = NULL;
|
||
|
}
|
||
|
free(wipe_fail);
|
||
|
wipe_fail = NULL;
|
||
|
|
||
|
for (i = 0; i < wiped; ++i) {
|
||
|
free(wipe_success[i]);
|
||
|
wipe_success[i] = NULL;
|
||
|
}
|
||
|
free(wipe_success);
|
||
|
wipe_success = NULL;
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* takes a filename and the number of passes to wipe the file
|
||
|
*/
|
||
|
int
|
||
|
do_wipe(char *filename, size_t passes, int recur)
|
||
|
{
|
||
|
struct stat sb;
|
||
|
size_t filesize, i;
|
||
|
int retval;
|
||
|
|
||
|
retval = EX_IOERR;
|
||
|
if (-1 == stat(filename, &sb)) {
|
||
|
warn("%s", filename);
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
if (recur && sb.st_mode & S_IFDIR) {
|
||
|
if (EX_OK != rmdirs(filename, passes)) {
|
||
|
printf("!");
|
||
|
return retval;
|
||
|
} else {
|
||
|
printf(".");
|
||
|
fflush(stdout);
|
||
|
retval = EX_OK;
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
filesize = sb.st_size;
|
||
|
|
||
|
for (i = 0; i < passes; ++i) {
|
||
|
if (EX_OK != wipe(filename)) {
|
||
|
printf("!");
|
||
|
return retval;
|
||
|
} else if (-1 == stat(filename, &sb)) {
|
||
|
printf("!");
|
||
|
return retval;
|
||
|
} else if (filesize != (size_t)sb.st_size) {
|
||
|
printf("!");
|
||
|
return retval;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (-1 == truncate(filename, (off_t)0))
|
||
|
warn("%s", filename);
|
||
|
|
||
|
if (-1 == unlink(filename)) {
|
||
|
warn("%s", filename);
|
||
|
} else {
|
||
|
printf(".");
|
||
|
fflush(stdout);
|
||
|
retval = EX_OK;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* takes a filename and attempts to overwrite it with random data
|
||
|
*/
|
||
|
int
|
||
|
wipe(char *filename)
|
||
|
{
|
||
|
struct stat sb;
|
||
|
|
||
|
size_t chunk, filesize, rdsz, wiped, wrsz;
|
||
|
FILE *devrandom, *target;
|
||
|
int retval;
|
||
|
char *rdata;
|
||
|
|
||
|
retval = EX_IOERR;
|
||
|
wiped = 0;
|
||
|
|
||
|
if (-1 == stat(filename, &sb))
|
||
|
return retval;
|
||
|
|
||
|
filesize = sb.st_size;
|
||
|
|
||
|
/*
|
||
|
* open devrandom first: if this fails, we don't want to touch the
|
||
|
* target file.
|
||
|
*/
|
||
|
devrandom = fopen(DEV_RANDOM, "r");
|
||
|
if (NULL == devrandom || -1 == ferror(devrandom)) {
|
||
|
warn("failed to open PRNG %s!", DEV_RANDOM);
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* for security purposes, we want to make sure to actually overwrite
|
||
|
* the file. r+ gives us read/write but more importantly, sets the
|
||
|
* write stream at the beginning of the file. a side note is that when
|
||
|
* overwriting a file, its size will never seem to change.
|
||
|
*/
|
||
|
target = fopen(filename, "r+");
|
||
|
if (NULL == target || -1 == ferror(target)) {
|
||
|
warn("failed to open %s", filename);
|
||
|
fclose(devrandom);
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
rewind(target);
|
||
|
|
||
|
/*
|
||
|
* wait to calloc until we really need the data - makes cleaning up less
|
||
|
* tricky.
|
||
|
*/
|
||
|
rdata = calloc(MAX_CHUNK, sizeof(char));
|
||
|
if (NULL == rdata) {
|
||
|
warn("could not allocate random data memory");
|
||
|
fclose(devrandom);
|
||
|
fclose(target);
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
while (wiped < filesize) {
|
||
|
chunk = filesize - wiped;
|
||
|
chunk = chunk > MAX_CHUNK ? MAX_CHUNK : chunk;
|
||
|
|
||
|
rdsz = fread( rdata, sizeof(char), chunk, devrandom );
|
||
|
wrsz = fwrite( rdata, sizeof(char), chunk, target );
|
||
|
|
||
|
if (-1 == stat(filename, &sb)) {
|
||
|
warn(" stat on %s failed", filename);
|
||
|
break;
|
||
|
}
|
||
|
if ((rdsz != wrsz) || (filesize != (unsigned int)sb.st_size)) {
|
||
|
warn("invalid read/write size");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
wiped += chunk;
|
||
|
}
|
||
|
|
||
|
if ((0 != fclose(devrandom)) || (0 != fclose(target)))
|
||
|
warn("%s", filename);
|
||
|
else
|
||
|
retval = EX_OK;
|
||
|
|
||
|
free(rdata);
|
||
|
rdata = NULL;
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* remove a directory, all files in it, and all subdirectories.
|
||
|
*/
|
||
|
int
|
||
|
rmdirs(const char *path, size_t passes)
|
||
|
{
|
||
|
char child[FILENAME_MAX + 1];
|
||
|
struct dirent *dp;
|
||
|
DIR *dirp;
|
||
|
int fail = 0;
|
||
|
|
||
|
if (NULL == (dirp = opendir(path)))
|
||
|
return EX_IOERR;
|
||
|
while (NULL != (dp = readdir(dirp))) {
|
||
|
if (0 == strncmp("..", dp->d_name, 3))
|
||
|
continue;
|
||
|
if (0 == strncmp(".", dp->d_name, 2))
|
||
|
continue;
|
||
|
snprintf(child, FILENAME_MAX, "%s/%s", path, dp->d_name);
|
||
|
if (DT_DIR == dp->d_type) {
|
||
|
fail = rmdirs(child, passes);
|
||
|
if (EX_IOERR == fail)
|
||
|
break;
|
||
|
} else {
|
||
|
fail = do_wipe(child, passes, 1);
|
||
|
if (EX_IOERR == fail)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (-1 == closedir(dirp))
|
||
|
return EX_IOERR;
|
||
|
if (EX_IOERR == fail)
|
||
|
return EX_IOERR;
|
||
|
|
||
|
if (-1 == rmdir(path))
|
||
|
return EX_IOERR;
|
||
|
else
|
||
|
return EX_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* print a quick usage message
|
||
|
*/
|
||
|
void
|
||
|
usage()
|
||
|
{
|
||
|
version();
|
||
|
printf("usage: %s [-v] [-n number] files\n", __progname);
|
||
|
printf(" -n passes specify number of passes\n");
|
||
|
printf(" (default is %d passes)\n", DEFAULT_PASSES);
|
||
|
printf(" -r recursive delete\n");
|
||
|
printf(" -v display list of failures and wiped files"
|
||
|
" after wiping\n"
|
||
|
" (verbose mode).\n");
|
||
|
printf(" -V print version information.");
|
||
|
|
||
|
printf("\n");
|
||
|
exit(EX_USAGE);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* print program version information
|
||
|
*/
|
||
|
void
|
||
|
version()
|
||
|
{
|
||
|
printf("%s\n", SRM_VERSION);
|
||
|
}
|
||
|
|
||
|
|