From 50b1242e993507d1a6d607a846bbf16885156d79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herm=C3=A8s=20B=C3=A9lusca-Ma=C3=AFto?= Date: Thu, 24 Jul 2025 22:28:50 +0200 Subject: [PATCH] [WLNTFYTESTS] Add extended tests for the Winlogon notifications Introduce and use a minimal testing framework (minitest.h) based on an updated version of `wine/test.h`. Each notification handler becomes its own test. Useful macro and function helpers have been introduced to simplify the code that is duplicated for each handler. See commit 38d07d3a24 (PR #8234) for the details of how to install and use the notification dll. These tests can exercise the notifications in the four cases, where asynchronous events and user impersonation can be independently enabled or disabled. To do this, the `Asynchronous` and `Impersonate` registry `REG_DWORD` values, inside the `WLNotifyTests` subkey of: `HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify` must be set to their intended values[^1]. ---- [^1]: https://learn.microsoft.com/en-us/windows/win32/secauthn/registry-entries ---- Current test results: - When each notification is emitted, the DLL is loaded then unloaded: ``` err:(modules\rostests\win32\winlogon\wlntfytests\wlntfytests.c:1307) WLNOTIFY(ac.b0): Entering `DllMain`(hInst: 0x10000000, dwReason: 0x1, pReserved: 0x00000000) ... err:(modules\rostests\win32\winlogon\wlntfytests\wlntfytests.c:1307) WLNOTIFY(ac.b0): Entering `DllMain`(hInst: 0x10000000, dwReason: 0x0, pReserved: 0x00000000) ``` This doesn't happen on Windows. And indeed, it should _not_ happen, because otherwise the DLL would loose any of its internal global state between consecutive notification calls. This currently happens in ReactOS, where we can observe the following: ``` modules\rostests\win32\winlogon\wlntfytests\wlntfytests.c:788: Test failed: **** WLEventLogon: ERROR: Wrong state NON-INITIALIZED, expected Startup or Logoff ... err:(modules\rostests\win32\winlogon\wlntfytests\wlntfytests.c:1036) **** WLEventLogon: Changing state NON-INITIALIZED to Logon ``` and similar for every other notification. - Test results for each notification: ``` WLEventStartup: 30 tests executed (0 marked as todo, 1 failure), 0 skipped. WLEventLogon: 30 tests executed (0 marked as todo, 4 failures), 2 skipped. WLEventStartShell: 30 tests executed (0 marked as todo, 4 failures), 2 skipped. -- Note: missing WLEventPostShell -- WLEventLock: 30 tests executed (0 marked as todo, 4 failures), 2 skipped. WLEventUnlock: 30 tests executed (0 marked as todo, 4 failures), 2 skipped. WLEventStartScreenSaver: 30 tests executed (0 marked as todo, 10 failures), 0 skipped. WLEventStopScreenSaver: 30 tests executed (0 marked as todo, 9 failures), 0 skipped. WLEventLogoff: 30 tests executed (0 marked as todo, 5 failures), 2 skipped. WLEventShutdown: 31 tests executed (0 marked as todo, 5 failures), 0 skipped. ``` --- .../win32/winlogon/wlntfytests/minitest.h | 589 +++++++++ .../win32/winlogon/wlntfytests/wlntfytests.c | 1159 +++++++++++++---- sdk/tools/gen_baseaddress.py | 3 +- 3 files changed, 1482 insertions(+), 269 deletions(-) create mode 100644 modules/rostests/win32/winlogon/wlntfytests/minitest.h diff --git a/modules/rostests/win32/winlogon/wlntfytests/minitest.h b/modules/rostests/win32/winlogon/wlntfytests/minitest.h new file mode 100644 index 00000000000..e2ec5f9f69d --- /dev/null +++ b/modules/rostests/win32/winlogon/wlntfytests/minitest.h @@ -0,0 +1,589 @@ +/* + * PROJECT: ReactOS Tests + * LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later) + * PURPOSE: Lightweight testing routines, based on an updated version of wine/test.h + * COPYRIGHT: Copyright 2025 Hermès Bélusca-Maïto + */ + +#ifndef __MINI_WINE_TEST_H +#define __MINI_WINE_TEST_H + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* debug level */ +extern int winetest_debug; + +extern int report_success; + +/* running in interactive mode? */ +extern int winetest_interactive; + +/* current platform */ +extern const char *winetest_platform; + +extern void winetest_set_location( const char* file, int line ); +extern void winetest_subtest(const char* name); +extern void winetest_start_todo( int is_todo ); +extern int winetest_loop_todo(void); +extern void winetest_end_todo(void); +extern void winetest_start_nocount(unsigned int flags); +extern int winetest_loop_nocount(void); +extern void winetest_end_nocount(void); +#if 0 +extern int winetest_get_mainargs( char*** pargv ); +#endif +extern LONG winetest_get_failures(void); +extern LONG winetest_get_successes(void); +extern void winetest_add_failures( LONG new_failures ); + +extern int broken( int condition ); +extern int winetest_vok( int condition, const char *msg, va_list ap ); +extern void winetest_vskip( const char *msg, va_list ap ); + +extern void __cdecl winetest_ok( int condition, const char *msg, ... ) __WINE_PRINTF_ATTR(2,3); +extern void __cdecl winetest_skip( const char *msg, ... ) __WINE_PRINTF_ATTR(1,2); +extern void __cdecl winetest_win_skip( const char *msg, ... ) __WINE_PRINTF_ATTR(1,2); +extern void __cdecl winetest_trace( const char *msg, ... ) __WINE_PRINTF_ATTR(1,2); +extern void __cdecl winetest_push_context( const char *fmt, ... ) __WINE_PRINTF_ATTR(1,2); +extern void winetest_pop_context(void); + +#define subtest_(file, line) (winetest_set_location(file, line), 0) ? (void)0 : winetest_subtest +#define ok_(file, line) (winetest_set_location(file, line), 0) ? (void)0 : winetest_ok +#define skip_(file, line) (winetest_set_location(file, line), 0) ? (void)0 : winetest_skip +#define win_skip_(file, line) (winetest_set_location(file, line), 0) ? (void)0 : winetest_win_skip +#define trace_(file, line) (winetest_set_location(file, line), 0) ? (void)0 : winetest_trace + +#define subtest subtest_(__RELFILE__, __LINE__) +#define ok ok_(__RELFILE__, __LINE__) +#define skip skip_(__RELFILE__, __LINE__) +#define win_skip win_skip_(__RELFILE__, __LINE__) +#define trace trace_(__RELFILE__, __LINE__) + +#define todo_if(is_todo) for (winetest_start_todo(is_todo); \ + winetest_loop_todo(); \ + winetest_end_todo()) + +#define todo_ros todo_if(!strcmp(winetest_platform, "reactos")) +#define todo_ros_if(is_todo) todo_if((is_todo) && !strcmp(winetest_platform, "reactos")) +#ifdef USE_WINE_TODOS +#define todo_wine todo_ros +#define todo_wine_if todo_ros_if +#else +#define todo_wine todo_if(!strcmp(winetest_platform, "wine")) +#define todo_wine_if(is_todo) todo_if((is_todo) && !strcmp(winetest_platform, "wine")) +#endif + +#define ros_skip_flaky for (winetest_start_nocount(3); \ + winetest_loop_nocount(); \ + winetest_end_nocount()) + +#define disable_success_count for (winetest_start_nocount(1); \ + winetest_loop_nocount(); \ + winetest_end_nocount()) + +#define skip_2k3_crash if (_winver < 0x600) skip("Test skipped, because it crashes on win 2003\n"); else +#define skip_2k3_fail if (_winver < 0x600) skip("Test skipped, because it fails on win 2003\n"); else + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + + +/************************************************************************/ +/* Below is the implementation of the various functions, to be included + * directly into the generated testlist.c file. + * It is done that way so that the dlls can build the test routines with + * different includes or flags if needed. + */ + +#ifdef STANDALONE + +#include + +/* Define WINETEST_MSVC_IDE_FORMATTING to alter the output format winetest will use for file/line numbers. + This alternate format makes the file/line numbers clickable in visual studio, to directly jump to them. */ +#if defined(WINETEST_MSVC_IDE_FORMATTING) +# define __winetest_file_line_prefix "%s(%d)" +#else +# define __winetest_file_line_prefix "%s:%d" +#endif + +struct test +{ + const char *name; + void (*func)(void); +}; + +extern const struct test winetest_testlist[]; + +/* debug level */ +int winetest_debug = 1; + +/* interactive mode? */ +int winetest_interactive = 0; + +/* current platform */ +const char *winetest_platform = "windows"; + +/* report successful tests (BOOL) */ +int report_success = 0; + +#if 0 +/* passing arguments around */ +static int winetest_argc; +static char** winetest_argv; +#endif + +static const struct test *current_test; /* test currently being run */ + +static LONG winetest_successes; /* number of successful tests */ +static LONG winetest_failures; /* number of failures */ +static LONG winetest_skipped; /* number of skipped test chunks */ +static LONG winetest_todo_successes; /* number of successful tests inside todo block */ +static LONG winetest_todo_failures; /* number of failures inside todo block */ + +/* The following data must be kept track of on a per-thread basis */ +struct winetest_thread_data +{ + const char* current_file; /* file of current check */ + int current_line; /* line of current check */ + unsigned int todo_level; /* current todo nesting level */ + unsigned int nocount_level; + int todo_do_loop; +#if 0 + char *str_pos; /* position in debug buffer */ + char strings[2000]; /* buffer for debug strings */ +#endif + char context[8][128]; /* data to print before messages */ + unsigned int context_count; /* number of context prefixes */ +} tls_data; + +static DWORD tls_index; + +static struct winetest_thread_data *winetest_get_thread_data(void) +{ + struct winetest_thread_data *data; + DWORD last_error; + + last_error = GetLastError(); + data = TlsGetValue( tls_index ); + if (!data) + { + data = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*data) ); +#if 0 + data->str_pos = data->strings; +#endif + TlsSetValue( tls_index, data ); + } + SetLastError( last_error ); + return data; +} + +static int winetest_vprintf( const char *msg, va_list args ) +{ + static struct __wine_debug_functions s_Debug = {NULL}; + if (!s_Debug.dbg_vprintf) + __wine_dbg_set_functions(NULL, &s_Debug, sizeof(s_Debug)); + + return s_Debug.dbg_vprintf( msg, args ); +} + +void winetest_set_location( const char* file, int line ) +{ + struct winetest_thread_data *data = winetest_get_thread_data(); +#if 1 /*|| defined(WINETEST_MSVC_IDE_FORMATTING)*/ + data->current_file = file; +#else + data->current_file=strrchr(file,'/'); + if (data->current_file==NULL) + data->current_file=strrchr(file,'\\'); + if (data->current_file==NULL) + data->current_file=file; + else + data->current_file++; +#endif + data->current_line=line; +} + +static int __cdecl winetest_printf( const char *msg, ... ) __WINE_PRINTF_ATTR(1,2); +static int __cdecl winetest_printf( const char *msg, ... ) +{ + va_list valist; + int ret; + + va_start( valist, msg ); + ret = winetest_vprintf( msg, valist ); + va_end( valist ); + + return ret; +} + +static void __cdecl winetest_print_location( const char *msg, ... ) __WINE_PRINTF_ATTR(1,2); +static void __cdecl winetest_print_location( const char *msg, ... ) +{ + struct winetest_thread_data *data = winetest_get_thread_data(); + // char elapsed[64]; + va_list valist; + + // winetest_printf( "%s:%d:%s ", data->current_file, data->current_line, winetest_elapsed( elapsed ) ); + winetest_printf( __winetest_file_line_prefix ": ", data->current_file, data->current_line ); + va_start( valist, msg ); + winetest_vprintf( msg, valist ); + va_end( valist ); +} + +static void __cdecl winetest_print_context( const char *msgtype ) +{ + struct winetest_thread_data *data = winetest_get_thread_data(); + unsigned int i; + + winetest_print_location( "%s", msgtype ); + for (i = 0; i < data->context_count; ++i) + winetest_printf( "%s: ", data->context[i] ); +} + +void winetest_subtest(const char* name) +{ + winetest_print_location( "Subtest %s\n", name ); +} + +int broken( int condition ) +{ + return ((strcmp(winetest_platform, "windows") == 0) +#ifndef USE_WINE_TODOS + || (strcmp(winetest_platform, "reactos") == 0) +#endif + ) && condition; +} + +/* + * Checks condition. + * Parameters: + * - condition - condition to check; + * - msg test description; + * - file - test application source code file name of the check + * - line - test application source code file line number of the check + * Return: + * 0 if condition does not have the expected value, 1 otherwise + */ +int winetest_vok( int condition, const char *msg, va_list args ) +{ + struct winetest_thread_data *data = winetest_get_thread_data(); + + if (data->todo_level) + { + if (condition) + { + winetest_print_context( "Test succeeded inside todo block: " ); + winetest_vprintf( msg, args ); + if ((data->nocount_level & 2) == 0) + InterlockedIncrement( &winetest_todo_failures ); + return 0; + } + else + { + /* show todos even if traces are disabled*/ + /*if (winetest_debug > 0)*/ + { + winetest_print_context( "Test marked todo: " ); + winetest_vprintf( msg, args ); + } + if ((data->nocount_level & 1) == 0) + InterlockedIncrement( &winetest_todo_successes ); + return 1; + } + } + else + { + if (!condition) + { + winetest_print_context( "Test failed: " ); + winetest_vprintf( msg, args ); + if ((data->nocount_level & 2) == 0) + InterlockedIncrement( &winetest_failures ); + return 0; + } + else + { + if (report_success && (data->nocount_level & 1) == 0) + { + winetest_print_location("Test succeeded\n"); + } + if ((data->nocount_level & 1) == 0) + InterlockedIncrement( &winetest_successes ); + return 1; + } + } +} + +void __cdecl winetest_ok( int condition, const char *msg, ... ) +{ + va_list valist; + + va_start(valist, msg); + winetest_vok(condition, msg, valist); + va_end(valist); +} + +void __cdecl winetest_trace( const char *msg, ... ) +{ + va_list valist; + + if (winetest_debug > 0) + { + winetest_print_context( "" ); + va_start(valist, msg); + winetest_vprintf( msg, valist ); + va_end(valist); + } +} + +void winetest_vskip( const char *msg, va_list args ) +{ + winetest_print_context( "Tests skipped: " ); + winetest_vprintf( msg, args ); + InterlockedIncrement( &winetest_skipped ); +} + +void __cdecl winetest_skip( const char *msg, ... ) +{ + va_list valist; + va_start(valist, msg); + winetest_vskip(msg, valist); + va_end(valist); +} + +void __cdecl winetest_win_skip( const char *msg, ... ) +{ + va_list valist; + va_start(valist, msg); + if ((strcmp(winetest_platform, "windows") == 0) +#if !defined(USE_WINE_TODOS) || defined(USE_WIN_SKIP) + || (strcmp(winetest_platform, "reactos") == 0) +#endif + ) + winetest_vskip(msg, valist); + else + winetest_vok(0, msg, valist); + va_end(valist); +} + +void winetest_start_todo( int is_todo ) +{ + struct winetest_thread_data *data = winetest_get_thread_data(); + data->todo_level = (data->todo_level << 1) | (is_todo != 0); + data->todo_do_loop=1; +} + +int winetest_loop_todo(void) +{ + struct winetest_thread_data *data = winetest_get_thread_data(); + int do_loop=data->todo_do_loop; + data->todo_do_loop=0; + return do_loop; +} + +void winetest_end_todo(void) +{ + struct winetest_thread_data *data = winetest_get_thread_data(); + data->todo_level >>= 1; +} + +void winetest_start_nocount(unsigned int flags) +{ + struct winetest_thread_data *data = winetest_get_thread_data(); + + /* The lowest 2 bits of nocount_level specify whether counting of successes + and/or failures is disabled. For each nested level the bits are shifted + left, the new lowest 2 bits are copied from the previous state and ored + with the new mask. This allows nested handling of both states up tp a + level of 16. */ + flags |= data->nocount_level & 3; + data->nocount_level = (data->nocount_level << 2) | flags; + data->todo_do_loop = 1; +} + +int winetest_loop_nocount(void) +{ + struct winetest_thread_data *data = winetest_get_thread_data(); + int do_loop = data->todo_do_loop; + data->todo_do_loop = 0; + return do_loop; +} + +void winetest_end_nocount(void) +{ + struct winetest_thread_data *data = winetest_get_thread_data(); + data->nocount_level >>= 2; +} + +void __cdecl winetest_push_context(const char* fmt, ...) +{ + struct winetest_thread_data *data = winetest_get_thread_data(); + va_list valist; + + if (data->context_count < ARRAY_SIZE(data->context)) + { + va_start(valist, fmt); + vsnprintf(data->context[data->context_count], sizeof(data->context[data->context_count]), fmt, valist); + va_end(valist); + data->context[data->context_count][sizeof(data->context[data->context_count]) - 1] = 0; + } + ++data->context_count; +} + +void winetest_pop_context(void) +{ + struct winetest_thread_data *data = winetest_get_thread_data(); + + if (data->context_count) + --data->context_count; +} + +#if 0 +int winetest_get_mainargs( char*** pargv ) +{ + *pargv = winetest_argv; + return winetest_argc; +} +#endif + +LONG winetest_get_failures(void) +{ + return winetest_failures; +} + +LONG winetest_get_successes(void) +{ + return winetest_successes; +} + +void winetest_add_failures( LONG new_failures ) +{ + while (new_failures-- > 0) + InterlockedIncrement( &winetest_failures ); +} + +#if 0 +static char *_strdup(const char *str) +{ + char* ptr = malloc((strlen(str)+1)*sizeof(char)); + if (ptr) + strcpy(ptr, str); + return ptr; +} +#endif + +/* Initialize testing support */ +static void init_test( const struct test *test ) +{ + char p[128]; + +#if 0 + winetest_argc = __argc; + winetest_argv = __argv; +#endif + + // if (GetEnvironmentVariableA( "WINETEST_PLATFORM", p, sizeof(p) )) winetest_platform = _strdup(p); + if (GetEnvironmentVariableA( "WINETEST_DEBUG", p, sizeof(p) )) winetest_debug = atoi(p); + if (GetEnvironmentVariableA( "WINETEST_INTERACTIVE", p, sizeof(p) )) winetest_interactive = atoi(p); + if (GetEnvironmentVariableA( "WINETEST_REPORT_SUCCESS", p, sizeof(p) )) report_success = atoi(p); + + if (!winetest_interactive) SetErrorMode( SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX ); + + current_test = test; + winetest_successes = winetest_failures = winetest_skipped = winetest_todo_successes = winetest_todo_failures = 0; + tls_index = TlsAlloc(); +} + +/* Executed once test is finished: shows results and returns exit status */ +static int fini_test(void) +{ + int status; + const struct test *test = current_test; + + /* show test results even if traces are disabled */ + /*if (winetest_debug)*/ + { + //winetest_printf + winetest_print_location( "\n%s: %d tests executed (%d marked as todo, %d %s), %d skipped.\n", + test->name, (int)(winetest_successes + winetest_failures + winetest_todo_successes + winetest_todo_failures), + (int)winetest_todo_successes, (int)(winetest_failures + winetest_todo_failures), + (winetest_failures + winetest_todo_failures != 1) ? "failures" : "failure", + (int)winetest_skipped ); + } + status = (winetest_failures + winetest_todo_failures < 255) ? winetest_failures + winetest_todo_failures : 255; + return status; +} + +#endif /* STANDALONE */ + +// Some helpful definitions + +#define ok_hex_(file, line, expression, result) \ + do { \ + int _value = (expression); \ + int _result = (result); \ + ok_(file, line)(_value == _result, "Wrong value for '%s', expected: " #result " (0x%x), got: 0x%x\n", \ + #expression, _result, _value); \ + } while (0) +#define ok_hex(expression, result) ok_hex_(__RELFILE__, __LINE__, expression, result) + +#define ok_dec_(file, line, expression, result) \ + do { \ + int _value = (expression); \ + int _result = (result); \ + ok_(file, line)(_value == _result, "Wrong value for '%s', expected: " #result " (%d), got: %d\n", \ + #expression, _result, _value); \ + } while (0) +#define ok_dec(expression, result) ok_dec_(__RELFILE__, __LINE__, expression, result) + +#define ok_ptr_(file, line, expression, result) \ + do { \ + const void *_value = (expression); \ + const void *_result = (result); \ + ok_(file, line)(_value == _result, "Wrong value for '%s', expected: " #result " (%p), got: %p\n", \ + #expression, _result, _value); \ + } while (0) +#define ok_ptr(expression, result) ok_ptr_(__RELFILE__, __LINE__, expression, result) + +#define ok_size_t_(file, line, expression, result) \ + do { \ + size_t _value = (expression); \ + size_t _result = (result); \ + ok_(file, line)(_value == _result, "Wrong value for '%s', expected: " #result " (%Ix), got: %Ix\n", \ + #expression, _result, _value); \ + } while (0) +#define ok_size_t(expression, result) ok_size_t_(__RELFILE__, __LINE__, expression, result) + +#define ok_char(expression, result) ok_hex(expression, result) + +#define ok_err_(file, line, error) \ + ok_(file, line)(GetLastError() == (error), "Wrong last error. Expected " #error ", got 0x%lx\n", GetLastError()) +#define ok_err(error) ok_err_(__RELFILE__, __LINE__, error) + +#define ok_str_(file, line, x, y) \ + ok_(file, line)(strcmp(x, y) == 0, "Wrong string. Expected '%s', got '%s'\n", y, x) +#define ok_str(x, y) ok_str_(__RELFILE__, __LINE__, x, y) + +#define ok_wstr_(file, line, x, y) \ + ok_(file, line)(wcscmp(x, y) == 0, "Wrong string. Expected '%S', got '%S'\n", y, x) +#define ok_wstr(x, y) ok_wstr_(__RELFILE__, __LINE__, x, y) + +#define ok_long(expression, result) ok_hex(expression, result) +#define ok_int(expression, result) ok_dec(expression, result) +#define ok_int_(file, line, expression, result) ok_dec_(file, line, expression, result) +#define ok_ntstatus(status, expected) ok_hex(status, expected) +#define ok_hdl ok_ptr + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __MINI_WINE_TEST_H */ diff --git a/modules/rostests/win32/winlogon/wlntfytests/wlntfytests.c b/modules/rostests/win32/winlogon/wlntfytests/wlntfytests.c index 68684b42555..24d1f9a5729 100644 --- a/modules/rostests/win32/winlogon/wlntfytests/wlntfytests.c +++ b/modules/rostests/win32/winlogon/wlntfytests/wlntfytests.c @@ -10,6 +10,8 @@ * - https://rsdn.org/article/baseserv/winlogon.xml */ +/* HEADERS *******************************************************************/ + /* C Headers */ #include @@ -27,11 +29,28 @@ #include -#include -WINE_DEFAULT_DEBUG_CHANNEL(wlnotify_tests); +#define STANDALONE +#include "minitest.h" // Includes also -#define NOTIFY_PKG_NAME L"WLNotifyTests" -#define NOTIFY_REG_PATH L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Notify\\" NOTIFY_PKG_NAME +#define WINE_DEFAULT_DEBUG_CHANNEL_EX(ch, flags) \ + static struct __wine_debug_channel __wine_dbch_##ch = { (unsigned char)(flags), #ch }; \ + static struct __wine_debug_channel * const __wine_dbch___default = &__wine_dbch_##ch + +// WINE_DEFAULT_DEBUG_CHANNEL(wlnotify_tests); +WINE_DEFAULT_DEBUG_CHANNEL_EX(wlnotify_tests, + (1 << __WINE_DBCL_TRACE) | (1 << __WINE_DBCL_WARN) | + (1 << __WINE_DBCL_ERR) | (1 << __WINE_DBCL_FIXME)); + + +/* GLOBALS *******************************************************************/ + +#define NOTIFY_PKG_NAME L"WLNotifyTests" +#define NOTIFY_REG_PATH L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Notify\\" NOTIFY_PKG_NAME + +#define DEFAULT_WINSTA0 L"WinSta0" +#define DESKTOP_WINLOGON L"Winlogon" +#define DESKTOP_DEFAULT L"Default" +#define DESKTOP_SCRSAVE L"Screen-saver" typedef enum _WLNOTIFY_STATE { @@ -70,7 +89,40 @@ static const PCSTR NotifyStateName[] = static WLNOTIFY_STATE g_CurrentState = WLNotify_NonInitialized; static WLNOTIFY_STATE g_PreviousState = WLNotify_NonInitialized; +static ULONG g_fLogoffShutdownFlags = EWX_LOGOFF; static HMODULE g_hModule = NULL; +static BOOL g_bInitialized = FALSE; ///< TRUE when the flags have been initialized. +static struct { + UINT8 bAsync : 1; + UINT8 bImpersonate : 1; + UINT8 Reserved : 6; +} g_fFlags = {0}; + +FORCEINLINE +BOOL +IsUserLoggedIn( + _In_ WLNOTIFY_STATE State) +{ + /* + * - The user is not logged in when State is either of: + * WLNotify_NonInitialized, WLNotify_Startup, WLNotify_Shutdown, + * WLNotify_Logoff, and perhaps (TODO TBD.): WLNotify_Disconnect, + * WLNotify_Reconnect. + * + * - The user is logged in when State is either of: + * WLNotify_Logon, WLNotify_StartShell, WLNotify_PostShell, + * WLNotify_Lock, WLNotify_Unlock. + * + * Note that WLNotify_StartScreenSaver and WLNotify_StopScreenSaver + * are not reliable for this test, because they can be invoked from + * either a logged-off or a logged-on state. + */ + return ((State == WLNotify_Logon) || + (State >= WLNotify_StartShell && State <= WLNotify_Unlock)); +} + + +/* DEBUGGING HELPERS *********************************************************/ #if 0 void DPRINTF_FN(PCSTR FunctionName, PCWSTR Format, ...) @@ -122,6 +174,71 @@ NotifyStateToName( return NotifyStateName[i]; } + +/* SYSTEM INFORMATION HELPERS ************************************************/ + +/** + * @brief + * Retrieves the notification settings from the registry. + **/ +static VOID +GetSettings(VOID) +{ + HKEY hNotifyKey; + LSTATUS lError; + DWORD dwValue, dwSize, dwType; + + if (InterlockedCompareExchange((PLONG)&g_bInitialized, TRUE, FALSE)) + return; + + lError = RegOpenKeyExW(HKEY_LOCAL_MACHINE, + NOTIFY_REG_PATH, + 0, + KEY_QUERY_VALUE, + &hNotifyKey); + if (lError != ERROR_SUCCESS) + { + ERR("RegOpenKeyExW() failed; error %lu\n", lError); + return; // HRESULT_FROM_WIN32(lError); + } + + dwSize = sizeof(dwValue); + lError = RegQueryValueExW(hNotifyKey, + L"Asynchronous", + NULL, + &dwType, + (PBYTE)&dwValue, + &dwSize); + if ((lError == ERROR_SUCCESS) && (dwType == REG_DWORD) && (dwSize == sizeof(dwValue))) + g_fFlags.bAsync = !!dwValue; + + dwSize = sizeof(dwValue); + lError = RegQueryValueExW(hNotifyKey, + L"Impersonate", + NULL, + &dwType, + (PBYTE)&dwValue, + &dwSize); + if ((lError == ERROR_SUCCESS) && (dwType == REG_DWORD) && (dwSize == sizeof(dwValue))) + g_fFlags.bImpersonate = !!dwValue; + +#if 0 + dwSize = sizeof(dwValue); + lError = RegQueryValueExW(hNotifyKey, + L"MaxWait", + NULL, + &dwType, + (PBYTE)&dwValue, + &dwSize); + if ((lError == ERROR_SUCCESS) && (dwType == REG_DWORD) && (dwSize == sizeof(dwValue))) + g_dwMaxWait = !!dwValue; +#endif + + RegCloseKey(hNotifyKey); + + return; // S_OK; +} + /** * @brief * Retrieves the name of the specified window station or desktop object. @@ -190,75 +307,349 @@ GetUserObjectName( return pszBuffer; } +/** + * @brief + * Retrieves the user and domain names corresponding to the given token. + * + * @param[in] hToken + * Handle to the user token, for which to retrieve the user and domain names. + * + * @param[out] UserName + * Receives in output, a pointer to an allocated string representing + * the user name. The string is allocated with LocalAlloc(). After usage, + * free the pointer with LocalFree(). + * + * @param[out] DomainName + * Receives in output, a pointer to an allocated string representing + * the domain name. The string is allocated with LocalAlloc(). After usage, + * free the pointer with LocalFree(). + * + * @return + * TRUE if the information has been retrieved successfully; FALSE if not. + * + * @see + * Copied and adapted from dll/win32/userenv/environment.c!GetUserAndDomainName + * See also base/shell/progman/main.c!GetUserAndDomainName + **/ +static BOOL +GetUserAndDomainName( + _In_ HANDLE hToken, + _Out_ PWSTR* UserName, + _Out_ PWSTR* DomainName) +{ + BOOL bRet = FALSE; + DWORD cbTokenBuffer = 0; + PTOKEN_USER pUserToken; + + PWSTR pUserName = NULL; + PWSTR pDomainName = NULL; + DWORD cbUserName = 0; + DWORD cbDomainName = 0; + + SID_NAME_USE SidNameUse; + + /* Retrieve token's information */ + if (!GetTokenInformation(hToken, TokenUser, NULL, 0, &cbTokenBuffer)) + { + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return FALSE; + } + + pUserToken = LocalAlloc(LMEM_FIXED, cbTokenBuffer); + if (!pUserToken) + return FALSE; + + if (!GetTokenInformation(hToken, TokenUser, pUserToken, cbTokenBuffer, &cbTokenBuffer)) + { + LocalFree(pUserToken); + return FALSE; + } + + /* Retrieve the domain and user name */ + if (!LookupAccountSidW(NULL, + pUserToken->User.Sid, + NULL, + &cbUserName, + NULL, + &cbDomainName, + &SidNameUse)) + { + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + goto done; + } + + pUserName = LocalAlloc(LPTR, cbUserName * sizeof(WCHAR)); + if (!pUserName) + goto done; + + pDomainName = LocalAlloc(LPTR, cbDomainName * sizeof(WCHAR)); + if (!pDomainName) + goto done; + + if (!LookupAccountSidW(NULL, + pUserToken->User.Sid, + pUserName, + &cbUserName, + pDomainName, + &cbDomainName, + &SidNameUse)) + { + goto done; + } + + *UserName = pUserName; + *DomainName = pDomainName; + bRet = TRUE; + +done: + if (bRet == FALSE) + { + if (pUserName) + LocalFree(pUserName); + if (pDomainName) + LocalFree(pDomainName); + } + LocalFree(pUserToken); + + return bRet; +} + + +typedef struct _SYSTEM_USER_INFO +{ + HANDLE hProcToken, hThrdToken; + PWSTR pProcUserName, pProcDomainName; + PWSTR pThrdUserName, pThrdDomainName; + PWSTR pNotifUserName, pNotifDomainName; + HWINSTA hWinSta; + HDESK hThreadDesk, hInputDesk; + PWSTR pszWinSta, pszThreadDesk, pszInputDesk, pszNotifDesk; + // BYTE Data[ANYSIZE_ARRAY]; +} SYSTEM_USER_INFO, *PSYSTEM_USER_INFO; + +static PSYSTEM_USER_INFO +GetSystemUserInfo( + _In_ HANDLE hNotifToken, + _In_ HDESK hNotifDesktop) +{ + SYSTEM_USER_INFO LocalInfo = {NULL}, *pSysUserInfo; + PWSTR pString; + SIZE_T BufSize; + WCHAR szBuffer0[MAX_PATH], szBuffer1[MAX_PATH], szBuffer2[MAX_PATH], szBuffer3[MAX_PATH]; + + /* Retrieve the process' token and the corresponding user/domain */ + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &LocalInfo.hProcToken)) + { + GetUserAndDomainName(LocalInfo.hProcToken, &LocalInfo.pProcUserName, &LocalInfo.pProcDomainName); + // CloseHandle(LocalInfo.hProcToken); + } + + /* Retrieve the current thread's impersonation token, if any (otherwise + * no impersonation occurs), and the corresponding user/domain */ + if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &LocalInfo.hThrdToken)) + { + GetUserAndDomainName(LocalInfo.hThrdToken, &LocalInfo.pThrdUserName, &LocalInfo.pThrdDomainName); + // CloseHandle(LocalInfo.hThrdToken); + } + + /* Retrieve the notification's corresponding user/domain */ + GetUserAndDomainName(hNotifToken, &LocalInfo.pNotifUserName, &LocalInfo.pNotifDomainName); + + /* Retrieve the process' window station name */ + LocalInfo.hWinSta = GetProcessWindowStation(); + LocalInfo.pszWinSta = GetUserObjectName(LocalInfo.hWinSta, szBuffer0, _countof(szBuffer0)); + + /* Retrieve the thread desktop name */ + LocalInfo.hThreadDesk = GetThreadDesktop(GetCurrentThreadId()); + LocalInfo.pszThreadDesk = GetUserObjectName(LocalInfo.hThreadDesk, szBuffer1, _countof(szBuffer1)); + + /* Retrieve the input desktop name */ + LocalInfo.hInputDesk = OpenInputDesktop(0, FALSE, STANDARD_RIGHTS_READ); + LocalInfo.pszInputDesk = GetUserObjectName(LocalInfo.hInputDesk, szBuffer2, _countof(szBuffer2)); + // CloseDesktop(LocalInfo.hInputDesk); + + /* Retrieve the notification desktop name */ + LocalInfo.pszNotifDesk = GetUserObjectName(hNotifDesktop, szBuffer3, _countof(szBuffer3)); + + + /* Allocate a single buffer to hold all the retrieved information */ + BufSize = sizeof(*pSysUserInfo) + + ((LocalInfo.pProcUserName ? wcslen(LocalInfo.pProcUserName) + 1 : 0) + + (LocalInfo.pProcDomainName ? wcslen(LocalInfo.pProcDomainName) + 1 : 0) + + (LocalInfo.pThrdUserName ? wcslen(LocalInfo.pThrdUserName) + 1 : 0) + + (LocalInfo.pThrdDomainName ? wcslen(LocalInfo.pThrdDomainName) + 1 : 0) + + (LocalInfo.pNotifUserName ? wcslen(LocalInfo.pNotifUserName) + 1 : 0) + + (LocalInfo.pNotifDomainName ? wcslen(LocalInfo.pNotifDomainName) + 1 : 0) + + (LocalInfo.pszWinSta ? wcslen(LocalInfo.pszWinSta) + 1 : 0) + + (LocalInfo.pszThreadDesk ? wcslen(LocalInfo.pszThreadDesk) + 1 : 0) + + (LocalInfo.pszInputDesk ? wcslen(LocalInfo.pszInputDesk) + 1 : 0) + + (LocalInfo.pszNotifDesk ? wcslen(LocalInfo.pszNotifDesk) + 1 : 0)) * sizeof(WCHAR); + pSysUserInfo = LocalAlloc(LMEM_FIXED, BufSize); + if (!pSysUserInfo) + goto Done; + + /* Copy the fixed part of the structure */ + *pSysUserInfo = LocalInfo; + + /* Copy the strings and adjust the pointers */ + pString = (PWSTR)(pSysUserInfo + 1); + if (LocalInfo.pProcUserName) + { + wcscpy(pString, LocalInfo.pProcUserName); + pSysUserInfo->pProcUserName = pString; + pString += wcslen(pString) + 1; + } + if (LocalInfo.pProcDomainName) + { + wcscpy(pString, LocalInfo.pProcDomainName); + pSysUserInfo->pProcDomainName = pString; + pString += wcslen(pString) + 1; + } + if (LocalInfo.pThrdUserName) + { + wcscpy(pString, LocalInfo.pThrdUserName); + pSysUserInfo->pThrdUserName = pString; + pString += wcslen(pString) + 1; + } + if (LocalInfo.pThrdDomainName) + { + wcscpy(pString, LocalInfo.pThrdDomainName); + pSysUserInfo->pThrdDomainName = pString; + pString += wcslen(pString) + 1; + } + if (LocalInfo.pNotifUserName) + { + wcscpy(pString, LocalInfo.pNotifUserName); + pSysUserInfo->pNotifUserName = pString; + pString += wcslen(pString) + 1; + } + if (LocalInfo.pNotifDomainName) + { + wcscpy(pString, LocalInfo.pNotifDomainName); + pSysUserInfo->pNotifDomainName = pString; + pString += wcslen(pString) + 1; + } + if (LocalInfo.pszWinSta) + { + wcscpy(pString, LocalInfo.pszWinSta); + pSysUserInfo->pszWinSta = pString; + pString += wcslen(pString) + 1; + } + if (LocalInfo.pszThreadDesk) + { + wcscpy(pString, LocalInfo.pszThreadDesk); + pSysUserInfo->pszThreadDesk = pString; + pString += wcslen(pString) + 1; + } + if (LocalInfo.pszInputDesk) + { + wcscpy(pString, LocalInfo.pszInputDesk); + pSysUserInfo->pszInputDesk = pString; + pString += wcslen(pString) + 1; + } + if (LocalInfo.pszNotifDesk) + { + wcscpy(pString, LocalInfo.pszNotifDesk); + pSysUserInfo->pszNotifDesk = pString; + } + +Done: + if (LocalInfo.pszNotifDesk != szBuffer3) + LocalFree(LocalInfo.pszNotifDesk); + if (LocalInfo.pszInputDesk != szBuffer2) + LocalFree(LocalInfo.pszInputDesk); + if (LocalInfo.pszThreadDesk != szBuffer1) + LocalFree(LocalInfo.pszThreadDesk); + if (LocalInfo.pszWinSta != szBuffer0) + LocalFree(LocalInfo.pszWinSta); + + if (LocalInfo.pNotifUserName) + LocalFree(LocalInfo.pNotifUserName); + if (LocalInfo.pNotifDomainName) + LocalFree(LocalInfo.pNotifDomainName); + if (LocalInfo.pThrdUserName) + LocalFree(LocalInfo.pThrdUserName); + if (LocalInfo.pThrdDomainName) + LocalFree(LocalInfo.pThrdDomainName); + if (LocalInfo.pProcUserName) + LocalFree(LocalInfo.pProcUserName); + if (LocalInfo.pProcDomainName) + LocalFree(LocalInfo.pProcDomainName); + + /* If we failed to allocate the buffer, close the opened handles */ + if (!pSysUserInfo) + { + CloseDesktop(LocalInfo.hInputDesk); + CloseHandle(LocalInfo.hThrdToken); + CloseHandle(LocalInfo.hProcToken); + } + + return pSysUserInfo; +} + +static VOID +FreeSystemUserInfo( + _Inout_ PSYSTEM_USER_INFO SysUserInfo) +{ + CloseHandle(SysUserInfo->hProcToken); + CloseHandle(SysUserInfo->hThrdToken); + CloseDesktop(SysUserInfo->hInputDesk); + LocalFree(SysUserInfo); +} + + static VOID DumpNotificationState( _In_ PCSTR FileName, _In_ INT LineNum, _In_ PCSTR FuncName, + _In_ PSYSTEM_USER_INFO SysUserInfo, _In_ PWLX_NOTIFICATION_INFO pInfo) { - HWINSTA hWinSta; - HDESK hThreadDesk, hInputDesk; - PWSTR pszWinSta, pszNotifDesk, pszThreadDesk, pszInputDesk; - WCHAR szBuffer0[MAX_PATH], szBuffer1[MAX_PATH], szBuffer2[MAX_PATH], szBuffer3[MAX_PATH]; - - /* Retrieve the process' window station name */ - hWinSta = GetProcessWindowStation(); - pszWinSta = GetUserObjectName(hWinSta, szBuffer0, _countof(szBuffer0)); - - /* Retrieve the notification desktop name */ - pszNotifDesk = GetUserObjectName(pInfo->hDesktop, szBuffer1, _countof(szBuffer1)); - - /* Retrieve the thread desktop name */ - hThreadDesk = GetThreadDesktop(GetCurrentThreadId()); - pszThreadDesk = GetUserObjectName(hThreadDesk, szBuffer2, _countof(szBuffer2)); - - /* Retrieve the input desktop name */ - hInputDesk = OpenInputDesktop(0, FALSE, STANDARD_RIGHTS_READ); - pszInputDesk = GetUserObjectName(hInputDesk, szBuffer3, _countof(szBuffer3)); - CloseDesktop(hInputDesk); - /* * Dump the information and the WLX_NOTIFICATION_INFO structure. */ + // TRACE( ros_dbg_log(__WINE_DBCL_ERR, __wine_dbch___default, FileName, FuncName, LineNum, - /*TRACE(*/"\nWLNOTIFY(%lx.%lx): Entering `%s`\n" - "\tProcess WinSta : 0x%p '%S'\n" - "\tNotif Desktop : 0x%p '%S'\n" - "\tThread Desktop : 0x%p '%S'\n" - "\tInput Desktop : 0x%p '%S'\n" - "\tInfo.Size : %lu\n" - "\tInfo.Flags : 0x%lx\n" - "\tInfo.UserName : '%S'\n" - "\tInfo.Domain : '%S'\n" - "\tInfo.WindowStation : '%S'\n" - "\tInfo.hToken : 0x%p\n" - "\tInfo.hDesktop : 0x%p\n" - "\tInfo.pStatusCallback: 0x%p\n", - GetCurrentProcessId(), // NtCurrentTeb()->ClientId.UniqueProcess - GetCurrentThreadId(), // NtCurrentTeb()->ClientId.UniqueThread - FuncName, - hWinSta, pszWinSta, - pInfo->hDesktop, pszNotifDesk, - hThreadDesk, pszThreadDesk, - hInputDesk, pszInputDesk, - pInfo->Size, pInfo->Flags, pInfo->UserName, - pInfo->Domain, pInfo->WindowStation, pInfo->hToken, - pInfo->hDesktop, pInfo->pStatusCallback); - - if (pszInputDesk != szBuffer3) - LocalFree(pszInputDesk); - if (pszThreadDesk != szBuffer2) - LocalFree(pszThreadDesk); - if (pszNotifDesk != szBuffer1) - LocalFree(pszNotifDesk); - if (pszWinSta != szBuffer0) - LocalFree(pszWinSta); + "\nWLNOTIFY(%lx.%lx) [Async: %s, Impers: %s]: Entering `%s`\n" + "\tProcess Token : 0x%p - User: '%S\\%S'\n" + "\tThread Token : 0x%p - User: '%S\\%S'\n" + "\tNotif Token : 0x%p - User: '%S\\%S'\n" + "\tProcess WinSta : 0x%p '%S'\n" + "\tThread Desktop : 0x%p '%S'\n" + "\tInput Desktop : 0x%p '%S'\n" + "\tNotif Desktop : 0x%p '%S'\n", + GetCurrentProcessId(), // NtCurrentTeb()->ClientId.UniqueProcess + GetCurrentThreadId(), // NtCurrentTeb()->ClientId.UniqueThread + g_fFlags.bAsync ? "TRUE" : "FALSE", + g_fFlags.bImpersonate ? "TRUE" : "FALSE", + FuncName, + SysUserInfo->hProcToken, SysUserInfo->pProcDomainName, SysUserInfo->pProcUserName, + SysUserInfo->hThrdToken, SysUserInfo->pThrdDomainName, SysUserInfo->pThrdUserName, + pInfo->hToken, SysUserInfo->pNotifDomainName, SysUserInfo->pNotifUserName, + SysUserInfo->hWinSta, SysUserInfo->pszWinSta, + SysUserInfo->hThreadDesk, SysUserInfo->pszThreadDesk, + SysUserInfo->hInputDesk, SysUserInfo->pszInputDesk, + pInfo->hDesktop, SysUserInfo->pszNotifDesk); + // if (__WINE_IS_DEBUG_ON(_ERR, __wine_dbch___default)) + DPRINTF( + "\tInfo.Size : %lu\n" + "\tInfo.Flags : 0x%lx\n" + "\tInfo.UserName : '%S'\n" + "\tInfo.Domain : '%S'\n" + "\tInfo.WindowStation : '%S'\n" + "\tInfo.hToken : 0x%p\n" + "\tInfo.hDesktop : 0x%p\n" + "\tInfo.pStatusCallback: 0x%p\n", + pInfo->Size, pInfo->Flags, pInfo->UserName, + pInfo->Domain, pInfo->WindowStation, pInfo->hToken, + pInfo->hDesktop, pInfo->pStatusCallback); } -#define DUMP_WLX_NOTIFICATION(pInfo) \ - DumpNotificationState(__RELFILE__, __LINE__, __FUNCTION__, pInfo) +#define DUMP_WLX_NOTIFICATION(SysUserInfo, pInfo) \ + DumpNotificationState(__RELFILE__, __LINE__, __FUNCTION__, (SysUserInfo), (pInfo)) + static VOID ChangeNotificationState( @@ -268,9 +659,10 @@ ChangeNotificationState( _In_ BOOL bChange, _In_ WLNOTIFY_STATE NewState) { + // TRACE( ros_dbg_log(__WINE_DBCL_ERR, __wine_dbch___default, FileName, FuncName, LineNum, - /*TRACE(*/"**** %s: %s state %s %s %s\n", + "**** %s: %s state %s %s %s\n", FuncName, bChange ? "Changing" : "Restoring", NotifyStateToName(g_CurrentState), @@ -283,6 +675,7 @@ ChangeNotificationState( #define CHANGE_STATE(bChange, NewState) \ ChangeNotificationState(__RELFILE__, __LINE__, __FUNCTION__, (bChange), (NewState)) + static DWORD DisplayWlxMessageW( _In_opt_ PFNMSGECALLBACK pStatusCallback, @@ -329,328 +722,558 @@ DisplayWlxMessageA( } -/* NOTIFICATION CALLBACKS ****************************************************/ +/* TESTING SUPPORT HELPERS ***************************************************/ -/** - * @brief Invoked on system startup. - **/ -VOID -WINAPI -WLEventStartup( - _In_ PWLX_NOTIFICATION_INFO pInfo) -{ - DUMP_WLX_NOTIFICATION(pInfo); - - DisplayWlxMessageA(pInfo->pStatusCallback, FALSE, __FUNCTION__); - - /* We must be called from the non-initialized state */ - if (g_CurrentState != WLNotify_NonInitialized) +#define BEGIN_TEST \ +do { \ + static const struct test winetest_testlist[] = { { __FUNCTION__, NULL } }; \ + init_test( &winetest_testlist[0] ); \ + winetest_push_context("**** %s", __FUNCTION__); \ { - ERR("**** %s: ERROR: Wrong state %s, expected %s\n", - __FUNCTION__, - NotifyStateToName(g_CurrentState), - NotifyStateToName(WLNotify_NonInitialized)); + +#define END_TEST \ + } \ + winetest_pop_context(); \ + (void)fini_test(); \ +} while(0); + +#define ok_state_1(state1) \ + ok(g_CurrentState == (state1), \ + "ERROR: Wrong state %s, expected %s\n", \ + NotifyStateToName(g_CurrentState), \ + NotifyStateToName(state1)) + +#define ok_state_2(state1, state2) \ + ok(g_CurrentState == (state1) || g_CurrentState == (state2), \ + "ERROR: Wrong state %s, expected %s or %s\n", \ + NotifyStateToName(g_CurrentState), \ + NotifyStateToName(state1), \ + NotifyStateToName(state2)) + +#define ok_state_3(state1, state2, state3) \ + ok(g_CurrentState == (state1) || g_CurrentState == (state2) || g_CurrentState == (state3), \ + "ERROR: Wrong state %s, expected %s or %s or %s\n", \ + NotifyStateToName(g_CurrentState), \ + NotifyStateToName(state1), \ + NotifyStateToName(state2), \ + NotifyStateToName(state3)) + +typedef struct _TEST_ENTRY +{ + _Field_size_(NumStates) WLNOTIFY_STATE* AllowedStates; + ULONG NumStates; ///< Number of elements in the AllowedStates array. + // BOOL bProcToken; + // PCWSTR pProcUserName, pProcDomainName; + BOOL bThrdToken, bUserLoggedIn; ///< When bUserLoggedIn is TRUE we expect a valid notification user token. + PCWSTR pszWinSta, pszThreadDesk; + _Maybenull_ PCWSTR pszInputDesk; + PCWSTR pszNotifDesk; +} TEST_ENTRY, *PTEST_ENTRY; + +static void +DoTest(const char* file, int line, const char* funcname, + _In_ PSYSTEM_USER_INFO SysUserInfo, + _In_ PWLX_NOTIFICATION_INFO pInfo, + _In_ PTEST_ENTRY pTest) +{ + UNREFERENCED_PARAMETER(file); + UNREFERENCED_PARAMETER(line); + UNREFERENCED_PARAMETER(funcname); // Displayed via winetest_push_context() + + if (pTest->NumStates == 1) + ok_state_1(pTest->AllowedStates[0]); + else if (pTest->NumStates == 2) + ok_state_2(pTest->AllowedStates[0], pTest->AllowedStates[1]); + else if (pTest->NumStates == 3) + ok_state_3(pTest->AllowedStates[0], pTest->AllowedStates[1], pTest->AllowedStates[2]); + else + skip("Unsupported number of allowed notify states: %lu\n", pTest->NumStates); + + ok(SysUserInfo->hProcToken != NULL, "Expected hProcToken != NULL\n"); + ok(SysUserInfo->pProcDomainName != NULL, "Expected pProcDomainName != NULL\n"); + ok(SysUserInfo->pProcUserName != NULL, "Expected pProcUserName != NULL\n"); + + if (pTest->bThrdToken) + { + ok(SysUserInfo->hThrdToken != NULL, "Expected hThrdToken != NULL\n"); + + ok(SysUserInfo->pThrdDomainName != NULL, "Expected pThrdDomainName != NULL\n"); + if (!SysUserInfo->pThrdDomainName || !SysUserInfo->pNotifDomainName) + skip("Skipped pThrdDomainName/pNotifDomainName test\n"); + else + ok_wstr(SysUserInfo->pThrdDomainName, SysUserInfo->pNotifDomainName); + + ok(SysUserInfo->pThrdUserName != NULL, "Expected pThrdUserName != NULL\n"); + if (!SysUserInfo->pThrdUserName || !SysUserInfo->pNotifUserName) + skip("Skipped pThrdUserName/pNotifUserName test\n"); + else + ok_wstr(SysUserInfo->pThrdUserName, SysUserInfo->pNotifUserName); } + else + { + ok(SysUserInfo->hThrdToken == NULL, "Expected hThrdToken == NULL\n"); + ok(SysUserInfo->pThrdDomainName == NULL, "Expected pThrdDomainName == NULL\n"); + ok(SysUserInfo->pThrdUserName == NULL, "Expected pThrdUserName == NULL\n"); + } + + if (pTest->bUserLoggedIn) + { + /* See also pInfo->UserName and pInfo->Domain below */ + ok(pInfo->hToken != NULL, "Expected pInfo->hToken != NULL\n"); + ok(SysUserInfo->pNotifDomainName != NULL, "Expected pNotifDomainName != NULL\n"); + ok(SysUserInfo->pNotifUserName != NULL, "Expected pNotifUserName != NULL\n"); + } + else + { + ok(pInfo->hToken == NULL, "Expected pInfo->hToken == NULL\n"); + ok(SysUserInfo->pNotifDomainName == NULL, "Expected pNotifDomainName == NULL\n"); + ok(SysUserInfo->pNotifUserName == NULL, "Expected pNotifUserName == NULL\n"); + } + + // if (pTest->pszWinSta) // Expected winsta -- DEFAULT_WINSTA0 + ok(SysUserInfo->hWinSta != NULL, "Expected hProcWinSta != NULL\n"); + ok(SysUserInfo->pszWinSta != NULL, "Expected pszWinSta != NULL\n"); + if (!SysUserInfo->pszWinSta) + skip("Skipped pszWinSta test\n"); + else + ok_wstr(SysUserInfo->pszWinSta, /*pTest->pszWinSta*/ DEFAULT_WINSTA0); + + // if (pTest->pszThreadDesk) // Expected thread desk -- DESKTOP_WINLOGON + ok(SysUserInfo->hThreadDesk != NULL, "Expected hThreadDesk != NULL\n"); + ok(SysUserInfo->pszThreadDesk != NULL, "Expected pszThreadDesk != NULL\n"); + if (!SysUserInfo->pszThreadDesk) + skip("Skipped pszThreadDesk test\n"); + else + ok_wstr(SysUserInfo->pszThreadDesk, DESKTOP_WINLOGON); + + if (pTest->pszInputDesk) + { + ok(SysUserInfo->hInputDesk != NULL, "Expected hInputDesk != NULL\n"); + ok(SysUserInfo->pszInputDesk != NULL, "Expected pszInputDesk != NULL\n"); + if (!SysUserInfo->pszInputDesk) + skip("Skipped pszInputDesk test\n"); + else + ok_wstr(SysUserInfo->pszInputDesk, pTest->pszInputDesk); + } + else + { + ok(SysUserInfo->hInputDesk == NULL, "Expected hInputDesk == NULL\n"); + ok(SysUserInfo->pszInputDesk == NULL, "Expected pszInputDesk == NULL\n"); + } + + // if (pTest->pszNotifDesk) + ok(pInfo->hDesktop != NULL, "Expected pInfo->hDesktop != NULL\n"); + ok(SysUserInfo->pszNotifDesk != NULL, "Expected pszNotifDesk != NULL\n"); + if (!SysUserInfo->pszNotifDesk) + skip("Skipped pszNotifDesk test\n"); + else + ok_wstr(SysUserInfo->pszNotifDesk, pTest->pszNotifDesk); + + ok_int(pInfo->Size, sizeof(*pInfo)); // == sizeof(WLX_NOTIFICATION_INFO); + // ok_int(pInfo->Flags, 0); // This is tested separately: more than one flag is possible. + + if (pTest->bUserLoggedIn) + { + ok(pInfo->UserName != NULL, "Expected pInfo->UserName != NULL\n"); + ok(pInfo->Domain != NULL, "Expected pInfo->Domain != NULL\n"); + // ok(pInfo->hToken != NULL, "Expected pInfo->hToken != NULL\n"); + + if (!pInfo->UserName || !SysUserInfo->pNotifUserName) + skip("Skipped UserName/pNotifUserName test\n"); + else + ok_wstr(pInfo->UserName, SysUserInfo->pNotifUserName); + + if (!pInfo->Domain || !SysUserInfo->pNotifDomainName) + skip("Skipped Domain/pNotifDomainName test\n"); + else + ok_wstr(pInfo->Domain, SysUserInfo->pNotifDomainName); + } + else + { + ok(pInfo->UserName == NULL, "Expected pInfo->UserName == NULL\n"); + ok(pInfo->Domain == NULL, "Expected pInfo->Domain == NULL\n"); + // ok(pInfo->hToken == NULL, "Expected pInfo->hToken == NULL\n"); + } + + ok(pInfo->WindowStation != NULL, "Expected pInfo->WindowStation != NULL\n"); + if (!pInfo->WindowStation || !SysUserInfo->pszWinSta) + skip("Skipped WindowStation/pszWinSta test\n"); + else + ok_wstr(pInfo->WindowStation, SysUserInfo->pszWinSta); + + if (wcscmp(pTest->pszNotifDesk, /*pTest->pszThreadDesk*/ DESKTOP_WINLOGON) == 0) + ok(pInfo->hDesktop == SysUserInfo->hThreadDesk, "Expected pInfo->hDesktop == hThreadDesk\n"); + else + ok(pInfo->hDesktop != SysUserInfo->hThreadDesk, "Expected pInfo->hDesktop != hThreadDesk\n"); + + if (g_fFlags.bAsync) + ok(pInfo->pStatusCallback == NULL, "Expected pStatusCallback == NULL, was: 0x%p\n", pInfo->pStatusCallback); + else + ok(pInfo->pStatusCallback != NULL, "Expected pStatusCallback != NULL\n"); +} + + +/* NOTIFICATION HANDLERS *****************************************************/ + +#define __HANDLER_PARAM(pNotifInfo)(_In_ PWLX_NOTIFICATION_INFO pNotifInfo) +#define WLNOTIFY_HANDLER(name) \ + VOID WINAPI name __HANDLER_PARAM + +#define BEGIN_HANDLER /*(SysUserInfo, pInfo)*/ \ + PSYSTEM_USER_INFO SysUserInfo; \ + SysUserInfo = GetSystemUserInfo(pInfo->hToken, pInfo->hDesktop); \ + DUMP_WLX_NOTIFICATION(SysUserInfo, pInfo); \ + DisplayWlxMessageA(pInfo->pStatusCallback, FALSE, __FUNCTION__); \ DbgBreakOnEvent(); +#define END_HANDLER(bChangeState, NewState) \ + FreeSystemUserInfo(SysUserInfo); \ + /* Change (bChangeState == TRUE) or Restore previous (FALSE) state */ \ + CHANGE_STATE(bChangeState, NewState); + + +/** + * @brief Invoked at system startup. + **/ +WLNOTIFY_HANDLER(WLEventStartup)(pInfo) +{ + /* Initially retrieve the notification registry settings */ + GetSettings(); + + BEGIN_HANDLER; + + BEGIN_TEST + { + /* We must be called from the non-initialized state */ + WLNOTIFY_STATE States[] = {WLNotify_NonInitialized}; + TEST_ENTRY StartupTest = + { States, _countof(States), FALSE, FALSE, DEFAULT_WINSTA0, + DESKTOP_WINLOGON, DESKTOP_WINLOGON, DESKTOP_WINLOGON }; + + DoTest(__RELFILE__, __LINE__, __FUNCTION__, SysUserInfo, pInfo, &StartupTest); + + ok_int(pInfo->Flags, 0); + } + END_TEST + /* Change state */ - CHANGE_STATE(TRUE, WLNotify_Startup); + END_HANDLER(TRUE, WLNotify_Startup); } /** - * @brief Invoked on system shutdown. + * @brief Invoked at system shutdown. **/ -VOID -WINAPI -WLEventShutdown( - _In_ PWLX_NOTIFICATION_INFO pInfo) +WLNOTIFY_HANDLER(WLEventShutdown)(pInfo) { - DUMP_WLX_NOTIFICATION(pInfo); + BEGIN_HANDLER; - DisplayWlxMessageA(pInfo->pStatusCallback, FALSE, __FUNCTION__); - - /* We must be called from either Startup or previous Logoff */ - if (g_CurrentState != WLNotify_Startup && - g_CurrentState != WLNotify_Logoff) + BEGIN_TEST { - ERR("**** %s: ERROR: Wrong state %s, expected %s or %s\n", - __FUNCTION__, - NotifyStateToName(g_CurrentState), - NotifyStateToName(WLNotify_Startup), - NotifyStateToName(WLNotify_Logoff)); + /* We must be called from either Startup or previous Logoff */ + WLNOTIFY_STATE States[] = {WLNotify_Startup, WLNotify_Logoff}; + TEST_ENTRY ShutdownTest = + { States, _countof(States), FALSE, FALSE, DEFAULT_WINSTA0, + DESKTOP_WINLOGON, DESKTOP_WINLOGON, DESKTOP_WINLOGON }; + + DoTest(__RELFILE__, __LINE__, __FUNCTION__, SysUserInfo, pInfo, &ShutdownTest); + + /* Compare these flags with those from WLEventLogoff */ + ok_int(pInfo->Flags, g_fLogoffShutdownFlags); + /* + * - If the logged-on user chooses to shutdown or reboot the computer, + * WLEventLogoff() is first invoked with pInfo->Flags == EWX_SHUTDOWN + * or EWX_SHUTDOWN | EWX_REBOOT (stored in g_fLogoffShutdownFlags), + * then WLEventShutdown() is invoked with the same values. + * + * - If the logged-on user chooses to log off only, WLEventLogoff() + * is invoked with pInfo->Flags == 0 (== EWX_LOGOFF). + * + * - Independently of whether the user chose to shutdown or reboot + * the computer at the SAS dialog before logging in first (thus, + * g_CurrentState == WLNotify_Startup), or when coming back at the + * SAS dialog after logged off (g_CurrentState == WLNotify_Logoff), + * WLEventShutdown() is invoked with pInfo->Flags == 0. + * (In both these cases, g_fLogoffShutdownFlags == 0 too.) + */ + ok(pInfo->Flags == EWX_LOGOFF || + pInfo->Flags == EWX_SHUTDOWN || + pInfo->Flags == (EWX_SHUTDOWN | EWX_REBOOT), + "Expected pInfo->Flags == EWX_LOGOFF (0) or EWX_SHUTDOWN (1) or EWX_SHUTDOWN|EWX_REBOOT (3), got %lu\n", + pInfo->Flags); } - DbgBreakOnEvent(); + END_TEST /* Change state */ - CHANGE_STATE(TRUE, WLNotify_Shutdown); + END_HANDLER(TRUE, WLNotify_Shutdown); } /** - * @brief Invoked on user logon. + * @brief Invoked at user logon. **/ -VOID -WINAPI -WLEventLogon( - _In_ PWLX_NOTIFICATION_INFO pInfo) +WLNOTIFY_HANDLER(WLEventLogon)(pInfo) { - DUMP_WLX_NOTIFICATION(pInfo); + BEGIN_HANDLER; - DisplayWlxMessageA(pInfo->pStatusCallback, FALSE, __FUNCTION__); - - /* We must be called from either Startup or previous Logoff */ - if (g_CurrentState != WLNotify_Startup && - g_CurrentState != WLNotify_Logoff) + BEGIN_TEST { - ERR("**** %s: ERROR: Wrong state %s, expected %s or %s\n", - __FUNCTION__, - NotifyStateToName(g_CurrentState), - NotifyStateToName(WLNotify_Startup), - NotifyStateToName(WLNotify_Logoff)); + /* We must be called from either Startup or previous Logoff */ + WLNOTIFY_STATE States[] = {WLNotify_Startup, WLNotify_Logoff}; + PWSTR pszInputDesk = (g_fFlags.bImpersonate ? NULL : DESKTOP_WINLOGON); + TEST_ENTRY LogonTest = + { States, _countof(States), g_fFlags.bImpersonate, TRUE, + DEFAULT_WINSTA0, DESKTOP_WINLOGON, pszInputDesk, DESKTOP_DEFAULT }; + + DoTest(__RELFILE__, __LINE__, __FUNCTION__, SysUserInfo, pInfo, &LogonTest); + + ok_int(pInfo->Flags, 0); } - DbgBreakOnEvent(); + END_TEST /* Change state */ - CHANGE_STATE(TRUE, WLNotify_Logon); + END_HANDLER(TRUE, WLNotify_Logon); } /** - * @brief Invoked on user logoff. + * @brief Invoked at user logoff. **/ -VOID -WINAPI -WLEventLogoff( - _In_ PWLX_NOTIFICATION_INFO pInfo) +WLNOTIFY_HANDLER(WLEventLogoff)(pInfo) { - DUMP_WLX_NOTIFICATION(pInfo); + BEGIN_HANDLER; - DisplayWlxMessageA(pInfo->pStatusCallback, FALSE, __FUNCTION__); + /* Save the flags for comparison with those in WLEventShutdown */ + InterlockedExchange((PLONG)&g_fLogoffShutdownFlags, pInfo->Flags); - /* We must be called from PostShell, or StartShell if we failed to start - * the shell (the PostShell notification isn't emitted in this case) */ - if (g_CurrentState != WLNotify_PostShell && - g_CurrentState != WLNotify_StartShell) + BEGIN_TEST { - ERR("**** %s: ERROR: Wrong state %s, expected %s or %s\n", - __FUNCTION__, - NotifyStateToName(g_CurrentState), - NotifyStateToName(WLNotify_PostShell), - NotifyStateToName(WLNotify_StartShell)); + /* We must be called from PostShell, or StartShell if we failed to start + * the shell (the PostShell notification isn't emitted in this case) */ + WLNOTIFY_STATE States[] = {WLNotify_PostShell, WLNotify_StartShell}; + TEST_ENTRY LogoffTest = + { States, _countof(States), g_fFlags.bImpersonate, TRUE, + DEFAULT_WINSTA0, DESKTOP_WINLOGON, DESKTOP_DEFAULT, DESKTOP_WINLOGON }; + + DoTest(__RELFILE__, __LINE__, __FUNCTION__, SysUserInfo, pInfo, &LogoffTest); + + ok(pInfo->Flags == EWX_LOGOFF || + pInfo->Flags == EWX_SHUTDOWN || + pInfo->Flags == (EWX_SHUTDOWN | EWX_REBOOT), + "Expected pInfo->Flags == EWX_LOGOFF (0) or EWX_SHUTDOWN (1) or EWX_SHUTDOWN|EWX_REBOOT (3), got %lu\n", + pInfo->Flags); } - DbgBreakOnEvent(); + END_TEST /* Change state */ - CHANGE_STATE(TRUE, WLNotify_Logoff); + END_HANDLER(TRUE, WLNotify_Logoff); } /** * @brief Invoked just before starting the user shell. **/ -VOID -WINAPI -WLEventStartShell( - _In_ PWLX_NOTIFICATION_INFO pInfo) +WLNOTIFY_HANDLER(WLEventStartShell)(pInfo) { - DUMP_WLX_NOTIFICATION(pInfo); + BEGIN_HANDLER; - DisplayWlxMessageA(pInfo->pStatusCallback, FALSE, __FUNCTION__); - - /* We must be called from Logon */ - if (g_CurrentState != WLNotify_Logon) + BEGIN_TEST { - ERR("**** %s: ERROR: Wrong state %s, expected %s\n", - __FUNCTION__, - NotifyStateToName(g_CurrentState), - NotifyStateToName(WLNotify_Logon)); + /* We must be called from Logon */ + WLNOTIFY_STATE States[] = {WLNotify_Logon}; + PWSTR pszInputDesk = (g_fFlags.bImpersonate ? NULL : DESKTOP_WINLOGON); + TEST_ENTRY StartShellTest = + { States, _countof(States), g_fFlags.bImpersonate, TRUE, + DEFAULT_WINSTA0, DESKTOP_WINLOGON, pszInputDesk, DESKTOP_DEFAULT }; + + DoTest(__RELFILE__, __LINE__, __FUNCTION__, SysUserInfo, pInfo, &StartShellTest); + + ok_int(pInfo->Flags, 0); } - DbgBreakOnEvent(); + END_TEST /* Change state */ - CHANGE_STATE(TRUE, WLNotify_StartShell); + END_HANDLER(TRUE, WLNotify_StartShell); } /** * @brief Invoked just after starting the user shell. **/ -VOID -WINAPI -WLEventPostShell( - _In_ PWLX_NOTIFICATION_INFO pInfo) +WLNOTIFY_HANDLER(WLEventPostShell)(pInfo) { - DUMP_WLX_NOTIFICATION(pInfo); + BEGIN_HANDLER; - DisplayWlxMessageA(pInfo->pStatusCallback, FALSE, __FUNCTION__); - - /* We must be called from StartShell */ - if (g_CurrentState != WLNotify_StartShell) + BEGIN_TEST { - ERR("**** %s: ERROR: Wrong state %s, expected %s\n", - __FUNCTION__, - NotifyStateToName(g_CurrentState), - NotifyStateToName(WLNotify_StartShell)); + /* We must be called from StartShell */ + WLNOTIFY_STATE States[] = {WLNotify_StartShell}; + PWSTR pszInputDesk = + (g_fFlags.bAsync ? DESKTOP_DEFAULT + : (g_fFlags.bImpersonate ? NULL : DESKTOP_WINLOGON)); + TEST_ENTRY PostShellTest = + { States, _countof(States), g_fFlags.bImpersonate, TRUE, + DEFAULT_WINSTA0, DESKTOP_WINLOGON, pszInputDesk, DESKTOP_WINLOGON }; + + DoTest(__RELFILE__, __LINE__, __FUNCTION__, SysUserInfo, pInfo, &PostShellTest); + + ok_int(pInfo->Flags, 0); } - DbgBreakOnEvent(); + END_TEST /* Change state */ - CHANGE_STATE(TRUE, WLNotify_PostShell); + END_HANDLER(TRUE, WLNotify_PostShell); } /** - * @brief Invoked on workstation locking. + * @brief Invoked at workstation locking. **/ -VOID -WINAPI -WLEventLock( - _In_ PWLX_NOTIFICATION_INFO pInfo) +WLNOTIFY_HANDLER(WLEventLock)(pInfo) { - DUMP_WLX_NOTIFICATION(pInfo); + BEGIN_HANDLER; - DisplayWlxMessageA(pInfo->pStatusCallback, FALSE, __FUNCTION__); - - /* We must be called from PostShell */ - if (g_CurrentState != WLNotify_PostShell) + BEGIN_TEST { - ERR("**** %s: ERROR: Wrong state %s, expected %s\n", - __FUNCTION__, - NotifyStateToName(g_CurrentState), - NotifyStateToName(WLNotify_PostShell)); + /* We must be called from PostShell */ + WLNOTIFY_STATE States[] = {WLNotify_PostShell}; + PWSTR pszInputDesk = (g_fFlags.bImpersonate ? NULL : DESKTOP_WINLOGON); + TEST_ENTRY LockTest = + { States, _countof(States), g_fFlags.bImpersonate, TRUE, + DEFAULT_WINSTA0, DESKTOP_WINLOGON, pszInputDesk, DESKTOP_WINLOGON }; + + DoTest(__RELFILE__, __LINE__, __FUNCTION__, SysUserInfo, pInfo, &LockTest); + + ok_int(pInfo->Flags, 0); } - DbgBreakOnEvent(); + END_TEST /* Change state */ - CHANGE_STATE(TRUE, WLNotify_Lock); + END_HANDLER(TRUE, WLNotify_Lock); } /** - * @brief Invoked on workstation unlocking. + * @brief Invoked at workstation unlocking. **/ -VOID -WINAPI -WLEventUnlock( - _In_ PWLX_NOTIFICATION_INFO pInfo) +WLNOTIFY_HANDLER(WLEventUnlock)(pInfo) { - DUMP_WLX_NOTIFICATION(pInfo); + BEGIN_HANDLER; - DisplayWlxMessageA(pInfo->pStatusCallback, FALSE, __FUNCTION__); - - /* We must be called from Lock */ - if (g_CurrentState != WLNotify_Lock) + BEGIN_TEST { - ERR("**** %s: ERROR: Wrong state %s, expected %s\n", - __FUNCTION__, - NotifyStateToName(g_CurrentState), - NotifyStateToName(WLNotify_Lock)); + /* We must be called from Lock */ + WLNOTIFY_STATE States[] = {WLNotify_Lock}; + PWSTR pszInputDesk = + (g_fFlags.bAsync ? DESKTOP_DEFAULT + : (g_fFlags.bImpersonate ? NULL : DESKTOP_WINLOGON)); + TEST_ENTRY UnlockTest = + { States, _countof(States), g_fFlags.bImpersonate, TRUE, + DEFAULT_WINSTA0, DESKTOP_WINLOGON, pszInputDesk, DESKTOP_WINLOGON }; + + DoTest(__RELFILE__, __LINE__, __FUNCTION__, SysUserInfo, pInfo, &UnlockTest); + + ok_int(pInfo->Flags, 0); } - DbgBreakOnEvent(); + END_TEST /* Restore previous state */ - CHANGE_STATE(FALSE, g_PreviousState); + END_HANDLER(FALSE, g_PreviousState); } /** - * @brief Invoked on screensaver start. + * @brief Invoked at screensaver start. **/ -VOID -WINAPI -WLEventStartScreenSaver( - _In_ PWLX_NOTIFICATION_INFO pInfo) +WLNOTIFY_HANDLER(WLEventStartScreenSaver)(pInfo) { - DUMP_WLX_NOTIFICATION(pInfo); + BEGIN_HANDLER; - DisplayWlxMessageA(pInfo->pStatusCallback, FALSE, __FUNCTION__); - - /* We must be called from Startup, Logoff, PostShell */ - if (g_CurrentState != WLNotify_Startup && - g_CurrentState != WLNotify_Logoff && - g_CurrentState != WLNotify_PostShell) + BEGIN_TEST { - ERR("**** %s: ERROR: Wrong state %s, expected %s or %s or %s\n", - __FUNCTION__, - NotifyStateToName(g_CurrentState), - NotifyStateToName(WLNotify_Startup), - NotifyStateToName(WLNotify_Logoff), - NotifyStateToName(WLNotify_PostShell)); + /* We must be called from Startup, Logoff, PostShell. + * In case of Startup or Logoff, no user is logged in, + * therefore no notification user token is present. */ + WLNOTIFY_STATE States[] = {WLNotify_Startup, WLNotify_Logoff, WLNotify_PostShell}; + BOOL bUserLoggedIn = IsUserLoggedIn(g_CurrentState); + BOOL bImpersonate = (g_fFlags.bImpersonate && bUserLoggedIn); + TEST_ENTRY StartScreenSaverTest = + { States, _countof(States), bImpersonate, bUserLoggedIn, + DEFAULT_WINSTA0, DESKTOP_WINLOGON, DESKTOP_SCRSAVE, DESKTOP_SCRSAVE }; + + DoTest(__RELFILE__, __LINE__, __FUNCTION__, SysUserInfo, pInfo, &StartScreenSaverTest); + + ok_int(pInfo->Flags, 0); } - DbgBreakOnEvent(); + END_TEST /* Change state */ - CHANGE_STATE(TRUE, WLNotify_StartScreenSaver); + END_HANDLER(TRUE, WLNotify_StartScreenSaver); } /** - * @brief Invoked on screensaver stop. + * @brief Invoked at screensaver stop. **/ -VOID -WINAPI -WLEventStopScreenSaver( - _In_ PWLX_NOTIFICATION_INFO pInfo) +WLNOTIFY_HANDLER(WLEventStopScreenSaver)(pInfo) { - DUMP_WLX_NOTIFICATION(pInfo); + BEGIN_HANDLER; - DisplayWlxMessageA(pInfo->pStatusCallback, FALSE, __FUNCTION__); - - /* We must be called from StartScreenSaver */ - if (g_CurrentState != WLNotify_StartScreenSaver) + BEGIN_TEST { - ERR("**** %s: ERROR: Wrong state %s, expected %s\n", - __FUNCTION__, - NotifyStateToName(g_CurrentState), - NotifyStateToName(WLNotify_StartScreenSaver)); + /* We must be called from StartScreenSaver. + * In case StartScreenSaver was called originally from Startup or Logoff, + * no user is logged in, therefore no notification user token is present. */ + WLNOTIFY_STATE States[] = {WLNotify_StartScreenSaver}; + BOOL bUserLoggedIn = IsUserLoggedIn(g_PreviousState); + BOOL bImpersonate = (g_fFlags.bImpersonate && bUserLoggedIn); + PWSTR pszInputDesk = + (g_fFlags.bAsync ? (bImpersonate ? NULL : DESKTOP_WINLOGON) + : DESKTOP_SCRSAVE); + TEST_ENTRY StopScreenSaverTest = + { States, _countof(States), bImpersonate, bUserLoggedIn, + DEFAULT_WINSTA0, DESKTOP_WINLOGON, pszInputDesk, DESKTOP_WINLOGON }; + + DoTest(__RELFILE__, __LINE__, __FUNCTION__, SysUserInfo, pInfo, &StopScreenSaverTest); + + ok_int(pInfo->Flags, 0); } - DbgBreakOnEvent(); + END_TEST /* Restore previous state */ - CHANGE_STATE(FALSE, g_PreviousState); + END_HANDLER(FALSE, g_PreviousState); } /** - * @brief Invoked on workstation disconnect (Terminal Services). + * @brief Invoked at workstation disconnect (Terminal Services). **/ -VOID -WINAPI -WLEventDisconnect( - _In_ PWLX_NOTIFICATION_INFO pInfo) +WLNOTIFY_HANDLER(WLEventDisconnect)(pInfo) { - DUMP_WLX_NOTIFICATION(pInfo); + BEGIN_HANDLER; - DisplayWlxMessageA(pInfo->pStatusCallback, FALSE, __FUNCTION__); - - ERR("**** %s: Previous state %s\n", - __FUNCTION__, NotifyStateToName(g_CurrentState)); - DbgBreakOnEvent(); + BEGIN_TEST + { + ERR("**** %s: Previous state %s\n", + __FUNCTION__, NotifyStateToName(g_CurrentState)); + } + END_TEST /* Change state */ - CHANGE_STATE(TRUE, WLNotify_Disconnect); + END_HANDLER(TRUE, WLNotify_Disconnect); } /** - * @brief Invoked on workstation reconnect (Terminal Services). + * @brief Invoked at workstation reconnect (Terminal Services). **/ -VOID -WINAPI -WLEventReconnect( - _In_ PWLX_NOTIFICATION_INFO pInfo) +WLNOTIFY_HANDLER(WLEventReconnect)(pInfo) { - DUMP_WLX_NOTIFICATION(pInfo); + BEGIN_HANDLER; - DisplayWlxMessageA(pInfo->pStatusCallback, FALSE, __FUNCTION__); - - /* We must be called from Disconnect */ - if (g_CurrentState != WLNotify_Disconnect) + BEGIN_TEST { - ERR("**** %s: ERROR: Wrong state %s, expected %s\n", - __FUNCTION__, - NotifyStateToName(g_CurrentState), - NotifyStateToName(WLNotify_Disconnect)); + /* We must be called from Disconnect */ + ok_state_1(WLNotify_Disconnect); } - DbgBreakOnEvent(); + END_TEST /* Restore previous state */ - CHANGE_STATE(FALSE, g_PreviousState); + END_HANDLER(FALSE, g_PreviousState); } @@ -663,7 +1286,7 @@ DllMain( _In_ DWORD dwReason, _In_ PVOID pReserved) { - // MESSAGE // DPRINTF + // DPRINTF TRACE("\nWLNOTIFY(%lx.%lx): Entering `%s`(hInst: 0x%p, dwReason: 0x%x, pReserved: 0x%p)\n", GetCurrentProcessId(), // NtCurrentTeb()->ClientId.UniqueProcess GetCurrentThreadId(), // NtCurrentTeb()->ClientId.UniqueThread @@ -673,16 +1296,16 @@ DllMain( { case DLL_PROCESS_ATTACH: g_hModule = GetInstanceModule(hInstance); - // DisableThreadLibraryCalls(g_hModule); + DisableThreadLibraryCalls(g_hModule); __fallthrough; case DLL_PROCESS_DETACH: { - // MESSAGE // DPRINTF + // DPRINTF ERR("\nWLNOTIFY(%lx.%lx): Entering `%s`(hInst: 0x%p, dwReason: 0x%x, pReserved: 0x%p)\n", - GetCurrentProcessId(), // NtCurrentTeb()->ClientId.UniqueProcess - GetCurrentThreadId(), // NtCurrentTeb()->ClientId.UniqueThread - __FUNCTION__, hInstance, dwReason, pReserved); + GetCurrentProcessId(), // NtCurrentTeb()->ClientId.UniqueProcess + GetCurrentThreadId(), // NtCurrentTeb()->ClientId.UniqueThread + __FUNCTION__, hInstance, dwReason, pReserved); /* Reset to detect any unwanted reloads */ g_PreviousState = g_CurrentState = WLNotify_NonInitialized; @@ -753,7 +1376,7 @@ HRESULT WINAPI DllRegisterServer(VOID) } RegSetValueExW(hNotifyKey, L"DllName", 0, REG_EXPAND_SZ, - (PBYTE)szModule, (wcslen(szModule) + 1) * sizeof(WCHAR)); + (PBYTE)szModule, (DWORD)(wcslen(szModule) + 1) * sizeof(WCHAR)); /* Make the notifications synchronous by default */ dwValue = 0; @@ -782,7 +1405,7 @@ HRESULT WINAPI DllRegisterServer(VOID) RegSetValueExW(hNotifyKey, NotifyEvents[i].ValueName, 0, REG_SZ, (PBYTE)NotifyEvents[i].Value, - (wcslen(NotifyEvents[i].Value) + 1) * sizeof(WCHAR)); + (DWORD)(wcslen(NotifyEvents[i].Value) + 1) * sizeof(WCHAR)); } RegCloseKey(hNotifyKey); diff --git a/sdk/tools/gen_baseaddress.py b/sdk/tools/gen_baseaddress.py index 0d850ef7652..3210687f63a 100644 --- a/sdk/tools/gen_baseaddress.py +++ b/sdk/tools/gen_baseaddress.py @@ -211,10 +211,11 @@ EXCLUDE = ( 'MyEventProvider.dll', 'redirtest1.dll', 'redirtest2.dll', + 'testvdd.dll', 'win32u_2k3sp2.dll', 'win32u_vista.dll', 'win32u_xpsp2.dll', - 'testvdd.dll', + 'wlntfytests.dll', ) IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b