1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS EventCreate Command
4  * FILE:            base/applications/cmdutils/eventcreate/eventcreate.c
5  * PURPOSE:         Allows reporting custom user events in event logs,
6  *                  by using the old-school NT <= 2k3 logging API.
7  * PROGRAMMER:      Hermes Belusca-Maito
8  *
9  * RATIONALE AND NOTE ABOUT THE IMPLEMENTATION:
10  *
11  *    Contrary to what can be expected, there is no simple way of logging inside
12  *    a NT event log an arbitrary string, for example a description text, that
13  *    can then be viewed in a human-readable form under an event viewer. Indeed,
14  *    a NT log entry is not just a simple arbitrary string, but is instead made
15  *    of an identifier (ID), an event "source" and an arbitrary data chunk.
16  *    To make things somewhat simpler, the data chunk is divided in two parts:
17  *    an array of data strings and a raw (binary) data chunk.
18  *
19  *    How then can a log entry be reconstructed? At each NT log is associated
20  *    one or many event "sources", which are binary files (PE format) containing
21  *    a table of predefined string templates (message table resource), indexed
22  *    by identifiers. The ID and event source specified in a given log entry
23  *    inside a given log allows to refer to one of the string template inside
24  *    the specified event source of the log. A human-readable event description
25  *    that is shown by an event viewer is obtained by associating the string
26  *    template together with the array of data strings of the log entry.
27  *    Each of the data strings is a parameter for the string template (formatted
28  *    in a printf-like format).
29  *
30  *    Thus we see that the human-readable event description of a log entry is
31  *    not completely arbitrary but is dictated by both the string templates and
32  *    the data strings of the log entry. Only the data strings can be arbitrary.
33  *
34  *    Therefore, what can we do to be able to report arbitrary human-readable
35  *    events, the description of which the user specifies at the command-line?
36  *    There is actually only one possible way: store the description text as
37  *    a string inside the array of data strings of the event. But we need the
38  *    event to be displayed correctly. For that it needs to be associated with
39  *    an event source, and its ID must point to a suitable string template, the
40  *    association of which with the user-specified arbitrary data string should
41  *    directly display this arbitrary string. The suitable string template is
42  *    therefore the identity template: "%1" (in the format for message strings).
43  *    The last problem, that may constitute a limitation of this technique, is
44  *    that this string template is tied to a given event ID. What if the user
45  *    wants to use a different event ID? The solution is the event source to
46  *    contain as many same identity templates as different IDs the user can use.
47  *    This is quite a redundant and limiting technique!
48  *
49  *    On MS Windows, the EventCreate.exe command contains the identity template
50  *    for all IDs from 1 to 1001 included, yet it is only possible to specify
51  *    an event ID from 1 to 1000 included.
52  *    The Powershell command "Write-EventLog" allows using IDs from 0 to 65535
53  *    included, thus covering all of the 2-byte unsigned integer space; its
54  *    corresponding event source file "EventLogMessages.dll"
55  *    (inside "%SystemRoot%\Microsoft.NET\Framework\vX.Y.ZZZZZ") therefore
56  *    contains the identity template for all IDs from 0 to 65535, making it a
57  *    large file.
58  *
59  *    For ReactOS I want to have a compromise between disk space and usage
60  *    flexibility, therefore I choose to include as well the identity template
61  *    for all IDs from 0 to 65535 included, as done by Powershell. If somebody
62  *    wants to change these limits, one has to perform the following steps:
63  *
64  *    0- Update the "/ID EventID" description in the help string "IDS_HELP"
65  *       inside the lang/xx-YY.rc resource files;
66  *
67  *    1- Change in this file the two #defines EVENT_ID_MIN and EVENT_ID_MAX
68  *       to other values of one's choice (called 'ID_min' and 'ID_max');
69  *
70  *    2- Regenerate and replace the event message string templates file using
71  *       the event message string templates file generator (evtmsggen tool):
72  *         $ evtmsggen ID_min ID_max evtmsgstr.mc
73  *
74  *    3- Recompile the EventCreate command.
75  *
76  */
77 
78 #include <stdio.h>
79 #include <stdlib.h> // EXIT_SUCCESS, EXIT_FAILURE
80 
81 #include <windef.h>
82 #include <winbase.h>
83 #include <winreg.h>
84 
85 #include <conutils.h>
86 
87 #include <strsafe.h>
88 
89 #include "resource.h"
90 
91 /*
92  * The minimal and maximal values of the allowed ID range.
93  * See the "NOTE ABOUT THE IMPLEMENTATION" above.
94  *
95  * Here are some examples of values:
96  * Windows' EventCreate.exe command   : ID_min = 1 and ID_max = 1000
97  * Powershell "Write-EventLog" command: ID_min = 0 and ID_max = 65535
98  *
99  * ReactOS' EventCreate.exe command uses the same limits as Powershell.
100  */
101 #define EVENT_ID_MIN    0
102 #define EVENT_ID_MAX    65535
103 
104 /*
105  * The EventCreate command internal name (used for both setting the default
106  * event source name and specifying the default event source file path).
107  */
108 #define APPLICATION_NAME    L"EventCreate"
109 
110 
PrintError(DWORD dwError)111 VOID PrintError(DWORD dwError)
112 {
113     if (dwError == ERROR_SUCCESS)
114         return;
115 
116     ConMsgPuts(StdErr, FORMAT_MESSAGE_FROM_SYSTEM,
117                NULL, dwError, LANG_USER_DEFAULT);
118     ConPuts(StdErr, L"\n");
119 }
120 
121 
122 static BOOL
GetUserToken(OUT PTOKEN_USER * ppUserToken)123 GetUserToken(
124     OUT PTOKEN_USER* ppUserToken)
125 {
126     BOOL Success = FALSE;
127     DWORD dwError;
128     HANDLE hToken;
129     DWORD cbTokenBuffer = 0;
130     PTOKEN_USER pUserToken = NULL;
131 
132     *ppUserToken = NULL;
133 
134     /* Get the process token */
135     if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
136         return FALSE;
137 
138     /* Retrieve token's information */
139     if (!GetTokenInformation(hToken, TokenUser, NULL, 0, &cbTokenBuffer))
140     {
141         dwError = GetLastError();
142         if (dwError != ERROR_INSUFFICIENT_BUFFER)
143             goto Quit;
144     }
145 
146     pUserToken = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbTokenBuffer);
147     if (!pUserToken)
148     {
149         dwError = ERROR_NOT_ENOUGH_MEMORY;
150         goto Quit;
151     }
152 
153     if (!GetTokenInformation(hToken, TokenUser, pUserToken, cbTokenBuffer, &cbTokenBuffer))
154     {
155         dwError = GetLastError();
156         goto Quit;
157     }
158 
159     Success = TRUE;
160     dwError = ERROR_SUCCESS;
161     *ppUserToken = pUserToken;
162 
163 Quit:
164     if (Success == FALSE)
165     {
166         if (pUserToken)
167             HeapFree(GetProcessHeap(), 0, pUserToken);
168     }
169 
170     CloseHandle(hToken);
171 
172     SetLastError(dwError);
173 
174     return Success;
175 }
176 
177 static LONG
InstallEventSource(IN HKEY hEventLogKey,IN LPCWSTR EventLogSource)178 InstallEventSource(
179     IN HKEY    hEventLogKey,
180     IN LPCWSTR EventLogSource)
181 {
182     LONG lRet;
183     HKEY hSourceKey = NULL;
184     DWORD dwDisposition = 0;
185     DWORD dwData;
186 
187     LPCWSTR EventMessageFile;
188     DWORD PathSize;
189     WCHAR ExePath[MAX_PATH];
190 
191     lRet = RegCreateKeyExW(hEventLogKey,
192                            EventLogSource,
193                            0, NULL, REG_OPTION_NON_VOLATILE,
194                            KEY_SET_VALUE, NULL,
195                            &hSourceKey, &dwDisposition);
196     if (lRet != ERROR_SUCCESS)
197         goto Quit;
198     if (dwDisposition != REG_CREATED_NEW_KEY)
199     {
200         /* The source key already exists, just quit */
201         goto Quit;
202     }
203 
204     /* We just have created the new source. Add the values. */
205 
206     /*
207      * Retrieve the full path of the current running executable.
208      * We need it to install our custom event source.
209      * - In case of success, try to replace the ReactOS installation path
210      *   (if present in the executable path) by %SystemRoot%.
211      * - In case of failure, use a default path.
212      */
213     PathSize = GetModuleFileNameW(NULL, ExePath, ARRAYSIZE(ExePath));
214     if ((PathSize == 0) || (GetLastError() == ERROR_INSUFFICIENT_BUFFER))
215     {
216         /* We failed, copy the default value */
217         StringCchCopyW(ExePath, ARRAYSIZE(ExePath),
218                        L"%SystemRoot%\\System32\\" APPLICATION_NAME L".exe");
219     }
220     else
221     {
222         /* Alternatively one can use SharedUserData->NtSystemRoot */
223         WCHAR TmpDir[ARRAYSIZE(ExePath)];
224         PathSize = GetSystemWindowsDirectoryW(TmpDir, ARRAYSIZE(TmpDir));
225         if ((PathSize > 0) && (_wcsnicmp(ExePath, TmpDir, PathSize) == 0))
226         {
227             StringCchCopyW(TmpDir, ARRAYSIZE(TmpDir), L"%SystemRoot%");
228             StringCchCatW(TmpDir, ARRAYSIZE(TmpDir), ExePath + PathSize);
229             StringCchCopyW(ExePath, ARRAYSIZE(ExePath), TmpDir);
230         }
231     }
232     EventMessageFile = ExePath;
233 
234     lRet = ERROR_SUCCESS;
235 
236     dwData = 1;
237     RegSetValueExW(hSourceKey, L"CustomSource", 0, REG_DWORD,
238                    (LPBYTE)&dwData, sizeof(dwData));
239 
240     // FIXME: Set those flags according to caller's rights?
241     // Or, if we are using the Security log?
242     dwData = EVENTLOG_SUCCESS | EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE
243             /* | EVENTLOG_AUDIT_SUCCESS | EVENTLOG_AUDIT_FAILURE */ ;
244     RegSetValueExW(hSourceKey, L"TypesSupported", 0, REG_DWORD,
245                    (LPBYTE)&dwData, sizeof(dwData));
246 
247     RegSetValueExW(hSourceKey, L"EventMessageFile", 0, REG_EXPAND_SZ,
248                    (LPBYTE)EventMessageFile, (wcslen(EventMessageFile) + 1) * sizeof(WCHAR));
249 
250     RegFlushKey(hSourceKey);
251 
252 Quit:
253     if (hSourceKey)
254         RegCloseKey(hSourceKey);
255 
256     return lRet;
257 }
258 
259 static BOOL
CheckLogOrSourceExistence(IN LPCWSTR UNCServerName OPTIONAL,IN LPCWSTR EventLogName,IN LPCWSTR EventLogSource,IN BOOL AllowAppSources OPTIONAL)260 CheckLogOrSourceExistence(
261     IN LPCWSTR UNCServerName OPTIONAL,
262     IN LPCWSTR EventLogName,
263     IN LPCWSTR EventLogSource,
264     IN BOOL    AllowAppSources OPTIONAL)
265 {
266     /*
267      * The 'AllowAppSources' parameter allows the usage of
268      * application (non-custom) sources, when set to TRUE.
269      * Its default value is FALSE.
270      */
271 
272 #define MAX_KEY_LENGTH 255 // or 256 ??
273 
274     BOOL Success = FALSE;
275     LONG lRet;
276     HKEY hEventLogKey = NULL, hLogKey = NULL;
277     DWORD NameLen;
278     DWORD dwIndex;
279 
280     BOOL LogNameValid, LogSourceValid;
281     BOOL FoundLog = FALSE, FoundSource = FALSE;
282     BOOL SourceAlreadyExists = FALSE, SourceCreated = FALSE, IsCustomSource = FALSE;
283 
284     WCHAR LogName[MAX_KEY_LENGTH];    // Current event log being tested for.
285     WCHAR LogNameErr[MAX_KEY_LENGTH]; // Event log in which the source already exists.
286 
287     UNREFERENCED_PARAMETER(UNCServerName); // FIXME: Use remote server if needed!
288 
289     LogNameValid   = (EventLogName && *EventLogName);
290     LogSourceValid = (EventLogSource && *EventLogSource);
291 
292     /*
293      * If neither the log name nor the log source are specified,
294      * there is no need to continue. Just fail.
295      */
296     if (!LogNameValid && !LogSourceValid)
297         return FALSE;
298 
299     lRet = RegOpenKeyExW(HKEY_LOCAL_MACHINE, // FIXME: Use remote server if needed!
300                          L"SYSTEM\\CurrentControlSet\\Services\\EventLog",
301                          0, KEY_ENUMERATE_SUB_KEYS,
302                          &hEventLogKey);
303     if (lRet != ERROR_SUCCESS)
304         goto Quit;
305 
306     /*
307      * If we just have a valid log name but no specified source, check whether
308      * the log key exist by atttempting to open it. If we fail: no log.
309      * In all cases we do not perform other tests nor create any source.
310      */
311     if (LogNameValid && !LogSourceValid)
312     {
313         lRet = RegOpenKeyExW(hEventLogKey,
314                              EventLogName,
315                              0, KEY_QUERY_VALUE,
316                              &hLogKey);
317         RegCloseKey(hLogKey);
318         FoundLog = (lRet == ERROR_SUCCESS);
319 
320         if (FoundLog)
321         {
322             /* Set the flags to consistent values */
323             SourceCreated  = TRUE;
324             IsCustomSource = TRUE;
325         }
326         goto Finalize;
327     }
328 
329     /* Here, LogSourceValid is always TRUE */
330 
331     /*
332      * We now have a valid source and either an event log name or none.
333      * Search for the source existence over all the existing logs:
334      * we loop through the event logs and we will:
335      * - localize whether the specified source exists and in which log it does;
336      * - and at the same time, check whether the specified log does exist.
337      */
338     dwIndex = 0;
339     while (TRUE)
340     {
341         NameLen = ARRAYSIZE(LogName);
342         LogName[0] = L'\0';
343 
344         lRet = RegEnumKeyExW(hEventLogKey, dwIndex, LogName, &NameLen,
345                              NULL, NULL, NULL, NULL);
346         if (dwIndex > 0)
347         {
348             if (lRet == ERROR_NO_MORE_ITEMS)
349             {
350                 /*
351                  * We may/may not have found our log and may/may not have found
352                  * our source. Quit the loop, we will check those details after.
353                  */
354                 break; // goto Finalize;
355             }
356         }
357         if (lRet != ERROR_SUCCESS)
358         {
359             /* A registry error happened, just fail */
360             goto Quit;
361         }
362 
363         /* We will then continue with the next log */
364         ++dwIndex;
365 
366         /* If we have specified a log, check whether we have found it */
367         if (LogNameValid && _wcsicmp(LogName, EventLogName) == 0)
368         {
369             /*
370              * We have found the specified log, but do not break yet: if we have
371              * a specified source, we want to be sure it does not exist elsewhere.
372              */
373             FoundLog = TRUE;
374         }
375 
376         /*
377          * The following case: if (LogNameValid && !LogSourceValid) {...}
378          * was already dealt with before. Here, LogSourceValid is always TRUE.
379          */
380 
381         /* Now determine whether we need to continue */
382         if (/* LogNameValid && */ FoundLog)
383         {
384 #if 0
385             if (!LogSourceValid)
386             {
387                 /*
388                  * We have found our log and we do not use any source,
389                  * we can stop scanning now.
390                  */
391                 /* Set the flags to consistent values */
392                 SourceCreated  = TRUE;
393                 IsCustomSource = TRUE;
394                 break; // goto Finalize;
395             }
396 #endif
397             if (SourceAlreadyExists)
398             {
399                 /*
400                  * We have finally found our log but the source existed elsewhere,
401                  * stop scanning and we will error that the source is not in the
402                  * expected log. On the contrary, if our log was not found yet,
403                  * continue scanning to attempt to find it and, if the log is not
404                  * found at the end we will error that the log does not exist.
405                  */
406                 break; // goto Finalize;
407             }
408         }
409 
410         /*
411          * If we have specified a source and have not found it so far,
412          * check for its presence in this log.
413          * NOTE: Here, LogSourceValid is always TRUE.
414          */
415         if (LogSourceValid && !FoundSource)
416         {
417             HKEY hKeySource = NULL;
418 
419             /* Check the sources inside this log */
420             lRet = RegOpenKeyExW(hEventLogKey, LogName, 0, KEY_READ, &hLogKey);
421             if (lRet != ERROR_SUCCESS)
422             {
423                 /* A registry error happened, just fail */
424                 goto Quit;
425             }
426 
427             /*
428              * Attempt to open the source key.
429              *
430              * NOTE: Alternatively we could have scanned each source key
431              * in this log by using RegEnumKeyExW.
432              */
433             lRet = RegOpenKeyExW(hLogKey, EventLogSource,
434                                  0, KEY_QUERY_VALUE,
435                                  &hKeySource);
436 
437             /* Get rid of the log key handle */
438             RegCloseKey(hLogKey);
439             hLogKey = NULL;
440 
441             if (lRet == ERROR_SUCCESS) // || lRet == ERROR_ACCESS_DENIED
442             {
443                 /*
444                  * We have found our source in this log (it can be
445                  * in a different log than the one specified).
446                  */
447                 FoundSource = TRUE;
448                 // lRet = ERROR_SUCCESS;
449             }
450             else if (lRet == ERROR_FILE_NOT_FOUND)
451             {
452                 /* Our source was not found there */
453                 lRet = ERROR_SUCCESS;
454                 hKeySource = NULL;
455             }
456             else // if (lRet != ERROR_SUCCESS && lRet != ERROR_FILE_NOT_FOUND)
457             {
458                 /* A registry error happened, but we continue scanning the other logs... */
459                 hKeySource = NULL;
460             }
461 
462             /* If we have not found our source, continue scanning the other logs */
463             if (!FoundSource)
464                 continue;
465 
466             /*
467              * We have found our source, but is it in the correct log?
468              *
469              * NOTE: We check only in the case we have specified a log,
470              * otherwise we just care about the existence of the source
471              * and we do not check for its presence in the other logs.
472              */
473             if (LogNameValid && !(FoundLog && _wcsicmp(LogName, EventLogName) == 0))
474             {
475                 /* Now get rid of the source key handle */
476                 RegCloseKey(hKeySource);
477                 hKeySource = NULL;
478 
479                 /* The source is in another log than the specified one */
480                 SourceAlreadyExists = TRUE;
481 
482                 /* Save the log name in which the source already exists */
483                 RtlCopyMemory(LogNameErr, LogName, sizeof(LogName));
484 
485                 /*
486                  * We continue because we want to also know whether we can
487                  * still find our specified log (and we will error that the
488                  * source exists elsewhere), or whether the log does not exist
489                  * (and we will error accordingly).
490                  */
491                 continue;
492             }
493 
494             /*
495              * We have found our source, and if we have specified a log,
496              * the source is in the correct log.
497              */
498             SourceCreated = TRUE;
499 
500             /*
501              * Check whether this is one of our custom sources
502              * (application sources do not have this value present).
503              */
504             IsCustomSource = FALSE;
505 
506             lRet = RegQueryValueExW(hKeySource, L"CustomSource", NULL, NULL, NULL, NULL);
507 
508             /* Now get rid of the source key handle */
509             RegCloseKey(hKeySource);
510             hKeySource = NULL;
511 
512             if (lRet == ERROR_SUCCESS)
513             {
514                 IsCustomSource = TRUE;
515             }
516             else if (lRet == ERROR_FILE_NOT_FOUND)
517             {
518                 // IsCustomSource = FALSE;
519             }
520             else // if (lRet != ERROR_SUCCESS && lRet != ERROR_FILE_NOT_FOUND)
521             {
522                 /* A registry error happened, just fail */
523                 goto Quit;
524             }
525 
526             /*
527              * We have found our source and it may be (or not) a custom source,
528              * and it is in the correct event log (if we have specified one).
529              * Break the search loop.
530              */
531             break; // goto Finalize;
532         }
533     }
534 
535     /*
536      * No errors happened so far.
537      * Perform last validity checks (the flags are all valid and 'LogName'
538      * contains the name of the last log having been tested for).
539      */
540 Finalize:
541     lRet = ERROR_SUCCESS; // but do not set Success to TRUE yet.
542 
543     // FIXME: Shut up a GCC warning/error about 'SourceCreated' being unused.
544     // We will use it later on.
545     UNREFERENCED_PARAMETER(SourceCreated);
546 
547     /*
548      * The source does not exist (SourceCreated == FALSE), create it.
549      * Note that we then must have a specified log that exists on the system.
550      */
551     // NOTE: IsCustomSource always FALSE here.
552 
553     if (LogNameValid && !FoundLog)
554     {
555         /* We have specified a log but it does not exist! */
556         ConResPrintf(StdErr, IDS_LOG_NOT_FOUND, EventLogName);
557         goto Quit;
558     }
559 
560     //
561     // Here, LogNameValid == TRUE  && FoundLog == TRUE, or
562     //       LogNameValid == FALSE && FoundLog == FALSE.
563     //
564 
565     if (LogNameValid /* && FoundLog */ && !LogSourceValid /* && !FoundSource && !SourceAlreadyExists */)
566     {
567         /* No source, just use the log */
568         // NOTE: For this case, SourceCreated and IsCustomSource were both set to TRUE.
569         Success = TRUE;
570         goto Quit;
571     }
572 
573     if (/* LogSourceValid && */ FoundSource && SourceAlreadyExists)
574     {
575         /* The source is in another log than the specified one */
576         ConResPrintf(StdErr, IDS_SOURCE_EXISTS, LogNameErr);
577         goto Quit;
578     }
579 
580     if (/* LogSourceValid && */ FoundSource && !SourceAlreadyExists)
581     {
582         /* We can directly use the source */
583 
584         // if (SourceCreated)
585         {
586             /* The source already exists, check whether this is a custom one */
587             if (IsCustomSource || AllowAppSources)
588             {
589                 /* This is a custom source, fine! */
590                 Success = TRUE;
591                 goto Quit;
592             }
593             else
594             {
595                 /* This is NOT a custom source, we must return an error! */
596                 ConResPuts(StdErr, IDS_SOURCE_NOT_CUSTOM);
597                 goto Quit;
598             }
599         }
600     }
601 
602     if (LogSourceValid && !FoundSource)
603     {
604         if (!LogNameValid /* && !FoundLog */)
605         {
606             /* The log name is not specified, we cannot create the source */
607             ConResPuts(StdErr, IDS_SOURCE_NOCREATE);
608             goto Quit;
609         }
610         else // LogNameValid && FoundLog
611         {
612             /* Create a new source in the specified log */
613 
614             lRet = RegOpenKeyExW(hEventLogKey,
615                                  EventLogName,
616                                  0, KEY_CREATE_SUB_KEY, // KEY_WRITE
617                                  &hLogKey);
618             if (lRet != ERROR_SUCCESS)
619                 goto Quit;
620 
621             /* Register the new event source */
622             lRet = InstallEventSource(hLogKey, EventLogSource);
623 
624             RegCloseKey(hLogKey);
625 
626             if (lRet != ERROR_SUCCESS)
627             {
628                 PrintError(lRet);
629                 ConPrintf(StdErr, L"Impossible to create the source `%s' for log `%s'!\n",
630                           EventLogSource, EventLogName);
631                 goto Quit;
632             }
633 
634             SourceCreated = TRUE;
635             Success = TRUE;
636         }
637     }
638 
639 Quit:
640     if (hEventLogKey)
641         RegCloseKey(hEventLogKey);
642 
643     SetLastError(lRet);
644 
645     return Success;
646 }
647 
648 
649 /**************************   P A R S E R    A P I   **************************/
650 
651 enum TYPE
652 {
653     TYPE_None = 0,
654     TYPE_Str,
655 //  TYPE_U8,
656 //  TYPE_U16,
657     TYPE_U32,
658 };
659 
660 #define OPTION_ALLOWED_LIST 0x01
661 #define OPTION_NOT_EMPTY    0x02
662 #define OPTION_TRIM_SPACE   0x04
663 #define OPTION_EXCLUSIVE    0x08
664 #define OPTION_MANDATORY    0x10
665 
666 typedef struct _OPTION
667 {
668     /* Constant data */
669     PWSTR OptionName;       // Option switch name
670     ULONG Type;             // Type of data stored in the 'Value' member (UNUSED) (bool, string, int, ..., or function to call)
671     ULONG Flags;            // Flags (preprocess the string or not, cache the string, stop processing...)
672     ULONG MaxOfInstances;   // Maximum number of times this option can be seen in the command line (or 0: do not care)
673     // PWSTR OptionHelp;       // Help string, or resource ID of the (localized) string (use the MAKEINTRESOURCE macro to create this value).
674     // PVOID Callback() ??
675     PWSTR AllowedValues;    // Optional list of allowed values, given as a string of values separated by a pipe symbol '|'.
676 
677     /* Parsing data */
678     PWSTR OptionStr;        // Pointer to the original option string
679     ULONG Instances;        // Number of times this option is seen in the command line
680     ULONG ValueSize;        // Size of the buffer pointed by 'Value' ??
681     PVOID Value;            // A pointer to part of the command line, or an allocated buffer
682 } OPTION, *POPTION;
683 
684 #define NEW_OPT(Name, Type, Flags, MaxOfInstances, ValueSize, ValueBuffer) \
685     {(Name), (Type), (Flags), (MaxOfInstances), NULL, NULL, 0, (ValueSize), (ValueBuffer)}
686 
687 #define NEW_OPT_EX(Name, Type, Flags, AllowedValues, MaxOfInstances, ValueSize, ValueBuffer) \
688     {(Name), (Type), (Flags), (MaxOfInstances), (AllowedValues), NULL, 0, (ValueSize), (ValueBuffer)}
689 
690 static PWSTR
TrimLeftRightWhitespace(IN PWSTR String)691 TrimLeftRightWhitespace(
692     IN PWSTR String)
693 {
694     PWSTR pStr;
695 
696     /* Trim whitespace on left (just advance the pointer) */
697     while (*String && iswspace(*String))
698         ++String;
699 
700     /* Trim whitespace on right (NULL-terminate) */
701     pStr = String + wcslen(String) - 1;
702     while (pStr >= String && iswspace(*pStr))
703         --pStr;
704     *++pStr = L'\0';
705 
706     /* Return the modified pointer */
707     return String;
708 }
709 
710 typedef enum _PARSER_ERROR
711 {
712     Success = 0,
713     InvalidSyntax,
714     InvalidOption,
715     ValueRequired,
716     ValueIsEmpty,
717     InvalidValue,
718     ValueNotAllowed,
719     TooManySameOption,
720     MandatoryOptionAbsent,
721 } PARSER_ERROR;
722 
723 typedef VOID (__cdecl *PRINT_ERROR_FUNC)(IN PARSER_ERROR, ...);
724 
725 BOOL
DoParse(IN INT argc,IN WCHAR * argv[],IN OUT POPTION Options,IN ULONG NumOptions,IN PRINT_ERROR_FUNC PrintErrorFunc OPTIONAL)726 DoParse(
727     IN INT argc,
728     IN WCHAR* argv[],
729     IN OUT POPTION Options,
730     IN ULONG NumOptions,
731     IN PRINT_ERROR_FUNC PrintErrorFunc OPTIONAL)
732 {
733     BOOL ExclusiveOptionPresent = FALSE;
734     PWSTR OptionStr = NULL;
735     UINT i;
736 
737     /*
738      * The 'Option' index is reset to 'NumOptions' (total number of elements in
739      * the 'Options' list) before retrieving a new option. This is done so that
740      * we know it cannot index a valid option at that moment.
741      */
742     UINT Option = NumOptions;
743 
744     /* Parse command line for options */
745     for (i = 1; i < argc; ++i)
746     {
747         /* Check for new options */
748 
749         if (argv[i][0] == L'-' || argv[i][0] == L'/')
750         {
751             /// FIXME: This test is problematic if this concerns the last option in the command-line!
752             /// A hack-fix is to repeat this check after the 'for'-loop.
753             if (Option != NumOptions)
754             {
755                 if (PrintErrorFunc)
756                     PrintErrorFunc(ValueRequired, OptionStr);
757                 return FALSE;
758             }
759 
760             /*
761              * If we have already encountered an (unique) exclusive option,
762              * just break now.
763              */
764             if (ExclusiveOptionPresent)
765                 break;
766 
767             OptionStr = argv[i];
768 
769             /* Lookup for the option in the list of options */
770             for (Option = 0; Option < NumOptions; ++Option)
771             {
772                 if (_wcsicmp(OptionStr + 1, Options[Option].OptionName) == 0)
773                     break;
774             }
775 
776             if (Option >= NumOptions)
777             {
778                 if (PrintErrorFunc)
779                     PrintErrorFunc(InvalidOption, OptionStr);
780                 return FALSE;
781             }
782 
783 
784             /* An option is being set */
785 
786             if (Options[Option].MaxOfInstances != 0 &&
787                 Options[Option].Instances >= Options[Option].MaxOfInstances)
788             {
789                 if (PrintErrorFunc)
790                     PrintErrorFunc(TooManySameOption, OptionStr, Options[Option].MaxOfInstances);
791                 return FALSE;
792             }
793             ++Options[Option].Instances;
794 
795             Options[Option].OptionStr = OptionStr;
796 
797             /*
798              * If this option is exclusive, remember it for later.
799              * We will then short-circuit the regular validity checks
800              * and instead check whether this is the only option specified
801              * on the command-line.
802              */
803             if (Options[Option].Flags & OPTION_EXCLUSIVE)
804                 ExclusiveOptionPresent = TRUE;
805 
806             /* Preprocess the option before setting its value */
807             switch (Options[Option].Type)
808             {
809                 case TYPE_None: // ~= TYPE_Bool
810                 {
811                     /* Set the associated boolean */
812                     BOOL* pBool = (BOOL*)Options[Option].Value;
813                     *pBool = TRUE;
814 
815                     /* No associated value, so reset the index */
816                     Option = NumOptions;
817                 }
818 
819                 /* Fall-back */
820 
821                 case TYPE_Str:
822 
823                 // case TYPE_U8:
824                 // case TYPE_U16:
825                 case TYPE_U32:
826                     break;
827 
828                 default:
829                 {
830                     wprintf(L"PARSER: Unsupported option type %lu\n", Options[Option].Type);
831                     break;
832                 }
833             }
834         }
835         else
836         {
837             /* A value for an option is being set */
838             switch (Options[Option].Type)
839             {
840                 case TYPE_None:
841                 {
842                     /* There must be no associated value */
843                     if (PrintErrorFunc)
844                         PrintErrorFunc(ValueNotAllowed, OptionStr);
845                     return FALSE;
846                 }
847 
848                 case TYPE_Str:
849                 {
850                     /* Retrieve the string */
851                     PWSTR* pStr = (PWSTR*)Options[Option].Value;
852                     *pStr = argv[i];
853 
854                     /* Trim whitespace if needed */
855                     if (Options[Option].Flags & OPTION_TRIM_SPACE)
856                         *pStr = TrimLeftRightWhitespace(*pStr);
857 
858                     /* Check whether or not the value can be empty */
859                     if ((Options[Option].Flags & OPTION_NOT_EMPTY) && !**pStr)
860                     {
861                         /* Value cannot be empty */
862                         if (PrintErrorFunc)
863                             PrintErrorFunc(ValueIsEmpty, OptionStr);
864                         return FALSE;
865                     }
866 
867                     /* Check whether the value is part of the allowed list of values */
868                     if (Options[Option].Flags & OPTION_ALLOWED_LIST)
869                     {
870                         PWSTR AllowedValues, Scan;
871                         SIZE_T Length;
872 
873                         AllowedValues = Options[Option].AllowedValues;
874                         if (!AllowedValues)
875                         {
876                             /* The array is empty, no allowed values */
877                             if (PrintErrorFunc)
878                                 PrintErrorFunc(InvalidValue, *pStr, OptionStr);
879                             return FALSE;
880                         }
881 
882                         Scan = AllowedValues;
883                         while (*Scan)
884                         {
885                             /* Find the values separator */
886                             Length = wcscspn(Scan, L"|");
887 
888                             /* Check whether this is an allowed value */
889                             if ((wcslen(*pStr) == Length) &&
890                                 (_wcsnicmp(*pStr, Scan, Length) == 0))
891                             {
892                                 /* Found it! */
893                                 break;
894                             }
895 
896                             /* Go to the next test value */
897                             Scan += Length;
898                             if (*Scan) ++Scan; // Skip the separator
899                         }
900 
901                         if (!*Scan)
902                         {
903                             /* The value is not allowed */
904                             if (PrintErrorFunc)
905                                 PrintErrorFunc(InvalidValue, *pStr, OptionStr);
906                             return FALSE;
907                         }
908                     }
909 
910                     break;
911                 }
912 
913                 // case TYPE_U8:
914                 // case TYPE_U16:
915                 case TYPE_U32:
916                 {
917                     PWCHAR pszNext = NULL;
918 
919                     /* The number is specified in base 10 */
920                     // NOTE: We might use '0' so that the base is automatically determined.
921                     *(ULONG*)Options[Option].Value = wcstoul(argv[i], &pszNext, 10);
922                     if (*pszNext)
923                     {
924                         /* The value is not a valid numeric value and is not allowed */
925                         if (PrintErrorFunc)
926                             PrintErrorFunc(InvalidValue, argv[i], OptionStr);
927                         return FALSE;
928                     }
929                     break;
930                 }
931 
932                 default:
933                 {
934                     wprintf(L"PARSER: Unsupported option type %lu\n", Options[Option].Type);
935                     break;
936                 }
937             }
938 
939             /* Reset the index */
940             Option = NumOptions;
941         }
942     }
943 
944     /// HACK-fix for the check done inside the 'for'-loop.
945     if (Option != NumOptions)
946     {
947         if (PrintErrorFunc)
948             PrintErrorFunc(ValueRequired, OptionStr);
949         return FALSE;
950     }
951 
952     /* Finalize options validity checks */
953 
954     if (ExclusiveOptionPresent)
955     {
956         /*
957          * An exclusive option present on the command-line:
958          * check whether this is the only option specified.
959          */
960         for (i = 0; i < NumOptions; ++i)
961         {
962             if (!(Options[i].Flags & OPTION_EXCLUSIVE) && (Options[i].Instances != 0))
963             {
964                 /* A non-exclusive option is present on the command-line, fail */
965                 if (PrintErrorFunc)
966                     PrintErrorFunc(InvalidSyntax);
967                 return FALSE;
968             }
969         }
970 
971         /* No other checks needed, we are done */
972         return TRUE;
973     }
974 
975     /* Check whether the required options were specified */
976     for (i = 0; i < NumOptions; ++i)
977     {
978         /* Regular validity checks */
979         if ((Options[i].Flags & OPTION_MANDATORY) && (Options[i].Instances == 0))
980         {
981             if (PrintErrorFunc)
982                 PrintErrorFunc(MandatoryOptionAbsent, Options[i].OptionName);
983             return FALSE;
984         }
985     }
986 
987     /* All checks are done */
988     return TRUE;
989 }
990 
991 /******************************************************************************/
992 
993 
994 static VOID
995 __cdecl
PrintParserError(PARSER_ERROR Error,...)996 PrintParserError(PARSER_ERROR Error, ...)
997 {
998     /* WARNING: Please keep this lookup table in sync with the resources! */
999     static UINT ErrorIDs[] =
1000     {
1001         0,                  /* Success */
1002         IDS_BADSYNTAX_0,    /* InvalidSyntax */
1003         IDS_INVALIDSWITCH,  /* InvalidOption */
1004         IDS_BADSYNTAX_1,    /* ValueRequired */
1005         IDS_BADSYNTAX_2,    /* ValueIsEmpty */
1006         IDS_BADSYNTAX_3,    /* InvalidValue */
1007         IDS_BADSYNTAX_4,    /* ValueNotAllowed */
1008         IDS_BADSYNTAX_5,    /* TooManySameOption */
1009         IDS_BADSYNTAX_6,    /* MandatoryOptionAbsent */
1010     };
1011 
1012     va_list args;
1013 
1014     if (Error < ARRAYSIZE(ErrorIDs))
1015     {
1016         va_start(args, Error);
1017         ConResPrintfV(StdErr, ErrorIDs[Error], args);
1018         va_end(args);
1019 
1020         if (Error != Success)
1021             ConResPuts(StdErr, IDS_USAGE);
1022     }
1023     else
1024     {
1025         ConPrintf(StdErr, L"PARSER: Unknown error %d\n", Error);
1026     }
1027 }
1028 
wmain(int argc,WCHAR * argv[])1029 int wmain(int argc, WCHAR* argv[])
1030 {
1031     BOOL Success = FALSE;
1032     HANDLE hEventLog;
1033     PTOKEN_USER pUserToken;
1034 
1035     /* Default option values */
1036     BOOL  bDisplayHelp  = FALSE;
1037     PWSTR szSystem      = NULL;
1038     PWSTR szDomainUser  = NULL;
1039     PWSTR szPassword    = NULL;
1040     PWSTR szLogName     = NULL;
1041     PWSTR szEventSource = NULL;
1042     PWSTR szEventType   = NULL;
1043     PWSTR szDescription = NULL;
1044     ULONG ulEventType   = EVENTLOG_INFORMATION_TYPE;
1045     ULONG ulEventCategory   = 0;
1046     ULONG ulEventIdentifier = 0;
1047 
1048     OPTION Options[] =
1049     {
1050         /* Help */
1051         NEW_OPT(L"?", TYPE_None, // ~= TYPE_Bool,
1052                 OPTION_EXCLUSIVE,
1053                 1,
1054                 sizeof(bDisplayHelp), &bDisplayHelp),
1055 
1056         /* System */
1057         NEW_OPT(L"S", TYPE_Str,
1058                 OPTION_NOT_EMPTY | OPTION_TRIM_SPACE,
1059                 1,
1060                 sizeof(szSystem), &szSystem),
1061 
1062         /* Domain & User */
1063         NEW_OPT(L"U", TYPE_Str,
1064                 OPTION_NOT_EMPTY | OPTION_TRIM_SPACE,
1065                 1,
1066                 sizeof(szDomainUser), &szDomainUser),
1067 
1068         /* Password */
1069         NEW_OPT(L"P", TYPE_Str,
1070                 0,
1071                 1,
1072                 sizeof(szPassword), &szPassword),
1073 
1074         /* Log name */
1075         NEW_OPT(L"L", TYPE_Str,
1076                 OPTION_NOT_EMPTY | OPTION_TRIM_SPACE,
1077                 1,
1078                 sizeof(szLogName), &szLogName),
1079 
1080         /* Event source */
1081         NEW_OPT(L"SO", TYPE_Str,
1082                 OPTION_NOT_EMPTY | OPTION_TRIM_SPACE,
1083                 1,
1084                 sizeof(szEventSource), &szEventSource),
1085 
1086         /* Event type */
1087         NEW_OPT_EX(L"T", TYPE_Str,
1088                    OPTION_MANDATORY | OPTION_NOT_EMPTY | OPTION_TRIM_SPACE | OPTION_ALLOWED_LIST,
1089                    L"SUCCESS|ERROR|WARNING|INFORMATION",
1090                    1,
1091                    sizeof(szEventType), &szEventType),
1092 
1093         /* Event category (ReactOS additional option) */
1094         NEW_OPT(L"C", TYPE_U32,
1095                 0,
1096                 1,
1097                 sizeof(ulEventCategory), &ulEventCategory),
1098 
1099         /* Event ID */
1100         NEW_OPT(L"ID", TYPE_U32,
1101                 OPTION_MANDATORY,
1102                 1,
1103                 sizeof(ulEventIdentifier), &ulEventIdentifier),
1104 
1105         /* Event description */
1106         NEW_OPT(L"D", TYPE_Str,
1107                 OPTION_MANDATORY,
1108                 1,
1109                 sizeof(szDescription), &szDescription),
1110     };
1111 #define OPT_SYSTEM  (Options[1])
1112 #define OPT_USER    (Options[2])
1113 #define OPT_PASSWD  (Options[3])
1114 #define OPT_EVTID   (Options[8])
1115 
1116     /* Initialize the Console Standard Streams */
1117     ConInitStdStreams();
1118 
1119     /* Parse command line for options */
1120     if (!DoParse(argc, argv, Options, ARRAYSIZE(Options), PrintParserError))
1121         return EXIT_FAILURE;
1122 
1123     /* Finalize options validity checks */
1124 
1125     if (bDisplayHelp)
1126     {
1127         if (argc > 2)
1128         {
1129             /* Invalid syntax */
1130             PrintParserError(InvalidSyntax);
1131             return EXIT_FAILURE;
1132         }
1133 
1134         ConResPuts(StdOut, IDS_HELP);
1135         return EXIT_SUCCESS;
1136     }
1137 
1138     if (szSystem || szDomainUser || szPassword)
1139     {
1140         // TODO: Implement!
1141         if (szSystem)
1142             ConResPrintf(StdOut, IDS_SWITCH_UNIMPLEMENTED, OPT_SYSTEM.OptionStr);
1143         if (szDomainUser)
1144             ConResPrintf(StdOut, IDS_SWITCH_UNIMPLEMENTED, OPT_USER.OptionStr);
1145         if (szPassword)
1146             ConResPrintf(StdOut, IDS_SWITCH_UNIMPLEMENTED, OPT_PASSWD.OptionStr);
1147         return EXIT_FAILURE;
1148     }
1149 
1150     if (ulEventIdentifier < EVENT_ID_MIN || ulEventIdentifier > EVENT_ID_MAX)
1151     {
1152         /* Invalid event identifier */
1153         ConResPrintf(StdErr, IDS_BADSYNTAX_7, OPT_EVTID.OptionStr, EVENT_ID_MIN, EVENT_ID_MAX);
1154         ConResPuts(StdErr, IDS_USAGE);
1155         return EXIT_FAILURE;
1156     }
1157 
1158     /*
1159      * Set the event type. Note that we forbid the user
1160      * to use security auditing types.
1161      */
1162     if (_wcsicmp(szEventType, L"SUCCESS") == 0)
1163         ulEventType = EVENTLOG_SUCCESS;
1164     else
1165     if (_wcsicmp(szEventType, L"ERROR") == 0)
1166         ulEventType = EVENTLOG_ERROR_TYPE;
1167     else
1168     if (_wcsicmp(szEventType, L"WARNING") == 0)
1169         ulEventType = EVENTLOG_WARNING_TYPE;
1170     else
1171     if (_wcsicmp(szEventType, L"INFORMATION") == 0)
1172         ulEventType = EVENTLOG_INFORMATION_TYPE;
1173     else
1174     {
1175         /* Use a default event type */
1176         ulEventType = EVENTLOG_SUCCESS;
1177     }
1178 
1179     /*
1180      * If we have a source, do not care about the log (as long as we will be
1181      * able to find the source later).
1182      * But if we do not have a source, then two cases:
1183      * - either we have a log name so that we will use OpenEventLog (and use
1184      *   default log's source), unless this is the Application log in which case
1185      *   we use the default source;
1186      * - or we do not have a log name so that we use default log and source names.
1187      */
1188     if (!szEventSource)
1189     {
1190         if (!szLogName)
1191             szLogName = L"Application";
1192 
1193         if (_wcsicmp(szLogName, L"Application") == 0)
1194             szEventSource = APPLICATION_NAME;
1195     }
1196 
1197     // FIXME: Check whether szLogName == L"Security" !!
1198 
1199     /*
1200      * The event APIs OpenEventLog and RegisterEventSource fall back to using
1201      * the 'Application' log when the specified log name or event source do not
1202      * exist on the system.
1203      * To prevent that and be able to error the user that the specified log name
1204      * or event source do not exist, we need to manually perform the existence
1205      * checks by ourselves.
1206      *
1207      * Check whether either the specified event log OR event source exist on
1208      * the system. If the event log does not exist, return an error. Otherwise
1209      * check whether a specified source already exists (everywhere). If found
1210      * in a different log, return an error. If not found, create the source
1211      * in the specified event log.
1212      *
1213      * NOTE: By default we forbid the usage of application (non-custom) sources.
1214      * An optional switch can be added to EventCreate to allow such sources
1215      * to be used.
1216      */
1217     if (!CheckLogOrSourceExistence(szSystem, szLogName, szEventSource, FALSE))
1218     {
1219         PrintError(GetLastError());
1220         return EXIT_FAILURE;
1221     }
1222 
1223     /* Open the event log, by source or by log name */
1224     if (szEventSource) // && *szEventSource
1225         hEventLog = RegisterEventSourceW(szSystem, szEventSource);
1226     else
1227         hEventLog = OpenEventLogW(szSystem, szLogName);
1228 
1229     if (!hEventLog)
1230     {
1231         PrintError(GetLastError());
1232         return EXIT_FAILURE;
1233     }
1234 
1235     /* Retrieve the current user token and report the event */
1236     if (GetUserToken(&pUserToken))
1237     {
1238         Success = ReportEventW(hEventLog,
1239                                ulEventType,
1240                                ulEventCategory,
1241                                ulEventIdentifier,
1242                                pUserToken->User.Sid,
1243                                1,   // One string
1244                                0,   // No raw data
1245                                (LPCWSTR*)&szDescription,
1246                                NULL // No raw data
1247                                );
1248         if (!Success)
1249         {
1250             PrintError(GetLastError());
1251             ConPuts(StdErr, L"Failed to report event!\n");
1252         }
1253         else
1254         {
1255             /* Show success */
1256             ConPuts(StdOut, L"\n");
1257             if (!szEventSource)
1258                 ConResPrintf(StdOut, IDS_SUCCESS_1, szEventType, szLogName);
1259             else if (!szLogName)
1260                 ConResPrintf(StdOut, IDS_SUCCESS_2, szEventType, szEventSource);
1261             else
1262                 ConResPrintf(StdOut, IDS_SUCCESS_3, szEventType, szLogName, szEventSource);
1263         }
1264 
1265         HeapFree(GetProcessHeap(), 0, pUserToken);
1266     }
1267     else
1268     {
1269         PrintError(GetLastError());
1270         ConPuts(StdErr, L"GetUserToken() failed!\n");
1271     }
1272 
1273     /* Close the event log */
1274     if (szEventSource && *szEventSource)
1275         DeregisterEventSource(hEventLog);
1276     else
1277         CloseEventLog(hEventLog);
1278 
1279     return (Success ? EXIT_SUCCESS : EXIT_FAILURE);
1280 }
1281