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