forked from gordrs/thc-secure-delete
-
Notifications
You must be signed in to change notification settings - Fork 1
/
srm.c
310 lines (281 loc) · 9.98 KB
/
srm.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
/* Secure RM - by van Hauser / [THC], vh@thc.org
*
* Secure ReMove first overwrites then renames and finally deletes the target
* file(s) specified via parameters.
* For security reasons full 32kb blocks are written so that the whole block
* on which the file(s) live are overwritten. (change #define #BLOCKSIZE)
* The option -l overwrites two times the data.
* The option -ll overwrites the data once.
* Standard mode is a real security wipe for 38 times, flushing
* the caches after every write. The wipe technique was proposed by Peter
* Gutmann at Usenix '96 and includes 10 random overwrites plus 28 special
* defined characters. Take a look at the paper of him, it's really worth
* your time.
*
* Advice : set "alias rm 'srm -v'"
*
* Read the manual for limitations.
* Compiles clean on OpenBSD, Linux, Solaris, AIX and I guess all others.
*
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <strings.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <dirent.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/resource.h>
#include "sdel.h"
int slow = O_SYNC;
int recursive = 0;
int zero = 0;
unsigned long bufsize = BLOCKSIZE;
int fd;
void help () {
printf("srm %s (c) 1997-2003 by %s <%s>\n\n", VERSION, AUTHOR, EMAIL);
printf("Syntax: %s [-dflrvz] file1 file2 etc.\n\n", prg);
printf("Options:\n");
printf("\t-d ignore the two dot special files \".\" and \"..\".\n");
printf("\t-f fast (and insecure mode): no /dev/urandom, no synchronize mode.\n");
printf("\t-l lessens the security (use twice for total insecure mode).\n");
printf("\t-r recursive mode, deletes all subdirectories.\n");
printf("\t-v is verbose mode.\n");
printf("\t-z last wipe writes zeros instead of random data.\n");
printf("\nsrm does a secure overwrite/rename/delete of the target file(s).\n");
printf("Default is secure mode (38 writes).\n");
printf("You can find updates at %s\n", WEB);
exit(1);
}
int smash_it(char *filename, int mode) {
struct stat filestat;
struct stat controlstat;
int i_am_a_directory = 0;
/* get the file stats */
if (lstat(filename, &filestat))
return 1;
if (S_ISREG(filestat.st_mode) && filestat.st_nlink > 1) {
fprintf(stderr, "Error: File %s - file is hardlinked %lu time(s), skipping!\n",
filename, (unsigned long)filestat.st_nlink - 1);
return -1;
}
/* if the blocksize on the filesystem is bigger than the on compiled with, enlarge! */
if (filestat.st_blksize > bufsize) {
if (filestat.st_blksize > 65532) {
bufsize = 65535;
} else {
bufsize = (((filestat.st_blksize / 3) + 1) * 3);
}
}
/* handle the recursive mode */
if (recursive)
if (S_ISDIR(filestat.st_mode)) {
DIR *dir;
struct dirent *dir_entry;
struct stat cwd_stat;
char current_dir[4097];
int res;
int chdir_success = 1;
if (verbose) printf("DIRECTORY (going recursive now)\n");
getcwd(current_dir, 4096);
current_dir[4096] = '\0';
/* a won race will chmod a file to 0700 if the user is owner/root
I'll think about a secure solution to this, however, I think
there isn't one - anyone with an idea? */
if (chdir(filename)) {
(void) chmod(filename, 0700); /* ignore permission errors */
if (chdir(filename)) {
fprintf(stderr,"Can't chdir() to %s, hence I can't wipe it.\n", filename);
chdir_success = 0;
}
}
if (chdir_success) {
lstat(".", &controlstat);
lstat("..", &cwd_stat);
if ( (filestat.st_dev != controlstat.st_dev) || (filestat.st_ino != controlstat.st_ino) ) {
fprintf(stderr, "Race found! (directory %s became a link)\n", filename);
} else {
if ((dir = opendir (".")) != NULL) {
(void) chmod(".", 0700); /* ignore permission errors */
dir = opendir (".");
}
if (dir != NULL) {
while ((dir_entry = readdir(dir)) != NULL)
if (strcmp(dir_entry->d_name, ".") && strcmp(dir_entry->d_name, "..")) {
if (verbose) printf("Wiping %s ", dir_entry->d_name);
if ( (res = smash_it(dir_entry->d_name, mode)) > 0) {
if (res == 3)
fprintf(stderr,"File %s was raced, hence I won't wipe it.\n", dir_entry->d_name);
else {
fprintf(stderr,"Couldn't delete %s. ", dir_entry->d_name);
perror("");
}
} else
if (verbose) printf(" Done\n");
}
closedir(dir);
}
}
if(chdir(current_dir) != 0) {
fprintf(stderr, "Error: Can't chdir to %s (aborting) - ", current_dir);
perror("");
exit(1);
}
/*
lstat(current_dir, &controlstat);
if ( (cwd_stat.st_dev != controlstat.st_dev) || (cwd_stat.st_ino != controlstat.st_ino) ) {
fprintf(stderr, "Race found! (directory %s was exchanged or its your working directory)\n", current_dir);
exit(1);
}
*/
i_am_a_directory = 1;
}
}
/* end of recursive function */
if (S_ISREG(filestat.st_mode)) {
/* open the file for writing in sync. mode */
if ((fd = open(filename, O_RDWR | O_LARGEFILE | slow)) < 0) {
/* here again this has a race problem ... hmmm */
/* make it writable for us if possible */
(void) chmod(filename, 0600); /* ignore errors */
if ((fd = open(filename, O_RDWR | O_LARGEFILE | slow)) < 0)
return 1;
}
fstat(fd, &controlstat);
if ((filestat.st_dev != controlstat.st_dev) || (filestat.st_ino != controlstat.st_ino) || (! S_ISREG(controlstat.st_mode))) {
close(fd);
return 3;
}
if (sdel_overwrite(mode, fd, 0, bufsize, filestat.st_size > 0 ? filestat.st_size : 1, zero) == 0)
return sdel_unlink(filename, 0, 1, slow);
} /* end IS_REG() */
else {
if (S_ISDIR(filestat.st_mode)) {
if (i_am_a_directory == 0) {
fprintf(stderr,"Warning: %s is a directory. I will not remove it, because the -r option is missing!\n", filename);
return 0;
} else
return sdel_unlink(filename, 1, 0, slow);
} else
if (! S_ISDIR(filestat.st_mode)) {
fprintf(stderr,"Warning: %s is not a regular file, rename/unlink only!", filename);
if (! verbose)
printf("\n");
return sdel_unlink(filename, 0, 0, slow);
}
}
return 99; // not reached
}
void cleanup(int signo) {
fprintf(stderr,"Terminated by signal. Clean exit.\n");
if (fd >= 0)
close(fd);
FLUSH;
exit(1);
}
int main (int argc, char *argv[]) {
int errors = 0;
int dot = 0;
int result;
int secure = 2; /* Standard is now SECURE mode (38 overwrites) [since v2.0] */
int loop;
struct rlimit rlim;
prg = argv[0];
if (argc < 2 || strncmp(argv[1], "-h", 2) == 0|| strncmp(argv[1], "--h", 3) == 0)
help();
while (1) {
result = getopt(argc, argv, "DdFfLlRrSsVvZz");
if (result < 0) break;
switch (result) {
case 'd' :
case 'D' : dot = 1;
break;
case 'F' :
case 'f' : slow = 0;
break;
case 'L' :
case 'l' : if (secure) secure--;
break;
case 'R' :
case 'r' : recursive++;
break;
case 'S' :
case 's' : secure++;
break;
case 'V' :
case 'v' : verbose++;
break;
case 'Z' :
case 'z' : zero++;
break;
default : help();
}
}
loop = optind;
if (loop == argc)
help();
signal(SIGINT, cleanup);
signal(SIGTERM, cleanup);
signal(SIGHUP, cleanup);
sdel_init(slow);
if (verbose) {
char type[15] = "random";
if (zero) strcpy(type, "zero");
switch (secure) {
case 0 : printf("Wipe mode is insecure (one pass [%s])\n",type);
break;
case 1 : printf("Wipe mode is insecure (two passes [0xff/%s])\n",type);
break;
default: printf("Wipe mode is secure (38 special passes)\n");
}
}
#ifdef RLIM_INFINITY
#ifdef RLIMIT_FSIZE
rlim.rlim_cur = RLIM_INFINITY;
rlim.rlim_max = RLIM_INFINITY;
if (setrlimit(RLIMIT_FSIZE, &rlim) != 0)
fprintf(stderr, "Warning: Could not reset ulimit for filesize.\n");
#else
fprintf(stderr, "Warning: Not compiled with support for resetting ulimit filesize.\n");
#endif
#endif
while (loop < argc) {
char rmfile[strlen(argv[loop]) + 1];
strcpy(rmfile, argv[loop]);
loop++;
if (strcmp("/", rmfile) == 0) {
fprintf(stderr,"Warning: Do you really want to remove the ROOT directory??\n");
fprintf(stderr,"I'm giving you 5 seconds to abort ... press Control-C\n");
sleep(6);
fprintf(stderr,"Doing my evil work now, don't whimp later, you had been informed!\n");
}
if (dot)
if ((strcmp(".", rmfile) == 0) || (strcmp("..", rmfile) == 0))
continue;
if (verbose)
printf("Wiping %s ", rmfile);
result = (int) smash_it(rmfile, secure);
switch (result) {
case 0 : if (verbose) printf(" Done\n");
break;
case 1 : fprintf(stderr, "Error: File %s - ", rmfile);
perror("");
break;
case -1: break;
case 3 : fprintf(stderr, "File %s was raced, hence I won't wipe it!\n", rmfile);
break;
default: fprintf(stderr, "Unknown error\n");
}
if (result)
errors++;
}
sdel_finnish();
if (errors)
return 1;
else
return 0;
}