add correct comments for buttons
[reactos.git] / reactos / base / applications / utils / dumprecbin / dumprecbin.c
1 /*
2 *
3 * dumprecbin - dumps a recycle bin database
4 *
5 * Copyright (c) 2005 by Thomas Weidenmueller <w3seek@reactos.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 *
21 * TODO: - Support for Vista recycle bins (read the DeleteInfo NTFS streams, also NT 5.x)
22 * - Support for INFO databases (win95)
23 */
24 #include <windows.h>
25 #include <winternl.h>
26 #include <sddl.h>
27 #include <ntsecapi.h>
28 #include <stdio.h>
29 #include <ctype.h>
30 #include <tchar.h>
31
32 #ifndef NT_SUCCESS
33 #define NT_SUCCESS(status) ((LONG)(status) >= 0)
34 #endif
35
36 typedef struct _RECYCLE_BIN
37 {
38 struct _RECYCLE_BIN *Next;
39 PSID Sid;
40 WCHAR User[255];
41 WCHAR Path[MAX_PATH + 1];
42 } RECYCLE_BIN, *PRECYCLE_BIN;
43
44 typedef enum
45 {
46 ivUnknown = 0,
47 ivINFO2
48 } INFO_VERSION, *PINFO_VERSION;
49
50 typedef struct _INFO2_HEADER
51 {
52 DWORD Version;
53 DWORD Zero1;
54 DWORD Zero2;
55 DWORD RecordSize;
56 } INFO2_HEADER, *PINFO2_HEADER;
57
58 typedef struct _INFO2_RECORD
59 {
60 DWORD Unknown;
61 CHAR AnsiFileName[MAX_PATH];
62 DWORD RecordNumber;
63 DWORD DriveLetter;
64 FILETIME DeletionTime;
65 DWORD DeletedPhysicalSize;
66 WCHAR FileName[MAX_PATH - 2];
67 } INFO2_RECORD, *PINFO2_RECORD;
68
69 static HANDLE
70 OpenAndMapInfoDatabase(IN LPTSTR szFileName,
71 OUT PVOID *MappingBasePtr,
72 OUT PLARGE_INTEGER FileSize)
73 {
74 HANDLE hFile, hMapping = INVALID_HANDLE_VALUE;
75
76 hFile = CreateFile(szFileName,
77 FILE_READ_DATA,
78 FILE_SHARE_READ,
79 NULL,
80 OPEN_EXISTING,
81 FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM,
82 NULL);
83 if (hFile != INVALID_HANDLE_VALUE)
84 {
85 if (GetFileSizeEx(hFile,
86 FileSize) &&
87 FileSize->QuadPart >= 0xF)
88 {
89 hMapping = CreateFileMapping(hFile,
90 NULL,
91 PAGE_READONLY,
92 0,
93 0,
94 NULL);
95 if (hMapping == NULL ||
96 !(*MappingBasePtr = MapViewOfFile(hMapping,
97 FILE_MAP_READ,
98 0,
99 0,
100 0)))
101 {
102 if (hMapping != NULL)
103 {
104 CloseHandle(hMapping);
105 }
106 hMapping = INVALID_HANDLE_VALUE;
107 }
108 }
109 CloseHandle(hFile);
110 }
111
112 return hMapping;
113 }
114
115 static VOID
116 UnmapAndCloseDatabase(IN HANDLE hMapping,
117 IN PVOID MappingBasePtr)
118 {
119 UnmapViewOfFile(MappingBasePtr);
120 CloseHandle(hMapping);
121 }
122
123 static INFO_VERSION
124 DetectDatabaseVersion(PVOID Header)
125 {
126 PINFO2_HEADER Info2 = (PINFO2_HEADER)Header;
127 INFO_VERSION Version = ivUnknown;
128
129 if (Info2->Version == 5 &&
130 Info2->Zero1 == 0 &&
131 Info2->Zero2 == 0 &&
132 Info2->RecordSize == 0x320)
133 {
134 Version = ivINFO2;
135 }
136
137 return Version;
138 }
139
140 static BOOL
141 IsValidRecycleBin(IN LPTSTR szPath)
142 {
143 TCHAR szFile[MAX_PATH + 1];
144 TCHAR szClsId[48];
145 INFO_VERSION DbVersion = ivUnknown;
146
147 _stprintf(szFile,
148 _T("%s\\desktop.ini"),
149 szPath);
150
151 /* check if directory contains a valid desktop.ini for the recycle bin */
152 if (GetPrivateProfileString(TEXT(".ShellClassInfo"),
153 TEXT("CLSID"),
154 NULL,
155 szClsId,
156 sizeof(szClsId) / sizeof(szClsId[0]),
157 szFile) &&
158 !_tcsicmp(_T("{645FF040-5081-101B-9F08-00AA002F954E}"),
159 szClsId))
160 {
161 HANDLE hDb;
162 LARGE_INTEGER FileSize;
163 PVOID pDbBase = NULL;
164
165 /* open the database and check the signature */
166 _stprintf(szFile,
167 _T("%s\\INFO2"),
168 szPath);
169 hDb = OpenAndMapInfoDatabase(szFile,
170 &pDbBase,
171 &FileSize);
172 if (hDb != INVALID_HANDLE_VALUE)
173 {
174 DbVersion = DetectDatabaseVersion(pDbBase);
175 UnmapAndCloseDatabase(hDb,
176 pDbBase);
177 }
178 }
179
180 return DbVersion != ivUnknown;
181 }
182
183 static BOOL
184 OpenLocalLSAPolicyHandle(IN ACCESS_MASK DesiredAccess,
185 OUT PLSA_HANDLE PolicyHandle)
186 {
187 LSA_OBJECT_ATTRIBUTES LsaObjectAttributes = {0};
188 NTSTATUS Status;
189
190 Status = LsaOpenPolicy(NULL,
191 &LsaObjectAttributes,
192 DesiredAccess,
193 PolicyHandle);
194 if (!NT_SUCCESS(Status))
195 {
196 SetLastError(LsaNtStatusToWinError(Status));
197 return FALSE;
198 }
199
200 return TRUE;
201 }
202
203 static BOOL
204 ConvertSIDToAccountName(IN PSID Sid,
205 OUT LPWSTR User)
206 {
207 DWORD AccountNameLen = 0;
208 DWORD DomainNameLen = 0;
209 SID_NAME_USE NameUse;
210 DWORD Error = ERROR_SUCCESS;
211 LPWSTR AccountName, DomainName;
212 BOOL Ret = FALSE;
213
214 if (!LookupAccountSidW(NULL,
215 Sid,
216 NULL,
217 &AccountNameLen,
218 NULL,
219 &DomainNameLen,
220 &NameUse))
221 {
222 Error = GetLastError();
223 if (Error == ERROR_NONE_MAPPED ||
224 Error != ERROR_INSUFFICIENT_BUFFER)
225 {
226 /* some unexpected error occured! */
227 goto ConvertSID;
228 }
229 }
230
231 AccountName = (LPWSTR)HeapAlloc(GetProcessHeap(),
232 0,
233 (AccountNameLen + DomainNameLen) * sizeof(WCHAR));
234 if (AccountName != NULL)
235 {
236 LSA_HANDLE PolicyHandle;
237 DomainName = AccountName + AccountNameLen;
238
239 if (!LookupAccountSidW(NULL,
240 Sid,
241 AccountName,
242 &AccountNameLen,
243 DomainName,
244 &DomainNameLen,
245 &NameUse))
246 {
247 goto BailFreeAccountName;
248 }
249
250 wcscpy(User,
251 AccountName);
252 Ret = TRUE;
253
254 if (OpenLocalLSAPolicyHandle(POLICY_LOOKUP_NAMES | POLICY_VIEW_LOCAL_INFORMATION,
255 &PolicyHandle))
256 {
257 PLSA_REFERENCED_DOMAIN_LIST ReferencedDomain;
258 PLSA_TRANSLATED_NAME Names;
259 PLSA_TRUST_INFORMATION Domain;
260 PLSA_UNICODE_STRING DomainName;
261 PPOLICY_ACCOUNT_DOMAIN_INFO PolicyAccountDomainInfo = NULL;
262 NTSTATUS Status;
263
264 Status = LsaLookupSids(PolicyHandle,
265 1,
266 &Sid,
267 &ReferencedDomain,
268 &Names);
269 if (NT_SUCCESS(Status))
270 {
271 if (ReferencedDomain != NULL &&
272 Names->DomainIndex >= 0)
273 {
274 Domain = &ReferencedDomain->Domains[Names->DomainIndex];
275 DomainName = &Domain->Name;
276 }
277 else
278 {
279 Domain = NULL;
280 DomainName = NULL;
281 }
282
283 switch (Names->Use)
284 {
285 case SidTypeAlias:
286 if (Domain != NULL)
287 {
288 /* query the domain name for BUILTIN accounts */
289 Status = LsaQueryInformationPolicy(PolicyHandle,
290 PolicyAccountDomainInformation,
291 (PVOID*)&PolicyAccountDomainInfo);
292 if (NT_SUCCESS(Status))
293 {
294 DomainName = &PolicyAccountDomainInfo->DomainName;
295 }
296 }
297 /* fall through */
298
299 case SidTypeUser:
300 {
301 if (Domain != NULL)
302 {
303 WCHAR *s;
304
305 /* NOTE: LSA_UNICODE_STRINGs are not always NULL-terminated! */
306
307 wcscpy(User,
308 AccountName);
309 wcscat(User,
310 L" (");
311 s = User + wcslen(User);
312 CopyMemory(s,
313 DomainName->Buffer,
314 DomainName->Length);
315 s += DomainName->Length / sizeof(WCHAR);
316 *(s++) = L'\\';
317 CopyMemory(s,
318 Names->Name.Buffer,
319 Names->Name.Length);
320 s += Names->Name.Length / sizeof(WCHAR);
321 *(s++) = L')';
322 *s = L'\0';
323 }
324 break;
325 }
326
327 case SidTypeWellKnownGroup:
328 {
329 break;
330 }
331
332 default:
333 {
334 _ftprintf(stderr,
335 _T("Unhandled SID type: 0x%x\n"),
336 Names->Use);
337 break;
338 }
339 }
340
341 if (PolicyAccountDomainInfo != NULL)
342 {
343 LsaFreeMemory(PolicyAccountDomainInfo);
344 }
345
346 LsaFreeMemory(ReferencedDomain);
347 LsaFreeMemory(Names);
348 }
349
350 LsaClose(PolicyHandle);
351
352 if (!NT_SUCCESS(Status))
353 {
354 Ret = FALSE;
355 goto BailFreeAccountName;
356 }
357 }
358 else
359 {
360 BailFreeAccountName:
361 HeapFree(GetProcessHeap(),
362 0,
363 AccountName);
364 goto ConvertSID;
365 }
366 }
367
368 ConvertSID:
369 if (!Ret)
370 {
371 LPWSTR StrSid;
372 Ret = ConvertSidToStringSidW(Sid,
373 &StrSid);
374 if (Ret)
375 {
376 wcscpy(User,
377 StrSid);
378 LocalFree((HLOCAL)StrSid);
379 }
380 }
381
382 return Ret;
383 }
384
385 static VOID
386 FreeRecycleBinsList(IN OUT PRECYCLE_BIN *RecycleBinsListHead)
387 {
388 PRECYCLE_BIN CurrentBin, NextBin;
389
390 CurrentBin = *RecycleBinsListHead;
391 while (CurrentBin != NULL)
392 {
393 NextBin = CurrentBin->Next;
394 LocalFree((HLOCAL)CurrentBin->Sid);
395 HeapFree(GetProcessHeap(),
396 0,
397 CurrentBin);
398 CurrentBin = NextBin;
399 }
400
401 *RecycleBinsListHead = NULL;
402 }
403
404 static BOOL
405 LocateRecycleBins(IN LPWSTR szDrive,
406 OUT PRECYCLE_BIN *RecycleBinsListHead)
407 {
408 TCHAR szRecBinPath[MAX_PATH + 1];
409 HANDLE FindResult;
410 WIN32_FIND_DATA FindData;
411 PRECYCLE_BIN NewBin;
412 BOOL Ret = FALSE;
413
414 FreeRecycleBinsList(RecycleBinsListHead);
415
416 /*
417 * search for recycle bins on volumes that support file security (NTFS)
418 */
419 _stprintf(szRecBinPath,
420 _T("%lS\\RECYCLER\\*"),
421 szDrive);
422 FindResult = FindFirstFile(szRecBinPath,
423 &FindData);
424 if (FindResult != INVALID_HANDLE_VALUE)
425 {
426 do
427 {
428 if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY &&
429 _tcscmp(FindData.cFileName,
430 _T("..")) &&
431 _tcscmp(FindData.cFileName,
432 _T(".")))
433 {
434 PSID Sid;
435
436 if (ConvertStringSidToSid(FindData.cFileName,
437 &Sid))
438 {
439 _stprintf(szRecBinPath,
440 _T("%s\\RECYCLER\\%s"),
441 szDrive,
442 FindData.cFileName);
443 if (IsValidRecycleBin(szRecBinPath))
444 {
445 NewBin = (PRECYCLE_BIN)HeapAlloc(GetProcessHeap(),
446 HEAP_ZERO_MEMORY,
447 sizeof(RECYCLE_BIN));
448 if (NewBin != NULL)
449 {
450 _tcscpy(NewBin->Path,
451 szRecBinPath);
452
453 /* convert the SID to an account name */
454 ConvertSIDToAccountName(Sid,
455 NewBin->User);
456
457 /* append the recycle bin */
458 *RecycleBinsListHead = NewBin;
459 RecycleBinsListHead = &NewBin->Next;
460
461 Ret = TRUE;
462 }
463 else
464 goto ContinueFreeSid;
465 }
466 else
467 {
468 ContinueFreeSid:
469 LocalFree((HLOCAL)Sid);
470 }
471 }
472 }
473 } while (FindNextFile(FindResult,
474 &FindData));
475
476 FindClose(FindResult);
477 }
478
479 /*
480 * search for recycle bins on volumes that don't support file security (FAT)
481 */
482 _stprintf(szRecBinPath,
483 _T("%s\\Recycled"),
484 szDrive);
485 FindResult = FindFirstFile(szRecBinPath,
486 &FindData);
487 if (FindResult != INVALID_HANDLE_VALUE)
488 {
489 if (IsValidRecycleBin(szRecBinPath))
490 {
491 SID_IDENTIFIER_AUTHORITY WorldSia = {SECURITY_WORLD_SID_AUTHORITY};
492 PSID EveryoneSid;
493
494 /* create an Everyone SID */
495 if (AllocateAndInitializeSid(&WorldSia,
496 1,
497 SECURITY_WORLD_RID,
498 0,
499 0,
500 0,
501 0,
502 0,
503 0,
504 0,
505 &EveryoneSid))
506 {
507 NewBin = (PRECYCLE_BIN)HeapAlloc(GetProcessHeap(),
508 HEAP_ZERO_MEMORY,
509 sizeof(RECYCLE_BIN));
510 if (NewBin != NULL)
511 {
512 _tcscpy(NewBin->Path,
513 szRecBinPath);
514
515 /* convert the SID to an account name */
516 ConvertSIDToAccountName(EveryoneSid,
517 NewBin->User);
518
519 /* append the recycle bin */
520 *RecycleBinsListHead = NewBin;
521 RecycleBinsListHead = &NewBin->Next;
522
523 Ret = TRUE;
524 }
525 else
526 FreeSid(EveryoneSid);
527 }
528 }
529 FindClose(FindResult);
530 }
531
532 return Ret;
533 }
534
535 static VOID
536 DiskFileNameFromRecord(OUT LPTSTR szShortFileName,
537 IN DWORD RecordNumber,
538 IN WCHAR cDriveLetter,
539 IN LPWSTR szFileName)
540 {
541 LPWSTR FileExt;
542
543 FileExt = wcsrchr(szFileName,
544 L'.');
545 if (FileExt != NULL)
546 {
547 _stprintf(szShortFileName,
548 _T("D%lC%d%lS"),
549 cDriveLetter,
550 RecordNumber,
551 FileExt);
552 }
553 else
554 {
555 _stprintf(szShortFileName,
556 _T("D%lC%d"),
557 cDriveLetter,
558 RecordNumber);
559 }
560 }
561
562 static BOOL
563 DumpRecycleBin(IN PRECYCLE_BIN RecycleBin)
564 {
565 WCHAR szFile[MAX_PATH + 1];
566 HANDLE hDb;
567 LARGE_INTEGER FileSize;
568 PVOID pDbBase = NULL;
569 INFO_VERSION Version = ivUnknown;
570
571 _tprintf(_T("Dumping recycle bin of \"%lS\":\n"),
572 RecycleBin->User);
573 _tprintf(_T("Directory: %lS\n\n"),
574 RecycleBin->Path);
575
576 _stprintf(szFile,
577 _T("%s\\INFO2"),
578 RecycleBin->Path);
579 hDb = OpenAndMapInfoDatabase(szFile,
580 &pDbBase,
581 &FileSize);
582 if (hDb != INVALID_HANDLE_VALUE)
583 {
584 Version = DetectDatabaseVersion(pDbBase);
585
586 /* dump the INFO2 database */
587 switch (Version)
588 {
589 case ivINFO2:
590 {
591 DWORD nRecords;
592 PINFO2_HEADER Info2Header = (PINFO2_HEADER)pDbBase;
593 PINFO2_RECORD Info2 = (PINFO2_RECORD)(Info2Header + 1);
594 int i = 0;
595
596 nRecords = (FileSize.QuadPart - sizeof(INFO2_HEADER)) / Info2Header->RecordSize;
597
598 while (nRecords != 0)
599 {
600 /* if the first character of the AnsiFileName is zero, the record
601 is considered deleted */
602 if (Info2->AnsiFileName[0] != '\0')
603 {
604 _tprintf(_T(" [%d] Record: #%d \"%lS\"\n"),
605 ++i,
606 Info2->RecordNumber,
607 Info2->FileName);
608
609 DiskFileNameFromRecord(szFile,
610 Info2->RecordNumber,
611 (WCHAR)Info2->DriveLetter + L'a',
612 Info2->FileName);
613 _tprintf(_T(" Name on disk: \"%s\"\n"),
614 szFile);
615 _tprintf(_T(" Deleted size on disk: %d KB\n"),
616 Info2->DeletedPhysicalSize / 1024);
617 }
618 nRecords--;
619 Info2++;
620 }
621
622 break;
623 }
624
625 default:
626 break;
627 }
628
629 UnmapAndCloseDatabase(hDb,
630 pDbBase);
631 }
632
633 return FALSE;
634 }
635
636 static BOOL
637 SelectRecycleBin(IN LPWSTR szDrive)
638 {
639 BOOL Ret;
640 PRECYCLE_BIN RecycleBinsList = NULL;
641
642 Ret = LocateRecycleBins(szDrive,
643 &RecycleBinsList);
644 if (Ret)
645 {
646 if (RecycleBinsList->Next != NULL)
647 {
648 PRECYCLE_BIN CurrentBin = RecycleBinsList;
649 int n = 0, i = 0;
650
651 /* if there are multiple recycle bins ask the user which one to dump */
652 _tprintf(_T("There are several recycle bins on this drive. Select one:\n"));
653
654 while (CurrentBin != NULL)
655 {
656 _tprintf(_T(" [%d] %lS\n"),
657 ++i,
658 CurrentBin->User);
659 CurrentBin = CurrentBin->Next;
660 n++;
661 }
662
663 _tprintf(_T("Enter the number: "));
664 DisplayPrompt:
665 _tscanf(_T("%d"),
666 &i);
667 if (i > n || i < 1)
668 {
669 _tprintf(_T("Please enter a number between 1 and %d: "),
670 n);
671 goto DisplayPrompt;
672 }
673
674 /* walk to the selected recycle bin */
675 CurrentBin = RecycleBinsList;
676 while (CurrentBin != NULL && i != 1)
677 {
678 CurrentBin = CurrentBin->Next;
679 i--;
680 }
681
682 /* dump it */
683 Ret = DumpRecycleBin(CurrentBin);
684 }
685 else
686 {
687 /* dump the first (and only) recycle bin */
688 Ret = DumpRecycleBin(RecycleBinsList);
689 }
690 }
691 else
692 {
693 _ftprintf(stderr,
694 _T("No recycle bins on this volume!\n"));
695 }
696
697 FreeRecycleBinsList(&RecycleBinsList);
698
699 return Ret;
700 }
701
702 static VOID
703 PrintHelp(VOID)
704 {
705 _ftprintf(stderr,
706 _T("Usage: dumprecbin C:\n"));
707 }
708
709 int
710 main(int argc,
711 char *argv[])
712 {
713 if (argc != 2 ||
714 strlen(argv[1]) != 2 || argv[1][1] != ':' ||
715 toupper(argv[1][0]) < 'A' || toupper(argv[1][0]) > 'Z')
716 {
717 PrintHelp();
718 return 1;
719 }
720 else
721 {
722 WCHAR szDrive[3];
723 _stprintf(szDrive,
724 _T("%lC:"),
725 argv[1][0]);
726
727 if (!SelectRecycleBin(szDrive))
728 return 1;
729 else
730 return 0;
731 }
732 }
733