Reactos
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
111VOID 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
122static BOOL
123GetUserToken(
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
163Quit:
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
177static LONG
178InstallEventSource(
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
252Quit:
253 if (hSourceKey)
254 RegCloseKey(hSourceKey);
255
256 return lRet;
257}
258
259static BOOL
260CheckLogOrSourceExistence(
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 */
540Finalize:
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
639Quit:
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
651enum 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
666typedef 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
690static PWSTR
691TrimLeftRightWhitespace(
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
710typedef 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
723typedef VOID (__cdecl *PRINT_ERROR_FUNC)(IN PARSER_ERROR, ...);
724
725BOOL
726DoParse(
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
994static VOID
995__cdecl
996PrintParserError(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
1029int 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}