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 
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
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
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
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
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
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
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 
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