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