From aa672225952ffab6fe6563eb50fac4ae50583306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herm=C3=A8s=20B=C3=A9lusca-Ma=C3=AFto?= Date: Sun, 10 Aug 2025 19:21:46 +0200 Subject: [PATCH] [MSGINA] CreateProfile(): Fix initialization of some `WLX_PROFILE_V2_0` members (#8321) The following members of the returned `WLX_PROFILE_V2_0` structure: `pszProfile`, `pszPolicy`, `pszNetworkDefaultUserProfile`, and `pszServerName`, have a specific meaning and are used when logging to (NT4, Active Directory...) domains. See the added code comments for details. In particular, `pszProfile` specifies the path to a *roaming* user profile[^1] on a domain server, if any. It **DOES NOT** specify the local `"C:\Documents and Settings"` path (got via `GetProfilesDirectoryW()`). Since we don't really support user login on domains, set these pointers to NULL. ---- Enabling UserEnv debug logging[^2] on Windows 2003, one can observe such following traces, for a `TestUser` roaming user profile: ``` USERENV(148.14c) 20:24:59:821 LoadUserProfile: Entering, hToken = <0x8bc>, lpProfileInfo = 0x6e5d8 USERENV(148.14c) 20:24:59:875 LoadUserProfile: lpProfileInfo->dwFlags = <0x0> USERENV(148.14c) 20:24:59:912 LoadUserProfile: lpProfileInfo->lpUserName = USERENV(148.14c) 20:24:59:966 LoadUserProfile: lpProfileInfo->lpProfilePath = USERENV(148.14c) 20:25:00:021 LoadUserProfile: lpProfileInfo->lpDefaultPath = <\\PC-H\netlogon\Default User> USERENV(148.14c) 20:25:00:075 LoadUserProfile: NULL server name ... USERENV(148.14c) 20:25:06:177 CopyProfileDirectoryEx: Found hive file NTUSER.DAT USERENV(148.14c) 20:25:06:395 ReconcileFile: C:\Documents and Settings\TestUser_Roaming\NTUSER.DAT ==> C:\Documents and Settings\TestUser\NTUSER.DAT [OK] ... ``` The `lpProfilePath` specifies a roaming profile directory (`"TestUser_Roaming"`) for a user named named `TestUser`, and UserEnv proceeds to image the roaming profile into the directory (`"TestUser"`) in the local computer. However, when the user has a local profile, the `lpProfilePath` is set to NULL in this case, and one observes instead: ``` USERENV(148.14c) 20:21:22:644 LoadUserProfile: NULL central profile path ``` ---- [^1]: The following links explain this, also demonstrating UserEnv debug logging: - https://web.archive.org/web/20130319204738/http://blogs.technet.com/b/askds/archive/2008/11/11/understanding-how-to-read-a-userenv-log-part-2.aspx - https://web.archive.org/web/20150405040409/http://blogs.technet.com/b/ad/archive/2007/08/20/tracking-user-environment-creation.aspx [^2]: For more details, see: https://www.betaarchive.com/wiki/index.php?title=Microsoft_KB_Archive/221833 (archived from: http://support.microsoft.com/kb/221833) UserEnv debug logging is achieved by adding a `REG_DWORD` value named `UserEnvDebugLevel` in the following registry sub-key: `HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon` with a non-zero value. To view the output in a debugger (e.g. WinDbg when kernel-debugging Windows), set the value to `0x00020002`. --- dll/win32/msgina/msgina.c | 98 ++++++++++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 27 deletions(-) diff --git a/dll/win32/msgina/msgina.c b/dll/win32/msgina/msgina.c index df5eb94b354..18760b33f8f 100644 --- a/dll/win32/msgina/msgina.c +++ b/dll/win32/msgina/msgina.c @@ -835,19 +835,21 @@ CreateProfile( IN PWSTR Password) { PWLX_PROFILE_V2_0 pProfile = NULL; - PWSTR pProfilePath = NULL; PWSTR pEnvironment = NULL; TOKEN_STATISTICS Stats; DWORD cbStats, cbSize; DWORD dwLength; - BOOL bResult; +#if 0 + BOOL bIsDomainLogon; + WCHAR ComputerName[MAX_COMPUTERNAME_LENGTH+1]; +#endif /* Store the logon time in the context */ GetLocalTime(&pgContext->LogonTime); /* Store user and domain in the context */ wcscpy(pgContext->UserName, UserName); - if (Domain == NULL || wcslen(Domain) == 0) + if (Domain == NULL || !Domain[0]) { dwLength = _countof(pgContext->DomainName); GetComputerNameW(pgContext->DomainName, &dwLength); @@ -856,25 +858,17 @@ CreateProfile( { wcscpy(pgContext->DomainName, Domain); } + /* From now on we use in UserName and Domain the captured values from pgContext */ + UserName = pgContext->UserName; + Domain = pgContext->DomainName; - /* Get profile path */ - cbSize = 0; - bResult = GetProfilesDirectoryW(NULL, &cbSize); - if (!bResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER) - { - pProfilePath = LocalAlloc(LMEM_FIXED, cbSize * sizeof(WCHAR)); - if (!pProfilePath) - { - WARN("LocalAlloc() failed\n"); - goto cleanup; - } - bResult = GetProfilesDirectoryW(pProfilePath, &cbSize); - } - if (!bResult) - { - WARN("GetUserProfileDirectoryW() failed\n"); - goto cleanup; - } +#if 0 + /* Determine whether this is really a domain logon, by verifying + * that the specified domain is different from the local computer */ + dwLength = _countof(ComputerName); + GetComputerNameW(ComputerName, &dwLength); + bIsDomainLogon = (_wcsicmp(ComputerName, Domain) != 0); +#endif /* Allocate memory for profile */ pProfile = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, sizeof(*pProfile)); @@ -884,10 +878,61 @@ CreateProfile( goto cleanup; } pProfile->dwType = WLX_PROFILE_TYPE_V2_0; - pProfile->pszProfile = pProfilePath; + /* + * TODO: For domain logon support: + * + * - pszProfile: Specifies the path to a *roaming* user profile on a + * domain server, if any. It is then used to create a local image + * (copy) of the profile on the local computer. + * ** This data should be retrieved from the LsaLogonUser() call + * made by MyLogonUser()! ** + * + * - pszPolicy (for domain logon): Path to a policy file. + * Windows' msgina.dll hardcodes it as: + * "\\\netlogon\ntconfig.pol" + * + * - pszNetworkDefaultUserProfile (for domain logon): Path to the + * default user profile. Windows' msgina.dll hardcodes it as: + * "\\\netlogon\Default User" + * + * - pszServerName (for domain logon): Name ("domain_controller") of + * the server (local computer; Active Directory domain controller...) + * that validated the logon. + * ** This data should be retrieved from the LsaLogonUser() call + * made by MyLogonUser()! ** + * + * NOTES: + * - The paths use the domain controllers' "netlogon" share. + * - These strings are LocalAlloc'd here, and LocalFree'd by Winlogon. + */ + pProfile->pszProfile = NULL; + pProfile->pszPolicy = NULL; + pProfile->pszNetworkDefaultUserProfile = NULL; + pProfile->pszServerName = NULL; +#if 0 + if (bIsDomainLogon) + { + PWSTR pServerName; + cbSize = sizeof(L"\\\\") + wcslen(Domain) * sizeof(WCHAR); + pServerName = LocalAlloc(LMEM_FIXED, cbSize); + if (!pServerName) + WARN("HeapAlloc() failed\n"); // Consider this optional, so no failure. + else + StringCbPrintfW(pServerName, cbSize, L"\\\\%ws", Domain); // See LogonServer below. + pProfile->pszServerName = pServerName; + } +#endif + + /* Build the minimal environment string block */ + // FIXME: LogonServer is the name of the server that processed the logon + // request ("domain_controller"). It can be different from the selected + // user's logon domain. + // See e.g.: + // - https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/ns-ntsecapi-msv1_0_interactive_profile + // - https://learn.microsoft.com/en-us/windows/win32/api/winwlx/ns-winwlx-wlx_consoleswitch_credentials_info_v1_0 cbSize = sizeof(L"LOGONSERVER=\\\\") + - wcslen(pgContext->DomainName) * sizeof(WCHAR) + + wcslen(Domain) * sizeof(WCHAR) + sizeof(UNICODE_NULL); pEnvironment = LocalAlloc(LMEM_FIXED, cbSize); if (!pEnvironment) @@ -896,19 +941,20 @@ CreateProfile( goto cleanup; } - StringCbPrintfW(pEnvironment, cbSize, L"LOGONSERVER=\\\\%ls", pgContext->DomainName); + StringCbPrintfW(pEnvironment, cbSize, L"LOGONSERVER=\\\\%ws", Domain); ASSERT(wcslen(pEnvironment) == cbSize / sizeof(WCHAR) - 2); pEnvironment[cbSize / sizeof(WCHAR) - 1] = UNICODE_NULL; pProfile->pszEnvironment = pEnvironment; + /* Return the other info */ if (!GetTokenInformation(pgContext->UserToken, TokenStatistics, &Stats, sizeof(Stats), &cbStats)) { - WARN("Couldn't get Authentication id from user token!\n"); + WARN("Couldn't get Authentication Id from user token!\n"); goto cleanup; } @@ -924,8 +970,6 @@ CreateProfile( cleanup: if (pEnvironment) LocalFree(pEnvironment); - if (pProfilePath) - LocalFree(pProfilePath); if (pProfile) LocalFree(pProfile); return FALSE;