xref: /reactos/base/applications/cacls/cacls.c (revision c2c66aff)
1 /*
2  * ReactOS Control ACLs Program
3  * Copyright (C) 2006 Thomas Weidenmueller
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19 
20 #include "precomp.h"
21 
22 /* command line options */
23 BOOL OptionT = FALSE, OptionE = FALSE, OptionC = FALSE;
24 BOOL OptionG = FALSE, OptionR = FALSE, OptionP = FALSE, OptionD = FALSE;
25 LPCTSTR GUser, GPerm, RUser, PUser, PPerm, DUser;
26 
27 static GENERIC_MAPPING FileGenericMapping =
28 {
29     FILE_GENERIC_READ,
30     FILE_GENERIC_WRITE,
31     FILE_GENERIC_EXECUTE,
32     FILE_ALL_ACCESS
33 };
34 
35 
36 static VOID
PrintError(DWORD dwError)37 PrintError(DWORD dwError)
38 {
39     if (dwError == ERROR_SUCCESS)
40         return;
41 
42     ConMsgPuts(StdErr, FORMAT_MESSAGE_FROM_SYSTEM,
43                NULL, dwError, LANG_USER_DEFAULT);
44 }
45 
46 static BOOL
PrintFileDacl(IN LPTSTR FilePath,IN LPTSTR FileName)47 PrintFileDacl(IN LPTSTR FilePath,
48               IN LPTSTR FileName)
49 {
50     SIZE_T Length;
51     PSECURITY_DESCRIPTOR SecurityDescriptor;
52     DWORD SDSize = 0;
53     TCHAR FullFileName[MAX_PATH + 1];
54     BOOL Error = FALSE, Ret = FALSE;
55 
56     Length = _tcslen(FilePath) + _tcslen(FileName);
57     if (Length > MAX_PATH)
58     {
59         /* file name too long */
60         SetLastError(ERROR_FILE_NOT_FOUND);
61         return FALSE;
62     }
63 
64     _tcscpy(FullFileName, FilePath);
65     _tcscat(FullFileName, FileName);
66 
67     /* find out how much memory we need */
68     if (!GetFileSecurity(FullFileName,
69                          DACL_SECURITY_INFORMATION,
70                          NULL,
71                          0,
72                          &SDSize) &&
73         GetLastError() != ERROR_INSUFFICIENT_BUFFER)
74     {
75         return FALSE;
76     }
77 
78     SecurityDescriptor = (PSECURITY_DESCRIPTOR)HeapAlloc(GetProcessHeap(),
79                                                          0,
80                                                          SDSize);
81     if (SecurityDescriptor != NULL)
82     {
83         if (GetFileSecurity(FullFileName,
84                             DACL_SECURITY_INFORMATION,
85                             SecurityDescriptor,
86                             SDSize,
87                             &SDSize))
88         {
89             PACL Dacl;
90             BOOL DaclPresent;
91             BOOL DaclDefaulted;
92 
93             if (GetSecurityDescriptorDacl(SecurityDescriptor,
94                                           &DaclPresent,
95                                           &Dacl,
96                                           &DaclDefaulted))
97             {
98                 if (DaclPresent)
99                 {
100                     PACCESS_ALLOWED_ACE Ace;
101                     DWORD AceIndex = 0;
102 
103                     /* dump the ACL */
104                     while (GetAce(Dacl,
105                                   AceIndex,
106                                   (PVOID*)&Ace))
107                     {
108                         SID_NAME_USE Use;
109                         DWORD NameSize = 0;
110                         DWORD DomainSize = 0;
111                         LPTSTR Name = NULL;
112                         LPTSTR Domain = NULL;
113                         LPTSTR SidString = NULL;
114                         DWORD IndentAccess;
115                         DWORD AccessMask = Ace->Mask;
116                         PSID Sid = (PSID)&Ace->SidStart;
117 
118                         /* attempt to translate the SID into a readable string */
119                         if (!LookupAccountSid(NULL,
120                                               Sid,
121                                               Name,
122                                               &NameSize,
123                                               Domain,
124                                               &DomainSize,
125                                               &Use))
126                         {
127                             if (GetLastError() == ERROR_NONE_MAPPED || NameSize == 0)
128                             {
129                                 goto BuildSidString;
130                             }
131                             else
132                             {
133                                 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
134                                 {
135                                     Error = TRUE;
136                                     break;
137                                 }
138 
139                                 Name = (LPTSTR)HeapAlloc(GetProcessHeap(),
140                                                          0,
141                                                          (NameSize + DomainSize) * sizeof(TCHAR));
142                                 if (Name == NULL)
143                                 {
144                                     SetLastError(ERROR_NOT_ENOUGH_MEMORY);
145                                     Error = TRUE;
146                                     break;
147                                 }
148 
149                                 Domain = Name + NameSize;
150                                 Name[0] = _T('\0');
151                                 if (DomainSize != 0)
152                                     Domain[0] = _T('\0');
153                                 if (!LookupAccountSid(NULL,
154                                                       Sid,
155                                                       Name,
156                                                       &NameSize,
157                                                       Domain,
158                                                       &DomainSize,
159                                                       &Use))
160                                 {
161                                     HeapFree(GetProcessHeap(),
162                                              0,
163                                              Name);
164                                     Name = NULL;
165                                     goto BuildSidString;
166                                 }
167                             }
168                         }
169                         else
170                         {
171 BuildSidString:
172                             if (!ConvertSidToStringSid(Sid,
173                                                        &SidString))
174                             {
175                                 Error = TRUE;
176                                 break;
177                             }
178                         }
179 
180                         /* print the file name or space */
181                         ConPrintf(StdOut, L"%s ", FullFileName);
182 
183                         /* attempt to map the SID to a user name */
184                         if (AceIndex == 0)
185                         {
186                             DWORD i = 0;
187 
188                             /* overwrite the full file name with spaces so we
189                                only print the file name once */
190                             while (FullFileName[i] != _T('\0'))
191                                 FullFileName[i++] = _T(' ');
192                         }
193 
194                         /* print the domain and/or user if possible, or the SID string */
195                         if (Name != NULL && Domain[0] != _T('\0'))
196                         {
197                             ConPrintf(StdOut, L"%s\\%s:", Domain, Name);
198                             IndentAccess = (DWORD)_tcslen(Domain) + _tcslen(Name);
199                         }
200                         else
201                         {
202                             LPTSTR DisplayString = (Name != NULL ? Name : SidString);
203 
204                             ConPrintf(StdOut, L"%s:", DisplayString);
205                             IndentAccess = (DWORD)_tcslen(DisplayString);
206                         }
207 
208                         /* print the ACE Flags */
209                         if (Ace->Header.AceFlags & CONTAINER_INHERIT_ACE)
210                         {
211                             IndentAccess += ConResPuts(StdOut, IDS_ABBR_CI);
212                         }
213                         if (Ace->Header.AceFlags & OBJECT_INHERIT_ACE)
214                         {
215                             IndentAccess += ConResPuts(StdOut, IDS_ABBR_OI);
216                         }
217                         if (Ace->Header.AceFlags & INHERIT_ONLY_ACE)
218                         {
219                             IndentAccess += ConResPuts(StdOut, IDS_ABBR_IO);
220                         }
221 
222                         IndentAccess += 2;
223 
224                         /* print the access rights */
225                         MapGenericMask(&AccessMask,
226                                        &FileGenericMapping);
227                         if (Ace->Header.AceType & ACCESS_DENIED_ACE_TYPE)
228                         {
229                             if (AccessMask == FILE_ALL_ACCESS)
230                             {
231                                 ConResPuts(StdOut, IDS_ABBR_NONE);
232                             }
233                             else
234                             {
235                                 ConResPuts(StdOut, IDS_DENY);
236                                 goto PrintSpecialAccess;
237                             }
238                         }
239                         else
240                         {
241                             if (AccessMask == FILE_ALL_ACCESS)
242                             {
243                                 ConResPuts(StdOut, IDS_ABBR_FULL);
244                             }
245                             else if (!(Ace->Mask & (GENERIC_READ | GENERIC_EXECUTE)) &&
246                                      AccessMask == (FILE_GENERIC_READ | FILE_EXECUTE))
247                             {
248                                 ConResPuts(StdOut, IDS_ABBR_READ);
249                             }
250                             else if (AccessMask == (FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_EXECUTE | DELETE))
251                             {
252                                 ConResPuts(StdOut, IDS_ABBR_CHANGE);
253                             }
254                             else if (AccessMask == FILE_GENERIC_WRITE)
255                             {
256                                 ConResPuts(StdOut, IDS_ABBR_WRITE);
257                             }
258                             else
259                             {
260                                 DWORD x, x2;
261                                 static const struct
262                                 {
263                                     DWORD Access;
264                                     UINT uID;
265                                 }
266                                 AccessRights[] =
267                                 {
268                                     {FILE_WRITE_ATTRIBUTES, IDS_FILE_WRITE_ATTRIBUTES},
269                                     {FILE_READ_ATTRIBUTES, IDS_FILE_READ_ATTRIBUTES},
270                                     {FILE_DELETE_CHILD, IDS_FILE_DELETE_CHILD},
271                                     {FILE_EXECUTE, IDS_FILE_EXECUTE},
272                                     {FILE_WRITE_EA, IDS_FILE_WRITE_EA},
273                                     {FILE_READ_EA, IDS_FILE_READ_EA},
274                                     {FILE_APPEND_DATA, IDS_FILE_APPEND_DATA},
275                                     {FILE_WRITE_DATA, IDS_FILE_WRITE_DATA},
276                                     {FILE_READ_DATA, IDS_FILE_READ_DATA},
277                                     {FILE_GENERIC_EXECUTE, IDS_FILE_GENERIC_EXECUTE},
278                                     {FILE_GENERIC_WRITE, IDS_FILE_GENERIC_WRITE},
279                                     {FILE_GENERIC_READ, IDS_FILE_GENERIC_READ},
280                                     {GENERIC_ALL, IDS_GENERIC_ALL},
281                                     {GENERIC_EXECUTE, IDS_GENERIC_EXECUTE},
282                                     {GENERIC_WRITE, IDS_GENERIC_WRITE},
283                                     {GENERIC_READ, IDS_GENERIC_READ},
284                                     {MAXIMUM_ALLOWED, IDS_MAXIMUM_ALLOWED},
285                                     {ACCESS_SYSTEM_SECURITY, IDS_ACCESS_SYSTEM_SECURITY},
286                                     {SPECIFIC_RIGHTS_ALL, IDS_SPECIFIC_RIGHTS_ALL},
287                                     {STANDARD_RIGHTS_REQUIRED, IDS_STANDARD_RIGHTS_REQUIRED},
288                                     {SYNCHRONIZE, IDS_SYNCHRONIZE},
289                                     {WRITE_OWNER, IDS_WRITE_OWNER},
290                                     {WRITE_DAC, IDS_WRITE_DAC},
291                                     {READ_CONTROL, IDS_READ_CONTROL},
292                                     {DELETE, IDS_DELETE},
293                                     {STANDARD_RIGHTS_ALL, IDS_STANDARD_RIGHTS_ALL},
294                                 };
295 
296                                 ConResPuts(StdOut, IDS_ALLOW);
297 
298 PrintSpecialAccess:
299                                 ConResPuts(StdOut, IDS_SPECIAL_ACCESS);
300 
301                                 /* print the special access rights */
302                                 x = ARRAYSIZE(AccessRights);
303                                 while (x-- != 0)
304                                 {
305                                     if ((Ace->Mask & AccessRights[x].Access) == AccessRights[x].Access)
306                                     {
307                                         ConPrintf(StdOut, L"\n%s ", FullFileName);
308                                         for (x2 = 0; x2 < IndentAccess; x2++)
309                                         {
310                                             ConPuts(StdOut, L" ");
311                                         }
312 
313                                         ConResPuts(StdOut, AccessRights[x].uID);
314                                     }
315                                 }
316 
317                                 ConPuts(StdOut, L"\n");
318                             }
319                         }
320 
321                         ConPuts(StdOut, L"\n");
322 
323                         /* free up all resources */
324                         if (Name != NULL)
325                         {
326                             HeapFree(GetProcessHeap(),
327                                      0,
328                                      Name);
329                         }
330 
331                         if (SidString != NULL)
332                         {
333                             LocalFree((HLOCAL)SidString);
334                         }
335 
336                         AceIndex++;
337                     }
338 
339                     if (!Error)
340                         Ret = TRUE;
341                 }
342                 else
343                 {
344                     SetLastError(ERROR_NO_SECURITY_ON_OBJECT);
345                 }
346             }
347         }
348 
349         HeapFree(GetProcessHeap(),
350                  0,
351                  SecurityDescriptor);
352     }
353     else
354     {
355         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
356     }
357 
358     return Ret;
359 }
360 
361 /* add a backslash at end to a path string if necessary */
362 static VOID
AddBackslash(LPTSTR FilePath)363 AddBackslash(LPTSTR FilePath)
364 {
365     INT len = lstrlen(FilePath);
366     LPTSTR pch = CharPrev(FilePath, FilePath + len);
367     if (*pch != _T('\\'))
368         lstrcat(pch, _T("\\"));
369 }
370 
371 static BOOL
GetPathOfFile(LPTSTR FilePath,LPCTSTR pszFiles)372 GetPathOfFile(LPTSTR FilePath, LPCTSTR pszFiles)
373 {
374     TCHAR FullPath[MAX_PATH];
375     LPTSTR pch;
376     DWORD attrs;
377 
378     lstrcpyn(FilePath, pszFiles, MAX_PATH);
379     pch = _tcsrchr(FilePath, _T('\\'));
380     if (pch != NULL)
381     {
382         *pch = 0;
383         if (!GetFullPathName(FilePath, MAX_PATH, FullPath, NULL))
384         {
385             PrintError(GetLastError());
386             return FALSE;
387         }
388         lstrcpyn(FilePath, FullPath, MAX_PATH);
389 
390         attrs = GetFileAttributes(FilePath);
391         if (attrs == 0xFFFFFFFF || !(attrs & FILE_ATTRIBUTE_DIRECTORY))
392         {
393             PrintError(ERROR_DIRECTORY);
394             return FALSE;
395         }
396     }
397     else
398         GetCurrentDirectory(MAX_PATH, FilePath);
399 
400     AddBackslash(FilePath);
401     return TRUE;
402 }
403 
404 static BOOL
PrintDaclsOfFiles(LPCTSTR pszFiles)405 PrintDaclsOfFiles(LPCTSTR pszFiles)
406 {
407     TCHAR FilePath[MAX_PATH];
408     WIN32_FIND_DATA FindData;
409     HANDLE hFind;
410     DWORD LastError;
411 
412     /*
413      * get the file path
414      */
415     if (!GetPathOfFile(FilePath, pszFiles))
416         return FALSE;
417 
418     /*
419      * search for the files
420      */
421     hFind = FindFirstFile(pszFiles, &FindData);
422     if (hFind == INVALID_HANDLE_VALUE)
423         return FALSE;
424 
425     do
426     {
427         if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
428             continue;
429 
430         if (!PrintFileDacl(FilePath, FindData.cFileName))
431         {
432             LastError = GetLastError();
433             if (LastError == ERROR_ACCESS_DENIED)
434             {
435                 PrintError(LastError);
436                 if (!OptionC)
437                 {
438                     FindClose(hFind);
439                     return FALSE;
440                 }
441             }
442             else
443             {
444                 break;
445             }
446         }
447         else
448         {
449             ConPuts(StdOut, L"\n");
450         }
451     } while(FindNextFile(hFind, &FindData));
452     LastError = GetLastError();
453     FindClose(hFind);
454 
455     if (LastError != ERROR_NO_MORE_FILES)
456     {
457         PrintError(LastError);
458         return FALSE;
459     }
460 
461     return TRUE;
462 }
463 
464 static BOOL
GrantUserAccessRights(LPCTSTR FilePath,LPCTSTR File,LPCTSTR User,TCHAR Perm)465 GrantUserAccessRights(LPCTSTR FilePath, LPCTSTR File, LPCTSTR User, TCHAR Perm)
466 {
467     /* TODO & FIXME */
468     switch(Perm)
469     {
470         case _T('R'): // Read
471             break;
472         case _T('W'): // Write
473             break;
474         case _T('C'): // Change (write)
475             break;
476         case _T('F'): // Full control
477             break;
478         default:
479             break;
480     }
481     return FALSE;
482 }
483 
484 static BOOL
ReplaceUserAccessRights(LPCTSTR FilePath,LPCTSTR File,LPCTSTR User,TCHAR Perm)485 ReplaceUserAccessRights(
486     LPCTSTR FilePath,
487     LPCTSTR File,
488     LPCTSTR User,
489     TCHAR Perm)
490 {
491     /* TODO & FIXME */
492     switch(Perm)
493     {
494         case _T('N'): // None
495             break;
496         case _T('R'): // Read
497             break;
498         case _T('W'): // Write
499             break;
500         case _T('C'): // Change (write)
501             break;
502         case _T('F'): // Full control
503             break;
504         default:
505             break;
506     }
507     return FALSE;
508 }
509 
510 static BOOL
EditUserAccessRights(LPCTSTR FilePath,LPCTSTR File,LPCTSTR User,TCHAR Perm)511 EditUserAccessRights(
512     LPCTSTR FilePath,
513     LPCTSTR File,
514     LPCTSTR User,
515     TCHAR Perm)
516 {
517     /* TODO & FIXME */
518     switch(Perm)
519     {
520         case _T('N'): // None
521             break;
522         case _T('R'): // Read
523             break;
524         case _T('W'): // Write
525             break;
526         case _T('C'): // Change (write)
527             break;
528         case _T('F'): // Full control
529             break;
530         default:
531             break;
532     }
533     return FALSE;
534 }
535 
536 static BOOL
DenyUserAccess(LPCTSTR FilePath,LPCTSTR File,LPCTSTR User)537 DenyUserAccess(LPCTSTR FilePath, LPCTSTR File, LPCTSTR User)
538 {
539     /* TODO & FIXME */
540     return FALSE;
541 }
542 
543 static BOOL
RevokeUserAccessRights(LPCTSTR FilePath,LPCTSTR File,LPCTSTR User)544 RevokeUserAccessRights(LPCTSTR FilePath, LPCTSTR File, LPCTSTR User)
545 {
546     /* TODO & FIXME */
547     return FALSE;
548 }
549 
550 static BOOL
ChangeFileACL(LPCTSTR FilePath,LPCTSTR File)551 ChangeFileACL(LPCTSTR FilePath, LPCTSTR File)
552 {
553     if (OptionG)
554     {
555         /* Grant specified user access rights. */
556         GrantUserAccessRights(FilePath, File, GUser, *GPerm);
557     }
558 
559     if (OptionP)
560     {
561         if (!OptionE)
562         {
563             /* Replace specified user's access rights. */
564             ReplaceUserAccessRights(FilePath, File, PUser, *PPerm);
565         }
566         else
567         {
568             /* Edit ACL instead of replacing it. */
569             EditUserAccessRights(FilePath, File, PUser, *PPerm);
570         }
571     }
572 
573     if (OptionD)
574     {
575         /* Deny specified user access. */
576         DenyUserAccess(FilePath, File, DUser);
577     }
578 
579     if (OptionR)
580     {
581         /* Revoke specified user's access rights. */
582         RevokeUserAccessRights(FilePath, File, RUser);
583     }
584 
585     return TRUE;
586 }
587 
588 static BOOL
ChangeACLsOfFiles(LPCTSTR pszFiles)589 ChangeACLsOfFiles(LPCTSTR pszFiles)
590 {
591     TCHAR FilePath[MAX_PATH];
592     HANDLE hFind;
593     WIN32_FIND_DATA FindData;
594     DWORD LastError;
595 
596     /*
597      * get the file path
598      */
599     if (!GetPathOfFile(FilePath, pszFiles))
600         return FALSE;
601 
602     /*
603      * search for files in current directory
604      */
605     hFind = FindFirstFile(pszFiles, &FindData);
606     if (hFind == INVALID_HANDLE_VALUE)
607         return FALSE;
608 
609     do
610     {
611         if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
612             continue;
613 
614         if (!ChangeFileACL(FilePath, FindData.cFileName))
615         {
616             LastError = GetLastError();
617             if (LastError == ERROR_ACCESS_DENIED)
618             {
619                 PrintError(LastError);
620                 if (!OptionC)
621                 {
622                     FindClose(hFind);
623                     return FALSE;
624                 }
625             }
626             else
627                 break;
628         }
629     } while(FindNextFile(hFind, &FindData));
630 
631     LastError = GetLastError();
632     FindClose(hFind);
633 
634     if (LastError != ERROR_NO_MORE_FILES)
635     {
636         PrintError(LastError);
637         return FALSE;
638     }
639 
640     return TRUE;
641 }
642 
643 static BOOL
ChangeACLsOfFilesInCurDir(LPCTSTR pszFiles)644 ChangeACLsOfFilesInCurDir(LPCTSTR pszFiles)
645 {
646     HANDLE hFind;
647     WIN32_FIND_DATA FindData;
648     TCHAR szCurDir[MAX_PATH];
649     DWORD LastError;
650 
651     /*
652      * get the file path (current directory)
653      */
654     GetCurrentDirectory(MAX_PATH, szCurDir);
655     AddBackslash(szCurDir);
656 
657     /*
658      * search for files in current directory
659      */
660     hFind = FindFirstFile(pszFiles, &FindData);
661     if (hFind == INVALID_HANDLE_VALUE)
662         return FALSE;
663 
664     do
665     {
666         if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
667             continue;
668 
669         if (!ChangeFileACL(szCurDir, FindData.cFileName))
670         {
671             LastError = GetLastError();
672             if (LastError == ERROR_ACCESS_DENIED)
673             {
674                 PrintError(LastError);
675                 if (!OptionC)
676                 {
677                     FindClose(hFind);
678                     return FALSE;
679                 }
680             }
681             else
682                 break;
683         }
684     } while(FindNextFile(hFind, &FindData));
685 
686     LastError = GetLastError();
687     FindClose(hFind);
688 
689     if (LastError != ERROR_NO_MORE_FILES)
690     {
691         PrintError(LastError);
692         return FALSE;
693     }
694 
695     /*
696      * search for subdirectory in current directory
697      */
698     hFind = FindFirstFile(_T("*"), &FindData);
699     if (hFind == INVALID_HANDLE_VALUE)
700         return FALSE;
701     do
702     {
703         if (_tcscmp(FindData.cFileName, _T(".")) == 0 ||
704             _tcscmp(FindData.cFileName, _T("..")) == 0)
705             continue;
706 
707         if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
708         {
709             GetCurrentDirectory(MAX_PATH, szCurDir);
710             if (SetCurrentDirectory(FindData.cFileName))
711             {
712                 ChangeACLsOfFilesInCurDir(pszFiles);
713                 SetCurrentDirectory(szCurDir);
714             }
715             else
716             {
717                 LastError = GetLastError();
718                 if (LastError == ERROR_ACCESS_DENIED)
719                 {
720                     PrintError(LastError);
721                     if (!OptionC)
722                     {
723                         FindClose(hFind);
724                         return FALSE;
725                     }
726                 }
727                 else
728                     break;
729             }
730         }
731     } while(FindNextFile(hFind, &FindData));
732     LastError = GetLastError();
733     FindClose(hFind);
734 
735     if (LastError != ERROR_NO_MORE_FILES)
736     {
737         PrintError(LastError);
738         return FALSE;
739     }
740     return TRUE;
741 }
742 
_tmain(int argc,const TCHAR * argv[])743 int _tmain(int argc, const TCHAR *argv[])
744 {
745     INT i;
746     LPTSTR pch;
747     BOOL InvalidParameter = FALSE;
748 
749     /* Initialize the Console Standard Streams */
750     ConInitStdStreams();
751 
752     if (argc <= 1)
753     {
754         ConResPuts(StdOut, IDS_HELP);
755         return 0;
756     }
757 
758     // FIXME: Convert to proper parsing, with support for /?
759 
760     /*
761      * parse command line options
762      */
763     for (i = 2; i < argc; i++)
764     {
765         if (lstrcmpi(argv[i], _T("/T")) == 0)
766         {
767             OptionT = TRUE;
768         }
769         else if (lstrcmpi(argv[i], _T("/E")) == 0)
770         {
771             OptionE = TRUE;
772         }
773         else if (lstrcmpi(argv[i], _T("/C")) == 0)
774         {
775             OptionC = TRUE;
776         }
777         else if (lstrcmpi(argv[i], _T("/G")) == 0)
778         {
779             if (i + 1 < argc)
780             {
781                 pch = _tcschr(argv[++i], _T(':'));
782                 if (pch != NULL)
783                 {
784                     OptionG = TRUE;
785                     *pch = 0;
786                     GUser = argv[i];
787                     GPerm = pch + 1;
788                     continue;
789                 }
790             }
791             InvalidParameter = TRUE;
792             break;
793         }
794         else if (lstrcmpi(argv[i], _T("/R")) == 0)
795         {
796             if (i + 1 < argc)
797             {
798                 RUser = argv[++i];
799                 OptionR = TRUE;
800                 continue;
801             }
802             InvalidParameter = TRUE;
803             break;
804         }
805         else if (lstrcmpi(argv[i], _T("/P")) == 0)
806         {
807             if (i + 1 < argc)
808             {
809                 pch = _tcschr(argv[++i], _T(':'));
810                 if (pch != NULL)
811                 {
812                     OptionP = TRUE;
813                     *pch = 0;
814                     PUser = argv[i];
815                     PPerm = pch + 1;
816                     continue;
817                 }
818             }
819             InvalidParameter = TRUE;
820             break;
821         }
822         else if (lstrcmpi(argv[i], _T("/D")) == 0)
823         {
824             if (i + 1 < argc)
825             {
826                 OptionD = TRUE;
827                 DUser = argv[++i];
828                 continue;
829             }
830             InvalidParameter = TRUE;
831             break;
832         }
833         else
834         {
835             InvalidParameter = TRUE;
836             break;
837         }
838     }
839 
840     if (InvalidParameter)
841     {
842         PrintError(ERROR_INVALID_PARAMETER);
843         ConResPuts(StdOut, IDS_HELP);
844         return 1;
845     }
846 
847     /* /R is only valid with /E */
848     if (OptionR && !OptionE)
849     {
850         OptionR = FALSE;
851     }
852 
853     PrintDaclsOfFiles(argv[1]);
854 
855     if (OptionT)
856     {
857         ChangeACLsOfFilesInCurDir(argv[1]);
858     }
859     else
860     {
861         ChangeACLsOfFiles(argv[1]);
862     }
863 
864     return 0;
865 }
866