reactos/base/applications/cmdutils/eventcreate/eventcreate.c

1281 lines
42 KiB
C

/*
* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS EventCreate Command
* FILE: base/applications/cmdutils/eventcreate/eventcreate.c
* PURPOSE: Allows reporting custom user events in event logs,
* by using the old-school NT <= 2k3 logging API.
* PROGRAMMER: Hermes Belusca-Maito
*
* RATIONALE AND NOTE ABOUT THE IMPLEMENTATION:
*
* Contrary to what can be expected, there is no simple way of logging inside
* a NT event log an arbitrary string, for example a description text, that
* can then be viewed in a human-readable form under an event viewer. Indeed,
* a NT log entry is not just a simple arbitrary string, but is instead made
* of an identifier (ID), an event "source" and an arbitrary data chunk.
* To make things somewhat simpler, the data chunk is divided in two parts:
* an array of data strings and a raw (binary) data chunk.
*
* How then can a log entry be reconstructed? At each NT log is associated
* one or many event "sources", which are binary files (PE format) containing
* a table of predefined string templates (message table resource), indexed
* by identifiers. The ID and event source specified in a given log entry
* inside a given log allows to refer to one of the string template inside
* the specified event source of the log. A human-readable event description
* that is shown by an event viewer is obtained by associating the string
* template together with the array of data strings of the log entry.
* Each of the data strings is a parameter for the string template (formatted
* in a printf-like format).
*
* Thus we see that the human-readable event description of a log entry is
* not completely arbitrary but is dictated by both the string templates and
* the data strings of the log entry. Only the data strings can be arbitrary.
*
* Therefore, what can we do to be able to report arbitrary human-readable
* events, the description of which the user specifies at the command-line?
* There is actually only one possible way: store the description text as
* a string inside the array of data strings of the event. But we need the
* event to be displayed correctly. For that it needs to be associated with
* an event source, and its ID must point to a suitable string template, the
* association of which with the user-specified arbitrary data string should
* directly display this arbitrary string. The suitable string template is
* therefore the identity template: "%1" (in the format for message strings).
* The last problem, that may constitute a limitation of this technique, is
* that this string template is tied to a given event ID. What if the user
* wants to use a different event ID? The solution is the event source to
* contain as many same identity templates as different IDs the user can use.
* This is quite a redundant and limiting technique!
*
* On MS Windows, the EventCreate.exe command contains the identity template
* for all IDs from 1 to 1001 included, yet it is only possible to specify
* an event ID from 1 to 1000 included.
* The Powershell command "Write-EventLog" allows using IDs from 0 to 65535
* included, thus covering all of the 2-byte unsigned integer space; its
* corresponding event source file "EventLogMessages.dll"
* (inside "%SystemRoot%\Microsoft.NET\Framework\vX.Y.ZZZZZ") therefore
* contains the identity template for all IDs from 0 to 65535, making it a
* large file.
*
* For ReactOS I want to have a compromise between disk space and usage
* flexibility, therefore I choose to include as well the identity template
* for all IDs from 0 to 65535 included, as done by Powershell. If somebody
* wants to change these limits, one has to perform the following steps:
*
* 0- Update the "/ID EventID" description in the help string "IDS_HELP"
* inside the lang/xx-YY.rc resource files;
*
* 1- Change in this file the two #defines EVENT_ID_MIN and EVENT_ID_MAX
* to other values of one's choice (called 'ID_min' and 'ID_max');
*
* 2- Regenerate and replace the event message string templates file using
* the event message string templates file generator (evtmsggen tool):
* $ evtmsggen ID_min ID_max evtmsgstr.mc
*
* 3- Recompile the EventCreate command.
*
*/
#include <stdio.h>
#include <stdlib.h> // EXIT_SUCCESS, EXIT_FAILURE
#include <windef.h>
#include <winbase.h>
#include <winreg.h>
#include <conutils.h>
#include <strsafe.h>
#include "resource.h"
/*
* The minimal and maximal values of the allowed ID range.
* See the "NOTE ABOUT THE IMPLEMENTATION" above.
*
* Here are some examples of values:
* Windows' EventCreate.exe command : ID_min = 1 and ID_max = 1000
* Powershell "Write-EventLog" command: ID_min = 0 and ID_max = 65535
*
* ReactOS' EventCreate.exe command uses the same limits as Powershell.
*/
#define EVENT_ID_MIN 0
#define EVENT_ID_MAX 65535
/*
* The EventCreate command internal name (used for both setting the default
* event source name and specifying the default event source file path).
*/
#define APPLICATION_NAME L"EventCreate"
VOID PrintError(DWORD dwError)
{
if (dwError == ERROR_SUCCESS)
return;
ConMsgPuts(StdErr, FORMAT_MESSAGE_FROM_SYSTEM,
NULL, dwError, LANG_USER_DEFAULT);
ConPuts(StdErr, L"\n");
}
static BOOL
GetUserToken(
OUT PTOKEN_USER* ppUserToken)
{
BOOL Success = FALSE;
DWORD dwError;
HANDLE hToken;
DWORD cbTokenBuffer = 0;
PTOKEN_USER pUserToken = NULL;
*ppUserToken = NULL;
/* Get the process token */
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
return FALSE;
/* Retrieve token's information */
if (!GetTokenInformation(hToken, TokenUser, NULL, 0, &cbTokenBuffer))
{
dwError = GetLastError();
if (dwError != ERROR_INSUFFICIENT_BUFFER)
goto Quit;
}
pUserToken = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbTokenBuffer);
if (!pUserToken)
{
dwError = ERROR_NOT_ENOUGH_MEMORY;
goto Quit;
}
if (!GetTokenInformation(hToken, TokenUser, pUserToken, cbTokenBuffer, &cbTokenBuffer))
{
dwError = GetLastError();
goto Quit;
}
Success = TRUE;
dwError = ERROR_SUCCESS;
*ppUserToken = pUserToken;
Quit:
if (Success == FALSE)
{
if (pUserToken)
HeapFree(GetProcessHeap(), 0, pUserToken);
}
CloseHandle(hToken);
SetLastError(dwError);
return Success;
}
static LONG
InstallEventSource(
IN HKEY hEventLogKey,
IN LPCWSTR EventLogSource)
{
LONG lRet;
HKEY hSourceKey = NULL;
DWORD dwDisposition = 0;
DWORD dwData;
LPCWSTR EventMessageFile;
DWORD PathSize;
WCHAR ExePath[MAX_PATH];
lRet = RegCreateKeyExW(hEventLogKey,
EventLogSource,
0, NULL, REG_OPTION_NON_VOLATILE,
KEY_SET_VALUE, NULL,
&hSourceKey, &dwDisposition);
if (lRet != ERROR_SUCCESS)
goto Quit;
if (dwDisposition != REG_CREATED_NEW_KEY)
{
/* The source key already exists, just quit */
goto Quit;
}
/* We just have created the new source. Add the values. */
/*
* Retrieve the full path of the current running executable.
* We need it to install our custom event source.
* - In case of success, try to replace the ReactOS installation path
* (if present in the executable path) by %SystemRoot%.
* - In case of failure, use a default path.
*/
PathSize = GetModuleFileNameW(NULL, ExePath, ARRAYSIZE(ExePath));
if ((PathSize == 0) || (GetLastError() == ERROR_INSUFFICIENT_BUFFER))
{
/* We failed, copy the default value */
StringCchCopyW(ExePath, ARRAYSIZE(ExePath),
L"%SystemRoot%\\System32\\" APPLICATION_NAME L".exe");
}
else
{
/* Alternatively one can use SharedUserData->NtSystemRoot */
WCHAR TmpDir[ARRAYSIZE(ExePath)];
PathSize = GetSystemWindowsDirectoryW(TmpDir, ARRAYSIZE(TmpDir));
if ((PathSize > 0) && (_wcsnicmp(ExePath, TmpDir, PathSize) == 0))
{
StringCchCopyW(TmpDir, ARRAYSIZE(TmpDir), L"%SystemRoot%");
StringCchCatW(TmpDir, ARRAYSIZE(TmpDir), ExePath + PathSize);
StringCchCopyW(ExePath, ARRAYSIZE(ExePath), TmpDir);
}
}
EventMessageFile = ExePath;
lRet = ERROR_SUCCESS;
dwData = 1;
RegSetValueExW(hSourceKey, L"CustomSource", 0, REG_DWORD,
(LPBYTE)&dwData, sizeof(dwData));
// FIXME: Set those flags according to caller's rights?
// Or, if we are using the Security log?
dwData = EVENTLOG_SUCCESS | EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE
/* | EVENTLOG_AUDIT_SUCCESS | EVENTLOG_AUDIT_FAILURE */ ;
RegSetValueExW(hSourceKey, L"TypesSupported", 0, REG_DWORD,
(LPBYTE)&dwData, sizeof(dwData));
RegSetValueExW(hSourceKey, L"EventMessageFile", 0, REG_EXPAND_SZ,
(LPBYTE)EventMessageFile, (wcslen(EventMessageFile) + 1) * sizeof(WCHAR));
RegFlushKey(hSourceKey);
Quit:
if (hSourceKey)
RegCloseKey(hSourceKey);
return lRet;
}
static BOOL
CheckLogOrSourceExistence(
IN LPCWSTR UNCServerName OPTIONAL,
IN LPCWSTR EventLogName,
IN LPCWSTR EventLogSource,
IN BOOL AllowAppSources OPTIONAL)
{
/*
* The 'AllowAppSources' parameter allows the usage of
* application (non-custom) sources, when set to TRUE.
* Its default value is FALSE.
*/
#define MAX_KEY_LENGTH 255 // or 256 ??
BOOL Success = FALSE;
LONG lRet;
HKEY hEventLogKey = NULL, hLogKey = NULL;
DWORD NameLen;
DWORD dwIndex;
BOOL LogNameValid, LogSourceValid;
BOOL FoundLog = FALSE, FoundSource = FALSE;
BOOL SourceAlreadyExists = FALSE, SourceCreated = FALSE, IsCustomSource = FALSE;
WCHAR LogName[MAX_KEY_LENGTH]; // Current event log being tested for.
WCHAR LogNameErr[MAX_KEY_LENGTH]; // Event log in which the source already exists.
UNREFERENCED_PARAMETER(UNCServerName); // FIXME: Use remote server if needed!
LogNameValid = (EventLogName && *EventLogName);
LogSourceValid = (EventLogSource && *EventLogSource);
/*
* If neither the log name nor the log source are specified,
* there is no need to continue. Just fail.
*/
if (!LogNameValid && !LogSourceValid)
return FALSE;
lRet = RegOpenKeyExW(HKEY_LOCAL_MACHINE, // FIXME: Use remote server if needed!
L"SYSTEM\\CurrentControlSet\\Services\\EventLog",
0, KEY_ENUMERATE_SUB_KEYS,
&hEventLogKey);
if (lRet != ERROR_SUCCESS)
goto Quit;
/*
* If we just have a valid log name but no specified source, check whether
* the log key exist by atttempting to open it. If we fail: no log.
* In all cases we do not perform other tests nor create any source.
*/
if (LogNameValid && !LogSourceValid)
{
lRet = RegOpenKeyExW(hEventLogKey,
EventLogName,
0, KEY_QUERY_VALUE,
&hLogKey);
RegCloseKey(hLogKey);
FoundLog = (lRet == ERROR_SUCCESS);
if (FoundLog)
{
/* Set the flags to consistent values */
SourceCreated = TRUE;
IsCustomSource = TRUE;
}
goto Finalize;
}
/* Here, LogSourceValid is always TRUE */
/*
* We now have a valid source and either an event log name or none.
* Search for the source existence over all the existing logs:
* we loop through the event logs and we will:
* - localize whether the specified source exists and in which log it does;
* - and at the same time, check whether the specified log does exist.
*/
dwIndex = 0;
while (TRUE)
{
NameLen = ARRAYSIZE(LogName);
LogName[0] = L'\0';
lRet = RegEnumKeyExW(hEventLogKey, dwIndex, LogName, &NameLen,
NULL, NULL, NULL, NULL);
if (dwIndex > 0)
{
if (lRet == ERROR_NO_MORE_ITEMS)
{
/*
* We may/may not have found our log and may/may not have found
* our source. Quit the loop, we will check those details after.
*/
break; // goto Finalize;
}
}
if (lRet != ERROR_SUCCESS)
{
/* A registry error happened, just fail */
goto Quit;
}
/* We will then continue with the next log */
++dwIndex;
/* If we have specified a log, check whether we have found it */
if (LogNameValid && _wcsicmp(LogName, EventLogName) == 0)
{
/*
* We have found the specified log, but do not break yet: if we have
* a specified source, we want to be sure it does not exist elsewhere.
*/
FoundLog = TRUE;
}
/*
* The following case: if (LogNameValid && !LogSourceValid) {...}
* was already dealt with before. Here, LogSourceValid is always TRUE.
*/
/* Now determine whether we need to continue */
if (/* LogNameValid && */ FoundLog)
{
#if 0
if (!LogSourceValid)
{
/*
* We have found our log and we do not use any source,
* we can stop scanning now.
*/
/* Set the flags to consistent values */
SourceCreated = TRUE;
IsCustomSource = TRUE;
break; // goto Finalize;
}
#endif
if (SourceAlreadyExists)
{
/*
* We have finally found our log but the source existed elsewhere,
* stop scanning and we will error that the source is not in the
* expected log. On the contrary, if our log was not found yet,
* continue scanning to attempt to find it and, if the log is not
* found at the end we will error that the log does not exist.
*/
break; // goto Finalize;
}
}
/*
* If we have specified a source and have not found it so far,
* check for its presence in this log.
* NOTE: Here, LogSourceValid is always TRUE.
*/
if (LogSourceValid && !FoundSource)
{
HKEY hKeySource = NULL;
/* Check the sources inside this log */
lRet = RegOpenKeyExW(hEventLogKey, LogName, 0, KEY_READ, &hLogKey);
if (lRet != ERROR_SUCCESS)
{
/* A registry error happened, just fail */
goto Quit;
}
/*
* Attempt to open the source key.
*
* NOTE: Alternatively we could have scanned each source key
* in this log by using RegEnumKeyExW.
*/
lRet = RegOpenKeyExW(hLogKey, EventLogSource,
0, KEY_QUERY_VALUE,
&hKeySource);
/* Get rid of the log key handle */
RegCloseKey(hLogKey);
hLogKey = NULL;
if (lRet == ERROR_SUCCESS) // || lRet == ERROR_ACCESS_DENIED
{
/*
* We have found our source in this log (it can be
* in a different log than the one specified).
*/
FoundSource = TRUE;
// lRet = ERROR_SUCCESS;
}
else if (lRet == ERROR_FILE_NOT_FOUND)
{
/* Our source was not found there */
lRet = ERROR_SUCCESS;
hKeySource = NULL;
}
else // if (lRet != ERROR_SUCCESS && lRet != ERROR_FILE_NOT_FOUND)
{
/* A registry error happened, but we continue scanning the other logs... */
hKeySource = NULL;
}
/* If we have not found our source, continue scanning the other logs */
if (!FoundSource)
continue;
/*
* We have found our source, but is it in the correct log?
*
* NOTE: We check only in the case we have specified a log,
* otherwise we just care about the existence of the source
* and we do not check for its presence in the other logs.
*/
if (LogNameValid && !(FoundLog && _wcsicmp(LogName, EventLogName) == 0))
{
/* Now get rid of the source key handle */
RegCloseKey(hKeySource);
hKeySource = NULL;
/* The source is in another log than the specified one */
SourceAlreadyExists = TRUE;
/* Save the log name in which the source already exists */
RtlCopyMemory(LogNameErr, LogName, sizeof(LogName));
/*
* We continue because we want to also know whether we can
* still find our specified log (and we will error that the
* source exists elsewhere), or whether the log does not exist
* (and we will error accordingly).
*/
continue;
}
/*
* We have found our source, and if we have specified a log,
* the source is in the correct log.
*/
SourceCreated = TRUE;
/*
* Check whether this is one of our custom sources
* (application sources do not have this value present).
*/
IsCustomSource = FALSE;
lRet = RegQueryValueExW(hKeySource, L"CustomSource", NULL, NULL, NULL, NULL);
/* Now get rid of the source key handle */
RegCloseKey(hKeySource);
hKeySource = NULL;
if (lRet == ERROR_SUCCESS)
{
IsCustomSource = TRUE;
}
else if (lRet == ERROR_FILE_NOT_FOUND)
{
// IsCustomSource = FALSE;
}
else // if (lRet != ERROR_SUCCESS && lRet != ERROR_FILE_NOT_FOUND)
{
/* A registry error happened, just fail */
goto Quit;
}
/*
* We have found our source and it may be (or not) a custom source,
* and it is in the correct event log (if we have specified one).
* Break the search loop.
*/
break; // goto Finalize;
}
}
/*
* No errors happened so far.
* Perform last validity checks (the flags are all valid and 'LogName'
* contains the name of the last log having been tested for).
*/
Finalize:
lRet = ERROR_SUCCESS; // but do not set Success to TRUE yet.
// FIXME: Shut up a GCC warning/error about 'SourceCreated' being unused.
// We will use it later on.
UNREFERENCED_PARAMETER(SourceCreated);
/*
* The source does not exist (SourceCreated == FALSE), create it.
* Note that we then must have a specified log that exists on the system.
*/
// NOTE: IsCustomSource always FALSE here.
if (LogNameValid && !FoundLog)
{
/* We have specified a log but it does not exist! */
ConResPrintf(StdErr, IDS_LOG_NOT_FOUND, EventLogName);
goto Quit;
}
//
// Here, LogNameValid == TRUE && FoundLog == TRUE, or
// LogNameValid == FALSE && FoundLog == FALSE.
//
if (LogNameValid /* && FoundLog */ && !LogSourceValid /* && !FoundSource && !SourceAlreadyExists */)
{
/* No source, just use the log */
// NOTE: For this case, SourceCreated and IsCustomSource were both set to TRUE.
Success = TRUE;
goto Quit;
}
if (/* LogSourceValid && */ FoundSource && SourceAlreadyExists)
{
/* The source is in another log than the specified one */
ConResPrintf(StdErr, IDS_SOURCE_EXISTS, LogNameErr);
goto Quit;
}
if (/* LogSourceValid && */ FoundSource && !SourceAlreadyExists)
{
/* We can directly use the source */
// if (SourceCreated)
{
/* The source already exists, check whether this is a custom one */
if (IsCustomSource || AllowAppSources)
{
/* This is a custom source, fine! */
Success = TRUE;
goto Quit;
}
else
{
/* This is NOT a custom source, we must return an error! */
ConResPuts(StdErr, IDS_SOURCE_NOT_CUSTOM);
goto Quit;
}
}
}
if (LogSourceValid && !FoundSource)
{
if (!LogNameValid /* && !FoundLog */)
{
/* The log name is not specified, we cannot create the source */
ConResPuts(StdErr, IDS_SOURCE_NOCREATE);
goto Quit;
}
else // LogNameValid && FoundLog
{
/* Create a new source in the specified log */
lRet = RegOpenKeyExW(hEventLogKey,
EventLogName,
0, KEY_CREATE_SUB_KEY, // KEY_WRITE
&hLogKey);
if (lRet != ERROR_SUCCESS)
goto Quit;
/* Register the new event source */
lRet = InstallEventSource(hLogKey, EventLogSource);
RegCloseKey(hLogKey);
if (lRet != ERROR_SUCCESS)
{
PrintError(lRet);
ConPrintf(StdErr, L"Impossible to create the source `%s' for log `%s'!\n",
EventLogSource, EventLogName);
goto Quit;
}
SourceCreated = TRUE;
Success = TRUE;
}
}
Quit:
if (hEventLogKey)
RegCloseKey(hEventLogKey);
SetLastError(lRet);
return Success;
}
/************************** P A R S E R A P I **************************/
enum TYPE
{
TYPE_None = 0,
TYPE_Str,
// TYPE_U8,
// TYPE_U16,
TYPE_U32,
};
#define OPTION_ALLOWED_LIST 0x01
#define OPTION_NOT_EMPTY 0x02
#define OPTION_TRIM_SPACE 0x04
#define OPTION_EXCLUSIVE 0x08
#define OPTION_MANDATORY 0x10
typedef struct _OPTION
{
/* Constant data */
PWSTR OptionName; // Option switch name
ULONG Type; // Type of data stored in the 'Value' member (UNUSED) (bool, string, int, ..., or function to call)
ULONG Flags; // Flags (preprocess the string or not, cache the string, stop processing...)
ULONG MaxOfInstances; // Maximum number of times this option can be seen in the command line (or 0: do not care)
// PWSTR OptionHelp; // Help string, or resource ID of the (localized) string (use the MAKEINTRESOURCE macro to create this value).
// PVOID Callback() ??
PWSTR AllowedValues; // Optional list of allowed values, given as a string of values separated by a pipe symbol '|'.
/* Parsing data */
PWSTR OptionStr; // Pointer to the original option string
ULONG Instances; // Number of times this option is seen in the command line
ULONG ValueSize; // Size of the buffer pointed by 'Value' ??
PVOID Value; // A pointer to part of the command line, or an allocated buffer
} OPTION, *POPTION;
#define NEW_OPT(Name, Type, Flags, MaxOfInstances, ValueSize, ValueBuffer) \
{(Name), (Type), (Flags), (MaxOfInstances), NULL, NULL, 0, (ValueSize), (ValueBuffer)}
#define NEW_OPT_EX(Name, Type, Flags, AllowedValues, MaxOfInstances, ValueSize, ValueBuffer) \
{(Name), (Type), (Flags), (MaxOfInstances), (AllowedValues), NULL, 0, (ValueSize), (ValueBuffer)}
static PWSTR
TrimLeftRightWhitespace(
IN PWSTR String)
{
PWSTR pStr;
/* Trim whitespace on left (just advance the pointer) */
while (*String && iswspace(*String))
++String;
/* Trim whitespace on right (NULL-terminate) */
pStr = String + wcslen(String) - 1;
while (pStr >= String && iswspace(*pStr))
--pStr;
*++pStr = L'\0';
/* Return the modified pointer */
return String;
}
typedef enum _PARSER_ERROR
{
Success = 0,
InvalidSyntax,
InvalidOption,
ValueRequired,
ValueIsEmpty,
InvalidValue,
ValueNotAllowed,
TooManySameOption,
MandatoryOptionAbsent,
} PARSER_ERROR;
typedef VOID (__cdecl *PRINT_ERROR_FUNC)(IN PARSER_ERROR, ...);
BOOL
DoParse(
IN INT argc,
IN WCHAR* argv[],
IN OUT POPTION Options,
IN ULONG NumOptions,
IN PRINT_ERROR_FUNC PrintErrorFunc OPTIONAL)
{
BOOL ExclusiveOptionPresent = FALSE;
PWSTR OptionStr = NULL;
UINT i;
/*
* The 'Option' index is reset to 'NumOptions' (total number of elements in
* the 'Options' list) before retrieving a new option. This is done so that
* we know it cannot index a valid option at that moment.
*/
UINT Option = NumOptions;
/* Parse command line for options */
for (i = 1; i < argc; ++i)
{
/* Check for new options */
if (argv[i][0] == L'-' || argv[i][0] == L'/')
{
/// FIXME: This test is problematic if this concerns the last option in the command-line!
/// A hack-fix is to repeat this check after the 'for'-loop.
if (Option != NumOptions)
{
if (PrintErrorFunc)
PrintErrorFunc(ValueRequired, OptionStr);
return FALSE;
}
/*
* If we have already encountered an (unique) exclusive option,
* just break now.
*/
if (ExclusiveOptionPresent)
break;
OptionStr = argv[i];
/* Lookup for the option in the list of options */
for (Option = 0; Option < NumOptions; ++Option)
{
if (_wcsicmp(OptionStr + 1, Options[Option].OptionName) == 0)
break;
}
if (Option >= NumOptions)
{
if (PrintErrorFunc)
PrintErrorFunc(InvalidOption, OptionStr);
return FALSE;
}
/* An option is being set */
if (Options[Option].MaxOfInstances != 0 &&
Options[Option].Instances >= Options[Option].MaxOfInstances)
{
if (PrintErrorFunc)
PrintErrorFunc(TooManySameOption, OptionStr, Options[Option].MaxOfInstances);
return FALSE;
}
++Options[Option].Instances;
Options[Option].OptionStr = OptionStr;
/*
* If this option is exclusive, remember it for later.
* We will then short-circuit the regular validity checks
* and instead check whether this is the only option specified
* on the command-line.
*/
if (Options[Option].Flags & OPTION_EXCLUSIVE)
ExclusiveOptionPresent = TRUE;
/* Preprocess the option before setting its value */
switch (Options[Option].Type)
{
case TYPE_None: // ~= TYPE_Bool
{
/* Set the associated boolean */
BOOL* pBool = (BOOL*)Options[Option].Value;
*pBool = TRUE;
/* No associated value, so reset the index */
Option = NumOptions;
}
/* Fall-back */
case TYPE_Str:
// case TYPE_U8:
// case TYPE_U16:
case TYPE_U32:
break;
default:
{
wprintf(L"PARSER: Unsupported option type %lu\n", Options[Option].Type);
break;
}
}
}
else
{
/* A value for an option is being set */
switch (Options[Option].Type)
{
case TYPE_None:
{
/* There must be no associated value */
if (PrintErrorFunc)
PrintErrorFunc(ValueNotAllowed, OptionStr);
return FALSE;
}
case TYPE_Str:
{
/* Retrieve the string */
PWSTR* pStr = (PWSTR*)Options[Option].Value;
*pStr = argv[i];
/* Trim whitespace if needed */
if (Options[Option].Flags & OPTION_TRIM_SPACE)
*pStr = TrimLeftRightWhitespace(*pStr);
/* Check whether or not the value can be empty */
if ((Options[Option].Flags & OPTION_NOT_EMPTY) && !**pStr)
{
/* Value cannot be empty */
if (PrintErrorFunc)
PrintErrorFunc(ValueIsEmpty, OptionStr);
return FALSE;
}
/* Check whether the value is part of the allowed list of values */
if (Options[Option].Flags & OPTION_ALLOWED_LIST)
{
PWSTR AllowedValues, Scan;
SIZE_T Length;
AllowedValues = Options[Option].AllowedValues;
if (!AllowedValues)
{
/* The array is empty, no allowed values */
if (PrintErrorFunc)
PrintErrorFunc(InvalidValue, *pStr, OptionStr);
return FALSE;
}
Scan = AllowedValues;
while (*Scan)
{
/* Find the values separator */
Length = wcscspn(Scan, L"|");
/* Check whether this is an allowed value */
if ((wcslen(*pStr) == Length) &&
(_wcsnicmp(*pStr, Scan, Length) == 0))
{
/* Found it! */
break;
}
/* Go to the next test value */
Scan += Length;
if (*Scan) ++Scan; // Skip the separator
}
if (!*Scan)
{
/* The value is not allowed */
if (PrintErrorFunc)
PrintErrorFunc(InvalidValue, *pStr, OptionStr);
return FALSE;
}
}
break;
}
// case TYPE_U8:
// case TYPE_U16:
case TYPE_U32:
{
PWCHAR pszNext = NULL;
/* The number is specified in base 10 */
// NOTE: We might use '0' so that the base is automatically determined.
*(ULONG*)Options[Option].Value = wcstoul(argv[i], &pszNext, 10);
if (*pszNext)
{
/* The value is not a valid numeric value and is not allowed */
if (PrintErrorFunc)
PrintErrorFunc(InvalidValue, argv[i], OptionStr);
return FALSE;
}
break;
}
default:
{
wprintf(L"PARSER: Unsupported option type %lu\n", Options[Option].Type);
break;
}
}
/* Reset the index */
Option = NumOptions;
}
}
/// HACK-fix for the check done inside the 'for'-loop.
if (Option != NumOptions)
{
if (PrintErrorFunc)
PrintErrorFunc(ValueRequired, OptionStr);
return FALSE;
}
/* Finalize options validity checks */
if (ExclusiveOptionPresent)
{
/*
* An exclusive option present on the command-line:
* check whether this is the only option specified.
*/
for (i = 0; i < NumOptions; ++i)
{
if (!(Options[i].Flags & OPTION_EXCLUSIVE) && (Options[i].Instances != 0))
{
/* A non-exclusive option is present on the command-line, fail */
if (PrintErrorFunc)
PrintErrorFunc(InvalidSyntax);
return FALSE;
}
}
/* No other checks needed, we are done */
return TRUE;
}
/* Check whether the required options were specified */
for (i = 0; i < NumOptions; ++i)
{
/* Regular validity checks */
if ((Options[i].Flags & OPTION_MANDATORY) && (Options[i].Instances == 0))
{
if (PrintErrorFunc)
PrintErrorFunc(MandatoryOptionAbsent, Options[i].OptionName);
return FALSE;
}
}
/* All checks are done */
return TRUE;
}
/******************************************************************************/
static VOID
__cdecl
PrintParserError(PARSER_ERROR Error, ...)
{
/* WARNING: Please keep this lookup table in sync with the resources! */
static UINT ErrorIDs[] =
{
0, /* Success */
IDS_BADSYNTAX_0, /* InvalidSyntax */
IDS_INVALIDSWITCH, /* InvalidOption */
IDS_BADSYNTAX_1, /* ValueRequired */
IDS_BADSYNTAX_2, /* ValueIsEmpty */
IDS_BADSYNTAX_3, /* InvalidValue */
IDS_BADSYNTAX_4, /* ValueNotAllowed */
IDS_BADSYNTAX_5, /* TooManySameOption */
IDS_BADSYNTAX_6, /* MandatoryOptionAbsent */
};
va_list args;
if (Error < ARRAYSIZE(ErrorIDs))
{
va_start(args, Error);
ConResPrintfV(StdErr, ErrorIDs[Error], args);
va_end(args);
if (Error != Success)
ConResPuts(StdErr, IDS_USAGE);
}
else
{
ConPrintf(StdErr, L"PARSER: Unknown error %d\n", Error);
}
}
int wmain(int argc, WCHAR* argv[])
{
BOOL Success = FALSE;
HANDLE hEventLog;
PTOKEN_USER pUserToken;
/* Default option values */
BOOL bDisplayHelp = FALSE;
PWSTR szSystem = NULL;
PWSTR szDomainUser = NULL;
PWSTR szPassword = NULL;
PWSTR szLogName = NULL;
PWSTR szEventSource = NULL;
PWSTR szEventType = NULL;
PWSTR szDescription = NULL;
ULONG ulEventType = EVENTLOG_INFORMATION_TYPE;
ULONG ulEventCategory = 0;
ULONG ulEventIdentifier = 0;
OPTION Options[] =
{
/* Help */
NEW_OPT(L"?", TYPE_None, // ~= TYPE_Bool,
OPTION_EXCLUSIVE,
1,
sizeof(bDisplayHelp), &bDisplayHelp),
/* System */
NEW_OPT(L"S", TYPE_Str,
OPTION_NOT_EMPTY | OPTION_TRIM_SPACE,
1,
sizeof(szSystem), &szSystem),
/* Domain & User */
NEW_OPT(L"U", TYPE_Str,
OPTION_NOT_EMPTY | OPTION_TRIM_SPACE,
1,
sizeof(szDomainUser), &szDomainUser),
/* Password */
NEW_OPT(L"P", TYPE_Str,
0,
1,
sizeof(szPassword), &szPassword),
/* Log name */
NEW_OPT(L"L", TYPE_Str,
OPTION_NOT_EMPTY | OPTION_TRIM_SPACE,
1,
sizeof(szLogName), &szLogName),
/* Event source */
NEW_OPT(L"SO", TYPE_Str,
OPTION_NOT_EMPTY | OPTION_TRIM_SPACE,
1,
sizeof(szEventSource), &szEventSource),
/* Event type */
NEW_OPT_EX(L"T", TYPE_Str,
OPTION_MANDATORY | OPTION_NOT_EMPTY | OPTION_TRIM_SPACE | OPTION_ALLOWED_LIST,
L"SUCCESS|ERROR|WARNING|INFORMATION",
1,
sizeof(szEventType), &szEventType),
/* Event category (ReactOS additional option) */
NEW_OPT(L"C", TYPE_U32,
0,
1,
sizeof(ulEventCategory), &ulEventCategory),
/* Event ID */
NEW_OPT(L"ID", TYPE_U32,
OPTION_MANDATORY,
1,
sizeof(ulEventIdentifier), &ulEventIdentifier),
/* Event description */
NEW_OPT(L"D", TYPE_Str,
OPTION_MANDATORY,
1,
sizeof(szDescription), &szDescription),
};
#define OPT_SYSTEM (Options[1])
#define OPT_USER (Options[2])
#define OPT_PASSWD (Options[3])
#define OPT_EVTID (Options[8])
/* Initialize the Console Standard Streams */
ConInitStdStreams();
/* Parse command line for options */
if (!DoParse(argc, argv, Options, ARRAYSIZE(Options), PrintParserError))
return EXIT_FAILURE;
/* Finalize options validity checks */
if (bDisplayHelp)
{
if (argc > 2)
{
/* Invalid syntax */
PrintParserError(InvalidSyntax);
return EXIT_FAILURE;
}
ConResPuts(StdOut, IDS_HELP);
return EXIT_SUCCESS;
}
if (szSystem || szDomainUser || szPassword)
{
// TODO: Implement!
if (szSystem)
ConResPrintf(StdOut, IDS_SWITCH_UNIMPLEMENTED, OPT_SYSTEM.OptionStr);
if (szDomainUser)
ConResPrintf(StdOut, IDS_SWITCH_UNIMPLEMENTED, OPT_USER.OptionStr);
if (szPassword)
ConResPrintf(StdOut, IDS_SWITCH_UNIMPLEMENTED, OPT_PASSWD.OptionStr);
return EXIT_FAILURE;
}
if (ulEventIdentifier < EVENT_ID_MIN || ulEventIdentifier > EVENT_ID_MAX)
{
/* Invalid event identifier */
ConResPrintf(StdErr, IDS_BADSYNTAX_7, OPT_EVTID.OptionStr, EVENT_ID_MIN, EVENT_ID_MAX);
ConResPuts(StdErr, IDS_USAGE);
return EXIT_FAILURE;
}
/*
* Set the event type. Note that we forbid the user
* to use security auditing types.
*/
if (_wcsicmp(szEventType, L"SUCCESS") == 0)
ulEventType = EVENTLOG_SUCCESS;
else
if (_wcsicmp(szEventType, L"ERROR") == 0)
ulEventType = EVENTLOG_ERROR_TYPE;
else
if (_wcsicmp(szEventType, L"WARNING") == 0)
ulEventType = EVENTLOG_WARNING_TYPE;
else
if (_wcsicmp(szEventType, L"INFORMATION") == 0)
ulEventType = EVENTLOG_INFORMATION_TYPE;
else
{
/* Use a default event type */
ulEventType = EVENTLOG_SUCCESS;
}
/*
* If we have a source, do not care about the log (as long as we will be
* able to find the source later).
* But if we do not have a source, then two cases:
* - either we have a log name so that we will use OpenEventLog (and use
* default log's source), unless this is the Application log in which case
* we use the default source;
* - or we do not have a log name so that we use default log and source names.
*/
if (!szEventSource)
{
if (!szLogName)
szLogName = L"Application";
if (_wcsicmp(szLogName, L"Application") == 0)
szEventSource = APPLICATION_NAME;
}
// FIXME: Check whether szLogName == L"Security" !!
/*
* The event APIs OpenEventLog and RegisterEventSource fall back to using
* the 'Application' log when the specified log name or event source do not
* exist on the system.
* To prevent that and be able to error the user that the specified log name
* or event source do not exist, we need to manually perform the existence
* checks by ourselves.
*
* Check whether either the specified event log OR event source exist on
* the system. If the event log does not exist, return an error. Otherwise
* check whether a specified source already exists (everywhere). If found
* in a different log, return an error. If not found, create the source
* in the specified event log.
*
* NOTE: By default we forbid the usage of application (non-custom) sources.
* An optional switch can be added to EventCreate to allow such sources
* to be used.
*/
if (!CheckLogOrSourceExistence(szSystem, szLogName, szEventSource, FALSE))
{
PrintError(GetLastError());
return EXIT_FAILURE;
}
/* Open the event log, by source or by log name */
if (szEventSource) // && *szEventSource
hEventLog = RegisterEventSourceW(szSystem, szEventSource);
else
hEventLog = OpenEventLogW(szSystem, szLogName);
if (!hEventLog)
{
PrintError(GetLastError());
return EXIT_FAILURE;
}
/* Retrieve the current user token and report the event */
if (GetUserToken(&pUserToken))
{
Success = ReportEventW(hEventLog,
ulEventType,
ulEventCategory,
ulEventIdentifier,
pUserToken->User.Sid,
1, // One string
0, // No raw data
(LPCWSTR*)&szDescription,
NULL // No raw data
);
if (!Success)
{
PrintError(GetLastError());
ConPuts(StdErr, L"Failed to report event!\n");
}
else
{
/* Show success */
ConPuts(StdOut, L"\n");
if (!szEventSource)
ConResPrintf(StdOut, IDS_SUCCESS_1, szEventType, szLogName);
else if (!szLogName)
ConResPrintf(StdOut, IDS_SUCCESS_2, szEventType, szEventSource);
else
ConResPrintf(StdOut, IDS_SUCCESS_3, szEventType, szLogName, szEventSource);
}
HeapFree(GetProcessHeap(), 0, pUserToken);
}
else
{
PrintError(GetLastError());
ConPuts(StdErr, L"GetUserToken() failed!\n");
}
/* Close the event log */
if (szEventSource && *szEventSource)
DeregisterEventSource(hEventLog);
else
CloseEventLog(hEventLog);
return (Success ? EXIT_SUCCESS : EXIT_FAILURE);
}