/*++ Copyright (c) 1992 Microsoft Corporation Module Name: credman.cxx Abstract: WNet Credential Management API functions Author: Dan Lafferty (danl) 07-Dec-1992 Environment: User Mode - Win32 Revision History: 05-May-1999 jschwart Make provider addition/removal dynamic 19-Apr-1994 danl Fix timeout logic where we would ignore the provider-supplied timeout if the timeout was smaller than the default. Now, if all the providers know their timeouts, the larger of those timeouts is used. Even if smaller than the default. 09-Jun-1993 danl Fixed MaxWait in MprCheckTimeout() so that now it is passed in. Until now, it was left uninitialized. 07-Apr-1993 danl Initialize the pointer to the logon script to NULL prior to passing it to the provider to fill in. We are expecting it to be NULL if they don't have a logon script. 18-Jan-1993 danl WNetLogonNotify: If the provider returns an error that mpr doesn't understand, it should discontinue calling that provider. This is accomplished by setting the ContinueFlag for that provider to FALSE. 07-Dec-1992 danl Created --*/ // // INCLUDES // #include "precomp.hxx" #include // WCSSIZE // // DEFINES // typedef struct _RETRY_INFO { DWORD ProviderIndex; DWORD Status; DWORD ProviderWait; BOOL ContinueFlag; LPWSTR LogonScript; } RETRY_INFO, *LPRETRY_INFO; // // LOCAL FUNCTIONS // DWORD MprMakeRetryArray( LPCWSTR lpPrimaryAuthenticator, LPDWORD lpNumProviders, LPRETRY_INFO *lpRetryArray, LPDWORD lpRegMaxWait ); VOID MprUpdateTimeout( LPRETRY_INFO RetryArray, LPPROVIDER Provider, DWORD RegMaxWait, LPDWORD pMaxWait, DWORD StartTime, DWORD CallStatus ); VOID MprCheckTimeout( BOOL *ContinueFlag, DWORD StartTime, DWORD MaxWait, LPDWORD lpStatus ); DWORD APIENTRY WNetLogonNotify( LPCWSTR lpPrimaryAuthenticator, PLUID lpLogonId, LPCWSTR lpAuthentInfoType, LPVOID lpAuthentInfo, LPCWSTR lpPreviousAuthentInfoType, // may be NULL LPVOID lpPreviousAuthentInfo, // may be NULL LPWSTR lpStationName, LPVOID StationHandle, LPWSTR *lpLogonScripts ) /*++ Description: This function provides notification to provider dll's that must handle log-on events. Each Credential Manager Provider is allowed to return a single command line string which will execute a logon script. WNetLogonNotify gathers these strings into a MULTI_SZ string buffer. (Meaning each string is NULL terminated, and the set of strings is NULL terminated - thus making the last string doubly NULL terminated). !! IMPORTANT !! The caller of this function is responsible for freeing the buffer pointed to by *lpLogonScripts. The windows API function LocalFree() should be used to do this. Arguments: lpPrimaryAuthenticator - This is a pointer to a string that identifies the primary authenticator. The router uses this information to skip the credential manager identified by this string. Since it is the primary, it has already handled the logon. This string is obtained from the "\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\ Services\*(keyname)\NetworkProvider\Name" registry value. lpLogonId - The logon ID of the session currently being logged on. lpAuthentInfoType - This points to a string that identifies the AuthentInfo structure type. When Microsoft is the primary authenticator, the values that may be expected here are the ones described for the lpAuthentInfoType parameter to NPLogonNotify(). lpAuthentInfo - This points to a structure that contains the credentials used to successfully log the user on via the primary authenticator. The structures that may be specified when using Micosoft's primary authenticator are: When Microsoft is the primary authenticator, the structures that may be expected here are the ones described for the lpAuthentInfo parameter to NPLogonNotify(). lpPreviousAuthentInfoType - This is pointer to a string that identifies the PreviousAuthentInfo structure. If this pointer is NULL, then no PreviousAuthentInfo is available. The values that may be expected here are the same as the values that may be expected for the lpAuthentInfoType parameter. lpPreviousAuthentInfo - If the user was forced to change the password prioir to logging on, this points to a AuthentInfo structure that will contain the credential information used prior to the password change. If the user was not forced to change the password prior to logging on, then this pointer is NULL. The structures that may be expected here are the same as the structures that may be expected for the lpAuthentInfo parameter. lpStationName - This parameter contains the name of the station the user has logged onto. This may be used to determine whether or not interaction with the user to obtain additional (provider-specific) credentials is possible. This information will also have a bearing on the meaning and use of the StationHandle parameter. When Microsoft is the primary authenticator, the values that may be expected here are the ones described for the lpStationName parameter to NPLogonNotify(). StationHandle - Is a 32-bit value whose meaning is dependent upon the name (and consequently, the type) of station being logged onto. When Microsoft is the primary authenticator, the values that may be expected here are the ones described for the lpStationHandle parameter to NPLogonNotify(). lpLogonScripts - This is a pointer to a location where a pointer to a MULTI_SZ string may be returned. Each null terminated string in the MULTI_SZ string is assumed to contain the name of a program to execute and parameters to pass to the program. The memory allocated to hold the returned string must be deallocatable by the calling routine. The caller of this routine is responsible for freeing the memory used to house this string when it is no longer needed. Return Value: --*/ { DWORD status = WN_SUCCESS; DWORD numProviders; LPPROVIDER provider; BOOL fcnSupported = FALSE; // Is fcn supported by a provider? DWORD i; BOOL ContinueFlag; LPRETRY_INFO RetryArray; DWORD RegMaxWait=0; DWORD MaxWait=0; DWORD StartTime; DWORD scriptSize=0; LPWSTR pScript; MprCheckProviders(); CProviderSharedLock PLock; INIT_IF_NECESSARY(CREDENTIAL_LEVEL,status); MPR_LOG0(TRACE,"Entered WNetLogonNotify\n"); // // Now create an array of information about the providers so that we // can retry until timeout, or all providers are functional. // Note: The Status field in each retry array element is initialized // WN_NO_NETWORK. // __try { *lpLogonScripts = NULL; status = MprMakeRetryArray( lpPrimaryAuthenticator, &numProviders, &RetryArray, &RegMaxWait); } __except(MPR_EXCEPTION_FILTER) { status = GetExceptionCode(); if (status != EXCEPTION_ACCESS_VIOLATION) { MPR_LOG(ERROR,"WNetLogonNotify:Unexpected Exception 0x%lx\n",status); } status = WN_BAD_POINTER; } if ((status != WN_SUCCESS) || (numProviders == 0)) { MPR_LOG2(TRACE,"WNetLogonNotify: Error - status=%d, numProviders=%d\n", status,numProviders); return(status); } // // Initialize the timer. // if (RegMaxWait != 0) { MaxWait = RegMaxWait; } StartTime = GetTickCount(); // // Loop through the list of providers notifying each one in turn. // If the underlying service or driver is not available for the // provider such that it cannot complete this call, then we will // wait for it to become available. // do { ContinueFlag = FALSE; for (i=0; iLogonNotify != NULL)) { fcnSupported = TRUE; RetryArray[i].LogonScript = NULL; __try { MPR_LOG(TRACE,"Calling (%ws) LogonNotify function\n", provider->Resource.lpProvider); status = provider->LogonNotify( lpLogonId, lpAuthentInfoType, lpAuthentInfo, lpPreviousAuthentInfoType, lpPreviousAuthentInfo, lpStationName, StationHandle, (LPWSTR *)&(RetryArray[i].LogonScript)); } __except(MPR_EXCEPTION_FILTER) { status = GetExceptionCode(); if (status != EXCEPTION_ACCESS_VIOLATION) { MPR_LOG(ERROR,"WNetLogonNotify:Unexpected Exception " "0x%lx\n",status); } status = WN_BAD_POINTER; } switch (status) { case WN_SUCCESS: // // Because this provider may have put up dialogs, and // taken a long time to complete, we want to skip the // timeout check and try all the providers once more. // We force the index to 0 in order to assure that all // providers will be tried again prior to checking for // a timeout. // RetryArray[i].Status = WN_SUCCESS; RetryArray[i].ContinueFlag = FALSE; if (RetryArray[i].LogonScript != NULL) { // // If the provider returns an empty logon script, // don't add the empty string to the list as that // shows up as the end of the list to the caller. // DWORD dwSize = WCSSIZE(RetryArray[i].LogonScript); if (dwSize > sizeof(WCHAR)) { scriptSize += dwSize; } } ContinueFlag = FALSE; i=0; break; case WN_NO_NETWORK: case WN_FUNCTION_BUSY: // // The provider is not ready to be notified (its underlying // driver or service is not running yet). Attempt to // find out how long to wait, or if it will ever start. // This function will update MaxWait if the provider can // give us a wait hint. // MprUpdateTimeout( &(RetryArray[i]), provider, RegMaxWait, &MaxWait, StartTime, status); break; default: RetryArray[i].Status = status; RetryArray[i].ContinueFlag = FALSE; break; } // End Switch (Get providers timeouts). ContinueFlag |= RetryArray[i].ContinueFlag; } // end check to see if function is supported. } // end for i sizeof(WCHAR)) { wcscpy(pScript, RetryArray[i].LogonScript); // // Update the pointer to point beyond the last null // terminator. // pScript += (dwSize / sizeof(WCHAR)); } } } // // Add the double NULL terminator. // *pScript = L'\0'; } else { // // The allocation failed. (ERROR LOG?) // MPR_LOG0(ERROR,"Logon scripts will be lost - could not " "allocate memory\n"); } } } else { SetLastError(status); } if (RetryArray != NULL) { LocalFree(RetryArray); } MPR_LOG1(TRACE,"Leaving WNetLogonNotify status = %d\n",status); return(status); } DWORD APIENTRY WNetPasswordChangeNotify( LPCWSTR lpPrimaryAuthenticator, LPCWSTR lpAuthentInfoType, LPVOID lpAuthentInfo, LPCWSTR lpPreviousAuthentInfoType, LPVOID lpPreviousAuthentInfo, LPWSTR lpStationName, LPVOID StationHandle, DWORD dwChangeInfo ) /*++ Description: This function is used to notify credential managers of a password change for an account. Arguments: lpPrimaryAuthenticator - This is a pointer to a string that identifies the primary authenticator. Credential Manager does not need the password notification since it already handled the change. This string is obtained from the "\HKEY_LOCAL_MACHINE\SYSTEM\ CurrentControlSet\Services\*(keyname)\NetworkProvider\Name" registry value. lpAuthentInfoType - This points to a string that identifies the AuthentInfo structure type. When Microsoft is the primary authenticator, the values that may be expected here are the ones described for the lpAuthentInfoType parameter to NPLogonNotify(). lpAuthentInfo - This points to a structure that contains the new credentials. When Microsoft is the primary authenticator, the structures that may be expected here are the ones described for the lpAuthentInfo parameter to NPLogonNotify(). lpPreviousAuthentInfoType - This points to the string that identifies the PreviousAuthentInfo structure type. The values that may be expected here are the same as the values that may be expected for the lpAuthentInfoType parameter. lpPreviousAuthentInfo - This points to an AuthentInfo structure that contains the previous credential information. (old password and such). The structures that may be expected here are the same as the structures that may be expected for the lpAuthentInfo parameter. lpStationName - This parameter contains the name of the station the user performed the authentication information change from. This may be used to determine whether or not interaction with the user to obtain additional (provider-specific) information is possible. This information will also have a bearing on the meaning and use of the StationHandle parameter. When Microsoft is the primary authenticator, the values that may be expected here are the ones described for the lpStationName parameter to NPLogonNotify(). StationHandle - Is a 32-bit value whose meaning is dependent upon the name (and consequently, the type) of station being logged onto. When Microsoft is the primary authenticator, the values that may be expected here are the ones described for the lpStationHandle parameter to NPLogonNotify(). dwChangeInfo - This is a set of flags that provide information about the change. Currently the following possible values are defined: WN_VALID_LOGON_ACCOUNT - If this flag is set, then the password (or, more accurately, the authentication information) that was changed will affect future logons. Some authentication information changes will only affect connections made in untrusted domains. These are accounts that the user cannot use to logon to this machine anyway. In these cases, this flag will not be set. Return Value: Note: --*/ { DWORD status = WN_SUCCESS; LPDWORD indexArray; DWORD localArray[DEFAULT_MAX_PROVIDERS]; DWORD numProviders; LPPROVIDER provider; BOOL fcnSupported = FALSE; // Is fcn supported by a provider? DWORD i,j; DWORD primaryIndex; BOOL oneSuccess=FALSE; MprCheckProviders(); CProviderSharedLock PLock; INIT_IF_NECESSARY(CREDENTIAL_LEVEL,status); // // Find the list of providers to call for this request. // indexArray = localArray; // // If there are no active providers, MprFindCallOrder returns // WN_NO_NETWORK. // status = MprFindCallOrder( NULL, &indexArray, &numProviders, CREDENTIAL_TYPE); if (status != WN_SUCCESS) { return(status); } __try { // // Remove the primary authenticator from the list. // if (MprGetProviderIndex((LPTSTR)lpPrimaryAuthenticator,&primaryIndex)) { for (i=0; iPasswordChangeNotify != NULL) { fcnSupported = TRUE; __try { MPR_LOG(TRACE,"Calling (%ws) ChangePasswordNotify function\n", provider->Resource.lpProvider); status = provider->PasswordChangeNotify( lpAuthentInfoType, lpAuthentInfo, lpPreviousAuthentInfoType, lpPreviousAuthentInfo, lpStationName, StationHandle, dwChangeInfo); } __except(MPR_EXCEPTION_FILTER) { status = GetExceptionCode(); if (status != EXCEPTION_ACCESS_VIOLATION) { MPR_LOG(ERROR,"WNetChangePassword:Unexpected Exception 0x%lx\n",status); } status = WN_BAD_POINTER; } if (status == WN_SUCCESS) { // // If the call was successful, then we indicate that at least // one of the calls was successful. // oneSuccess = TRUE; } } } if (fcnSupported == FALSE) { // // No providers in the list support the API function. Therefore, // we assume that no networks are installed. // status = WN_NOT_SUPPORTED; } // // If memory was allocated by MprFindCallOrder, free it. // if (indexArray != localArray) { LocalFree(indexArray); } // // Handle normal errors passed back from the provider // if (oneSuccess == TRUE) { status = WN_SUCCESS; } else { SetLastError(status); } return(status); } #ifdef REMOVE DWORD APIENTRY WNetLogoffNotify( HWND hwndOwner, LPCWSTR lpPrimaryAuthenticator, PLUID lpLogonId, WN_OPERATION_TYPE OperationType ) /*++ Description: This function provides log-off notification to credential managers. Arguments: hwndOwner - Identifies the owner window. lpPrimaryAuthenticator - This is a pointer to a string that identifies the primary authenticator. Credential Manager does not need the password notification since it already handled the change. lpLogonId - The logon ID of the session currently being logged on. OperationType - The type of operation. This indicates whether the function call is from an interactive program (winlogon), or a background program (service controller). User dialogs should not be displayed if the call was from a background process. Return Value: WN_SUCCESS - This is returned if we are able to successfully notify at least one of the providers which has registered an entry point for the Logoff event. WN_NO_NETWORK - This is returned if there are no providers, or if there is only one provider, but its supporting service/driver is not available. WN_BAD_POINTER - This is returned if one of the pointer parameters is bad and causes an exception. WN_NOT_SUPPORTED - This is returned if none of the active providers support this function. system errors such as ERROR_OUT_OF_MEMORY are also reported. Note: --*/ { DWORD status = WN_SUCCESS; LPDWORD indexArray; DWORD localArray[DEFAULT_MAX_PROVIDERS]; DWORD numProviders; LPPROVIDER provider; BOOL fcnSupported = FALSE; // Is fcn supported by a provider? DWORD i,j; DWORD primaryIndex; BOOL oneSuccess=FALSE; MprCheckProviders(); CProviderSharedLock PLock; INIT_IF_NECESSARY(CREDENTIAL_LEVEL,status); // // Find the list of providers to call for this request. // indexArray = localArray; // // If there are no active providers, MprFindCallOrder returns // WN_NO_NETWORK. // status = MprFindCallOrder( NULL, &indexArray, &numProviders, CREDENTIAL_TYPE); if (status != WN_SUCCESS) { return(status); } try { // // Remove the primary authenticator from the list. // if (MprGetProviderIndex((LPTSTR)lpPrimaryAuthenticator,&primaryIndex)) { for (i=0; iInitClass & CREDENTIAL_TYPE) && (provider->LogoffNotify != NULL)) { fcnSupported = TRUE; try { status = provider->LogoffNotify( hwndOwner, lpLogonId, OperationType); } except(MPR_EXCEPTION_FILTER) { status = GetExceptionCode(); if (status != EXCEPTION_ACCESS_VIOLATION) { MPR_LOG(ERROR,"WNetLogoffNotify:Unexpected Exception 0x%lx\n",status); } status = WN_BAD_POINTER; } if (status == WN_SUCCESS) { // // If the call was successful, then we indicate that at least // one of the calls was successful. // oneSuccess = TRUE; } } } if (fcnSupported == FALSE) { // // No providers in the list support the API function. Therefore, // we assume that no networks are installed. // status = WN_NOT_SUPPORTED; } // // If memory was allocated by MprFindCallOrder, free it. // if (indexArray != localArray) { LocalFree(indexArray); } // // Handle normal errors passed back from the provider // if (oneSuccess == TRUE) { status = WN_SUCCESS; } else { SetLastError(status); } return(status); } #endif // REMOVE DWORD MprMakeRetryArray( LPCWSTR lpPrimaryAuthenticator, LPDWORD lpNumProviders, LPRETRY_INFO *lpRetryArray, LPDWORD lpRegMaxWait ) /*++ Routine Description: This function returns with an array of RETRY_INFO structures. There is one array element for each credential manager provider that is NOT the current primary authenticator. The structures contain such information as the provider index in the global array of provider information, a location for the provider's timeout value, status and continue flag. This function will also attempt to obtain the WaitTimeout from the registry if it exists. IMPORTANT! This function allocates memory for lpRetryArray. The caller is expected to free this memory with LocalFree(). If an error is returned, or if the data at lpNumProviders is 0, then memory is not allocated. Arguments: lpPrimaryAuthenticator - This is a pointer to a string that identifies the primary authenticator. The router uses this information to skip the credential manager identified by this string. Since it is the primary, it has already handled the logon. lpNumProviders - This is a pointer to a location where the number of providers in the lpRetryArray are stored. lpRetryArray - This is an array of RETRY_INFO structures. There is one element in the array for each Credential Manager provider that is not the current PrimaryAuthenticator. The status in the array is initialized to WN_NO_NETWORK. lpRegMaxWait - This is a pointer to the location to where the Maximum Wait Timeout from the registry is to be placed. If there is no MaxWait value in the registry, zero is returned. Return Value: WN_SUCCESS is returned if we successfully created an array of RetryInfo structures, or if there are no credential manager providers. If we are unable to allocate memory for the RetryInfo structures, then the failure from LocalAlloc is returned. --*/ { DWORD status; DWORD i,j; HKEY providerKeyHandle; DWORD primaryIndex; DWORD ValueType; DWORD Temp; DWORD numProviders; LPDWORD indexArray; DWORD localArray[DEFAULT_MAX_PROVIDERS]; ASSERT(MPRProviderLock.Have()); // // Find the list of providers to call for this request. // indexArray = localArray; // // If there are no active providers, or none of the active providers are // in this InitClass MprFindCallOrder returns WN_NO_NETWORK. // status = MprFindCallOrder( NULL, &indexArray, &numProviders, CREDENTIAL_TYPE); if ((status != WN_SUCCESS) || (numProviders == 0)) { // // If there aren't any credential managers, then just return. // MPR_LOG0(TRACE,"MprMakeRetryArray: There aren't any Credential Managers\n"); *lpNumProviders = 0; return(WN_SUCCESS); } *lpNumProviders = numProviders; // // Remove the primary authenticator from the list. // if (MprGetProviderIndex((LPTSTR)lpPrimaryAuthenticator,&primaryIndex)) { for (i=0; iGetAuthentCaps; if (pGetCaps == NULL) { pGetCaps = Provider->GetCaps; } // // If this is the first pass through, we don't have the // wait times figured out for each provider. Do that // now. // if (RetryInfo->ProviderWait == 0) { MPR_LOG1(TRACE,"Call GetCaps to get (%ws)provider start timeout\n", Provider->Resource.lpProvider); if (pGetCaps != NULL) { providerStatus = pGetCaps(WNNC_START); switch (providerStatus) { case PROVIDER_WILL_NOT_START: MPR_LOG0(TRACE,"Provider will not start\n"); RetryInfo->ContinueFlag = FALSE; RetryInfo->Status = CallStatus; break; case NO_TIME_ESTIMATE: MPR_LOG0(TRACE,"No Time estimate for Provider start\n"); if (RegMaxWait != 0) { RetryInfo->ProviderWait = RegMaxWait; } else { RetryInfo->ProviderWait = DEFAULT_WAIT_TIME; } if (*pMaxWait < RetryInfo->ProviderWait) { *pMaxWait = RetryInfo->ProviderWait; } break; default: MPR_LOG1(TRACE,"Time estimate for Provider start = %d\n", providerStatus); // // In this case, the providerStatus is actually // the amount of time we should wait for this // provider. We set MaxWait to the longest of // the times specified by the providers. // if ((providerStatus <= MAX_ALLOWED_WAIT_TIME) && (providerStatus > *pMaxWait)) { *pMaxWait = providerStatus; } RetryInfo->ProviderWait = *pMaxWait; break; } } else { MPR_LOG0(TRACE,"There is no GetCaps function. So we cannot " "obtain the provider start timeout\n"); RetryInfo->ContinueFlag = FALSE; RetryInfo->Status = CallStatus; } } // // If the status for this provider has just changed to // WN_FUNCTION_BUSY from some other status, then calculate // a timeout time by getting the provider's new timeout // and adding that to the elapsed time since start. This // gives a total elapsed time until timeout - which can // be compared with the current MaxWait. // if ((CallStatus == WN_FUNCTION_BUSY) && (RetryInfo->Status == WN_NO_NETWORK)) { MPR_LOG1(TRACE,"Provider status just changed to FUNCTION_BUSY " "from some other status - Call GetCaps to get (%ws)provider " " start timeout\n", Provider->Resource.lpProvider); if (pGetCaps != NULL) { providerStatus = pGetCaps(WNNC_START); switch (providerStatus) { case PROVIDER_WILL_NOT_START: MPR_LOG0(TRACE,"Provider will not start - bizzare case\n"); // // This is bizzare to find the status = BUSY, // and then have the Provider not starting. // RetryInfo->ContinueFlag = FALSE; break; case NO_TIME_ESTIMATE: MPR_LOG0(TRACE,"No Time estimate for Provider start\n"); // // No need to alter the timeout for this one. // break; default: MPR_LOG1(TRACE,"Time estimate for Provider start = %d\n", providerStatus); // // Make sure this new timeout information will take // less than the maximum allowed time for // providers. // if (providerStatus <= MAX_ALLOWED_WAIT_TIME) { CurrentTime = GetTickCount(); // // Determine how much time has elapsed since // we started. // ElapsedTime = CurrentTime - StartTime; // // Add the Elapsed time to the new timeout // we just received from the provider to come // up with a timeout value that can be // compared with MaxWait. // providerStatus += ElapsedTime; // // If the new timeout is larger than MaxWait, // then use the new timeout. // if (providerStatus > *pMaxWait) { *pMaxWait = providerStatus; } } // EndIf (Make sure time out is < max allowed). break; } // End Switch (changed status). } } // End If (change state from NO_NET to BUSY) // // Store the status (either NO_NET or BUSY) with the // retry info. // RetryInfo->Status = CallStatus; MPR_LOG0(TRACE,"Leaving MprUpdateTimeout\n"); return; } VOID MprCheckTimeout( BOOL *ContinueFlag, DWORD StartTime, DWORD MaxWait, LPDWORD lpStatus ) /*++ Routine Description: This function checks to see if a timeout occured. Arguments: ContinueFlag - This is a pointer to the location of the continue flag. This is set to FALSE if a timeout occured. StartTime - This is the tick count at the beginning of the operation. lpStatus - This is a pointer to the current status for the operation. This is updated only if a timeout occurs. Return Value: none --*/ { DWORD CurrentTime; DWORD ElapsedTime; MPR_LOG0(TRACE,"Entering MprCheckTimeout\n"); if (*ContinueFlag) { // // Determine what the elapsed time from the start is. // CurrentTime = GetTickCount(); ElapsedTime = CurrentTime - StartTime; // // If a timeout occured, then don't continue. Otherwise, sleep // for a bit and loop again through all providers. // if (ElapsedTime > MaxWait) { MPR_LOG0(TRACE,"WNetLogonNotify:Timed out while waiting " "for Credential Managers\n"); *ContinueFlag = FALSE; *lpStatus = ERROR_SERVICE_REQUEST_TIMEOUT; } else { Sleep(2000); } } MPR_LOG0(TRACE,"Leaving MprCheckTimeout\n"); }