|
|
1.1 root 1: /****************************************************************************\
2: *
3: * MODULE: sidclean.c
4: *
5: * NT never deletes SIDs, so the name of this sample is most
6: * accurately be intrepreted as "Clean up SID ownership and
7: * ACE's that relate to SIDs that still (and always will)
8: * exist, but for which the corresponding user account has been
9: * deleted"
10: *
11: *
12: * PURPOSE: Demonstrate some of the Win32 security api(s), and provide a
13: * sample of how a utility could be written that recovers
14: * on-disk resources remaining allocated to deleted user
15: * accounts. The on-disk resources recovered are 1) Files that
16: * are still owned by accounts that have been deleted are
17: * assigned ownership to the account logged on when this sample
18: * is run, and 2) ACE's for deleted accounts are edited
19: * (deleted) out of the ACLs of files to which the deleted
20: * accounts had been granted authorizations (eg., Read access)
21: *
22: * It may be that running this sample as a utility has no
23: * practical value in many environments, as the number of files
24: * belonging to deleted user accounts will often be quite
25: * small, and the number of bytes recovered on disk by editing
26: * out ACEs for deleted accounts may well not be worth the time
27: * it takes to run this sample. The time it takes to run this
28: * sample may be quite significant when processing an entire
29: * hard disk or partition
30: *
31: * This sample is not a supported utility
32: *
33: *
34: * TO RUN: You must log on using an account, such as Administrator, that
35: * has the priviledges to take file ownership and edit ACls
36: *
37: * The ACL editing part of this sample can only be excercised for
38: * files on a partition that has ACLs NT processes: NTFS
39: *
40: * Typical test scenario: Create a user account or two, log on
41: * as each of these accounts in turn, while logged on for each
42: * account, go to an NTFS partition, create a couple of files
43: * so the test accounts each own a few files, use the file
44: * manager to edit permissions for those files so that each
45: * test user has some authorities (e.g., Read) explicitly
46: * granted for those files. Logon as Administrator, authorize
47: * each test user to a few Administrator-owned files. Delete
48: * the test accounts. Run the sample in the directories where
49: * you put the files the test accounts owned or were authorized
50: * to
51: *
52: *
53: * OVERALL APPROACH: The command line interface is kept inflexible to simplify
54: * it's parsing in this sample. The user must pass in a switch
55: * argument, a directory spec, and a file search pattern
56: *
57: * The sample positions the current directory (of the process the
58: * sample runs in) to the dir spec, and uses FindFirstFile and
59: * FindNextFile to walk through the directory specified looking
60: * for files that match the file pattern specified
61: *
62: * The switch argument can cause subdirectories to be searched
63: * recursively
64: *
65: * The switch argument lets the user choose only to take
66: * ownerships, only to edit ACLs, do both, or do neither, in
67: * which case the sample merely reports on what ownerships
68: * would have been taken, and what ACE's would have been
69: * deleted
70: *
71: * As the directories are walked, each file that matches the
72: * file pattern is processed right then
73: *
74: * Note that we process files in a directory, and we also process
75: * the directory itself that contains the files. We process
76: * directories because they can also be owned by deleted
77: * accounts, or could have ACEs that will no longer be used
78: *
79: * Note also that we process all directories that we check for
80: * files, regardless of the spelling of the directory name
81: *
82: * Counters are kept of file ownerships taken, ACEs deleted and
83: * total files checked, to print a summary line at the end of
84: * the run
85: *
86: * The sample considers it perfectly acceptable if 0 files match
87: * the file pattern for the entire run
88: *
89: *
90: * FUNCTIONS: DoMatchingFilesInOneDir
91: *
92: * Look in one dir or sub-dir for files that match the file
93: * pattern. For each match call DoOneFileOrDir
94: *
95: * DoAllDirsInOneDir
96: *
97: * For all the sub-dirs in a dir, set the current directory to be
98: * that directory, check for files that match the file pattern,
99: * and if any match, call DoMatchingFilesInOneDir to process
100: * those. Then reset the current directory
101: *
102: * GetFullFileOrDirName
103: *
104: * Get the full name of the file or dir for simplified processing
105: * (and for display on the console)
106: *
107: * DoOneFileOrDir
108: *
109: * Get the file's SD (Security Descriptor), and call
110: * TakeOwnershipIfAppropriate and/or DeleteACEsAsAppropriate as
111: * needed
112: *
113: * TakeOwnershipIfAppropriate
114: *
115: * Get the owning SID of the file from the file's SD that
116: * DoOneFileOrDir passed in, check that SID to see if the
117: * account is deleted. If so, edit into the file's SD a new
118: * owning SID (the SID of the process running the sample).
119: * Then write the modified file SD to disk
120: *
121: * DeleteACEsAsAppropriate
122: *
123: * Get the DACL of the file from the file's SD that
124: * DoOneFileOrDir passed in. Walk through the ACE list for
125: * that DACL, checking each ACE to see what SID the ACE refers
126: * to. For the SID referred to, check to see if the account is
127: * deleted. If so, delete that ACE from the DACL. When all
128: * ACE's have been examined, write the new DACL into the file's
129: * SD. Then write the modified file SD to disk
130: *
131: * GetProcessSid
132: *
133: * Retrieve into a global variable the SID of the user account
134: * logged on when this sample is run. This is the SID used by
135: * TakeOwnershipIfAppropriate
136: *
137: * CrackArgs
138: *
139: * Process the command line, cracking (parsing/decoding) the
140: * switch argument into boolean global variables (see below).
141: * Call DisplayHelp if anything illegal is found in the command
142: * line, or if the user asked for help
143: *
144: * DisplayHelp
145: *
146: * Display help text on the console
147: *
148: *
149: * GLOBAL VARS:
150: * BOOL bTakeOwnership
151: * BOOL bEditACLs
152: * BOOL bRecurse
153: * BOOL bJustCount
154: *
155: * These store the values the user specified on the command
156: * line's first argument (the switches argument).
157: * Respectively, they record whether we are to do the
158: * processing to Take Ownerships, Edit ACLs, whether we are to
159: * recurse into all subdirectories, and whether we are just
160: * counting up what would be processed (in which case we take
161: * no ownerships and edit no ACLs)
162: *
163: * DWORD dwFilesChecked
164: * DWORD dwFilesOwned
165: * DWORD dwACEsDeleted
166: *
167: * These count, respecively, the total files we checked, the
168: * number of files we took ownership of (or would have if we
169: * had not been told only to count), and the number of ACEs we
170: * deleted (or would have if we had not been told only to
171: * count). Note that the total number of files checked does
172: * not include files in the directories we process that do not
173: * match the file pattern
174: *
175: * Note, however, that we process directories regardless of
176: * whether they match the file pattern
177: *
178: * UCHAR ucProcessSIDBuf
179: * PSID psidProcessOwnerSID
180: *
181: * These store the SID of the account logged on as this sample
182: * runs, and a pointer to that SID
183: *
184: \****************************************************************************/
185:
186:
187: /****************************************************************************\
188: * INCLUDES, DEFINES, TYPEDEFS
189: \****************************************************************************/
190: #include <windows.h>
191: #include <stdio.h>
192: #include <string.h>
193:
194: #define PERR(api) printf("%s: Error %d from %s on line %d\n", \
195: __FILE__, GetLastError(), api, __LINE__);
196: #define PMSG(msg) printf("%s line %d: %s\n", \
197: __FILE__, __LINE__, msg);
198:
199: #define PrintAppStyleAPIError(ApiTxt,MsgTxt) { \
200: DWORD dwLastError; \
201: dwLastError = GetLastError(); \
202: switch (dwLastError) \
203: { case ERROR_FILE_NOT_FOUND : \
204: printf("\nFile not found (%s) line %d",MsgTxt,__LINE__); \
205: break; \
206: case ERROR_INVALID_NAME : \
207: printf("\nInvalid name (%s) line %d",MsgTxt,__LINE__); \
208: break; \
209: case ERROR_PATH_NOT_FOUND : \
210: printf("\nError path not found (%s) line %d",MsgTxt,__LINE__); \
211: break; \
212: case ERROR_SHARING_VIOLATION : \
213: printf("\nSharing violation - shut down net and/or stop other sessions (%s) line %d",MsgTxt,__LINE__); \
214: break; \
215: case ERROR_ACCESS_DENIED : \
216: printf("\nAccess denied (%s) line %d",MsgTxt,__LINE__); \
217: break; \
218: default : \
219: printf("\n" #ApiTxt " - unexpected return code=%d (%s) line %d",dwLastError,MsgTxt,__LINE__); \
220: break; \
221: } \
222: }
223:
224: /****************************************************************************\
225: * GLOBAL VARIABLES
226: \****************************************************************************/
227:
228: BOOL bTakeOwnership = FALSE;
229: BOOL bEditACLs = FALSE;
230: BOOL bRecurse = FALSE;
231: BOOL bJustCount = FALSE;
232:
233: DWORD dwFilesChecked = 0;
234: DWORD dwFilesOwned = 0;
235: DWORD dwACEsDeleted = 0;
236:
237: #define SZ_PROCESS_SID_BUF 16
238: UCHAR ucProcessSIDBuf[SZ_PROCESS_SID_BUF];
239: PSID psidProcessOwnerSID = &ucProcessSIDBuf;
240: // Why we allocate 16 bytes is explained in GetProcessSid
241:
242:
243: /****************************************************************************\
244: * FUNCTION PROTOTYPES
245: \****************************************************************************/
246:
247: BOOL DoMatchingFilesInOneDir(HANDLE hFound,
248: WIN32_FIND_DATA ffdFoundData);
249: BOOL DoAllDirsInOneDir(char *FilePattern);
250: BOOL GetFullFileOrDirName(LPTSTR lpszFileName);
251: BOOL DoOneFileOrDir(LPTSTR lpszFullName);
252: BOOL TakeOwnershipIfAppropriate(PSECURITY_DESCRIPTOR psdFileSD,
253: LPTSTR lpszFullName);
254: BOOL DeleteACEsAsAppropriate (PSECURITY_DESCRIPTOR psdFileSD,
255: LPTSTR lpszFullName);
256: BOOL GetProcessSid(VOID);
257: BOOL CrackArgs(UINT argc, char *argv[]);
258: VOID DisplayHelp(VOID);
259:
260: /****************************************************************************\
261: *
262: * FUNCTION: Main
263: *
264: \****************************************************************************/
265:
266: UINT main(UINT argc, char *argv[])
267: {
268: WIN32_FIND_DATA ffdFoundData;
269: HANDLE hFound;
270: #define SZ_NAME_BUF MAX_PATH
271: UCHAR ucPathBuf[SZ_NAME_BUF];
272: LPTSTR lpszFullName = (LPTSTR)&ucPathBuf;
273:
274: /**************************************************************************\
275: *
276: * Store the process's SID in a global variable for later use (in taking
277: * ownership).
278: *
279: \**************************************************************************/
280:
281: if (!GetProcessSid())
282: { PERR("Can't proceed without process SID - see earlier error messages");
283: return(1);
284: }
285:
286: if (!CrackArgs(argc,argv))
287: return(1);
288:
289: /**************************************************************************\
290: *
291: * CrackArgs has set our global processing switches, and proven argv[2] and
292: * argv[3] are our non-blank dir-spec and file-pattern strings. Now we
293: * must see that the file-spec is acceptable to the Win32 api's. Argv[2]
294: * is the file-spec to pass to SetCurrentDirectory, and argv[3] is the
295: * file-pattern to pass to FindFirstFile
296: *
297: * First we have to expand the dir-spec in argv[2], because if we set the
298: * current directory to it before expansion,the expansion will have a
299: * different result if argv[2] is something like ..\..
300: *
301: \**************************************************************************/
302:
303: strcpy(lpszFullName,argv[2]);
304:
305: if (!GetFullFileOrDirName(lpszFullName))
306: { PMSG("Failed to expand to full name the 2nd argument (directory specification)");
307: return(1);
308: }
309:
310: /**************************************************************************\
311: *
312: * Now we pass the un-expanded argv[2] to SetCurrentDirectory for validity
313: * checking. GetFullPathName (called by GetFullFileOrDirName) does not
314: * validity check
315: *
316: \**************************************************************************/
317:
318: if (!SetCurrentDirectory(argv[2]))
319: { PrintAppStyleAPIError(SetCurrentDirectory,"2nd argument (directory specification)");
320: return(1);
321: }
322:
323: /**************************************************************************\
324: *
325: * We begin processing with the current directory, using the expanded form we
326: * got before. We have to use the expanded form, because if we set to
327: * ..\.. and then try to process the string ..\.. as a dir name, instead of
328: * processing the dir two levels up from where we are we'll process the dir
329: * four levels up
330: *
331: \**************************************************************************/
332:
333: if (!DoOneFileOrDir(lpszFullName))
334: return(1);
335:
336: /**************************************************************************\
337: *
338: * It's OK to get no hits. The files-checked counter will show how many
339: * files we looked at, and it's OK to look at 0
340: *
341: * On the else branch, Argv[3] has been verified, and we have a good handle.
342: * We now pass to DoMatchingFilesInOneDir for processing the handle and
343: * found data we just got from FindFirstFile
344: *
345: \**************************************************************************/
346:
347: hFound = FindFirstFile(argv[3],
348: (LPWIN32_FIND_DATA)&ffdFoundData);
349: if ((HANDLE)(-1) == hFound)
350: { if (GetLastError() != ERROR_FILE_NOT_FOUND)
351: { PrintAppStyleAPIError(FindFirstFile,"3rd argument");
352: return(1);
353: }
354: }
355: else if (!DoMatchingFilesInOneDir(hFound,ffdFoundData))
356: return(1);
357:
358: /**************************************************************************\
359: *
360: * Pass the original file pattern for recursive calling to DoAllDirsInOneDir
361: *
362: \**************************************************************************/
363:
364: if (!DoAllDirsInOneDir(argv[3]))
365: return(1);
366:
367: if (bJustCount)
368: printf("\nChecked %d files, would have taken ownership of %d files, would have deleted %d ACEs\n",
369: dwFilesChecked,dwFilesOwned,dwACEsDeleted);
370: else
371: printf("\nChecked %d files, took ownership of %d files, deleted %d ACEs\n",
372: dwFilesChecked,dwFilesOwned,dwACEsDeleted);
373:
374: return(0);
375: }
376:
377: /****************************************************************************\
378: *
379: * FUNCTION: DoMatchingFilesInOneDir
380: *
381: \****************************************************************************/
382:
383: BOOL DoMatchingFilesInOneDir(HANDLE hFound,
384: WIN32_FIND_DATA ffdFoundData)
385: {
386: BOOL bDoneWithHandle = FALSE;
387:
388: /**************************************************************************\
389: *
390: * Process all files referred to by the handle, but not including
391: * directories, because directories are handled with separate calls to
392: * DoOneFileOrDir. Such separate calls are made as we are setting the
393: * current directory to be the directory to be processed
394: *
395: \**************************************************************************/
396:
397: while (!bDoneWithHandle)
398: {
399: if (!(FILE_ATTRIBUTE_DIRECTORY & ffdFoundData.dwFileAttributes))
400: {
401: if (!DoOneFileOrDir(ffdFoundData.cFileName))
402: return(FALSE);
403: }
404:
405: if (!FindNextFile(hFound,
406: (LPWIN32_FIND_DATA)&ffdFoundData))
407: if (GetLastError() == ERROR_NO_MORE_FILES)
408: bDoneWithHandle = TRUE;
409: else
410: { PrintAppStyleAPIError(FindNextFile,"on FindNext");
411: return(FALSE);
412: }
413: }
414: }
415:
416: /****************************************************************************\
417: *
418: * FUNCTION: DoAllDirsInOneDir
419: *
420: \****************************************************************************/
421:
422: BOOL DoAllDirsInOneDir(char *FilePattern)
423: {
424: HANDLE hFound;
425: WIN32_FIND_DATA ffdFoundData;
426: BOOL bDoneWithHandle = FALSE;
427:
428: /**************************************************************************\
429: *
430: * If not recursing into dirs, simply return
431: *
432: \**************************************************************************/
433:
434: if (!bRecurse)
435: return TRUE;
436:
437: /**************************************************************************\
438: *
439: * Since we are recursing, get a handle that points to entire directory, and
440: * walk the handle picking off only directories to recurse into
441: *
442: \**************************************************************************/
443:
444: hFound = FindFirstFile("*.*",
445: (LPWIN32_FIND_DATA)&ffdFoundData);
446: if ((HANDLE)(-1) == hFound)
447: { PrintAppStyleAPIError(FindFirstFile,"on dir *.* FindFirst");
448: return(FALSE);
449: }
450:
451: while (!bDoneWithHandle)
452: {
453: /************************************************************************\
454: *
455: * We only do dirs here, and we only do directories with textual names
456: * (i.e., not "." and not "..")
457: *
458: \************************************************************************/
459:
460: if ( (FILE_ATTRIBUTE_DIRECTORY & ffdFoundData.dwFileAttributes)
461: && (0 != strcmp("." ,ffdFoundData.cFileName))
462: && (0 != strcmp("..",ffdFoundData.cFileName)))
463: {
464: HANDLE hFile2;
465: WIN32_FIND_DATA ffdFound2;
466:
467: /**********************************************************************\
468: *
469: * We begin processing the new current directory by processing it itself,
470: * then setting the current dir to be the dir itself
471: *
472: \**********************************************************************/
473:
474: if (!DoOneFileOrDir(ffdFoundData.cFileName))
475: return(FALSE);
476:
477: if (!SetCurrentDirectory(ffdFoundData.cFileName))
478: { PrintAppStyleAPIError(SetCurrentDirectory,"recursive set");
479: return(FALSE);
480: }
481:
482: /**********************************************************************\
483: *
484: * It's OK to get no hits. The files-checked counter will show how many
485: * files we looked at, and it's OK to look at 0
486: *
487: \**********************************************************************/
488:
489: hFile2 = FindFirstFile(FilePattern,
490: (LPWIN32_FIND_DATA)&ffdFound2);
491: if ((HANDLE)(-1) == hFile2)
492: { if (GetLastError() != ERROR_FILE_NOT_FOUND)
493: { PrintAppStyleAPIError(FindFirstFile,"during recursion");
494: return(FALSE);
495: }
496: }
497: else if (!DoMatchingFilesInOneDir(hFile2,ffdFound2))
498: return(FALSE);
499:
500: if (!DoAllDirsInOneDir(FilePattern))
501: return(FALSE);
502:
503: if (!SetCurrentDirectory(".."))
504: { PrintAppStyleAPIError(SetCurrentDirectory,"un-recursive set");
505: return(FALSE);
506: }
507: }
508:
509: /************************************************************************\
510: *
511: * Get next recursion candidate (file or dir at this point, however at loop
512: * top files are screened out)
513: *
514: \************************************************************************/
515:
516: if (!FindNextFile(hFound,
517: (LPWIN32_FIND_DATA)&ffdFoundData))
518: if (GetLastError() == ERROR_NO_MORE_FILES)
519: bDoneWithHandle = TRUE;
520: else
521: { PrintAppStyleAPIError(FindNextFile,"on dir *.* FindNext");
522: return(FALSE);
523: }
524: }
525: return(TRUE);
526: }
527:
528: /****************************************************************************\
529: *
530: * FUNCTION: GetFullFileOrDirName
531: *
532: \****************************************************************************/
533:
534: BOOL GetFullFileOrDirName(LPTSTR lpszFileName)
535: {
536: UCHAR ucPathBuf[SZ_NAME_BUF];
537: DWORD dwSzReturned;
538: LPTSTR lpszLastNamePart;
539: LPTSTR lpszFullName;
540:
541: dwSzReturned = GetFullPathName
542: (lpszFileName,
543: (DWORD)SZ_NAME_BUF,
544: (LPTSTR)&ucPathBuf,
545: (LPTSTR *)&lpszLastNamePart);
546: if (0 == dwSzReturned)
547: switch (GetLastError())
548: { case ERROR_INVALID_NAME :
549: printf("\nError invalid file full-name (on GetFullPathName)");
550: return(FALSE);
551: default :
552: PERR("GetFullPathName - unexpected return code");
553: return(FALSE);
554: }
555:
556: if (dwSzReturned > SZ_NAME_BUF)
557: { PERR("GetFullPathName - buffer too small");
558: return(FALSE);
559: }
560:
561: lpszFullName = CharLower((LPTSTR)&ucPathBuf);
562:
563: if (!lpszFullName)
564: { PERR("CharLower failure");
565: return(FALSE);
566: }
567:
568: /**************************************************************************\
569: *
570: * Copy the expanded and upper-case-shifted name to the buffer pointed to by
571: * the input argument
572: *
573: \**************************************************************************/
574:
575: strcpy(lpszFileName,lpszFullName);
576: }
577:
578: /****************************************************************************\
579: *
580: * FUNCTION: DoOneFileOrDir
581: *
582: \****************************************************************************/
583:
584: BOOL DoOneFileOrDir(LPTSTR lpszFullName)
585: {
586: #define SZ_SD_BUF 1000
587: UCHAR ucBuf [SZ_SD_BUF];
588: DWORD dwSDLength = SZ_SD_BUF;
589: DWORD dwSDLengthNeeded;
590: PSECURITY_DESCRIPTOR psdFileSD;
591:
592: if (!GetFullFileOrDirName(lpszFullName))
593: return(FALSE);
594:
595: /**************************************************************************\
596: *
597: * Now the input argument's name is accurate: it is expanded and lower-case
598: *
599: \**************************************************************************/
600:
601: printf("\nChecking %s",lpszFullName);
602:
603: dwFilesChecked++;
604:
605: psdFileSD = (PSECURITY_DESCRIPTOR)&ucBuf;
606:
607: if (!GetFileSecurity
608: (lpszFullName,
609: (SECURITY_INFORMATION)(OWNER_SECURITY_INFORMATION
610: | DACL_SECURITY_INFORMATION),
611: psdFileSD,
612: dwSDLength,
613: (LPDWORD)&dwSDLengthNeeded))
614: { PERR("GetFileSecurity");
615: return(FALSE);
616: }
617:
618: /**************************************************************************\
619: *
620: * This validity check is here for demonstration pruposes. It's not likely a
621: * real app would need to check the validity of this returned SD. The
622: * validity check APIs are more intended to check validity after app code
623: * has manipulated the structure and is about to hand it back to the system
624: *
625: \**************************************************************************/
626:
627: if (!IsValidSecurityDescriptor(psdFileSD))
628: { PERR("IsValidSecurityDescriptor said bad SD");
629: return(FALSE);
630: }
631:
632: if (bTakeOwnership)
633: if (!TakeOwnershipIfAppropriate(psdFileSD,lpszFullName))
634: return(FALSE);
635:
636: if (bEditACLs)
637: if (!DeleteACEsAsAppropriate (psdFileSD,lpszFullName))
638: return(FALSE);
639:
640: return(TRUE);
641: }
642:
643: /****************************************************************************\
644: *
645: * FUNCTION: TakeOwnershipIfAppropriate
646: *
647: \****************************************************************************/
648:
649: BOOL TakeOwnershipIfAppropriate(PSECURITY_DESCRIPTOR psdFileSD,
650: LPTSTR lpszFullName)
651: {
652: PSID psidFileOwnerSID;
653: {
654: BOOL bOwnerDefaulted;
655:
656: if (!GetSecurityDescriptorOwner
657: (psdFileSD,
658: (PSID *)&psidFileOwnerSID,
659: (LPBOOL)&bOwnerDefaulted))
660: { PERR("GetSecurityDescriptorOwner");
661: return(FALSE);
662: }
663:
664: /************************************************************************\
665: *
666: * This validity check is here for demonstration pruposes. It's not likely
667: * a real app would need to check the validity of this returned SID. The
668: * validity check APIs are more intended to check validity after app code
669: * has manipulated the structure and is about to hand it back to the
670: * system
671: *
672: \************************************************************************/
673:
674: if (!IsValidSid(psidFileOwnerSID))
675: { PERR("IsValidSid said bad SID!");
676: return(FALSE);
677: }
678: }
679:
680: {
681: DWORD dwLastError = NO_ERROR;
682: #define SZ_ACCT_NAME_BUF 1000
683: UCHAR ucNameBuf [SZ_ACCT_NAME_BUF];
684: DWORD dwNameLength = SZ_ACCT_NAME_BUF;
685: #define SZ_DMN_NAME_BUF 1000
686: UCHAR ucDomainNmBuf [SZ_DMN_NAME_BUF ];
687: DWORD dwDNameLength = SZ_DMN_NAME_BUF ;
688: SID_NAME_USE peAcctNameUse;
689:
690: if (!LookupAccountSid
691: ((LPTSTR)"", // Look on local machine
692: psidFileOwnerSID,
693: (LPTSTR)&ucNameBuf,
694: (LPDWORD)&dwNameLength,
695: (LPTSTR)&ucDomainNmBuf,
696: (LPDWORD)&dwDNameLength,
697: (PSID_NAME_USE)&peAcctNameUse))
698: { dwLastError = GetLastError();
699: if (ERROR_NONE_MAPPED != dwLastError)
700: { PERR("LookupAccountSID");
701: return(FALSE);
702: }
703: }
704:
705: /************************************************************************\
706: *
707: * If deleted account, take ownership. This routine's caller checked the
708: * global switches that said we are in take ownership mode
709: *
710: * In some cases, the account lookup will fail with ERROR_NONE_MAPPED,
711: * meaning there is no deleted account mapped to the SID, in which case
712: * we also take ownership
713: *
714: * We check that first to avoid referencing peAcctNameUse, which in that
715: * case is not set
716: *
717: \************************************************************************/
718:
719: if ( (ERROR_NONE_MAPPED == dwLastError)
720: || (SidTypeDeletedAccount == peAcctNameUse))
721: {
722: dwFilesOwned++;
723:
724: if (bJustCount)
725: { printf(" - would have taken ownership");
726: return(TRUE);
727: }
728: else
729: {
730: /********************************************************************\
731: *
732: * Modify the SD in virtual memory. No check on the new owning SID
733: * here, because we validity checked it when we fetched it in
734: * GetProcessSid
735: *
736: \********************************************************************/
737:
738: if (!SetSecurityDescriptorOwner
739: (psdFileSD,
740: psidProcessOwnerSID,
741: FALSE)) // New owner explicitly specified
742: { PERR("SetSecurityDescriptorOwner");
743: return(FALSE);
744: }
745:
746: /********************************************************************\
747: *
748: * This validity check is something a real app might actually like to
749: * do. We manupulated the SD, so before we write it back out to the
750: * file system, a check is worth considering.
751: *
752: \********************************************************************/
753:
754: if (!IsValidSecurityDescriptor(psdFileSD))
755: { PERR("IsValidSecurityDescriptor said bad SD");
756: return(FALSE);
757: }
758:
759: /********************************************************************\
760: *
761: * Modify the SD on the hard disk
762: *
763: \********************************************************************/
764:
765: if (!SetFileSecurity
766: (lpszFullName,
767: (SECURITY_INFORMATION)OWNER_SECURITY_INFORMATION,
768: psdFileSD))
769: { PERR("SetFileSecurity");
770: return(FALSE);
771: }
772:
773: printf(" - took ownership");
774: }
775: }
776: }
777:
778: return(TRUE);
779:
780: }
781:
782: /****************************************************************************\
783: *
784: * FUNCTION: DeleteACEsAsAppropriate
785: *
786: \****************************************************************************/
787:
788: BOOL DeleteACEsAsAppropriate(PSECURITY_DESCRIPTOR psdFileSD,
789: LPTSTR lpszFullName)
790: {
791: PACL paclFile;
792: BOOL bHasACL;
793: BOOL bOwnerDefaulted;
794: DWORD dwAcl_i;
795: DWORD dwACEsDeletedBeforeNow;
796:
797: ACL_SIZE_INFORMATION asiAclSize;
798: DWORD dwBufLength = sizeof(asiAclSize);
799: ACCESS_ALLOWED_ACE *paaAllowedAce;
800:
801: if (!GetSecurityDescriptorDacl(psdFileSD,
802: (LPBOOL)&bHasACL,
803: (PACL *)&paclFile,
804: (LPBOOL)&bOwnerDefaulted))
805: { PERR("GetSecurityDescriptorDacl");
806: return(FALSE);
807: }
808:
809: if (!bHasACL) // No ACL to process, so OK, return
810: return(TRUE);
811:
812: /**************************************************************************\
813: *
814: * This validity check is here for demonstration pruposes. It's not likely a
815: * real app would need to check the validity of this returned ACL. The
816: * validity check APIs are more intended to check validity after app code
817: * has manipulated the structure and is about to hand it back to the system
818: *
819: \**************************************************************************/
820:
821: if (!IsValidAcl(paclFile))
822: { PERR("IsValidAcl said bad ACL!");
823: return(FALSE);
824: }
825:
826: if (!GetAclInformation(paclFile,
827: (LPVOID)&asiAclSize,
828: (DWORD)dwBufLength,
829: (ACL_INFORMATION_CLASS)AclSizeInformation))
830: { PERR("GetAclInformation");
831: return(FALSE);
832: }
833:
834: dwACEsDeletedBeforeNow = dwACEsDeleted;
835:
836: /**************************************************************************\
837: *
838: * We loop through in reverse order, because that's simpler, given that we
839: * potentially delete ACEs as we loop through. If started at 0 and went
840: * up, if we deleted the 0th ACE, then the 1th ACE would become the 0th,
841: * and we'd have to check the 0th ACE again
842: *
843: \**************************************************************************/
844:
845: for (dwAcl_i = asiAclSize.AceCount-1; ((int)dwAcl_i) >= 0; dwAcl_i--)
846: {
847: /************************************************************************\
848: *
849: * It doesn't matter for this sample that we don't yet know the ACE type,
850: * because they all start with the header field and that's what we need
851: *
852: \************************************************************************/
853:
854: if (!GetAce(paclFile,
855: dwAcl_i,
856: (LPVOID *)&paaAllowedAce))
857: { PERR("GetAce");
858: return(FALSE);
859: }
860:
861: /************************************************************************\
862: *
863: * There are only four Ace Types pre-defined, so this next check is
864: * redundant in a real app, but useful as a sanity check and a
865: * demonstration in a sample
866: *
867: \************************************************************************/
868:
869: if (!( (paaAllowedAce->Header.AceType == ACCESS_ALLOWED_ACE_TYPE)
870: ||(paaAllowedAce->Header.AceType == ACCESS_DENIED_ACE_TYPE )
871: ||(paaAllowedAce->Header.AceType == SYSTEM_AUDIT_ACE_TYPE )
872: ||(paaAllowedAce->Header.AceType == SYSTEM_ALARM_ACE_TYPE )))
873: { PERR("Invalid AceType");
874: return(FALSE);
875: }
876:
877: { // Find SID of ACE, check if acct deleted
878:
879: UCHAR ucNameBuf [SZ_ACCT_NAME_BUF];
880: DWORD dwNameLength = SZ_ACCT_NAME_BUF;
881: UCHAR ucDomainNmBuf [SZ_DMN_NAME_BUF];
882: DWORD dwDNameLength = SZ_DMN_NAME_BUF;
883: SID_NAME_USE peAcctNameUse;
884: DWORD dwLastError = NO_ERROR;
885:
886: /**********************************************************************\
887: *
888: * This validity check is here for demonstration pruposes. It's not
889: * likely a real app would need to check the validity of the SID
890: * contained in the returned ACL. The validity check APIs are more
891: * intended to check validity after app code has manipulated the
892: * structure and is about to hand it back to the system
893: *
894: \**********************************************************************/
895:
896: if (!IsValidSid((PSID)&(paaAllowedAce->SidStart)))
897: { PERR("IsValidSid said bad SID!");
898: return(FALSE);
899: }
900:
901: if (!LookupAccountSid
902: ((LPTSTR)"", // Look on local machine
903: (PSID)&(paaAllowedAce->SidStart),
904: (LPTSTR)&ucNameBuf,
905: (LPDWORD)&dwNameLength,
906: (LPTSTR)&ucDomainNmBuf,
907: (LPDWORD)&dwDNameLength,
908: (PSID_NAME_USE)&peAcctNameUse))
909: { dwLastError = GetLastError();
910: if (ERROR_NONE_MAPPED != dwLastError)
911: { PERR("LookupAccountSID");
912: return(FALSE);
913: }
914: }
915:
916: if ( (ERROR_NONE_MAPPED == dwLastError)
917: || (SidTypeDeletedAccount == peAcctNameUse))
918: {
919: dwACEsDeleted++;
920:
921: if (bJustCount)
922: { printf(" - would have edited ACL");
923: return(TRUE);
924: }
925:
926: if (!DeleteAce(paclFile,dwAcl_i))
927: { PERR("DeleteAce");
928: return(FALSE);
929: }
930: }
931: }
932: }
933:
934: if (dwACEsDeletedBeforeNow < dwACEsDeleted)
935: {
936: /************************************************************************\
937: *
938: * This validity check is something a real app might actually like to do.
939: * We manupulated the ACL, so before we write it back into an SD, a check
940: * is worth considering
941: *
942: \************************************************************************/
943:
944: if (!IsValidAcl(paclFile))
945: { PERR("IsValidAcl said bad ACL!");
946: return(FALSE);
947: }
948:
949: /************************************************************************\
950: *
951: * Modify the SD in virtual memory
952: *
953: \************************************************************************/
954:
955: if (!SetSecurityDescriptorDacl
956: (psdFileSD,
957: TRUE, // Yes, set the DACL
958: paclFile,
959: FALSE)) // New DACL explicitly specified
960: { PERR("SetSecurityDescriptorDacl");
961: return(FALSE);
962: }
963:
964: /************************************************************************\
965: *
966: * This validity check is something a real app might actually like to do.
967: * We manupulated the SD, so before we write it back out to the file
968: * system, a check is worth considering
969: *
970: \************************************************************************/
971:
972: if (!IsValidSecurityDescriptor(psdFileSD))
973: { PERR("IsValidSecurityDescriptor said bad SD");
974: return(FALSE);
975: }
976:
977: /************************************************************************\
978: *
979: * Modify the SD on the hard disk
980: *
981: \************************************************************************/
982:
983: if (!SetFileSecurity
984: (lpszFullName,
985: (SECURITY_INFORMATION)DACL_SECURITY_INFORMATION,
986: psdFileSD))
987: { PERR("SetFileSecurity");
988: return(FALSE);
989: }
990:
991: printf(" - edited ACL");
992: }
993:
994: return(TRUE);
995:
996: }
997:
998: /****************************************************************************\
999: *
1000: * FUNCTION: GetProcessSid
1001: *
1002: \****************************************************************************/
1003:
1004: BOOL GetProcessSid(VOID)
1005: {
1006: HANDLE hProcess;
1007: PSECURITY_DESCRIPTOR psdProcessSD;
1008: PSID psidProcessOwnerSIDTemp;
1009:
1010: UCHAR ucBuf [SZ_SD_BUF];
1011: DWORD dwSDLength = SZ_SD_BUF;
1012: DWORD dwSDLengthNeeded;
1013: BOOL bOwnerDefaulted;
1014:
1015: hProcess = GetCurrentProcess();
1016:
1017: if (!hProcess)
1018: { PERR("GetCurrentProcess");
1019: return(FALSE);
1020: }
1021:
1022: psdProcessSD = (PSECURITY_DESCRIPTOR)ucBuf;
1023:
1024: if (!GetKernelObjectSecurity
1025: (hProcess,
1026: (SECURITY_INFORMATION)(OWNER_SECURITY_INFORMATION),
1027: psdProcessSD,
1028: dwSDLength,
1029: (LPDWORD)&dwSDLengthNeeded))
1030: { PERR("GetKernelObjectSecurity on current process handle");
1031: return(FALSE);
1032: }
1033:
1034: /**************************************************************************\
1035: *
1036: * This validity check is here for demonstration purposes. It's not likely a
1037: * real app would need to check the validity of this returned SD. The
1038: * validity check APIs are more intended to check validity after app code
1039: * has manipulated the structure and is about to hand it back to the system
1040: *
1041: \**************************************************************************/
1042:
1043: if (!IsValidSecurityDescriptor(psdProcessSD))
1044: { PERR("IsValidSecurityDescriptor said bad SD");
1045: return(FALSE);
1046: }
1047:
1048: if (!GetSecurityDescriptorOwner
1049: (psdProcessSD,
1050: (PSID *)&psidProcessOwnerSIDTemp,
1051: (LPBOOL)&bOwnerDefaulted))
1052: { PERR("GetSecurityDescriptorOwner of current process");
1053: return(FALSE);
1054: }
1055:
1056: /**************************************************************************\
1057: *
1058: * This validity check is here for demonstration pruposes. It's not likely a
1059: * real app would need to check the validity of this returned SID. The
1060: * validity check APIs are more intended to check validity after app code
1061: * has manipulated the structure and is about to hand it back to the system
1062: *
1063: \**************************************************************************/
1064:
1065: if (!IsValidSid(psidProcessOwnerSIDTemp))
1066: { PERR("IsValidSid said bad process SID!");
1067: return(FALSE);
1068: }
1069:
1070: /**************************************************************************\
1071: *
1072: * On the other hand, we are about to call GetLengthSid on the returned SID,
1073: * and calling GetLengthSid with an invalid SID is a bad idea, since then
1074: * GetLengthSid's result is undefined, and an undefined result is hard to
1075: * handle cleanly
1076: *
1077: * An app that handles a lot of SIDs would probably call GetLengthSid on the
1078: * SIDs it retrieves, then VirtualAlloc or malloc storage to hold copies of
1079: * the SIDs (when holding copies is necessary). Since this sample only
1080: * needs to hold one retrieved SID for any length of time, it's simpler to
1081: * statically allocate the size needed, and only use the call to
1082: * GetLengthSid to verify that the static area is large enough. The static
1083: * area will always be large enough unless perhaps something in the SID
1084: * data structures changes in a future build of NT. So, SZ_PROCESS_SID_BUF
1085: * is defined to be 16 simply because that's the value we know GetLengthSid
1086: * returns in this case (we know it's 16 by examining the value using the
1087: * debugger during app development).
1088: *
1089: \**************************************************************************/
1090:
1091: if (SZ_PROCESS_SID_BUF < GetLengthSid(psidProcessOwnerSIDTemp))
1092: { PERR("Can't do CopySid because buffer too small");
1093: return(FALSE);
1094: }
1095:
1096: if (!CopySid((DWORD)SZ_PROCESS_SID_BUF,
1097: psidProcessOwnerSID,
1098: psidProcessOwnerSIDTemp))
1099: { PERR("CopySid");
1100: return(FALSE);
1101: }
1102:
1103: /**************************************************************************\
1104: *
1105: * This validity check is here for demonstration pruposes only (see above).
1106: *
1107: \**************************************************************************/
1108:
1109: if (!IsValidSid(psidProcessOwnerSID))
1110: { PERR("IsValidSid said bad process SID!");
1111: return(FALSE);
1112: }
1113: }
1114:
1115: /****************************************************************************\
1116: *
1117: * FUNCTION: CrackArgs
1118: *
1119: \****************************************************************************/
1120:
1121: BOOL CrackArgs(UINT argc, char *argv[])
1122: {
1123: char *p;
1124:
1125: /**************************************************************************\
1126: *
1127: * There must be three arguments
1128: *
1129: \**************************************************************************/
1130:
1131: if (argc != 4)
1132: { DisplayHelp();
1133: return(FALSE);
1134: }
1135:
1136: p=argv[1];
1137:
1138: /**************************************************************************\
1139: *
1140: * The switch argument must be 2-5 chars long
1141: *
1142: \**************************************************************************/
1143:
1144: if ((strlen(p) < 2) || (strlen(p) > 5))
1145: { DisplayHelp();
1146: return(FALSE);
1147: }
1148:
1149: /**************************************************************************\
1150: *
1151: * The first char in the switch argument must be /
1152: *
1153: \**************************************************************************/
1154:
1155: if ('/' != *p)
1156: { DisplayHelp();
1157: return(FALSE);
1158: }
1159:
1160: /**************************************************************************\
1161: *
1162: * Chars 2-5 of the switch argument must be O or A or R or C
1163: *
1164: \**************************************************************************/
1165:
1166: for (p=p+1; *p; p++)
1167: switch (*p)
1168: { case 'o':
1169: case 'O':
1170: bTakeOwnership = TRUE;
1171: break;
1172: case 'a':
1173: case 'A':
1174: bEditACLs = TRUE;
1175: break;
1176: case 'r':
1177: case 'R':
1178: bRecurse = TRUE;
1179: break;
1180: case 'c':
1181: case 'C':
1182: bJustCount = TRUE;
1183: break;
1184: default :
1185: DisplayHelp();
1186: return(FALSE);
1187: }
1188:
1189: /**************************************************************************\
1190: *
1191: * Have to say one of O or A
1192: *
1193: \**************************************************************************/
1194:
1195: if (!(bTakeOwnership || bEditACLs))
1196: { DisplayHelp();
1197: return(FALSE);
1198: }
1199:
1200: return(TRUE);
1201: }
1202:
1203: /****************************************************************************\
1204: *
1205: * FUNCTION: DisplayHelp
1206: *
1207: \****************************************************************************/
1208:
1209: VOID DisplayHelp(VOID)
1210: {
1211: printf("\nTo run type SIDCLEAN and 3 parameters. Syntax:");
1212: printf("\n SIDCLEAN /roah dirspec filepattern");
1213: printf("\n /r Recursively process subdirectories");
1214: printf("\n /o For any files matching filepattern: Take ownership if");
1215: printf("\n file currently owned by any deleted SID");
1216: printf("\n /a For any files matching filepattern: Edit ACL, deleting");
1217: printf("\n ACEs associated with any deleted SID");
1218: printf("\n /c Overrides /o and /a, causes counts of /a or /o actions that");
1219: printf("\n would take place if /c not used. Counts always displayed");
1220: printf("\n /h Override other switch values, just display this message\n");
1221: printf("\n . and .. syntax allowed in dirspec");
1222: printf("\n * and ? wildcards allowed in filepattern");
1223: printf("\n Switch letters can be in any order, upper or lower case");
1224: printf("\nExamples:");
1225: printf("\n SIDCLEAN /o . *.* Take ownership of all files (but not subdirs) in ");
1226: printf("\n current dir that are owned by any deleted SID");
1227: printf("\n SIDCLEAN /a . *.* For any file in current dir (but not subdirs), delete");
1228: printf("\n any ACL info that is associated with any deleted SID");
1229: printf("\n SIDCLEAN /ro . *.* Same as first example, but also recursively process");
1230: printf("\n subdirectories");
1231: printf("\n SIDCLEAN /ar . *.* Same as second example, but also recursively process");
1232: printf("\n subdirectories");
1233: printf("\n SIDCLEAN /O \\ *.* Same as first example, but process files in root");
1234: printf("\n of current drive");
1235: printf("\n SIDCLEAN /oC .. *.* Same as first example, but looks at files in dir");
1236: printf("\n containing current dir, processes nothing, just counts");
1237: printf("\n SIDCLEAN /A d:\\ *.* Same as second example, but process files in root");
1238: printf("\n of D: drive");
1239: printf("\n SIDCLEAN Displays this message");
1240: printf("\n SIDCLEAN /h Displays this message (so do ? -? /? -h -H /H)\n");
1241: printf("\nThis utility must be run while logged on as Administrator\n");
1242:
1243: return;
1244: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.