//+-------------------------------------------------------------------- // // Microsoft Windows // // Copyright (c) Microsoft Corporation 2000 // // File: ntdigest.c // // Contents: main entrypoints for the digest security package // SpLsaModeInitialize // SpInitialize // SpShutdown // SpGetInfo // // Helper functions: // // History: KDamour 10Mar00 Stolen from msv_sspi\ntlm.cxx // //--------------------------------------------------------------------- #define NTDIGEST_GLOBAL #include "global.h" /* Debugging information setup */ DEFINE_DEBUG2(Digest); DEBUG_KEY MyDebugKeys[] = {{DEB_ERROR, "Error"}, {DEB_WARN, "Warning"}, {DEB_TRACE, "Trace"}, {DEB_TRACE_ASC, "TraceASC"}, {DEB_TRACE_ISC, "TraceISC"}, {DEB_TRACE_LSA, "TraceLSA"}, {DEB_TRACE_USER, "TraceUser"}, {DEB_TRACE_FUNC, "TraceFuncs"}, {DEB_TRACE_MEM, "TraceMem"}, {TRACE_STUFF, "Stuff"}, {0, NULL} }; // set to TRUE once initialized BOOL l_bDebugInitialized = FALSE; BOOL l_bDigestInitialized = FALSE; // Registry reading HKEY g_hkBase = NULL; HANDLE g_hParamEvent = NULL; HANDLE g_hWait = NULL; #define COMPUTER_NAME_SIZE (MAX_COMPUTERNAME_LENGTH + 1) //+-------------------------------------------------------------------- // // Function: SpLsaModeInitialize // // Synopsis: This function is called by the LSA when this DLL is loaded. // It returns security package function tables for all // security packages in the DLL. // // Arguments: LsaVersion - Version number of the LSA // PackageVersion - Returns version number of the package // Tables - Returns array of function tables for the package // TableCount - Returns number of entries in array of // function tables. // // Returns: PackageVersion (as above) // Tables (as above) // TableCount (as above) // // Notes: // //--------------------------------------------------------------------- NTSTATUS NTAPI SpLsaModeInitialize( IN ULONG LsaVersion, OUT PULONG PackageVersion, OUT PSECPKG_FUNCTION_TABLE * Tables, OUT PULONG TableCount ) { #if DBG DebugInitialize(); #endif DebugLog((DEB_TRACE_FUNC, "SpLsaModeInitialize: Entering\n")); SECURITY_STATUS Status = SEC_E_OK; if (LsaVersion != SECPKG_INTERFACE_VERSION) { DebugLog((DEB_ERROR, "SpLsaModeInitialize: Invalid LSA version: %d\n", LsaVersion)); Status = STATUS_INVALID_PARAMETER; goto CleanUp; } // Fill in the dispatch table for functions exported by ssp g_NtDigestFunctionTable.InitializePackage = NULL; g_NtDigestFunctionTable.LogonUser = NULL; g_NtDigestFunctionTable.CallPackage = LsaApCallPackage; g_NtDigestFunctionTable.LogonTerminated = LsaApLogonTerminated; g_NtDigestFunctionTable.CallPackageUntrusted = LsaApCallPackageUntrusted; g_NtDigestFunctionTable.LogonUserEx = NULL; g_NtDigestFunctionTable.LogonUserEx2 = LsaApLogonUserEx2; g_NtDigestFunctionTable.Initialize = SpInitialize; g_NtDigestFunctionTable.Shutdown = SpShutdown; g_NtDigestFunctionTable.GetInfo = SpGetInfo; g_NtDigestFunctionTable.AcceptCredentials = SpAcceptCredentials; g_NtDigestFunctionTable.AcquireCredentialsHandle = SpAcquireCredentialsHandle; g_NtDigestFunctionTable.FreeCredentialsHandle = SpFreeCredentialsHandle; g_NtDigestFunctionTable.SaveCredentials = SpSaveCredentials; g_NtDigestFunctionTable.GetCredentials = SpGetCredentials; g_NtDigestFunctionTable.DeleteCredentials = SpDeleteCredentials; g_NtDigestFunctionTable.InitLsaModeContext = SpInitLsaModeContext; g_NtDigestFunctionTable.AcceptLsaModeContext = SpAcceptLsaModeContext; g_NtDigestFunctionTable.DeleteContext = SpDeleteContext; g_NtDigestFunctionTable.ApplyControlToken = SpApplyControlToken; g_NtDigestFunctionTable.GetUserInfo = SpGetUserInfo; g_NtDigestFunctionTable.QueryCredentialsAttributes = SpQueryCredentialsAttributes ; g_NtDigestFunctionTable.GetExtendedInformation = SpGetExtendedInformation ; g_NtDigestFunctionTable.SetExtendedInformation = SpSetExtendedInformation ; g_NtDigestFunctionTable.CallPackagePassthrough = LsaApCallPackagePassthrough; *PackageVersion = SECPKG_INTERFACE_VERSION; *Tables = &g_NtDigestFunctionTable; *TableCount = 1; CleanUp: DebugLog((DEB_TRACE_FUNC, "SpLsaModeInitialize:Leaving\n")); return(Status); } //+-------------------------------------------------------------------- // // Function: SpInitialize // // Synopsis: Initializes the Security package // // Arguments: PackageId - Contains ID for this package assigned by LSA // Parameters - Contains machine-specific information // FunctionTable - Contains table of LSA helper routines // // Returns: None // // Notes: Everything that was done in LsaApInitializePackage // should be done here. Lsa assures us that only // one thread is executing this at a time. Don't // have to worry about concurrency problems.(BUGBUG verify) // //--------------------------------------------------------------------- NTSTATUS NTAPI SpInitialize( IN ULONG_PTR pPackageId, IN PSECPKG_PARAMETERS pParameters, IN PLSA_SECPKG_FUNCTION_TABLE pFunctionTable ) { SECURITY_STATUS Status = SEC_E_OK; DWORD dwWinErr = 0; NT_PRODUCT_TYPE NtProductType = NtProductWinNt; WCHAR wszComputerName[COMPUTER_NAME_SIZE]; DWORD dwComputerNameLen = COMPUTER_NAME_SIZE; DebugLog((DEB_TRACE_FUNC, "SpInitialize: Entering\n")); // Indicate that we completed initialization ASSERT(l_bDigestInitialized == FALSE); // never called more than once l_bDigestInitialized = TRUE; // Initialize global values ZeroMemory(&g_strNtDigestUTF8ServerRealm, sizeof(g_strNtDigestUTF8ServerRealm)); ZeroMemory(&g_strNTDigestISO8859ServerRealm, sizeof(g_strNTDigestISO8859ServerRealm)); // Define time for AcquirCredentialHandle // We really need this to be a day less than maxtime so when callers // of sspi convert to utc, they won't get time in the past. g_TimeForever.HighPart = 0x7FFFFFFF; g_TimeForever.LowPart = 0xFFFFFFFF; // // All the following are global // g_NtDigestState = NtDigestLsaMode; /* enum */ g_NtDigestPackageId = pPackageId; // // Save away the Lsa functions // g_LsaFunctions = pFunctionTable; // // Establish the packagename // RtlInitUnicodeString( &g_ustrNtDigestPackageName, WDIGEST_SP_NAME ); // Set the WorkstationName if (!GetComputerNameExW(ComputerNameNetBIOS, wszComputerName, &dwComputerNameLen)) { ZeroMemory(&g_ustrWorkstationName, sizeof(g_ustrWorkstationName)); DebugLog((DEB_ERROR, "SpInitialize: Get ComputerName error 0x%x\n", GetLastError())); } else { Status = UnicodeStringWCharDuplicate(&g_ustrWorkstationName, wszComputerName); if (!NT_SUCCESS (Status)) { DebugLog((DEB_ERROR, "SpInitialize: ComputerName copy status 0x%x\n", Status)); goto CleanUp; } } // Need to initialize Crypto stuff and nonce creations Status = NonceInitialize(); if (!NT_SUCCESS (Status)) { DebugLog((DEB_ERROR, "SpInitialize: Error from NonceInitialize status 0x%x\n", Status)); goto CleanUp; } // // Determine if this machine is running Windows NT or Lanman NT. // LanMan NT runs on a domain controller. // if ( !RtlGetNtProductType( &NtProductType ) ) { // Nt Product Type undefined - WinNt assumed NtProductType = NtProductWinNt; } if (NtProductType == NtProductLanManNt) { g_fDomainController = TRUE; // Allow password checking only on DomainControllers } // // Save the Parameters info to a global struct // g_NtDigestSecPkg.MachineState = pParameters->MachineState; g_NtDigestSecPkg.SetupMode = pParameters->SetupMode; g_NtDigestSecPkg.Version = pParameters->Version; Status = UnicodeStringDuplicate( &g_NtDigestSecPkg.DnsDomainName, &(pParameters->DnsDomainName)); if (!NT_SUCCESS (Status)) { DebugLog((DEB_ERROR, "SpInitialize: Error from UnicodeStringDuplicate status 0x%x\n", Status)); goto CleanUp; } Status = UnicodeStringDuplicate( &g_NtDigestSecPkg.DomainName, &(pParameters->DomainName)); if (!NT_SUCCESS (Status)) { DebugLog((DEB_ERROR, "SpInitialize: Error from UnicodeStringDuplicate status 0x%x\n", Status)); goto CleanUp; } if (pParameters->DomainSid != NULL) { Status = SidDuplicate( &g_NtDigestSecPkg.DomainSid, pParameters->DomainSid ); if (!NT_SUCCESS (Status)) { DebugLog((DEB_ERROR, "SpInitialize: Error from SidDuplicate status 0x%x\n", Status)); goto CleanUp; } } else g_NtDigestSecPkg.DomainSid = NULL; DebugLog((DEB_TRACE, "SpInitialize: DNSDomain = %wZ, Domain = %wZ\n", &(g_NtDigestSecPkg.DnsDomainName), &(g_NtDigestSecPkg.DomainName))); // For server challenges, precalculate the UTF-8 and ISO versions of the realm Status = EncodeUnicodeString(&(g_NtDigestSecPkg.DnsDomainName), CP_8859_1, &g_strNTDigestISO8859ServerRealm, NULL); if (!NT_SUCCESS(Status)) { DebugLog((DEB_WARN, "SpInitialize: Error in encoding domain in ISO-8859-1\n")); ZeroMemory(&g_strNTDigestISO8859ServerRealm, sizeof(STRING)); } Status = EncodeUnicodeString(&(g_NtDigestSecPkg.DnsDomainName), CP_UTF8, &g_strNtDigestUTF8ServerRealm, NULL); if (!NT_SUCCESS(Status)) { DebugLog((DEB_WARN, "SpInitialize: Error in encoding domain in UTF-8\n")); ZeroMemory(&g_strNtDigestUTF8ServerRealm, sizeof(STRING)); } // // Initialize the digest token source // RtlCopyMemory( g_DigestSource.SourceName, NTDIGEST_TOKEN_NAME_A, sizeof(NTDIGEST_TOKEN_NAME_A) ); NtAllocateLocallyUniqueId(&g_DigestSource.SourceIdentifier); // // Init the LogonSession stuff // Status = LogSessHandlerInit(); if (!NT_SUCCESS (Status)) { DebugLog((DEB_ERROR, "SpInitialize: Error from LogSessHandlerInit status 0x%x\n", Status)); goto CleanUp; } // // Init the Credential stuff // Status = CredHandlerInit(); if (!NT_SUCCESS (Status)) { DebugLog((DEB_ERROR, "SpInitialize: Error from CredHandlerInit status 0x%x\n", Status)); goto CleanUp; } // // Init the Context stuff // Status = CtxtHandlerInit(); if (!NT_SUCCESS (Status)) { DebugLog((DEB_ERROR, "SpInitialize: Error from ContextInitialize status 0x%x\n", Status)); goto CleanUp; } // // Read in the registry values for SSP configuration - in LSA space // SPLoadRegOptions(); CleanUp: if (!NT_SUCCESS (Status)) { SPUnloadRegOptions(); SpShutdown(); } DebugLog((DEB_TRACE_FUNC, "SpInitialize: Leaving\n")); return(Status); } //+-------------------------------------------------------------------- // // Function: SpShutdown // // Synopsis: Exported function to shutdown the Security package. // // Effects: Forces the freeing of all credentials, contexts // and frees all global data // // Arguments: none // // Returns: // // Notes: SEC_E_OK in all cases // Most of the stuff was taken from SspCommonShutdown() // from svcdlls\ntlmssp\common\initcomn.c // // //--------------------------------------------------------------------- NTSTATUS NTAPI SpShutdown( VOID ) { DebugLog((DEB_TRACE_FUNC, "SpShutdown: Entering\n")); // Need to identify how to shutdown without causing faults with // incoming messages DebugLog((DEB_TRACE_FUNC, "SpShutdown: Leaving\n")); return(SEC_E_OK); } //+-------------------------------------------------------------------- // // Function: SpGetInfo // // Synopsis: Returns information about the package // // Effects: returns pointers to global data // // Arguments: PackageInfo - Receives security package information // // Returns: SEC_E_OK in all cases // // Notes: Pointers to constants ok. Lsa will copy the data // before sending it to someone else. This function required // to return SUCCESS for the package to stay loaded. // //--------------------------------------------------------------------- NTSTATUS NTAPI SpGetInfo( OUT PSecPkgInfo PackageInfo ) { DebugLog((DEB_TRACE_FUNC, "SpGetInfo: Entering\n")); PackageInfo->fCapabilities = NTDIGEST_SP_CAPS; PackageInfo->wVersion = SECURITY_SUPPORT_PROVIDER_INTERFACE_VERSION; PackageInfo->wRPCID = RPC_C_AUTHN_DIGEST; PackageInfo->cbMaxToken = NTDIGEST_SP_MAX_TOKEN_SIZE; PackageInfo->Name = WDIGEST_SP_NAME; PackageInfo->Comment = NTDIGEST_SP_COMMENT; DebugLog((DEB_TRACE_FUNC, "SpGetInfo: Leaving\n")); return(SEC_E_OK); } // Misc SECPKG Functions NTSTATUS NTAPI SpGetUserInfo( IN PLUID LogonId, IN ULONG Flags, OUT PSecurityUserData * UserData ) { DebugLog((DEB_TRACE_FUNC, "SpGetUserInfo: Entering/Leaving\n")); // FIXIFX Fields of UserData are username, domain, server UNREFERENCED_PARAMETER(LogonId); UNREFERENCED_PARAMETER(Flags); UNREFERENCED_PARAMETER(UserData); return(SEC_E_UNSUPPORTED_FUNCTION); } //+--------------------------------------------------------------------------- // // Function: SpGetExtendedInformation // // Synopsis: Return extended information to the LSA // // Arguments: [Class] -- Information Class // [pInfo] -- Returned Information Pointer // // // Notes: // //---------------------------------------------------------------------------- NTSTATUS NTAPI SpGetExtendedInformation( IN SECPKG_EXTENDED_INFORMATION_CLASS Class, OUT PSECPKG_EXTENDED_INFORMATION * ppInformation ) { NTSTATUS Status = STATUS_SUCCESS; PSECPKG_EXTENDED_INFORMATION Information = NULL; ULONG Size = 0; DebugLog((DEB_TRACE_FUNC, "SpGetExtendedInformation: Entering\n")); switch ( Class ) { case SecpkgWowClientDll: // // This indicates that we're smart enough to handle wow client processes // Information = (PSECPKG_EXTENDED_INFORMATION) DigestAllocateMemory( sizeof( SECPKG_EXTENDED_INFORMATION ) + (MAX_PATH * sizeof(WCHAR) ) ); if ( Information == NULL ) { Status = STATUS_INSUFFICIENT_RESOURCES ; goto Cleanup ; } Information->Class = SecpkgWowClientDll ; Information->Info.WowClientDll.WowClientDllPath.Buffer = (PWSTR) (Information + 1); Size = ExpandEnvironmentStrings( L"%SystemRoot%\\" WOW64_SYSTEM_DIRECTORY_U L"\\" NTDIGEST_DLL_NAME, Information->Info.WowClientDll.WowClientDllPath.Buffer, MAX_PATH ); Information->Info.WowClientDll.WowClientDllPath.Length = (USHORT) (Size * sizeof(WCHAR)); Information->Info.WowClientDll.WowClientDllPath.MaximumLength = (USHORT) ((Size + 1) * sizeof(WCHAR) ); *ppInformation = Information ; Information = NULL ; break; default: Status = SEC_E_UNSUPPORTED_FUNCTION ; } Cleanup: if ( Information != NULL ) { DigestFreeMemory( Information ); } DebugLog((DEB_TRACE_FUNC, "SpGetExtendedInformation: Leaving Status %d\n", Status)); return Status ; } NTSTATUS NTAPI SpSetExtendedInformation( IN SECPKG_EXTENDED_INFORMATION_CLASS Class, IN PSECPKG_EXTENDED_INFORMATION Info ) { DebugLog((DEB_TRACE_FUNC, "SpSetExtendedInformation: Entering/Leaving \n")); UNREFERENCED_PARAMETER(Class); UNREFERENCED_PARAMETER(Info); return(SEC_E_UNSUPPORTED_FUNCTION) ; } // // Registry Reading routines // This routine is called in single-threaded mode from the LSA for SpInitialize and SPInstanceInit // In user applications only SPInstanceInit calls this function // BOOL SPLoadRegOptions(void) { if (NULL != g_hParamEvent) { // Already called - no need to re-execute DebugLog((DEB_TRACE, "SPLoadRegOptions: Already initialized - Leaving \n")); return TRUE; } g_hParamEvent = CreateEvent(NULL, FALSE, FALSE, NULL); DigestWatchParamKey(g_hParamEvent, FALSE); return TRUE; } void SPUnloadRegOptions(void) { if (NULL != g_hWait) { RtlDeregisterWaitEx(g_hWait, (HANDLE)-1); g_hWait = NULL; } if(NULL != g_hkBase) { RegCloseKey(g_hkBase); g_hkBase = NULL; } if(NULL != g_hParamEvent) { CloseHandle(g_hParamEvent); g_hParamEvent = NULL; } } // Helper function to read in a DWORD - sets value if not present in registry void ReadDwordRegistrySetting( HKEY hReadKey, HKEY hWriteKey, LPCTSTR pszValueName, DWORD * pdwValue, DWORD dwDefaultValue) { DWORD dwSize = 0; DWORD dwType = 0; dwSize = sizeof(DWORD); if(RegQueryValueEx(hReadKey, pszValueName, NULL, &dwType, (PUCHAR)pdwValue, &dwSize) != STATUS_SUCCESS) { *pdwValue = dwDefaultValue; if(hWriteKey) { RegSetValueEx(hWriteKey, pszValueName, 0, REG_DWORD, (PUCHAR)pdwValue, sizeof(DWORD)); } } } // Can be called at any time to change the default values // As long as a DWORD assignment can be done in a single step BOOL NtDigestReadRegistry(BOOL fFirstTime) { DWORD dwBool = 0; DWORD dwDebug = 0; HKEY hWriteKey = 0; // Open top-level key that has write access. if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, REG_DIGEST_BASE, 0, KEY_READ | KEY_SET_VALUE, &hWriteKey) != STATUS_SUCCESS) { hWriteKey = 0; } // "LifeTime" ReadDwordRegistrySetting( g_hkBase, hWriteKey, REG_DIGEST_OPT_LIFETIME, &g_dwParameter_Lifetime, PARAMETER_LIFETIME); // "Negotiate" Supported - BOOL value ReadDwordRegistrySetting( g_hkBase, hWriteKey, REG_DIGEST_OPT_NEGOTIATE, &dwBool, PARAMETER_NEGOTIATE); if (dwBool) g_fParameter_Negotiate = TRUE; else g_fParameter_Negotiate = FALSE; // UTF8 Supported in HTTP mode - BOOL value ReadDwordRegistrySetting( g_hkBase, hWriteKey, REG_DIGEST_OPT_UTF8HTTP, &dwBool, PARAMETER_UTF8_HTTP); if (dwBool) g_fParameter_UTF8HTTP = TRUE; else g_fParameter_UTF8HTTP = FALSE; // UTF8 supported in SASL - BOOL value ReadDwordRegistrySetting( g_hkBase, hWriteKey, REG_DIGEST_OPT_UTF8SASL, &dwBool, PARAMETER_UTF8_SASL); if (dwBool) g_fParameter_UTF8SASL = TRUE; else g_fParameter_UTF8SASL = FALSE; // MaxContextCount /* ReadDwordRegistrySetting( g_hkBase, hWriteKey, REG_DIGEST_OPT_MAXCTXTCOUNT, &g_dwParameter_MaxCtxtCount, PARAMETER_MAXCTXTCOUNT); */ #if DBG // DebugLevel ReadDwordRegistrySetting( g_hkBase, hWriteKey, REG_DIGEST_OPT_DEBUGLEVEL, &dwDebug, 0); DigestInfoLevel = dwDebug; // Turn on/off selected messages #endif if(hWriteKey) { RegCloseKey(hWriteKey); hWriteKey = 0; } DebugLog((DEB_TRACE, "NtDigestReadRegistry: Lifetime %lu, Negotiate %d, UTF-8 HTTP %d, UTF-8 SASL %d, DebugLevel 0x%x\n", g_dwParameter_Lifetime, g_fParameter_Negotiate, g_fParameter_UTF8HTTP, g_fParameter_UTF8SASL, dwDebug)); return TRUE; } // This routine is called in single-threaded mode from the LSA for SpLsaModeInitialize and SPInstanceInit // In user applications only SPInstanceInit calls this function void DebugInitialize(void) { #if DBG if (l_bDebugInitialized == TRUE) { return; } l_bDebugInitialized = TRUE; DigestInitDebug(MyDebugKeys); DigestInfoLevel = 0x0; // Turn on OFF messages - Registry read will adjust which ones to keep on #endif return; } //////////////////////////////////////////////////////////////////// // // Name: DigestWatchParamKey // // Synopsis: Sets RegNotifyChangeKeyValue() on param key, initializes // debug level, then utilizes thread pool to wait on // changes to this registry key. Enables dynamic debug // level changes, as this function will also be callback // if registry key modified. // // Arguments: pCtxt is actually a HANDLE to an event. This event // will be triggered when key is modified. // // Notes: . // VOID DigestWatchParamKey( PVOID pCtxt, BOOLEAN fWaitStatus) { NTSTATUS Status; LONG lRes = ERROR_SUCCESS; BOOL fFirstTime = FALSE; DWORD disp; if(g_hkBase == NULL) { // First time we've been called. Status = RegCreateKeyEx(HKEY_LOCAL_MACHINE, REG_DIGEST_BASE, 0, TEXT(""), REG_OPTION_NON_VOLATILE, KEY_READ, NULL, &g_hkBase, &disp); if(Status) { DebugLog((DEB_WARN,"Failed to open WDigest key: 0x%x\n", Status)); return; } fFirstTime = TRUE; } if(pCtxt != NULL) { if (NULL != g_hWait) { Status = RtlDeregisterWait(g_hWait); if(!NT_SUCCESS(Status)) { DebugLog((DEB_WARN, "Failed to Deregister wait on registry key: 0x%x\n", Status)); goto Reregister; } } lRes = RegNotifyChangeKeyValue( g_hkBase, TRUE, REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET, (HANDLE)pCtxt, TRUE); if (ERROR_SUCCESS != lRes) { DebugLog((DEB_ERROR,"Debug RegNotify setup failed: 0x%x\n", lRes)); // we're tanked now. No further notifications, so get this one } } NtDigestReadRegistry(fFirstTime); Reregister: if(pCtxt != NULL) { Status = RtlRegisterWait(&g_hWait, (HANDLE)pCtxt, DigestWatchParamKey, (HANDLE)pCtxt, INFINITE, WT_EXECUTEINPERSISTENTIOTHREAD| WT_EXECUTEONLYONCE); } }