1 /* vim: set sw=4 ts=4 noexpandtab : */
2 /*
3 * Copyright (C) 2007-2019 Abel Cheung.
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the project nor the names of its contributors
15 * may be used to endorse or promote products derived from this software
16 * without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31 #include "config.h"
32
33 #include <errno.h>
34 #include <stdlib.h>
35
36 #include "utils.h"
37 #ifdef G_OS_WIN32
38 # include "utils-win.h"
39 #endif
40
41 #include <glib/gi18n.h>
42 #include <glib/gstdio.h>
43
44 #include "rifiuti-vista.h"
45
46 static r2status exit_status = EXIT_SUCCESS;
47 static metarecord meta;
48
49
50 /*!
51 * Check if index file has sufficient amount of data for reading.
52 * If successful, its file content will be stored in buf.
53 * Return 0 if successful, non-zero on error
54 */
55 static r2status
validate_index_file(const char * filename,void ** filebuf,gsize * bufsize,uint64_t * ver,uint32_t * pathlen)56 validate_index_file (const char *filename,
57 void **filebuf,
58 gsize *bufsize,
59 uint64_t *ver,
60 uint32_t *pathlen)
61 {
62 gsize expected;
63 int status;
64 GError *err = NULL;
65 char *buf;
66
67 g_debug ("Start file validation for '%s'...", filename);
68
69 g_return_val_if_fail ( (filename != NULL) && (*filename != '\0'),
70 R2_ERR_INTERNAL );
71 g_return_val_if_fail ( (filebuf != NULL), R2_ERR_INTERNAL );
72 g_return_val_if_fail ( (bufsize != NULL), R2_ERR_INTERNAL );
73 g_return_val_if_fail ( (ver != NULL), R2_ERR_INTERNAL );
74 g_return_val_if_fail ( (pathlen != NULL), R2_ERR_INTERNAL );
75
76 if ( !g_file_get_contents (filename, &buf, bufsize, &err) )
77 {
78 g_critical (_("%s(): failed to retrieve file content for '%s': %s"),
79 __func__, filename, err->message);
80 g_clear_error (&err);
81 status = R2_ERR_OPEN_FILE;
82 goto validation_error;
83 }
84
85 g_debug ("Retrieval of '%s' is done, size = %" G_GSIZE_FORMAT, filename, *bufsize);
86
87 if (*bufsize <= VERSION1_FILENAME_OFFSET)
88 {
89 g_debug ("File size expected to be more than %" G_GSIZE_FORMAT,
90 (gsize) VERSION1_FILENAME_OFFSET);
91 g_printerr (_("File is truncated, or probably not a $Recycle.bin index file."));
92 g_printerr ("\n");
93 status = R2_ERR_BROKEN_FILE;
94 goto validation_error;
95 }
96
97 copy_field (ver, VERSION, FILESIZE);
98 *ver = GUINT64_FROM_LE (*ver);
99 g_debug ("version = %" G_GUINT64_FORMAT, *ver);
100
101 switch (*ver)
102 {
103 case VERSION_VISTA:
104
105 expected = VERSION1_FILE_SIZE;
106 /* see populate_record_data() for reason */
107 if ( (*bufsize != expected) && (*bufsize != expected - 1) )
108 {
109 g_debug ("File size expected to be %" G_GSIZE_FORMAT
110 " or %" G_GSIZE_FORMAT, expected, expected - 1);
111 g_printerr (_("Index file expected size and real size do not match."));
112 g_printerr ("\n");
113 status = R2_ERR_BROKEN_FILE;
114 goto validation_error;
115 }
116 *pathlen = WIN_PATH_MAX;
117 break;
118
119 case VERSION_WIN10:
120
121 copy_field (pathlen, VERSION1_FILENAME, VERSION2_FILENAME);
122 *pathlen = GUINT32_FROM_LE (*pathlen);
123
124 /* Header length + file name length in UTF-16 encoding */
125 expected = VERSION2_FILENAME_OFFSET + (*pathlen) * 2;
126 if (*bufsize != expected)
127 {
128 g_debug ("File size expected to be %" G_GSIZE_FORMAT, expected);
129 g_printerr (_("Index file expected size and real size do not match."));
130 g_printerr ("\n");
131 status = R2_ERR_BROKEN_FILE;
132 goto validation_error;
133 }
134 break;
135
136 default:
137 g_printerr (_("Unsupported file version, or probably not a $Recycle.bin index file."));
138 g_printerr ("\n");
139 status = R2_ERR_BROKEN_FILE;
140 goto validation_error;
141 }
142
143 *filebuf = buf;
144 return EXIT_SUCCESS;
145
146 validation_error:
147
148 *filebuf = NULL;
149 return status;
150 }
151
152
153 static rbin_struct *
populate_record_data(void * buf,uint64_t version,uint32_t pathlen,gboolean erraneous)154 populate_record_data (void *buf,
155 uint64_t version,
156 uint32_t pathlen,
157 gboolean erraneous)
158 {
159 uint64_t win_filetime;
160 rbin_struct *record;
161 size_t read;
162
163 record = g_malloc0 (sizeof (rbin_struct));
164 record->version = version;
165
166 /*
167 * In rare cases, the size of index file is 543 bytes versus (normal) 544 bytes.
168 * In such occasion file size only occupies 56 bit, not 64 bit as it ought to be.
169 * Actually this 56-bit file size is very likely wrong after all. Probably some
170 * bug inside Windows. This is observed during deletion of dd.exe from Forensic
171 * Acquisition Utilities (by George M. Garner Jr) in certain localized Vista.
172 */
173 memcpy (&record->filesize, buf + FILESIZE_OFFSET,
174 FILETIME_OFFSET - FILESIZE_OFFSET - (int) erraneous);
175 if (erraneous)
176 {
177 g_debug ("filesize field broken, 56 bit only, val=0x%" G_GINT64_MODIFIER "X",
178 record->filesize);
179 /* not printing the value because it was wrong and misleading */
180 record->filesize = G_MAXUINT64;
181 }
182 else
183 {
184 record->filesize = GUINT64_FROM_LE (record->filesize);
185 g_debug ("deleted file size = %" G_GUINT64_FORMAT, record->filesize);
186 }
187
188 /* File deletion time */
189 memcpy (&win_filetime, buf + FILETIME_OFFSET - (int) erraneous,
190 VERSION1_FILENAME_OFFSET - FILETIME_OFFSET);
191 win_filetime = GUINT64_FROM_LE (win_filetime);
192 record->deltime = win_filetime_to_epoch (win_filetime);
193
194 /* One extra char for safety, though path should have already been null terminated */
195 g_debug ("pathlen = %d", pathlen);
196 switch (version)
197 {
198 case VERSION_VISTA:
199 record->uni_path = conv_path_to_utf8_with_tmpl (
200 (const char *) (buf - erraneous + VERSION1_FILENAME_OFFSET),
201 NULL, "<\\u%04X>", &read, &exit_status);
202 break;
203
204 case VERSION_WIN10:
205 record->uni_path = conv_path_to_utf8_with_tmpl (
206 (const char *) (buf + VERSION2_FILENAME_OFFSET),
207 NULL, "<\\u%04X>", &read, &exit_status);
208 break;
209
210 default:
211 g_assert_not_reached ();
212 }
213
214 if (record->uni_path == NULL) {
215 g_warning (_("(Record %s) Error converting unicode path to UTF-8."),
216 record->index_s);
217 record->uni_path = "";
218 }
219
220 return record;
221 }
222
223 static void
parse_record_cb(char * index_file,GSList ** recordlist)224 parse_record_cb (char *index_file,
225 GSList **recordlist)
226 {
227 rbin_struct *record;
228 char *basename;
229 uint64_t version = 0;
230 uint32_t pathlen = 0;
231 gsize bufsize;
232 void *buf = NULL;
233 r2status validate_st;
234
235 basename = g_path_get_basename (index_file);
236
237 validate_st = validate_index_file (
238 index_file, &buf, &bufsize, &version, &pathlen);
239 if ( validate_st != EXIT_SUCCESS )
240 {
241 g_printerr (_("File '%s' fails validation."), basename);
242 g_printerr ("\n");
243 exit_status = validate_st;
244 goto parse_record_error;
245 }
246
247 g_debug ("Start populating record for '%s'...", basename);
248
249 switch (version)
250 {
251 case VERSION_VISTA:
252 /* see populate_record_data() for meaning of last parameter */
253 record = populate_record_data (buf, version, pathlen,
254 (bufsize == VERSION1_FILE_SIZE - 1));
255 break;
256
257 case VERSION_WIN10:
258 record = populate_record_data (buf, version, pathlen, FALSE);
259 break;
260
261 default:
262 g_assert_not_reached();
263 }
264
265 g_debug ("Parsing done for '%s'", basename);
266 record->index_s = basename;
267 record->meta = &meta;
268 *recordlist = g_slist_prepend (*recordlist, record);
269 g_free (buf);
270 return;
271
272 parse_record_error:
273
274 g_free (buf);
275 g_free (basename);
276 }
277
278
279 static int
sort_record_by_time(rbin_struct * a,rbin_struct * b)280 sort_record_by_time (rbin_struct *a,
281 rbin_struct *b)
282 {
283 /* sort primary key: deletion time; secondary key: index file name */
284 return ((a->deltime < b->deltime) ? -1 :
285 (a->deltime > b->deltime) ? 1 :
286 strcmp (a->index_s, b->index_s));
287 }
288
289
290 int
main(int argc,char ** argv)291 main (int argc,
292 char **argv)
293 {
294 GSList *filelist = NULL;
295 GSList *recordlist = NULL;
296 GOptionContext *context;
297
298 extern char **fileargs;
299
300 rifiuti_init (argv[0]);
301
302 /* TRANSLATOR: appears in help text short summary */
303 context = g_option_context_new (N_("DIR_OR_FILE"));
304 g_option_context_set_summary (context, N_(
305 "Parse index files in C:\\$Recycle.bin style folder "
306 "and dump recycle bin data. Can also dump a single index file."));
307 rifiuti_setup_opt_ctx (&context, RECYCLE_BIN_TYPE_DIR);
308 exit_status = rifiuti_parse_opt_ctx (&context, &argc, &argv);
309 if (exit_status != EXIT_SUCCESS)
310 goto cleanup;
311
312 exit_status = check_file_args (fileargs[0], &filelist, RECYCLE_BIN_TYPE_DIR);
313 if (exit_status != EXIT_SUCCESS)
314 goto cleanup;
315
316 g_slist_foreach (filelist, (GFunc) parse_record_cb, &recordlist);
317
318 /* Fill in recycle bin metadata */
319 meta.type = RECYCLE_BIN_TYPE_DIR;
320 meta.filename = fileargs[0];
321 meta.keep_deleted_entry = FALSE;
322 meta.is_empty = (filelist == NULL);
323 meta.has_unicode_path = TRUE;
324
325 /* NULL filelist at this point means a valid empty $Recycle.bin */
326 if ( !meta.is_empty && (recordlist == NULL) )
327 {
328 g_printerr (_("No valid recycle bin index file found."));
329 g_printerr ("\n");
330 exit_status = R2_ERR_BROKEN_FILE;
331 goto cleanup;
332 }
333 recordlist = g_slist_sort (recordlist, (GCompareFunc) sort_record_by_time);
334
335 /* detect global recycle bin version from versions of all files */
336 {
337 GSList *l = recordlist;
338 if (!l)
339 meta.version = VERSION_NOT_FOUND;
340 else
341 {
342 meta.version = (int64_t) ((rbin_struct *) recordlist->data)->version;
343
344 while (NULL != (l = l->next))
345 {
346 if ((int64_t) ((rbin_struct *) l->data)->version != meta.version)
347 {
348 meta.version = VERSION_INCONSISTENT;
349 break;
350 }
351 }
352
353 if (meta.version == VERSION_INCONSISTENT)
354 {
355 g_printerr (_("Index files come from multiple versions of Windows."
356 " Please check each file independently."));
357 g_printerr ("\n");
358 exit_status = R2_ERR_BROKEN_FILE;
359 goto cleanup;
360 }
361 }
362 }
363
364 /*
365 * No attempt is made to distinguish difference for Vista - 8.1.
366 * The corrupt filesize artifact on Vista can't be reproduced,
367 * therefore must be very rare.
368 */
369 switch (meta.version)
370 {
371 case VERSION_VISTA: meta.os_guess = OS_GUESS_VISTA; break;
372 case VERSION_WIN10: meta.os_guess = OS_GUESS_10; break;
373 default: meta.os_guess = OS_GUESS_UNKNOWN;
374 }
375
376 /* Print everything */
377 {
378 r2status s = prepare_output_handle ();
379 if (s != EXIT_SUCCESS) {
380 exit_status = s;
381 goto cleanup;
382 }
383 }
384
385 print_header (meta);
386 g_slist_foreach (recordlist, (GFunc) print_record_cb, NULL);
387 print_footer ();
388
389 close_output_handle ();
390
391 /* file descriptor should have been closed at this point */
392 {
393 r2status s = move_temp_file ();
394 if ( s != EXIT_SUCCESS )
395 exit_status = s;
396 }
397
398 cleanup:
399
400 /* Last minute error messages for accumulated non-fatal errors */
401 switch (exit_status)
402 {
403 case R2_ERR_USER_ENCODING:
404 g_printerr (_("Some entries could not be presented as correct "
405 "unicode path. The concerned characters are displayed "
406 "in escaped unicode sequences."));
407 g_printerr ("\n");
408 break;
409
410 default:
411 break;
412 }
413
414 g_debug ("Cleaning up...");
415
416 g_slist_free_full (recordlist, (GDestroyNotify) free_record_cb);
417 g_slist_free_full (filelist , (GDestroyNotify) g_free );
418 free_vars ();
419
420 return exit_status;
421 }
422