/*++ Copyright (c) 1991 Microsoft Corporation Module Name: connect.cxx Abstract: Contains the entry points for the Winnet Connection API supported by the Multi-Provider Router. Contains: WNetAddConnectionW WNetAddConnection2W WNetAddConnection3W WNetUseConnectionW WNetCancelConnection2W WNetCancelConnectionW WNetGetConnectionW WNetSetConnectionW WNetRestoreConnectionW WNetRestoreConnection2W DoRestoreConnection MprRestoreThisConnection MprCreateConnectionArray MprSortConnectionArray MprRefcountConnectionArray MprAddPrintersToConnArray MprForgetPrintConnection MprFreeConnectionArray MprNotifyErrors MprNotifyShell MprCancelConnection2 Author: Dan Lafferty (danl) 09-Oct-1991 Environment: User Mode -Win32 Notes: Revision History: 09-Oct-1991 danl created 03-Jan-1992 terryk Changed WNetRestoreConnections WNetRestoreConnection 13-Jan-1992 Johnl Added MPR.H to include file list 31-Jan-1992 Johnl Added fForgetConnection to WNetCancelConnectionW 01-Apr-1992 Johnl Changed CONNECTION_REMEMBER to CONNECT_UPDATE_PROFILE, updated WNetCancelConnection2 to match spec 22-Jul-1992 danl WNetAddConnection2: If attempting to connect to a drive that is already remembered in the registry, we will allow the connect as long as the remote name for the connection is the same as the one that is remembered. If the remote names are not the same return ERROR_DEVICE_ALREADY_REMEMBERED. 26-Aug-1992 danl WNetAddConnectionW: Put Try & except around STRLEN(lpLocalName). 04-Sept-1992 danl Re-Write MprRestoreThisConnection. 08-Sept-1992 danl WNetCancelConnect2W: If no providers claim responsibility for the cancel, and if the connect info is in the registry, then remove it, and return SUCCESS. 09-Sept-1992 danl WNetCancelConnect2W: If the provider returns WN_BAD_LOCALNAME, WN_NO_NETWORK, or WN_BAD_NETNAME, then return WN_NOT_CONNECTED. 22-Sept-1992 danl WNetRestoreConnection: For WN_CANCEL case, set continue Flag to false. We don't want to ask for password again if they already said CANCEL. 02-Nov-1992 danl Fail with NO_NETWORK if there are no providers. 24-Nov-1992 Yi-HsinS Added checking in the registry to see whether we need to restore connection or not. ( support of RAS ) 16-Nov-1993 Danl AddConnect2: If provider returns ERROR_INVALID_LEVEL or ERROR_INVALID_PARAMETER, continue trying other providers. 19-Apr-1994 Danl DoRestoreConnection: 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. AddConnection3: Fixed Prototype to be more like AddConnection2. 19-May-1994 Danl AddConnection3: Changed comments for dwType probe to match the code. Also re-arrange the code here to make it more efficient. 07-Feb-1995 AnirudhS MprGetConnection: Fixed so that, like AddConnection and CancelConnection, it doesn't stop routing on errors. 10-Feb-1995 AnirudhS WNetAddConnection3: If provider returns WN_ALREADY_CONNECTED, stop trying other providers. 08-May-1995 AnirudhS Add WNetUseConnection and make WNetAddConnection3 call through to it. 12-Jun-1995 AnirudhS Send WM_DEVICECHANGE message to notify shell of connections. 13-Jun-1995 AnirudhS Add WNetSetConnection. 07-Jul-1995 AnirudhS Tidy up auto-picking logic in WNetUseConnection and CAutoPickedDevice. 12-Jul-1995 AnirudhS Rename WNetRestoreConnection to WNetRestoreConnectionW to match winnetwk.w. 14-Sep-1995 AnirudhS WNetGetConnection: Optimize for non-network drives by not calling the providers. This enables the shell to open the "My Computer" folder much quicker. 18-Oct-1995 AnirudhS WNetUseConnection: When saving the connection to the registry, if a username is not supplied, get it from the provider, so that the right username will be used at the next logon. 15-Jan-1996 AnirudhS WNetRestoreConnection, etc.: Restore the connections in a deferred state if the remembered user name is the default user. 08-Mar-1996 AnirudhS WNetRestoreConnection, etc.: Use the provider type in preference to the provider name when saving and restoring connections, so that floating profiles work in a multi-language environment. See comments at MprReadConnectionInfo for details. 20-Mar-1996 Anirudhs Add WNetGetConnection3. 25-Mar-1996 AnirudhS WNetRestoreConnection, etc.: Don't display the "restoring connections" dialog if all connections are deferred. Don't defer connections if a registry flag says not to. 11-Apr-1996 AnirudhS WNetUseConnection: Convert to CRoutedOperation base class. Return WN_NO_MORE_DEVICES instead of WN_NO_MORE_ENTRIES. 04-Jun-1996 AnirudhS WNetRestoreConnection: Don't defer connections if logged on locally. This is a temporary fix for bug 36827 for NT 4.0. 07-Jun-1996 AnirudhS MprNotifyShell: Don't call BroadcastSystemMessage here. Instead use a simple scheme to have a trusted system component broadcast on our behalf. This fixes a deadlock that caused 20-second delays that were visible to the user. 28-Jun-1996 AnirudhS Don't defer a connection if a password was explicitly supplied when the connection was made. 08-Feb-1997 AnirudhS QFE 55011: Add WN_CONNECTED_OTHER_PASSWORD so the MPR knows if the provider prompted the user for an explicit password. 21-Feb-1997 AnirudhS Add DEFER_UNKNOWN so MPR automatically discovers whether to defer a connection at future logons. 03-Mar-1998 jschwart Fix bugs caused by moving WNetRestoreConnection from userinit.exe into winlogon.exe: Add impersonation code to WNetRestoreConnection so a reconnection is attempted in the user's context. Add code to close registry key handles that were leaked inWNetRestoreConnection, MprRestoreThisConnection, and CreateConnectionArray. 05-May-1999 jschwart Make provider addition/removal dynamic 04-Mar-2000 dsheldon Added WNetRestoreConnection2W with a flags field that allows us to reduce the amount of user interaction required to reconnect drives, and allow the shell to handle errors after logon instead. --*/ // // INCLUDES // #include "precomp.hxx" extern "C" { #include // LsaGetUserName #include // SC_BSM_EVENT_NAME } #include #include #include #include // WCSSIZE, STRLEN #include "connify.h" // MprAddConnectNotify #include "mprui.h" // ShowReconnectDialog, etc. #include // // DATA STRUCTURES // typedef struct _CONNECTION_INFO { BOOL ContinueFlag; DWORD ProviderIndex; DWORD ProviderWait; DWORD ProviderFlags; DWORD DeferFlags; // flags read from registry DWORD Status; LPTSTR UserName; BOOL Defer; // our computed decision on whether to defer NETRESOURCEW NetResource; HKEY RegKey; // handle to the key with the connection info HINSTANCE hProviderDll; // Variables to keep provider DLLs loaded PF_NPGetCaps pfGetCaps; PF_NPAddConnection3 pfAddConnection3; PF_NPAddConnection pfAddConnection; PF_NPCancelConnection pfCancelConnection; DWORD dwConnectCaps; } CONNECTION_INFO, *LPCONNECTION_INFO; // // EXTERNAL GLOBALS // extern DWORD GlobalNumActiveProviders; extern BOOL g_LUIDDeviceMapsEnabled; // // Defines // #define INVALID_WINDOW_HANDLE ((HWND) INVALID_HANDLE_VALUE) // // Local Function Prototypes // VOID DoRestoreConnection( PARAMETERS * Params ); DWORD MprRestoreThisConnection( HWND hWnd, PARAMETERS * Params, LPCONNECTION_INFO ConnectInfo, DWORD dwFlags ); DWORD MprCreateConnectionArray( LPDWORD lpNumConnections, LPCTSTR lpDevice, LPDWORD lpRegMaxWait, LPCONNECTION_INFO *ConnectArray ); VOID MprSortConnectionArray( LPCONNECTION_INFO lpcConnectArray, DWORD dwNumSubKeys ); VOID MprRefcountConnectionArray( LPCONNECTION_INFO lpcConnectArray, DWORD dwNumSubkeys ); DWORD MprAddPrintersToConnArray( LPDWORD lpNumConnections, LPCONNECTION_INFO *ConnectArray ); VOID MprFreeConnectionArray( LPCONNECTION_INFO ConnectArray, DWORD NumConnections ); BOOL MprUserNameMatch ( IN PUNICODE_STRING DomainName, IN PUNICODE_STRING UserName, IN LPCWSTR RememberedName, IN BOOL fMustMatchCompletely ); DWORD MprNotifyErrors( HWND hWnd, LPCONNECTION_INFO ConnectArray, DWORD NumConnections, DWORD dwFlags ); VOID MprNotifyShell( IN LPCWSTR pwszDevice ); DWORD MprCancelConnection2 ( IN LPCWSTR lpName, IN DWORD dwFlags, IN BOOL fForce, IN BOOL fCheckProviders ); BOOL MprBroadcastDriveChange( IN LPCWSTR pwszDevice, IN BOOL DeleteAction ); DWORD WNetAddConnectionW ( IN LPCWSTR lpRemoteName, IN LPCWSTR lpPassword, IN LPCWSTR lpLocalName ) /*++ Routine Description: This function allows the caller to redirect (connect) a local device to a network resource. The connection is remembered. Arguments: lpRemoteName - Specifies the network resource to connect to. lpPassword - Specifies the password to be used in making the connection. The NULL value may be passed in to indicate use of the 'default' password. An empty string may be used to indicate no password. lpLocalName - This should contain the name of a local device to be redirected, such as "F:" or "LPT1:" The string is treated in a case insensitive manner, and may be the empty string in which case a connection to the network resource is made without making a decision. Return Value: --*/ { DWORD status = WN_SUCCESS; NETRESOURCEW netResource; DWORD numchars; // // load up the net resource structure // netResource.dwScope = 0; netResource.dwUsage = 0; netResource.lpRemoteName = (LPWSTR) lpRemoteName; netResource.lpLocalName = (LPWSTR) lpLocalName; netResource.lpProvider = NULL; netResource.lpComment = NULL; __try { numchars = STRLEN(lpLocalName); } __except(EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); if (status != EXCEPTION_ACCESS_VIOLATION) { MPR_LOG(ERROR,"WNetAddConnectionW:Unexpected Exception 0x%lx\n",status); } status = WN_BAD_POINTER; } if (status != WN_SUCCESS) { SetLastError(status); return status; } if (numchars == 0) { netResource.dwType = RESOURCETYPE_ANY; } else if (numchars > 2) { netResource.dwType = RESOURCETYPE_PRINT; } else { netResource.dwType = RESOURCETYPE_DISK; } // // Call WNetUseConnection so it can do all the work. // return(WNetUseConnectionW ( NULL, // hwndOwner &netResource, // lpNetResource lpPassword, // lpPassword NULL, // lpUserID CONNECT_UPDATE_PROFILE, // dwFlags NULL, // lpAccessName NULL, // lpBufferSize NULL)); // lpResult } DWORD WNetAddConnection2W ( IN LPNETRESOURCEW lpNetResource, IN LPCWSTR lpPassword, IN LPCWSTR lpUserName, IN DWORD dwFlags ) /*++ Routine Description: This function allows the caller to redirect (connect) a local device to a network resource. It is similar to WNetAddConnection, except that it takes a pointer to a NETRESOURCE structure to describe the network resource to connect to. It also takes the additional parameters lpUserName, and dwFlags. Arguments: lpNetResource - This is a pointer to a network resource structure that specifies the network resource to connect to. The following fields must be set when making a connection, the others are ignored. lpRemoteName lpLocalName lpProvider dwType lpPassword - Specifies the password to be used in making the connection. The NULL value may be passed in to indicate use of the 'default' password. An empty string may be used to indicate no password. lpUserName- This specifies the username used to make the connection. If NULL, the default username (currently logged on user) will be applied. This is used when the user wishes to connect to a resource, but has a different user name or account assigned to him for that resource. dwFlags - This is a bitmask which may have any of the following bits set: CONNECT_UPDATE_PROFILE Return Value: --*/ { // // Call WNetUseConnection so it can do all the work. // It is called with a NULL HWND. // return(WNetUseConnectionW ( NULL, // hwndOwner lpNetResource, // lpNetResource lpPassword, // lpPassword lpUserName, // lpUserID dwFlags, // dwFlags NULL, // lpAccessName NULL, // lpBufferSize NULL)); // lpResult } DWORD WNetAddConnection3W ( IN HWND hwndOwner, IN LPNETRESOURCEW lpNetResource, IN LPCWSTR lpPassword, IN LPCWSTR lpUserName, IN DWORD dwFlags ) /*++ Routine Description: This function allows the caller to redirect (connect) a local device to a network resource. It is similar to WNetAddConnection2, except that it takes the additional parameter hwndOwner. Arguments: hwndOwner - A handle to a window which should be the owner for any messages or dialogs that the network provider might display. lpNetResource - This is a pointer to a network resource structure that specifies the network resource to connect to. The following fields must be set when making a connection, the others are ignored. lpRemoteName lpLocalName lpProvider dwType lpPassword - Specifies the password to be used in making the connection. The NULL value may be passed in to indicate use of the 'default' password. An empty string may be used to indicate no password. lpUserName- This specifies the username used to make the connection. If NULL, the default username (currently logged on user) will be applied. This is used when the user wishes to connect to a resource, but has a different user name or account assigned to him for that resource. dwFlags - This is a bitmask which may have any of the following bits set: CONNECT_UPDATE_PROFILE Return Value: --*/ { // // Call WNetUseConnection so it can do all the work. // It is called with no buffer for the access name and result flags. // return(WNetUseConnectionW ( hwndOwner, // hwndOwner lpNetResource, // lpNetResource lpPassword, // lpPassword lpUserName, // lpUserID dwFlags, // dwFlags NULL, // lpAccessName NULL, // lpBufferSize NULL)); // lpResult } /************************************************************************* NAME: CAutoPickedDevice SYNOPSIS: This class is meant for use by the WNetUseConnection function. It iterates through all possible local device names and finds an unused device name to be redirected. For efficiency, it does this only once in the lifetime of the object, viz. the first time the PickDevice() method is called. On subsequent calls it returns the result of the first call. The results of the PickDevice() call are saved in member variables, rather than being copied to the caller's buffer, because we don't know whether to return them to the caller until later. INTERFACE: CAVEATS: The Init method must be called before the PickDevice method. NOTES: HISTORY: AnirudhS 24-May-1995 Created **************************************************************************/ class CAutoPickedDevice { private: DWORD _bPicked; // whether already attempted to pick a device DWORD _dwError; // result of attempt to pick a device DWORD _dwDeviceType; // saved parameter from Init() DWORD _cchBufferSize; // saved parameter from Init() DWORD _cchReqBufferSize; // saved results of PickDevice() WCHAR _wszPickedName[ max(sizeof("A:"), sizeof("LPT99:")) ]; public: DWORD Init( IN LPWSTR lpAccessName, IN LPDWORD lpcchBufferSize, IN LPCWSTR pwszLocalName, IN LPCWSTR pwszRemoteName, IN DWORD dwDeviceType, IN DWORD dwFlags ); DWORD PickDevice(); DWORD dwError() { return _dwError; } DWORD cchReqBufferSize() { return _cchReqBufferSize; } LPWSTR wszPickedName() { return _wszPickedName; } } ; /************************************************************************* NAME: CAutoPickedDevice::Init SYNOPSIS: This function validates the parameters related to access names and auto-picking of local device names, and saves away some of them to be used in PickDevice(). Depending on the severity of the errors it finds, it does one of the following: - cause an access violation - return an error - return success, but save away an error to be returned from PickDevice() - return success, and save away WN_SUCCESS so that PickDevice() will try to pick a device **************************************************************************/ DWORD CAutoPickedDevice::Init( IN LPWSTR lpAccessName, IN LPDWORD lpcchBufferSize, IN LPCWSTR pwszLocalName, IN LPCWSTR pwszRemoteName, IN DWORD dwDeviceType, IN DWORD dwFlags ) { _bPicked = FALSE; _dwError = WN_SUCCESS; // // If out pointers are supplied, make sure they're writeable // (even if we don't use them) // if (ARGUMENT_PRESENT(lpcchBufferSize)) { _cchBufferSize = *(volatile DWORD *)lpcchBufferSize; // Probe *(volatile DWORD *)lpcchBufferSize = _cchBufferSize; // Probe } else { _cchBufferSize = 0; } if (ARGUMENT_PRESENT(lpAccessName)) { // // If an AccessName buffer is supplied, the size parameter must // be present and non-zero, else return right away // if (_cchBufferSize == 0) { return WN_BAD_VALUE; // Win95 compatibility } if (IsBadWritePtr(lpAccessName, _cchBufferSize * sizeof(WCHAR))) { return WN_BAD_POINTER; } _cchReqBufferSize = 0; // // If an access name is requested, and a local name is // specified, then we know that the access name will be the // specified local name, so we can validate the buffer // length up front // if (! IS_EMPTY_STRING(pwszLocalName)) { _cchReqBufferSize = wcslen(pwszLocalName)+1; } // // If an access name is requested, and no local name is // specified, and we aren't asked to auto-pick a local // device, then the access name is likely to be the specified // remote name, so we validate the buffer length up front // else if ((dwFlags & CONNECT_REDIRECT) == 0) { _cchReqBufferSize = wcslen(pwszRemoteName)+1; } if (_cchBufferSize < _cchReqBufferSize) { *lpcchBufferSize = _cchReqBufferSize; return WN_MORE_DATA; } // // Save other parameters for use in PickDevice() // _dwDeviceType = dwDeviceType; // // (If we auto-pick a local device, the buffer length will // be validated later, when we auto-pick) // } else { // // We won't be able to autopick, because no access name buffer // is supplied. But don't return the error until we're asked // to autopick. // _bPicked = TRUE; _dwError = WN_BAD_POINTER; } return WN_SUCCESS; } /************************************************************************* NAME: CAutoPickedDevice::PickDevice **************************************************************************/ DWORD CAutoPickedDevice::PickDevice() { // // If we've been called before then return the error we returned // last time // if (_bPicked) { return _dwError; } _bPicked = TRUE; // // Validate the access name buffer size depending on the device type // if (_dwDeviceType == RESOURCETYPE_DISK) { _cchReqBufferSize = sizeof( "A:" ); } else if (_dwDeviceType == RESOURCETYPE_PRINT) { _cchReqBufferSize = sizeof( "LPT1" ); } else // RESOURCETYPE_ANY -- can't autopick { _dwError = WN_BAD_VALUE; return _dwError; } if ( _cchBufferSize < _cchReqBufferSize ) { _dwError = WN_MORE_DATA; return _dwError; } if (_dwDeviceType == RESOURCETYPE_DISK) { DWORD dwDrives = GetLogicalDrives(); DWORD dwAvail = (1 << ('Z' - 'A')); WCHAR i; // // Try each drive from z: to c: and see if it's been used. // Check in reverse order to reduce the risk of conflicts // between remote and local drive letters (e.g., if hardware // is added after a drive is mapped). // for (i = L'Z'; i >= L'C'; i--, dwDrives <<= 1) { if (!(dwDrives & dwAvail)) { // // This drive is available // _wszPickedName[0] = i; _wszPickedName[1] = L':'; _wszPickedName[2] = L'\0'; _dwError = WN_SUCCESS; return _dwError; } } } else // (_dwDeviceType == RESOURCETYPE_PRINT) { WCHAR wszDeviceList[128]; DWORD cch; // // Try each device from LPT1 to LPT99 and see if it's been used // wcscpy(_wszPickedName, L"LPT"); for (ULONG i = 1; i <= 99; i++) { wsprintf(&_wszPickedName[sizeof("LPT")-1], L"%lu", i); cch = QueryDosDevice(_wszPickedName, wszDeviceList, sizeof(wszDeviceList)/sizeof(WCHAR)); if ((cch == 0) && (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { // // QueryDosDevice failed -- this device is free // _dwError = WN_SUCCESS; return _dwError; } } } // // No unused devices found // _dwError = WN_NO_MORE_DEVICES; return _dwError; } //=================================================================== // WNetUseConnectionW //=================================================================== class CUseConnection : public CRoutedOperation { public: CUseConnection( HWND hwndOwner, LPNETRESOURCEW lpNetResource, LPCWSTR lpPassword, LPCWSTR lpUserName, DWORD dwFlags, LPWSTR lpAccessName, LPDWORD lpBufferSize, LPDWORD lpResult ) : CRoutedOperation( DBGPARM("UseConnection") NULL, ROUTE_AGGRESSIVE ), _hwndOwner (hwndOwner ), _lpNetResource(lpNetResource), _lpPassword (lpPassword ), _lpUserName (lpUserName ), _dwFlags (dwFlags ), _lpAccessName (lpAccessName ), _lpBufferSize (lpBufferSize ), _lpResult (lpResult ) { _ExplicitPassword = (_lpPassword != NULL); _UsedDefaultCreds = FALSE; } protected: DWORD GetResult(); // overrides CRoutedOperation implementation private: DWORD TestProviderWorker( \ const PROVIDER *pProvider \ ); HWND _hwndOwner; LPNETRESOURCEW _lpNetResource; LPCWSTR _lpPassword; LPCWSTR _lpUserName; DWORD _dwFlags; LPWSTR _lpAccessName; LPDWORD _lpBufferSize; LPDWORD _lpResult; BOOL _ExplicitPassword; BOOL _UsedDefaultCreds; CAutoPickedDevice _AutoPickedDevice; NETRESOURCEW _ProviderNetResource; DWORD _dwProviderFlags; DECLARE_CROUTED }; DWORD CUseConnection::ValidateRoutedParameters( LPCWSTR * ppProviderName, LPCWSTR * ppRemoteName, LPCWSTR * ppLocalName ) { DWORD status; // // lpRemoteName must be non-empty and readable. // if (wcslen(_lpNetResource->lpRemoteName) == 0) // Probe { return WN_BAD_NETNAME; } if (! IS_EMPTY_STRING(_lpNetResource->lpLocalName)) { // // If a lpLocalName is supplied, it must be a redirectable device // name. // if (MprDeviceType(_lpNetResource->lpLocalName) != REDIR_DEVICE) { return WN_BAD_LOCALNAME; } } // // If a result pointer is supplied, make sure it's writeable // if (ARGUMENT_PRESENT(_lpResult)) { *_lpResult = 0; } if ((_lpNetResource->dwType != RESOURCETYPE_DISK) && (_lpNetResource->dwType != RESOURCETYPE_PRINT) && (_lpNetResource->dwType != RESOURCETYPE_ANY)) { return WN_BAD_VALUE; } if (_dwFlags & ~(CONNECT_TEMPORARY | CONNECT_INTERACTIVE | CONNECT_COMMANDLINE | CONNECT_CMD_SAVECRED | CONNECT_PROMPT | CONNECT_UPDATE_PROFILE | CONNECT_UPDATE_RECENT | CONNECT_REDIRECT | CONNECT_CURRENT_MEDIA)) { return WN_BAD_VALUE; } // // Win95 compatibility: Ignore CONNECT_PROMPT if CONNECT_INTERACTIVE // isn't set // if (!(_dwFlags & CONNECT_INTERACTIVE)) { _dwFlags &= ~CONNECT_PROMPT; // // Ditch the other prompting bits just to prevent later confusion // _dwFlags &= ~(CONNECT_COMMANDLINE|CONNECT_CMD_SAVECRED); } // // Validate parameters related to auto-picking of local device names. // Some errors are returned immediately; others are returned only // when we actually need to auto-pick a device. // status = _AutoPickedDevice.Init( _lpAccessName, _lpBufferSize, _lpNetResource->lpLocalName, _lpNetResource->lpRemoteName, _lpNetResource->dwType, _dwFlags ); if (status != WN_SUCCESS) { return status; } // // Set parameters used by base class. Set the local name to NULL // to prevent the check for "localness" in the base class. // *ppProviderName = _lpNetResource->lpProvider; *ppRemoteName = _lpNetResource->lpRemoteName; *ppLocalName = NULL; return WN_SUCCESS; } DWORD CUseConnection::TestProviderWorker( const PROVIDER * pProvider ) { DWORD status; ASSERT_INITIALIZED(NETWORK); if (pProvider->AddConnection3 == NULL && pProvider->AddConnection == NULL) { return WN_NOT_SUPPORTED; } // // We will retry this provider, with an autopicked local name, // at most once // for (BOOL fRetried = FALSE; ; fRetried = TRUE) { // // Call the provider's appropriate entry point // // // If, in the future, there are cases when the lpRemoteName // is some alias understood by the provider but not by the // NT name space, we'll need to add a NPAddConnection4 which // allows the provider to return an access name. // if (pProvider->AddConnection3 != NULL) { //************************************** // Actual call to Provider. //************************************** status = pProvider->AddConnection3( _hwndOwner, // hwndOwner &_ProviderNetResource, // lpNetResource (LPWSTR)_lpPassword, // lpPassword (LPWSTR)_lpUserName, // lpUserName _dwProviderFlags & pProvider->ConnectFlagCaps ); // dwFlags if (status == WN_CONNECTED_OTHER_PASSWORD) { // // The user had to be prompted for a password, so don't // defer the connection when restoring it at the next logon. // ASSERT(_dwProviderFlags & CONNECT_INTERACTIVE); _ExplicitPassword = TRUE; status = WN_SUCCESS; } else if (status == WN_CONNECTED_OTHER_PASSWORD_DEFAULT) { // // The provider successfully used the default credentials // for this connection. // _UsedDefaultCreds = TRUE; status = WN_SUCCESS; } } else // (pProvider->AddConnection != NULL) { //************************************** // Actual call to Provider. //************************************** status = pProvider->AddConnection( &_ProviderNetResource, // lpNetResource (LPWSTR)_lpPassword, // lpPassword (LPWSTR)_lpUserName); // lpUserName ASSERT(status != WN_CONNECTED_OTHER_PASSWORD); } // The provider mustn't return this error, or it will mess us // up later ASSERT(status != WN_MORE_DATA); if (fRetried) { if (status != WN_SUCCESS) { // Restore the null local name _ProviderNetResource.lpLocalName = NULL; } break; } // // If this provider can't handle a NULL local name, // and we've been allowed to auto-pick a local name, // and we successfully auto-pick a local name, // try again with the auto-picked local name // // Note that status is updated iff we've been allowed // to auto-pick // if (status == WN_BAD_LOCALNAME && _ProviderNetResource.lpLocalName == NULL && _lpAccessName != NULL && (status = _AutoPickedDevice.PickDevice()) == WN_SUCCESS) { _ProviderNetResource.lpLocalName = _AutoPickedDevice.wszPickedName(); MPR_LOG2(ROUTE, "CUseConnection: retrying %ws with device %ws ...\n", pProvider->Resource.lpProvider, _ProviderNetResource.lpLocalName); } else { // // Don't retry // break; } } // end retry loop for this provider return status; } BOOL InitCredUI( IN PWSTR pszRemoteName, OUT PWSTR pszServer, IN ULONG ulServerLength ) { // Make sure the first 2 characters are path separators: if ((pszRemoteName == NULL) || (pszRemoteName[0] != L'\\') || (pszRemoteName[1] != L'\\')) { SetLastError(WN_BAD_NETNAME); return FALSE; } PWSTR pszStart = pszRemoteName + 2; PWSTR pszEnd = NULL; // send only the server name, the string up to the first slash pszEnd = wcschr(pszStart, L'\\'); if (pszEnd == NULL) { pszEnd = pszStart + wcslen(pszStart); } DWORD_PTR dwLength = (DWORD_PTR)(pszEnd - pszStart); if ((dwLength == 0) || (dwLength >= ulServerLength)) { // The server is either an empty string or more than the maximum // number of characters we support: SetLastError(WN_BAD_NETNAME); return FALSE; } wcsncpy(pszServer, pszStart, dwLength); pszServer[dwLength] = L'\0'; return TRUE; } VOID DisplayFailureReason( DWORD Status, LPCWSTR Path ) /*++ Routine Description: This routine displays the reason for the authentication failure. The complete path name is displayed. We could modify credui to display this, but credui doesn't have the full path name. Arguments: Status - Status of the failure Path - Full path name of the failed authentication. Return Value: None. --*/ { DWORD MessageId; LPWSTR InsertionStrings[2]; HMODULE lpSource = NULL; DWORD MessageLength = 0; LPWSTR Buffer = NULL; BOOL MessageFormatted = FALSE; // // Pick the message based in the failure status // MessageId = ( Status == ERROR_LOGON_FAILURE) ? APE_UseBadPassOrUser : APE_UseBadPass; InsertionStrings[0] = (LPWSTR) Path; // // Load the message library and format the message from it // lpSource = LoadLibraryEx(MESSAGE_FILENAME, NULL, LOAD_LIBRARY_AS_DATAFILE); if ( lpSource ) { // // Format the message // MessageLength = FormatMessageW( FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE, lpSource, MessageId, 0, // LanguageId defaulted (LPWSTR) &Buffer, 1024, // Limit the buffer size (va_list *) InsertionStrings); if ( MessageLength ) { MessageFormatted = TRUE; } } // // If it failed, print a generic message // if ( !MessageFormatted ) { WCHAR NumberString[18]; // // get the message number in Unicode // _ultow(MessageId, NumberString, 16); // // setup insert strings // InsertionStrings[0] = NumberString; InsertionStrings[1] = MESSAGE_FILENAME; // // Use the system messge file. // MessageLength = FormatMessageW( FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, ERROR_MR_MID_NOT_FOUND, 0, // LanguageId defaulted (LPWSTR) &Buffer, 1024, (va_list *) InsertionStrings); } // // Avoid double newlines // if ( MessageLength >= 2) { if ( Buffer[MessageLength - 1] == L'\n' && Buffer[MessageLength - 2] == L'\r') { // // "\r\n" shows up as two newlines when using the CRT and piping // output to a file. Make it just one newline. // Buffer[MessageLength - 1] = L'\0'; Buffer[MessageLength - 2] = L'\n'; MessageLength --; } } // // Output the message to stdout // if ( MessageLength > 0 ) { HANDLE StdoutHandle = GetStdHandle( STD_OUTPUT_HANDLE ); if ( StdoutHandle != INVALID_HANDLE_VALUE ) { DWORD CharsWritten; if ( !WriteConsoleW( StdoutHandle, Buffer, MessageLength, &CharsWritten, NULL )) { // // Convert to ANSI before doing the write file // if ( NO_ERROR == OutputStringToAnsiInPlace( Buffer ) ) { CharsWritten = strlen( (LPSTR) Buffer ); WriteFile( StdoutHandle, Buffer, CharsWritten, &CharsWritten, NULL ); } } // // Append a newline for formatting reasons // Buffer[0] = '\n'; WriteFile( StdoutHandle, Buffer, 1, &CharsWritten, NULL ); } } // // Clean up before exitting // if ( lpSource != NULL ) { FreeLibrary( lpSource ); } if ( Buffer != NULL ) { LocalFree( Buffer ); } } DWORD CUseConnection::TestProvider( const PROVIDER * pProvider ) { DWORD dwErr; DWORD LocalProviderFlags; BOOL DoCommandLinePrompting = FALSE; // // If the caller asked for CONNECT_COMMANDLINE, // and the provider doesn't support it, // this routine should take over responsibility for doing it. // LocalProviderFlags = _dwProviderFlags; if ( (_dwProviderFlags & CONNECT_COMMANDLINE) != 0 && (pProvider->ConnectFlagCaps & CONNECT_COMMANDLINE) == 0 ) { DoCommandLinePrompting = TRUE; // // Don't let the provider do GUI prompting. // _dwProviderFlags &= ~(CONNECT_INTERACTIVE | CONNECT_PROMPT | CONNECT_COMMANDLINE | CONNECT_CMD_SAVECRED); } // // Try the connection once // (Unless we're asked not to before prompting.) // if ( !DoCommandLinePrompting || (LocalProviderFlags & CONNECT_PROMPT) == 0 ) { dwErr = TestProviderWorker( pProvider ); } else { dwErr = ERROR_LOGON_FAILURE; } // // If this routine is handling command line prompting, // and the connection failed for authentication reasons, // prompt for credentials here. // // Ignore COMMENT_COMMANDLINE_SAVECRED since we don't know what authentication mechanism // the provider uses. // if ( DoCommandLinePrompting && CREDUI_IS_AUTHENTICATION_ERROR(dwErr) ) { WCHAR szPassword[CREDUI_MAX_PASSWORD_LENGTH + 1]; // if the user needs to enter one WCHAR szUserName[CREDUI_MAX_USERNAME_LENGTH + 1]; WCHAR szServer[CRED_MAX_DOMAIN_TARGET_NAME_LENGTH + 1]; // // Display the reason for the failure with the full path name // DisplayFailureReason( dwErr, _ProviderNetResource.lpRemoteName ); // // Default the user name to the one provided by the caller. // szUserName[0] = L'\0'; if ( _lpUserName != NULL ) { if (wcslen(_lpUserName) > CREDUI_MAX_USERNAME_LENGTH) { return WN_BAD_USER; } wcscpy(szUserName, _lpUserName); } // // Prepare to use the credential manager user interface: // if (!InitCredUI(_ProviderNetResource.lpRemoteName, szServer, CRED_MAX_DOMAIN_TARGET_NAME_LENGTH)) { dwErr = GetLastError(); } else { DWORD dwCredErr; DWORD dwAuthErr; LPWSTR pszNewPassword; DWORD dwCredUIFlags = 0; // // Remember the original failure reason // dwAuthErr = dwErr; // // Set the appropriate flag to set the behavior of the common UI. // // Ask to not save the credential dwCredUIFlags |= CREDUI_FLAGS_DO_NOT_PERSIST | CREDUI_FLAGS_GENERIC_CREDENTIALS; // We don't (yet) know how to handle certificates dwCredUIFlags |= CREDUI_FLAGS_EXCLUDE_CERTIFICATES; // Ensure that the username syntax is correct (Not in MPR) // dwCredUIFlags |= CREDUI_FLAGS_VALIDATE_USERNAME; dwCredErr = CredUICmdLinePromptForCredentials( szServer, NULL, dwAuthErr, szUserName, CREDUI_MAX_USERNAME_LENGTH, szPassword, CREDUI_MAX_PASSWORD_LENGTH, NULL, // Creds not saved dwCredUIFlags); if (dwCredErr == ERROR_CANCELLED) { dwErr = WN_CANCEL; } else if ( dwCredErr == ERROR_SUCCESS ) { LPCWSTR OldPassword; // // Use the returned username and password // _lpUserName = szUserName; OldPassword = _lpPassword; _lpPassword = szPassword; // // Try the connection one more time // dwErr = TestProviderWorker( pProvider ); if ( dwErr == NO_ERROR ) { // // If typed password is different than the one passed in, // this is now an explicit password. // if ( OldPassword == NULL || wcscmp(OldPassword, _lpPassword) != 0 ) { _ExplicitPassword = TRUE; } } } // // clear any password from memory // ZeroMemory(szPassword, sizeof(szPassword)) ; } } // // Restore the flag to their initial value // _dwProviderFlags = LocalProviderFlags; return dwErr; } DWORD CUseConnection::GetResult() { DWORD status; // // If we're given a local name, // check the current list of remembered drives in the registry // to determine if the localName is already connected. // if (! IS_EMPTY_STRING(_lpNetResource->lpLocalName)) { // // If the local drive is already in the registry, and it is // for a different connection than that specified in the // lpNetResource, then indicate an error because the device // is already remembered. // LPWSTR remoteName; if (MprFindDriveInRegistry(_lpNetResource->lpLocalName, &remoteName)) { if (remoteName != NULL) { if (STRICMP(_lpNetResource->lpRemoteName, remoteName)!=0) { LocalFree(remoteName); return WN_DEVICE_ALREADY_REMEMBERED; } LocalFree(remoteName); } } } // // We modify some parameters before passing them to the provider // _ProviderNetResource = *_lpNetResource; if (IS_EMPTY_STRING(_ProviderNetResource.lpLocalName)) { _ProviderNetResource.lpLocalName = NULL; } _dwProviderFlags = _dwFlags & WNNC_CF_MAXIMUM; // // If we're not given a local name, but are explicitly asked to // redirect a device, we must autopick one // if ((_dwFlags & CONNECT_REDIRECT) && (_ProviderNetResource.lpLocalName == NULL) ) { status = _AutoPickedDevice.PickDevice(); if (status != WN_SUCCESS) { return status; } _ProviderNetResource.lpLocalName = _AutoPickedDevice.wszPickedName(); } INIT_IF_NECESSARY(NOTIFIEE_LEVEL,status); // // Notify all interested parties that a connection is being made. // (All providers are required to support deviceless connections, so // we shouldn't get a case where a notifyee might reject a connection // on the grounds that it has no local name, even though we would have // auto-picked a local name later) // NOTIFYADD NotifyAdd; NOTIFYINFO NotifyInfo; NotifyInfo.dwNotifyStatus = NOTIFY_PRE; NotifyInfo.dwOperationStatus= 0L; NotifyInfo.lpContext = MprAllocConnectContext(); NotifyAdd.hwndOwner = _hwndOwner; NotifyAdd.NetResource = *_lpNetResource; NotifyAdd.dwAddFlags = _dwFlags; __try { status = MprAddConnectNotify(&NotifyInfo, &NotifyAdd); } __except(EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); if (status != EXCEPTION_ACCESS_VIOLATION) { MPR_LOG(ERROR,"WNetUseConnectionW: ConnectNotify, Unexpected " "Exception %#lx\n",status); } status = WN_BAD_POINTER; } if (status != WN_SUCCESS) { return status; } do // Notification loop { // // Let the base class try all providers and figure out the best error // CRoutedOperation::GetResult calls INIT_IF_NECESSARY // status = CRoutedOperation::GetResult(); // // Notify all interested parties of the status of the connection. // NotifyInfo.dwNotifyStatus = NOTIFY_POST; NotifyInfo.dwOperationStatus = status; } while (MprAddConnectNotify(&NotifyInfo, &NotifyAdd) == WN_RETRY); MprFreeConnectContext(NotifyInfo.lpContext); // // Write info to the informational parameters // (lpAccessName, lpBufferSize, lpResult) // if (status == WN_MORE_DATA) { // // This error must have come from CAutoPickedDevice::PickDevice, // indicating that the access name buffer is too small // (unless it came from a buggy provider) // ASSERT(_AutoPickedDevice.dwError() == WN_MORE_DATA); if (ARGUMENT_PRESENT(_lpBufferSize)) { *_lpBufferSize = _AutoPickedDevice.cchReqBufferSize(); } } else if (status == WN_SUCCESS && ARGUMENT_PRESENT(_lpAccessName)) { LPWSTR pwszAccessName = _ProviderNetResource.lpLocalName; if (pwszAccessName == NULL) { pwszAccessName = _ProviderNetResource.lpRemoteName; } if (*_lpBufferSize < wcslen(pwszAccessName)+1) { // // We validated most of the cases up front, so the only way // this could happen is if we auto-picked and got a local // name that was longer than the remote name. // ASSERT(0); *_lpBufferSize = wcslen(pwszAccessName)+1; status = WN_MORE_DATA; } else { wcscpy(_lpAccessName, pwszAccessName); if (ARGUMENT_PRESENT(_lpResult) && _ProviderNetResource.lpLocalName != NULL) { *_lpResult = CONNECT_LOCALDRIVE; } } } if (status == WN_SUCCESS) { ASSERT_INITIALIZED(NETWORK); // // If the connection was added successfully, then write the connection // information to the registry to make it persistent. // Note: Failure to write to the registry is ignored. // if ((_dwFlags & CONNECT_UPDATE_PROFILE) && !IS_EMPTY_STRING(_ProviderNetResource.lpLocalName)) { BYTE ProviderFlags = 0; // // Get the username for the connection if the provider didn't report // that default creds were used: // // 1. If the username is explicitly given, store the // username returned by the provider // // 2. Otherwise, compare the username given by the // provider with the default username. If they're // different, store it. If they're the same, don't // // 3. If we can't get a username for whatever reason, // use the username in _lpUserName (which will be // NULL if no name was explicitly given) // if (LastProvider()->GetUser != NULL) { WCHAR wszUser[MAX_PATH + 1]; __try { DWORD cbBuffer = LENGTH(wszUser); DWORD status2 = LastProvider()->GetUser( _ProviderNetResource.lpLocalName, wszUser, &cbBuffer); ASSERT(status2 != WN_MORE_DATA); if (status2 == WN_SUCCESS) { // // Step 1 -- is the username explicitly given? // if (ARGUMENT_PRESENT(_lpUserName)) { MPR_LOG1(TRACE, "Explicit username being saved -- %ws\n", wszUser); _lpUserName = wszUser; } else { // // Step 2 -- is the user the default user? // PUNICODE_STRING UserName; PUNICODE_STRING DomainName; NTSTATUS ntStatus = LsaGetUserName(&UserName, &DomainName); if (NT_SUCCESS(ntStatus)) { if (MprUserNameMatch(DomainName, UserName, wszUser, TRUE)) { MPR_LOG1(TRACE, "User %ws is default user -- not saving username\n", wszUser); _lpUserName = NULL; } else { MPR_LOG1(TRACE, "User %ws is not default user -- saving username\n", wszUser); _lpUserName = wszUser; } } } } else if (status2 == WN_CONNECTED_OTHER_PASSWORD_DEFAULT) { // // WN_CONNECTED_OTHER_PASSWORD_DEFAULT will be returned // when user X mapped a drive as user Y and the credentials // for user Y were stored in CredMan when the connection // was made. Treat this as the default username so we don't // explicitly write user Y's username into the registry // (since CredMan will be unable to reestablish the connection // automatically in that case). // _lpUserName = NULL; } } __except (EXCEPTION_EXECUTE_HANDLER) { MPR_LOG(ERROR, "WNetUseConnectionW: exception %#lx from NPGetUser\n", GetExceptionCode()); } } // // Get an 8-bit datum that the provider may want saved along // with the connection. This will be passed back to the provider // in NPAddConnection3 when the connection is restored. // (Actually this is a hack for the NTLM provider to remember // whether a connection is a DFS connection or a regular LM // connection.) // if (LastProvider()->GetReconnectFlags != NULL) { // This is an internal entry point so we don't bother with // try-except DWORD status2 = LastProvider()->GetReconnectFlags( _ProviderNetResource.lpLocalName, &ProviderFlags ); if (status2 != WN_SUCCESS) { ProviderFlags = 0; } MPR_LOG3(RESTORE, "%ws wants flags %#x saved for %ws\n", LastProvider()->Resource.lpProvider, ProviderFlags, _ProviderNetResource.lpLocalName); } I_MprSaveConn( HKEY_CURRENT_USER, LastProvider()->Resource.lpProvider, LastProvider()->Type, _lpUserName, _ProviderNetResource.lpLocalName, _ProviderNetResource.lpRemoteName, _ProviderNetResource.dwType, ProviderFlags, _ExplicitPassword ? DEFER_EXPLICIT_PASSWORD : ( _UsedDefaultCreds ? DEFER_DEFAULT_CRED : 0 ) ); } // // Notify the shell of the connection. // (This will be replaced by real Plug'n'Play) // if( (g_LUIDDeviceMapsEnabled == TRUE) && (_ProviderNetResource.lpLocalName != NULL) ) { // Use LUID broadcast mechanism MprBroadcastDriveChange( _ProviderNetResource.lpLocalName, FALSE ); // not a Delete Message } else { MprNotifyShell(_ProviderNetResource.lpLocalName); } } return status; } DWORD WNetUseConnectionW ( IN HWND hwndOwner, IN LPNETRESOURCEW lpNetResource, IN LPCWSTR lpPassword, IN LPCWSTR lpUserName, IN DWORD dwFlags, OUT LPWSTR lpAccessName OPTIONAL, IN OUT LPDWORD lpBufferSize OPTIONAL, OUT LPDWORD lpResult OPTIONAL ) /*++ Routine Description: This function allows the caller to redirect (connect) a local device to a network resource. It is similar to WNetAddConnection3, except that it has the ability to auto-pick a local device to redirect. It also returns additional information about the connection in the lpResult parameter. This API is used by the shell to make links to: - Objects on networks that require a local device redirection - Objects served by applications that require a local device redirection (i.e. can't handle UNC paths) This API must redirect a local device in any of the following conditions: - The API is given a local device name to redirect - The API is asked to redirect a local device, by the CONNECT_REDIRECT flag - The network provider requires a redirection In the last 2 cases, if a local device name isn't given, the API must auto-pick a local device name. Arguments: hwndOwner - A handle to a window which should be the owner for any messages or dialogs that the network provider might display. lpNetResource - This is a pointer to a network resource structure that specifies the network resource to connect to. The following fields must be set when making a connection, the others are ignored. lpRemoteName lpLocalName lpProvider dwType lpPassword - Specifies the password to be used in making the connection. The NULL value may be passed in to indicate use of the 'default' password. An empty string may be used to indicate no password. lpUserName- This specifies the username used to make the connection. If NULL, the default username (currently logged on user) will be applied. This is used when the user wishes to connect to a resource, but has a different user name or account assigned to him for that resource. dwFlags - This is a bitmask which may have any of the following bits set: CONNECT_UPDATE_PROFILE lpAccessName - Points to a buffer to receive the name that can be used to make system requests on the connection. This conversion is useful when lpRemoteName is understood by the provider but not by the system's name space, and when this API autopicks a local device. If lpLocalName specifies a local device, then this buffer is optional, and if specified will have the local device name copied into it. Otherwise, if the network requires a local device redirection, or CONNECT_REDIRECT is set, then this buffer is required and the redirected local device is returned here. Otherwise, the name copied into the buffer is that of a remote resource, and if specified, this buffer must be at least as large as the string pointed to by lpRemoteName. lpBufferSize - Specifies the size, in characters, of the lpAccessName buffer. If the API returns WN_MORE_DATA the required size will be returned here. lpResult - Pointer to a DWORD in which is returned additional information about the connection. Currently has the following bit values: CONNECT_LOCALDRIVE - If set, the connection was made using a local device redirection. If lpAccessName points to a buffer then the local device name is copied to the buffer. Return Value: --*/ { CUseConnection UseConnection(hwndOwner, lpNetResource, lpPassword, lpUserName, dwFlags, lpAccessName, lpBufferSize, lpResult); return (UseConnection.Perform(TRUE)); } //=================================================================== // WNetCancelConnection2W //=================================================================== class CCancelConnection2 : public CRoutedOperation { public: CCancelConnection2( LPCWSTR lpName, DWORD dwFlags, BOOL fForce ) : CRoutedOperation(DBGPARM("CancelConnection2") PROVIDERFUNC(CancelConnection)), _lpName (lpName), _dwFlags(dwFlags), _fForce (fForce) { } protected: DWORD GetResult(); // overrides CRoutedOperation implementation private: LPCWSTR _lpName; DWORD _dwFlags; BOOL _fForce; DECLARE_CROUTED }; DWORD CCancelConnection2::ValidateRoutedParameters( LPCWSTR * ppProviderName, LPCWSTR * ppRemoteName, LPCWSTR * ppLocalName ) { if (((_dwFlags != 0) && (_dwFlags != CONNECT_UPDATE_PROFILE)) || wcslen(_lpName) == 0) // probe lpName { return WN_BAD_VALUE; } // // Use the specified remote name as a hint to pick the provider, but not // if it's a local device name // if (MprDeviceType(_lpName) != REDIR_DEVICE) { *ppRemoteName = _lpName; } *ppLocalName = _lpName; return WN_SUCCESS; } DWORD CCancelConnection2::TestProvider( const PROVIDER * pProvider ) { ASSERT_INITIALIZED(NETWORK); return (pProvider->CancelConnection((LPWSTR)_lpName, _fForce)); } DWORD CCancelConnection2::GetResult() { DWORD status; NOTIFYCANCEL NotifyCancel; NOTIFYINFO NotifyInfo; INIT_IF_NECESSARY(NOTIFIEE_LEVEL, status); // // Notify all interested parties that a connection is being cancelled // NotifyInfo.dwNotifyStatus = NOTIFY_PRE; NotifyInfo.dwOperationStatus= 0L; NotifyInfo.lpContext = MprAllocConnectContext(); NotifyCancel.lpName = (LPWSTR)_lpName; NotifyCancel.lpProvider = NULL; NotifyCancel.dwFlags = _dwFlags; NotifyCancel.fForce = _fForce; __try { status = MprCancelConnectNotify(&NotifyInfo, &NotifyCancel); } __except(EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); if (status != EXCEPTION_ACCESS_VIOLATION) { MPR_LOG(ERROR,"WNetCancelConnection2W: ConnectNotify, Unexpected " "Exception %#lx\n",status); } status = WN_BAD_POINTER; } if (status != WN_SUCCESS) { return status; } do // Notification loop { // // Let the base class try all providers and figure out the best error // CRoutedOperation::GetResult calls INIT_IF_NECESSARY // status = CRoutedOperation::GetResult(); // // Map these errors to a friendlier one for this API // if (status == WN_BAD_NETNAME || status == ERROR_BAD_NETPATH || status == WN_BAD_LOCALNAME || status == WN_NO_NETWORK || status == WN_NO_NET_OR_BAD_PATH) { MPR_LOG2(ROUTE, "CCancelConnection2: remapping %ld to %ld\n", status, WN_NOT_CONNECTED); status = WN_NOT_CONNECTED; } // // Notify all interested parties of the status of the connection. // NotifyInfo.dwNotifyStatus = NOTIFY_POST; NotifyInfo.dwOperationStatus = status; // Is this the right status?? if (status == WN_SUCCESS) { NotifyCancel.lpProvider = LastProvider()->Resource.lpProvider; } } while (MprCancelConnectNotify(&NotifyInfo, &NotifyCancel) == WN_RETRY); MprFreeConnectContext(NotifyInfo.lpContext); // // Regardless of whether the connection was cancelled successfully, // we still want to remove any connection information from the // registry if told to do so (dwFlags has CONNECT_UPDATE_PROFILE set). // if (_dwFlags & CONNECT_UPDATE_PROFILE) { if (MprDeviceType((LPWSTR)_lpName) == REDIR_DEVICE) { if (MprFindDriveInRegistry((LPWSTR)_lpName,NULL)) { // // If the connection was found in the registry, we want to // forget it and if no providers claimed responsibility, // return success. // MprForgetRedirConnection((LPWSTR)_lpName); if (status == WN_NOT_CONNECTED) { status = WN_SUCCESS; } } } } if (status == WN_SUCCESS) { // // Notify the shell of the disconnection. // if( (g_LUIDDeviceMapsEnabled == TRUE) && (_lpName != NULL) ) { // Use LUID broadcast mechanism MprBroadcastDriveChange( _lpName, TRUE ); // a Delete Message } else { MprNotifyShell(_lpName); } } return status; } DWORD WNetCancelConnection2W ( IN LPCWSTR lpName, IN DWORD dwFlags, IN BOOL fForce ) /*++ Routine Description: This function breaks an existing network connection. The persistance of the connection is determined by the dwFlags parameter. Arguments: lpName - The name of either the redirected local device, or the remote network resource to disconnect from. In the former case, only the redirection specified is broken. In the latter case, all connections to the remote network resource are broken. dwFlags - This is a bitmask which may have any of the following bits set: CONNECT_UPDATE_PROFILE fForce - Used to indicate if the disconnect should be done forcefully in the event of open files or jobs on the connection. If FALSE is specified, the call will fail if there are open files or jobs. Return Value: WN_SUCCESS - The call was successful. Otherwise, GetLastError should be called for extended error information. Extended error codes include the following: WN_NOT_CONNECTED - lpName is not a redirected device. or not currently connected to lpName. WN_OPEN_FILES - There are open files and fForce was FALSE. WN_EXTENDED_ERROR - A network specific error occured. WNetGetLastError should be called to obtain a description of the error. --*/ { // // Check the providers // return MprCancelConnection2(lpName, dwFlags, fForce, TRUE); } DWORD MprCancelConnection2 ( IN LPCWSTR lpName, IN DWORD dwFlags, IN BOOL fForce, IN BOOL fCheckProviders ) /*++ Routine Description: Wrapper to use the CCancelConnection2 class -- needed because otherwise, WNetRestoreConnectionW could call WNetCancelConnection2W, causing a deadlock on the provider lock. Arguments: Return Value: --*/ { CCancelConnection2 CancelConnection2(lpName, dwFlags, fForce); return (CancelConnection2.Perform(fCheckProviders)); } DWORD WNetCancelConnectionW ( IN LPCWSTR lpName, IN BOOL fForce ) /*++ Routine Description: This function breaks an existing network connection. The connection is always made non-persistent. Note that this is a stub routine that calls WNetCancelConnection2W and is only provided for Win3.1 compatibility. Arguments: Parameters are the same as WNetCancelConnection2W Return Value: Same as WNetCancelConnection2W --*/ { return MprCancelConnection2( lpName, CONNECT_UPDATE_PROFILE, fForce, TRUE ) ; } DWORD WNetGetConnectionW ( IN LPCWSTR lpLocalName, OUT LPWSTR lpRemoteName, IN OUT LPDWORD lpBufferSize ) /*++ Routine Description: Arguments: Return Value: --*/ { MprCheckProviders(); CProviderSharedLock PLock; return MprGetConnection( lpLocalName, lpRemoteName, lpBufferSize, NULL ) ; } DWORD MprGetConnection ( IN LPCWSTR lpLocalName, OUT LPTSTR lpRemoteName, IN OUT LPDWORD lpBufferSize, OUT LPDWORD lpProviderIndex OPTIONAL ) /*++ Routine Description: Retrieves the remote name associated with a device name and optionally the provider index. Behaviour is exactly the same as WNetGetConnectionW. Arguments: Return Value: --*/ { DWORD status = WN_SUCCESS; LPDWORD indexArray; DWORD localArray[DEFAULT_MAX_PROVIDERS]; DWORD numProviders; LPPROVIDER provider; DWORD statusFlag = 0; // used to indicate major error types BOOL fcnSupported = FALSE; // Is fcn supported by a provider? DWORD i; DWORD dwFirstError = WN_SUCCESS; DWORD dwFirstSignificantError = WN_SUCCESS; // // Validate the LocalName // __try { if (MprDeviceType((LPWSTR) lpLocalName) != REDIR_DEVICE) { status = WN_BAD_LOCALNAME; } } __except(EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); if (status != EXCEPTION_ACCESS_VIOLATION) { MPR_LOG(ERROR,"WNetGetConnection:Unexpected Exception 0x%lx\n",status); } status = WN_BAD_POINTER; } if (status != WN_SUCCESS) { SetLastError(status); return status; } // // Optimization: If the local name is a drive, load the providers // only if it's a remote drive. // if (lpLocalName[1] == L':') { WCHAR wszRootPath[] = L" :\\"; UINT uDriveType; wszRootPath[0] = lpLocalName[0]; uDriveType = GetDriveType(wszRootPath); if (uDriveType == DRIVE_REMOVABLE || uDriveType == DRIVE_FIXED || uDriveType == DRIVE_CDROM || uDriveType == DRIVE_RAMDISK) { status = WN_NOT_CONNECTED; SetLastError(status); return status; } else if (uDriveType != DRIVE_REMOTE) { // // It's not a remote drive, but it's not one in hardware // (either DRIVE_UNKNOWN or DRIVE_NO_ROOT_DIR). Since it // might have a name associated with it in the registry // (e.g., remembered connections), we have to load the // providers and check if necessary (done in // MprReadConnectionInformation) // status = WN_NOT_CONNECTED; goto CheckRemembered; } } INIT_IF_NECESSARY(NETWORK_LEVEL,status); // // Find the list of providers to call for this request. // indexArray = localArray; status = MprFindCallOrder( NULL, &indexArray, &numProviders, NETWORK_TYPE); if (status != WN_SUCCESS) { SetLastError(status); return status; } // // Loop through the list of providers until one answers the request, // or the list is exhausted. // for (i=0; iGetConnection != NULL) { fcnSupported = TRUE; __try { //************************************** // Actual call to Provider. //************************************** status = provider->GetConnection( (LPWSTR) lpLocalName, lpRemoteName, lpBufferSize); } __except(EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); if (status != EXCEPTION_ACCESS_VIOLATION) { MPR_LOG(ERROR,"WNetGetConnection:Unexpected Exception 0x%lx\n",status); } status = WN_BAD_POINTER; } //////////////////////////////////////////////////////////// // // // The following code attempts to give the user the // // most sensible error message. We have 3 clasess of // // errors. On first class we stop routing. On second // // continue but ignore that error (not significant // // because the provider didnt think it was his). On // // the last the provider returned an interesting (or // // significant) error. We still route, but remember // // it so it takes precedence over the non significant // // ones. // // // //////////////////////////////////////////////////////////// // // Remember the first error, if it hasn't already been set // if (dwFirstError == WN_SUCCESS) dwFirstError = status ; // // If the provider returns one of these errors, stop routing. // if ((status == WN_BAD_POINTER) || (status == WN_MORE_DATA) || (status == WN_SUCCESS)) { // // we either succeeded or have problems that means we // should not continue (eg. bad input causing exception). // and we make sure this is the error reported. // dwFirstError = status ; dwFirstSignificantError = status ; statusFlag = 0; if ( lpProviderIndex != NULL ) { *lpProviderIndex = indexArray[i]; } break; } // // If the provider returns one of these errors, continue // trying other providers, but do not remember as a // significant error, because the provider is probably not // interested. StatusFlag is use to detect the case where // a provider is not started. // else if (status == WN_NO_NETWORK) { statusFlag |= NO_NET; } else if ((status == WN_NOT_CONNECTED) || (status == WN_BAD_LOCALNAME)) { // // WN_NOT_CONNECTED means that lpLocalName is not a // redirected device for this provider. // statusFlag |= BAD_NAME; } // // If a provider returns one of these errors, we continue // trying, but remember the error as a significant one. We // report it to the user if it is the first. We do this so // that if the first provider returns NotConnected and last // returns BadPassword, we report the Password Error. // else { // // All other errors are considered more significant // than the ones above // if (!dwFirstSignificantError && status) dwFirstSignificantError = status ; statusFlag = OTHER_ERRS; } } } // // If we failed, set final error, in order of importance. // Significant errors take precedence. Otherwise we always // report the first error. // if (status != WN_SUCCESS) { status = (dwFirstSignificantError != WN_SUCCESS) ? dwFirstSignificantError : dwFirstError ; } 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 special errors. // if (statusFlag == (NO_NET | BAD_NAME)) { // // Check to see if there was a mix of special errors that occured. // If so, pass back the combined error message. Otherwise, let the // last error returned get passed back. // status = WN_NO_NET_OR_BAD_PATH; } CheckRemembered: // // Handle normal errors passed back from the provider // if (status != WN_SUCCESS) { if (status == WN_NOT_CONNECTED) { // // If not connected, but there is an entry for the LocalName // in the registry, then return the remote name that was stored // with it. // if (MprGetRemoteName( (LPWSTR) lpLocalName, lpBufferSize, lpRemoteName, &status)) { if (status == WN_SUCCESS) { status = WN_CONNECTION_CLOSED; } } } SetLastError(status); } return status; } DWORD WNetGetConnection2W ( IN LPWSTR lpLocalName, OUT LPVOID lpBuffer, IN OUT LPDWORD lpBufferSize ) /*++ Routine Description: Just like WNetGetConnectionW except this one returns the provider name that the device is attached through Arguments: lpBuffer will contain a WNET_CONNECTIONINFO structure lpBufferSize is the number of bytes required for the buffer Return Value: --*/ { DWORD status = WN_SUCCESS ; DWORD iProvider = 0 ; DWORD nBytesNeeded = 0 ; DWORD cchBuff = 0 ; DWORD nTotalSize = *lpBufferSize ; LPTSTR lpRemoteName= (LPTSTR) ((BYTE*) lpBuffer + sizeof(WNET_CONNECTIONINFO)) ; WNET_CONNECTIONINFO * pconninfo = (WNET_CONNECTIONINFO *) lpBuffer ; // // If they didn't pass in a big enough buffer for even the structure, // then make the size zero (so the buffer isn't accessed at all) and // let the API figure out the buffer size // if ( *lpBufferSize < sizeof( WNET_CONNECTIONINFO) ) { *lpBufferSize = 0 ; cchBuff = 0 ; } else { // // MprGetConnection is expecting character counts, so convert // after offsetting into the structure (places remote name directly // in the structure). // cchBuff = (*lpBufferSize - sizeof(WNET_CONNECTIONINFO))/sizeof(TCHAR) ; } MprCheckProviders(); CProviderSharedLock PLock; status = MprGetConnection( lpLocalName, lpRemoteName, &cchBuff, &iProvider); if ( status == WN_SUCCESS || status == WN_CONNECTION_CLOSED || status == WN_MORE_DATA ) { // // Now we need to determine the buffer requirements for the // structure and provider name // // (Note that if MprGetConnection returns WN_CONNECTION_CLOSED, it // does not touch the value of iProvider. So iProvider will retain // its somewhat arbitrary initial value of 0, meaning the first // provider in the array.) // LPTSTR lpProvider = GlobalProviderInfo[iProvider].Resource.lpProvider; // // Calculate the required buffer size. // nBytesNeeded = sizeof( WNET_CONNECTIONINFO ) + (STRLEN( lpProvider) + 1) * sizeof(TCHAR); if ( status == WN_MORE_DATA ) { nBytesNeeded += cchBuff * sizeof(TCHAR); } else { nBytesNeeded += (STRLEN( lpRemoteName) + 1) * sizeof(TCHAR); } if ( nTotalSize < nBytesNeeded ) { status = WN_MORE_DATA; *lpBufferSize = nBytesNeeded; return status; } // // Place the provider name in the buffer and initialize the // structure to point to the strings. // pconninfo->lpRemoteName = lpRemoteName ; pconninfo->lpProvider = STRCPY( (LPTSTR) ((BYTE*) lpBuffer + sizeof(WNET_CONNECTIONINFO) + (STRLEN( lpRemoteName ) + 1) * sizeof(TCHAR)), lpProvider); } return status; } //=================================================================== // WNetGetConnection3W //=================================================================== class CGetConnection3 : public CRoutedOperation { public: CGetConnection3( LPCWSTR lpLocalName, LPCWSTR lpProviderName, DWORD dwLevel, LPVOID lpBuffer, LPDWORD lpBufferSize ) : DBGPARM(CRoutedOperation("GetConnection3")) _lpLocalName (lpLocalName ), _lpProviderName(lpProviderName), _dwLevel (dwLevel ), _lpBuffer (lpBuffer ), _lpBufferSize (lpBufferSize ) { } private: LPCWSTR _lpLocalName; LPCWSTR _lpProviderName; DWORD _dwLevel; LPVOID _lpBuffer; LPDWORD _lpBufferSize; DECLARE_CROUTED }; DWORD CGetConnection3::ValidateRoutedParameters( LPCWSTR * ppProviderName, LPCWSTR * ppRemoteName, LPCWSTR * ppLocalName ) { if (_dwLevel != WNGC_INFOLEVEL_DISCONNECTED) { return WN_BAD_LEVEL; } if (MprDeviceType(_lpLocalName) != REDIR_DEVICE) { return WN_BAD_LOCALNAME; } if (IS_BAD_BYTE_BUFFER(_lpBuffer, _lpBufferSize)) { return WN_BAD_POINTER; } // // Set parameters used by base class. Note that we set *ppLocalName // to NULL to prevent the base class from checking it for "localness" // since this is a private API called by the shell for network drives // only. If we ever want to check the local name, the line below will // have to be changed to: // // *ppLocalName = _lpLocalName; // *ppProviderName = _lpProviderName; *ppLocalName = NULL; return WN_SUCCESS; } DWORD CGetConnection3::TestProvider( const PROVIDER * pProvider ) { ASSERT_INITIALIZED(NETWORK); if (pProvider->GetConnection3 != NULL) { return ( pProvider->GetConnection3( _lpLocalName, _dwLevel, _lpBuffer, _lpBufferSize) ); } else if (pProvider->GetConnection == NULL) { return WN_NOT_SUPPORTED; } else { // Just verify that the provider owns the connection, and if so, // assume that it's not disconnected WCHAR wszRemoteName[40]; DWORD nLength = LENGTH(wszRemoteName); DWORD status = pProvider->GetConnection( (LPWSTR)_lpLocalName, wszRemoteName, &nLength); if (status == WN_SUCCESS || status == WN_MORE_DATA) { // The provider owns the connection if (*_lpBufferSize < sizeof(WNGC_CONNECTION_STATE)) { *_lpBufferSize = sizeof(WNGC_CONNECTION_STATE); status = WN_MORE_DATA; } else { ((LPWNGC_CONNECTION_STATE)_lpBuffer)->dwState = WNGC_CONNECTED; status = WN_SUCCESS; } } return status; } } DWORD APIENTRY WNetGetConnection3W( IN LPCWSTR lpLocalName, IN LPCWSTR lpProviderName OPTIONAL, IN DWORD dwLevel, OUT LPVOID lpBuffer, IN OUT LPDWORD lpBufferSize ) /*++ Routine Description: This function returns miscellaneous information about a network connection, as specified by the info level parameter. Arguments: lpLocalName - The name of a redirected local device for which information is required. lpProviderName - The name of the provider responsible for the connection, if known. dwLevel - Level of information required. Supported levels are: 1 - Determine whether the connection is currently disconnected. lpBuffer - Buffer in which to return the information if the call is successful. The format of the information returned is as follows, depending on dwLevel: Level 1 - A DWORD is returned whose value is one of the following: WNGETCON_CONNECTED WNGETCON_DISCONNECTED lpBufferSize - On input, size of the buffer in bytes. If the buffer is too small, the required size will be written here. For level 1 the required size is sizeof(DWORD). Return Value: WN_SUCCESS - successful. WN_MORE_DATA - buffer is too small. WN_BAD_LOCALNAME - lpLocalName is not a valid device name. WN_BAD_PROVIDER - lpProviderName is not a recognized provider name. WN_NOT_CONNECTED - the device specified by lpLocalName is not redirected. WN_CONNECTION_CLOSED - the device specified by lpLocalName is not redirected, but is a remembered (unavailable) connection. --*/ { CGetConnection3 GetConnection3(lpLocalName, lpProviderName, dwLevel, lpBuffer, lpBufferSize); DWORD status = GetConnection3.Perform(TRUE); if (status == WN_NOT_CONNECTED && MprFindDriveInRegistry((LPWSTR)lpLocalName,NULL)) { status = WN_CONNECTION_CLOSED; } return status; } DWORD WNetRestoreConnectionW( IN HWND hWnd, IN LPCWSTR lpDevice ) { return WNetRestoreConnection2W(hWnd, lpDevice, 0, NULL); } DWORD WNetRestoreConnection2W( IN HWND hWnd, IN LPCWSTR lpDevice, IN DWORD dwFlags, OUT BOOL* pfReconnectFailed ) /*++ Routine Description: This function create another thread which does the connection. In the main thread it create a dialog window to monitor the state of the connection. Arguments: hwnd - This is a window handle that may be used as owner of any dialog brought up by MPR (eg. password prompt). lpDevice - This may be NULL or may contain a device name such as "x:". If NULL, all remembered connections are restored. Otherwise, the remembered connection for the specified device, if any are restored. dwFlags - WNRC_NOUI specifies that no UI should be shown. Connections that fail to restore will be reattempted at next logon. Return Value: --*/ { DWORD status = WN_SUCCESS; DWORD print_connect_status = WN_SUCCESS; DWORD numSubKeys; DWORD RegMaxWait = 0; HANDLE hThread = NULL; HANDLE ThreadID; LPCONNECTION_INFO ConnectArray; PARAMETERS *lpParams = NULL; BOOL DontDefer = FALSE; LONG fDoCleanup = FALSE; if (pfReconnectFailed) { *pfReconnectFailed = FALSE; } // // Check the registry to see if we need to restore connections or not, // and whether we can defer them. // This is done only if lpDevice is NULL (restoring all). // (If a particular device is specified, it is always restored undeferred.) // // Interpretation of the RestoreConnection value is: // 0 - don't restore connections (and ignore the DeferConnection value) // default - restore connections // // Interpretation of the DeferConnection value is: // 0 - don't defer any connections // default - defer every connection that can be deferred // if ( lpDevice == NULL ) { HKEY providerKeyHandle; DWORD ValueType; DWORD fRestoreConnection = TRUE; DWORD Temp = sizeof( fRestoreConnection ); if( MprOpenKey( HKEY_LOCAL_MACHINE, // hKey NET_PROVIDER_KEY, // lpSubKey &providerKeyHandle, // Newly Opened Key Handle DA_READ)) // Desired Access { if ( RegQueryValueEx( providerKeyHandle, RESTORE_CONNECTION_VALUE, NULL, &ValueType, // not used (LPBYTE) &fRestoreConnection, &Temp) == NO_ERROR ) { if ( !fRestoreConnection ) { MPR_LOG0(RESTORE, "Registry says NOT to restore connections\n"); RegCloseKey( providerKeyHandle ); return WN_SUCCESS; } } DWORD fDeferConnection; if (MprGetKeyDwordValue( providerKeyHandle, DEFER_CONNECTION_VALUE, &fDeferConnection) && fDeferConnection == 0 ) { MPR_LOG0(RESTORE, "Registry says NOT to defer restored connections\n"); DontDefer = TRUE; } RegCloseKey( providerKeyHandle ); } } __try { if (lpDevice != NULL) { if (MprDeviceType (lpDevice) != REDIR_DEVICE) { status = WN_BAD_LOCALNAME; } } } __except (EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); if (status != EXCEPTION_ACCESS_VIOLATION) { MPR_LOG (ERROR, "WNetRestoreConnectionW:Unexpected Exception 0x%1x\n",status); } status = WN_BAD_POINTER; } if (status != WN_SUCCESS) { SetLastError(status); return status; } // // MprCreateConnectionArray may use the providers // MprCheckProviders(); { CProviderSharedLock PLock; // // Read all the connection info from the registry. // status = MprCreateConnectionArray (&numSubKeys, lpDevice, &RegMaxWait, &ConnectArray); if (lpDevice == NULL) { // // only wory about Print if restoring all // print_connect_status = MprAddPrintersToConnArray (&numSubKeys, &ConnectArray); } // // if both failed, report first error. else do the best we can. // if (status != WN_SUCCESS && print_connect_status != WN_SUCCESS) { SetLastError (status); return status; } if (numSubKeys == 0) { return(WN_SUCCESS); } INIT_IF_NECESSARY(NETWORK_LEVEL,status); // // If there are no providers, return NO_NETWORK // if (GlobalNumActiveProviders == 0) { SetLastError(WN_NO_NETWORK); return(WN_NO_NETWORK); } // // Refcount all the provider DLLs we may use since we may // have to call DoProfileErrorDialog, which calls outside // mpr.dll and can potentially loop back, causing deadlock. // By refcounting here, we don't have to worry about // releasing/reacquiring the lock or over-refcounting the // provider DLLs later on. // MprRefcountConnectionArray(ConnectArray, numSubKeys); } // If lpDevice is not NULL, call MprRestoreThisConnection directly. if (lpDevice) { status = MprRestoreThisConnection (hWnd, NULL, &ConnectArray[0], dwFlags); ConnectArray[0].Status = status; if ((status != WN_SUCCESS) && (status != WN_CANCEL) && (status != WN_CONTINUE)) { if (!(dwFlags & WNRC_NOUI)) { DoProfileErrorDialog (hWnd, ConnectArray[0].NetResource.lpLocalName, ConnectArray[0].NetResource.lpRemoteName, ConnectArray[0].NetResource.lpProvider, ConnectArray[0].Status, FALSE, //No cancel button. NULL, NULL, NULL); // no skip future errors checkbox } if (pfReconnectFailed) { *pfReconnectFailed = TRUE; } } } else do // not a loop. error break out. { // // Initialize lpParams. // lpParams = (PARAMETERS *) LocalAlloc (LPTR, sizeof (PARAMETERS)); if ((lpParams == NULL) || (lpParams->hDlgCreated = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL || (lpParams->hDlgFailed = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL || (lpParams->hDonePassword = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL) { status = GetLastError(); if (lpParams != NULL) { if (lpParams->hDlgCreated != NULL) CloseHandle(lpParams->hDlgCreated); if (lpParams->hDlgFailed != NULL) CloseHandle(lpParams->hDlgFailed); if (lpParams->hDonePassword != NULL) CloseHandle(lpParams->hDonePassword); } break; } lpParams->numSubKeys = numSubKeys; lpParams->RegMaxWait = RegMaxWait; lpParams->ConnectArray = ConnectArray; lpParams->dwRestoreFlags = dwFlags; lpParams->fReconnectFailed = FALSE; // // Decide whether to show the "Restoring Connections" dialog. // In general, if a connection will be made as a user other than the // default logged-on user, we need to give the user a chance to enter // the password and have it validated, hence we need to contact the // server, so we should show the dialog. // BOOL NeedDialog; if (DontDefer) { NeedDialog = TRUE; } else { NeedDialog = FALSE; // // Get the default user and domain names to connect as // PUNICODE_STRING UserName; PUNICODE_STRING DomainName; NTSTATUS ntStatus = LsaGetUserName(&UserName, &DomainName); if (NT_SUCCESS(ntStatus)) { MPR_LOG2(RESTORE,"Default domain name = \"%ws\", user name = \"%ws\"\n", DomainName->Buffer, UserName->Buffer); } else { MPR_LOG(ERROR, "LsaGetUserName failed, %#lx\n", ntStatus); DomainName = NULL; UserName = NULL; } // // If the logon domain is the local machine, don't defer connections. // This is a NT 4.0 workaround for the most common case of bug 36827. // When connecting to an LM server, if the user name happens to // match a local user name on the target server, the server will // normally attempt to log on using THAT user's account; hence we // need to prompt for the password. We need to do this since this // behavior is by design in the redirector. // WCHAR ComputerName[MAX_COMPUTERNAME_LENGTH+1]; DWORD nSize = LENGTH(ComputerName); if (DomainName == NULL || GetComputerName(ComputerName, &nSize) == FALSE || _wcsicmp(ComputerName, DomainName->Buffer) == 0) { MPR_LOG0(RESTORE, "Local logon, will not defer connections\n"); NeedDialog = TRUE; } else { // // Prescan the connections to determine which ones we can // defer and whether we need the reconnect dialog. If all // connections are deferred, we do not bother with the dialog. // for (DWORD i = 0; i < numSubKeys; i++) { if (! ConnectArray[i].ContinueFlag) { continue; } // // The DEFER_UNKNOWN flag means don't defer the connection // on this logon, but try the default credentials and see // if they work; if so, the connection can be deferred at // subsequent logons. // // Don't defer the connection if a password was explicitly // specified when the connection was made. This covers // cases in which the redir won't send the default password // to the server at connect time because the server doesn't // support encrypted passwords. // if (! (ConnectArray[i].DeferFlags & DEFER_UNKNOWN) && ! (ConnectArray[i].DeferFlags & DEFER_EXPLICIT_PASSWORD) && MprUserNameMatch(DomainName, UserName, ConnectArray[i].UserName, FALSE)) { // // If the user name is the default user name, we can safely // replace it with a NULL. (This is not only more optimal, but // also required in order to work around a LM redir problem // with the way credentials for deferred connections are stored.) // LocalFree(ConnectArray[i].UserName); ConnectArray[i].UserName = NULL; // // It's OK to defer the connection iff the remembered user // name matches the default user name and the provider // supports deferred connections. // if ((ConnectArray[i].dwConnectCaps & WNNC_CON_DEFER) && (ConnectArray[i].pfAddConnection3 != NULL)) { // // Defer was initialized to 0 when the array was // allocated. // Note that we don't defer if an lpDevice was supplied. // ConnectArray[i].Defer = TRUE; } } if (! ConnectArray[i].Defer) { NeedDialog = TRUE; } } // for each connection MprSortConnectionArray(ConnectArray, numSubKeys); } if (DomainName != NULL) { LsaFreeMemory(DomainName->Buffer); LsaFreeMemory(DomainName); } if (UserName != NULL) { LsaFreeMemory(UserName->Buffer); LsaFreeMemory(UserName); } } // // If we are: // USING DIALOGS WHEN RESTORING CONNECTIONS... // // This main thread will used to service a windows event loop // by calling ShowReconnectDialog. Therefore, a new thread // must be created to actually restore the connections. As we // attempt to restore each connection, a message is posted in // this event loop that causes it to put up a dialog that // describes the connection and has a "cancel" option. // if (! NeedDialog) { lpParams->hDlg = INVALID_WINDOW_HANDLE; // CODEWORK: We are using this INVALID_WINDOW_HANDLE to tell // the various routines whether there are 2 threads or not. // Instead we should add a new field, BOOL fSeparateThread, // to PARAMETERS. This requires changing routines in mprui.dll // as well as mpr.dll. DoRestoreConnection(lpParams); } else { HANDLE hThreadToken; // If the main thread is impersonating, we have to copy its token to // the new thread that is being spawned so it runs in the correct context if (!OpenThreadToken (GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &hThreadToken) && !OpenProcessToken(GetCurrentProcess(), TOKEN_IMPERSONATE, &hThreadToken)) { // // Couldn't open a token on the thread or the process // status = GetLastError(); } else { // lpParams->hDlg was initialized to 0 by LocalAlloc // and will be set to an HWND by ShowReconnectDialog hThread = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE) &DoRestoreConnection, (LPVOID) lpParams, CREATE_SUSPENDED, (LPDWORD) &ThreadID); if (hThread == NULL) { status = GetLastError(); } else { // // Make sure the worker thread isn't killed if // this thread finishes while it's wedged in a // provider call (in MprRestoreThisConnection). // The DLL is unloaded by DoRestoreConnection // lpParams->hDll = LoadLibraryEx( L"mpr.dll", NULL, LOAD_WITH_ALTERED_SEARCH_PATH ); if (lpParams->hDll == NULL) { MPR_LOG1(RESTORE, "LoadLibraryEx for mpr.dll FAILED %d\n", GetLastError()); // // This shouldn't happen since all we're // doing is upping our refcount // ASSERT(FALSE); } // // Assign the impersonation token to the // thread and resume/start it // if (!SetThreadToken(&hThread, hThreadToken)) { status = GetLastError(); TerminateThread(hThread, NO_ERROR); CloseHandle(hThread); } else { ResumeThread(hThread); CloseHandle(hThread); status = ShowReconnectDialog(hWnd, lpParams); } if (status == WN_SUCCESS && lpParams->status != WN_CANCEL && lpParams->status != WN_SUCCESS ) { SetLastError (lpParams->status); } if (lpParams->status != WN_CANCEL) { status = MprNotifyErrors (hWnd, ConnectArray, numSubKeys, dwFlags); } } CloseHandle(hThreadToken); } } // // Only if we make it to the end of the do-while loop do the thread // parameters have to be cleaned up. Since either this thread or // the worker thread might exit first (if the user hits Cancel and // the worker thread is stuck in a provider call, this thread could // exit first), the InterlockedExchange calls here and at the end // of DoRestoreConnection ensure that the last thread leaving // does the cleanup. // // NOTE: This relies on the fact that the only break out of // this do-while occurs when LocalAlloc for lpParams FAILS. // Adding new break statements may break this logic! // // // Case 1: lpParams never gets allocated -- this bit of code // never gets hit since we break out of the loop above // and nobody cleans up (correctly) // // Case 2: CreateThread fails -- hThread is NULL, so this thread // needs to clean up // // (CODEWORK -- should this thread call DoRestoreConnection // directly in that case?) // // Case 3: The worker thread exits first -- it changes fDoCleanup // to TRUE before it exits and this thread cleans up // // Case 4: This thread exits first (user cancels dialog) -- this // thread changes fDoCleanup to TRUE and orphans the // worker thread. When it finishes its provider call, // it reads fDoCleanup and cleans up. // // Poll the worker thread's data to see if reconnect failed. if (pfReconnectFailed) { *pfReconnectFailed = lpParams->fReconnectFailed; } fDoCleanup = (InterlockedExchange(&lpParams->fDoCleanup, TRUE) || hThread == NULL); } while (FALSE); // // Is this thread supposed to clean up? // if (fDoCleanup) { MPR_LOG0(RESTORE, "Main thread will perform cleanup\n"); ASSERT(lpParams != NULL); // // Free up resources in preparation to return. // if (!CloseHandle(lpParams->hDlgCreated)) status = GetLastError(); if (!CloseHandle(lpParams->hDlgFailed)) status = GetLastError(); if (!CloseHandle(lpParams->hDonePassword)) status = GetLastError(); LocalFree(lpParams); MprFreeConnectionArray(ConnectArray, numSubKeys); } // Send a notification about the new network drive(s). if( (g_LUIDDeviceMapsEnabled == TRUE) && (lpDevice != NULL)) { // Use LUID broadcast mechanism MprBroadcastDriveChange( lpDevice, FALSE ); // not a Delete Message } else { MprNotifyShell(L" :"); } if (status != WN_SUCCESS) { SetLastError(status); } return status; } VOID MprSortConnectionArray( LPCONNECTION_INFO lpcConnectArray, DWORD dwNumSubKeys ) /*++ Routine Description: This function sorts the array of connections by placing the connections that can be deferred before those that can't Arguments: lpcConnectArray -- The array of CONNECTION_INFO structures dwNumSubKeys -- The number of structures in the array Notes: This sort is done to improve behavior at boot. Deferred connections are faster and less error-prone than non-deferred connections and errors can cause popups that allow the user to stop reconnecting drives, which leaves the remaining drives in the annoying "Unavailable" state. Note that most popups for drive reconnection at boot are for credentials, which is a popup associated only with non-deferred connection. By reconnecting deferred connections first, we avoid this problem. --*/ { INT nLow = 0; INT nHigh = dwNumSubKeys - 1; BOOL fSwap; CONNECTION_INFO ciTemp; do { // // Find the first non-deferred connection // while (nLow < (INT)dwNumSubKeys && lpcConnectArray[nLow].Defer) { nLow++; } // // Find the last deferred connection // while (nHigh >= 0 && !lpcConnectArray[nHigh].Defer) { nHigh--; } fSwap = (nLow < nHigh); // // Only swap if the pointers haven't crossed // (otherwise we'd be undoing the sorting) // if (fSwap) { ciTemp = lpcConnectArray[nLow]; lpcConnectArray[nLow++] = lpcConnectArray[nHigh]; lpcConnectArray[nHigh--] = ciTemp; } } while (fSwap); #if DBG MPR_LOG0(RESTORE, "Order of sorted connection array is as follows:\n"); for (UINT i = 0; i < dwNumSubKeys; i++) { MPR_LOG3(RESTORE, "\tLocal: %ws, \tRemote: %ws, \tDefer: %d\n", lpcConnectArray[i].NetResource.lpLocalName, lpcConnectArray[i].NetResource.lpRemoteName, lpcConnectArray[i].Defer); } #endif // DBG } VOID MprRefcountConnectionArray( LPCONNECTION_INFO lpcConnectArray, DWORD dwNumSubkeys ) /*++ Routine Description: This function sorts refcounts the provider DLLs in the array of connections in preparation for a call outside of mpr.dll (which requires releasing the provider lock to avoid the possibility of reentrancy and deadlock) Arguments: lpcConnectArray -- The array of CONNECTION_INFO structures dwNumSubKeys -- The number of structures in the array Notes: --*/ { UINT i; LPPROVIDER lpProvider; ASSERT_INITIALIZED(NETWORK); for (i = 0; i < dwNumSubkeys; i++) { // // If this hits, we're over-refcounting // ASSERT(lpcConnectArray[i].hProviderDll == NULL); lpProvider = &GlobalProviderInfo[lpcConnectArray[i].ProviderIndex]; lpcConnectArray[i].hProviderDll = LoadLibraryEx(lpProvider->DllName, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); if (lpcConnectArray[i].hProviderDll != NULL) { lpcConnectArray[i].pfGetCaps = lpProvider->GetCaps; lpcConnectArray[i].pfAddConnection3 = lpProvider->AddConnection3; lpcConnectArray[i].pfAddConnection = lpProvider->AddConnection; lpcConnectArray[i].pfCancelConnection = lpProvider->CancelConnection; lpcConnectArray[i].dwConnectCaps = lpProvider->ConnectCaps; } else { MPR_LOG2(ERROR, "MprRefcountConnectionArray: LoadLibraryEx on %ws failed %d\n", lpProvider->DllName, GetLastError()); } } } DWORD WNetSetConnectionW( IN LPCWSTR lpName, IN DWORD dwProperty, IN LPVOID pvValue ) /*++ Routine Description: This function changes the characteristics of a network connection. Arguments: lpName - The name of either the redirected local device or a remote network resource. dwProperty - Identifies the property to be changed. Current properties supported: NETPROPERTY_PERSISTENT - pvValue points to a DWORD. If TRUE, the connection is made persistent. If FALSE, the connection is made non-persistent. pvValue - Pointer to the property value. Type depends on dwProperty. Return Value: WN_SUCCESS - successful WN_BAD_LOCALNAME or WN_NOT_CONNECTED - lpName is not a redirected device --*/ { DWORD status = WN_SUCCESS; if (!(ARGUMENT_PRESENT(lpName) && ARGUMENT_PRESENT(pvValue))) { SetLastError(WN_BAD_POINTER); return(WN_BAD_POINTER); } // NPGetUser and some Mpr internal functions use lpName as a non-const // argument, so we have to make a copy WCHAR * lpNameCopy = (WCHAR *) LocalAlloc(LMEM_FIXED, WCSSIZE(lpName)); if (lpNameCopy == NULL) { SetLastError(WN_OUT_OF_MEMORY); return(WN_OUT_OF_MEMORY); } wcscpy(lpNameCopy, lpName); switch (dwProperty) { case NETPROPERTY_PERSISTENT: { MprCheckProviders(); CProviderSharedLock PLock; __try { // // Value should be a boolean DWORD telling whether to // remember or forget the connection // BOOL bRemember = * ((DWORD *) pvValue); // // Verify that lpName is a redirected local device, and // identify the provider responsible // WCHAR wszRemoteName[MAX_PATH+1]; DWORD cbBuffer = sizeof(wszRemoteName); DWORD iProvider; // provider index status = MprGetConnection( lpName, wszRemoteName, &cbBuffer, &iProvider); if (status == WN_CONNECTION_CLOSED) { // // It's already a remembered connection. Nothing to do. // status = WN_SUCCESS; __leave; } if (status != WN_SUCCESS) { ASSERT(status != WN_MORE_DATA); __leave; } if (bRemember) { // // Get the username for the connection from the provider // WCHAR wszUser[MAX_PATH+1]; cbBuffer = LENGTH(wszUser); status = GlobalProviderInfo[iProvider].GetUser( lpNameCopy, wszUser, &cbBuffer); if (status != WN_SUCCESS) { ASSERT(status != WN_MORE_DATA); __leave; } // // Get the provider flags, if any // ASSERT_INITIALIZED(NETWORK); BYTE ProviderFlags = 0; if (GlobalProviderInfo[iProvider].GetReconnectFlags != NULL) { // This is an internal entry point so we don't bother with // try-except DWORD status2 = GlobalProviderInfo[iProvider].GetReconnectFlags( lpNameCopy, &ProviderFlags ); if (status2 != WN_SUCCESS) { ProviderFlags = 0; } MPR_LOG3(RESTORE, "%ws wants flags %#x saved for %ws\n", GlobalProviderInfo[iProvider].Resource.lpProvider, ProviderFlags, lpNameCopy); } // // Make the connection persistent // status = I_MprSaveConn( HKEY_CURRENT_USER, GlobalProviderInfo[iProvider].Resource.lpProvider, GlobalProviderInfo[iProvider].Type, wszUser, lpNameCopy, wszRemoteName, (wcslen(lpName) == 2 && lpName[1] == L':') ? RESOURCETYPE_DISK : RESOURCETYPE_PRINT, ProviderFlags, DEFER_UNKNOWN ); } else { // // Make the connection non-persistent // MprForgetRedirConnection(lpNameCopy); status = WN_SUCCESS; } } __except (EXCEPTION_EXECUTE_HANDLER) { status = WN_BAD_POINTER; } break; } default: status = WN_BAD_VALUE; } LocalFree(lpNameCopy); if (status != WN_SUCCESS) { SetLastError(status); } return status; } typedef LRESULT WINAPI FN_PostMessage( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) ; #ifdef UNICODE #define USER32_DLL_NAME L"user32.dll" #define POST_MSG_API_NAME "PostMessageW" #else #define USER32_DLL_NAME "user32.dll" #define POST_MSG_API_NAME "PostMessageA" #endif static HMODULE _static_hUser32 = NULL; static FN_PostMessage * _static_pfPostMsg = NULL; VOID DoRestoreConnection( PARAMETERS *Params ) /*++ Routine Description: This function is run as a separate thread from the main thread (which services a windows event loop). It attempts to restore all connections that were saved in the registry for this user. For each connection that we try to restore, a message is posted to the main thread that causes it to put up a dialog box which describes the connection and allows the user the option of cancelling. If a longer timeout than the default is desired, this function will look in the following location in the registry for a timeout value: \HKEY_LOCAL_MACHINE\system\CurrentControlSet\Control\NetworkProvider RestoreTimeout = REG_DWORD ?? --*/ { DWORD status = WN_SUCCESS; DWORD providerIndex=0; DWORD Temp; DWORD numSubKeys = Params->numSubKeys; DWORD MaxWait = 0; // timeout interval DWORD RegMaxWait = Params->RegMaxWait; // timeout interval stored in registry. DWORD ElapsedTime; // interval between start and current time. DWORD CurrentTime; // Current ClockTick time DWORD StartTime; DWORD i; BOOL UserCancelled = FALSE; BOOL ContinueFlag; BOOL fDisconnect = FALSE; LPCONNECTION_INFO ConnectArray = Params->ConnectArray; HANDLE lpHandles[2]; DWORD dwSleepTime = RECONNECT_SLEEP_INCREMENT ; DWORD j; DWORD numStillMustContinue; DWORD dwValue; DWORD RestoredDrivesMask = 0; DWORD CurrDriveMask; LPWSTR lpLocalName; WCHAR DriveLetterName[3]; // ":" HINSTANCE hDll = Params->hDll; // This needs to be checked // Params is freed // // Don't check providers here as this isn't a top-level WNet API. // No need to grab the shared lock here because the ConnectArray // already contains all the entrypoints we need and the provider // DLLs have all been refcounted. // lpHandles[0] = Params->hDlgCreated; lpHandles[1] = Params->hDlgFailed; StartTime = GetTickCount(); if (RegMaxWait != 0) { MaxWait = RegMaxWait; } if ( _static_pfPostMsg == NULL ) { MprEnterLoadLibCritSect(); if (_static_hUser32 = LoadLibraryEx(USER32_DLL_NAME, NULL, LOAD_WITH_ALTERED_SEARCH_PATH)) { _static_pfPostMsg = (FN_PostMessage *) GetProcAddress( _static_hUser32, POST_MSG_API_NAME ); } MprLeaveLoadLibCritSect(); if ( _static_pfPostMsg == NULL ) { Params->status = GetLastError(); goto CleanExit; } } if(Params->hDlg == INVALID_WINDOW_HANDLE) { dwValue = 0; } else { dwValue = WaitForMultipleObjects (2, lpHandles, FALSE, INFINITE); } switch(dwValue) { case 1: // hDlgFailed signaled. Info dialog failed to construct. break; case 0: // hDlgCreated signaled. Info dialog constructed successfully. MPR_LOG0(RESTORE,"Enter Loop where we will attempt to restore connections\n"); do { // // If HideErrors becomes TRUE, stop displaying error dialogs. // BOOL fHideErrors = (Params->dwRestoreFlags & WNRC_NOUI) ? TRUE : FALSE; // // Each time we go through all the connections, we need to // reset the continue flag to FALSE. // ContinueFlag = FALSE; // // Go through each connection information in the array. // for (i = 0; i < numSubKeys; i++) { // Let dialog thread print out the current connection. if(Params->hDlg != INVALID_WINDOW_HANDLE) { (*_static_pfPostMsg) (Params->hDlg, SHOW_CONNECTION, (WPARAM) ConnectArray[i].NetResource.lpRemoteName, 0); } // // Setting status to SUCCESS forces us down the success path // if we are not to continue adding this connection. // status = WN_SUCCESS; if (ConnectArray[i].ContinueFlag) { status = MprRestoreThisConnection (NULL, Params, &(ConnectArray[i]), Params->dwRestoreFlags); } switch (status) { case WN_SUCCESS: ConnectArray[i].Status = WN_SUCCESS; ConnectArray[i].ContinueFlag = FALSE; if( g_LUIDDeviceMapsEnabled == TRUE ) { lpLocalName = ConnectArray[i].NetResource.lpLocalName; if( lpLocalName != NULL && wcslen(lpLocalName) == 2 && lpLocalName[1] == L':' && iswalpha(lpLocalName[0]) ) { DriveLetterName[0] = RtlUpcaseUnicodeChar( lpLocalName[0] ); RestoredDrivesMask |= 1<<(DriveLetterName[0] - L'A'); } } break; case WN_CONTINUE: break; case WN_NO_NETWORK: case WN_FUNCTION_BUSY: // // If this is the first pass through, we don't have // the wait times figured out for each provider. Do that // now. // if (ConnectArray[i].ProviderWait == 0) { MPR_LOG0(RESTORE,"Ask provider how long it will take " "for the net to come up\n"); Temp = ConnectArray[i].pfGetCaps(WNNC_START); MPR_LOG2(RESTORE,"GetCaps(START) for Provider %ws yields %d\n", ConnectArray[i].NetResource.lpProvider, Temp) switch (Temp) { case PROVIDER_WILL_NOT_START: MPR_LOG0(RESTORE,"Provider will not start\n"); ConnectArray[i].ContinueFlag = FALSE; ConnectArray[i].Status = status; break; case NO_TIME_ESTIMATE: MPR_LOG0(RESTORE,"Provider doesn't have time estimate\n"); if (RegMaxWait != 0) { ConnectArray[i].ProviderWait = RegMaxWait; } else { ConnectArray[i].ProviderWait = DEFAULT_WAIT_TIME; } if (MaxWait < ConnectArray[i].ProviderWait) { MaxWait = ConnectArray[i].ProviderWait; } break; default: MPR_LOG1(RESTORE,"Provider says it will take %d msec\n", Temp); if ((Temp <= MAX_ALLOWED_WAIT_TIME) && (Temp > MaxWait)) { MaxWait = Temp; } ConnectArray[i].ProviderWait = MaxWait; break; } } // // 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 ((status == WN_FUNCTION_BUSY) && (ConnectArray[i].Status == WN_NO_NETWORK)) { Temp = ConnectArray[i].pfGetCaps(WNNC_START); MPR_LOG2(RESTORE,"Changed from NO_NET to BUSY\n" "\tGetCaps(START) for Provider %ws yields %d\n", ConnectArray[i].NetResource.lpProvider, Temp); switch (Temp) { case PROVIDER_WILL_NOT_START: // // This is bizzare to find the status = BUSY, // but to have the Provider not starting. // ConnectArray[i].ContinueFlag = FALSE; break; case NO_TIME_ESTIMATE: // // No need to alter the timeout for this one. // break; default: // // Make sure this new timeout information will take // less than the maximum allowed time from providers. // if (Temp <= 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. // Temp += ElapsedTime; // // If the new timeout is larger that MaxWait, // then use the new timeout. // if (Temp > MaxWait) { MaxWait = Temp; } } } // End Switch(Temp) } // End If (change state from NO_NET to BUSY) // // Store the status (either NO_NET or BUSY) with the // connect info. // if (ConnectArray[i].ContinueFlag) { ConnectArray[i].Status = status; break; } case WN_CANCEL: ConnectArray[i].Status = status; default: // // For any other error, call the Error Dialog // Params->fReconnectFailed = TRUE; if (fHideErrors) { fDisconnect = FALSE; } else { // // Count the number of connections which have not // been resolved. If there is exactly one, do not // give the user the option to cancel. // numStillMustContinue = 0; for (j = 0; j < numSubKeys; j++) { if (ConnectArray[j].ContinueFlag) { numStillMustContinue++; } } MPR_LOG1(RESTORE,"DoProfileErrorDialog with " "%d connections remaining\n", numStillMustContinue); // // We need to bump up the refcount for mprui.dll // here since we're in a separate thread and // WNetRestoreConnectionsW could return with this // UI still up. If that happens and the process // calls WNetClearConnections, we'll AV as soon as // a message is sent to the UI window since // WNetClearConnections unloads mprui.dll (this can // happen in winlogon.exe). Note that this function // will load mprui.dll globally if it hasn't already // been done so after the first LoadLibrary call here, // we're merely playing around with the DLL refcount // rather than loading/unloading DLLs every time. // HINSTANCE hDll = LoadLibraryEx(L"mprui.dll", NULL, LOAD_WITH_ALTERED_SEARCH_PATH); if (hDll == NULL) { MPR_LOG1(RESTORE, "WNetRestoreConnectionW: loading mprui.dll failed %d\n", GetLastError()); fDisconnect = FALSE; } else { DoProfileErrorDialog( Params->hDlg == INVALID_WINDOW_HANDLE ? NULL : Params->hDlg, ConnectArray[i].NetResource.lpLocalName, ConnectArray[i].NetResource.lpRemoteName, ConnectArray[i].NetResource.lpProvider, status, (Params->hDlg != INVALID_WINDOW_HANDLE) && (numStillMustContinue > 1), &UserCancelled, &fDisconnect, &fHideErrors); FreeLibrary(hDll); } } if (fDisconnect) { if (ConnectArray[i].NetResource.lpLocalName) { status = ConnectArray[i].pfCancelConnection( ConnectArray[i].NetResource.lpLocalName, TRUE); } else { status = MprForgetPrintConnection( ConnectArray[i].NetResource.lpRemoteName) ; } } ConnectArray[i].ContinueFlag = FALSE; break; } // end switch(status) ContinueFlag |= ConnectArray[i].ContinueFlag; // // If the User cancelled all further connection restoration // work, then leave this loop. // if (UserCancelled) { status = WN_CANCEL; ContinueFlag = FALSE; break; } } // end For each connection. 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 connections. // if (ElapsedTime > MaxWait) { MPR_LOG0(RESTORE,"WNetRestoreConnectionW: Timed out while restoring\n"); ContinueFlag = FALSE; status = WN_SUCCESS; } else { Sleep(dwSleepTime); // // increase sleeptime as we loop, but cap at 4 times // the increment (currently that is 4 * 3 secs = 12 secs) // if (dwSleepTime < (RECONNECT_SLEEP_INCREMENT * 4)) { dwSleepTime += RECONNECT_SLEEP_INCREMENT ; } } } } while (ContinueFlag); break; default: status = GetLastError(); } Params->status = status; if(Params->hDlg != INVALID_WINDOW_HANDLE) { (*_static_pfPostMsg) (Params->hDlg, WM_QUIT, 0, 0); } CleanExit: // // Is this thread supposed to clean up? // if (InterlockedExchange(&Params->fDoCleanup, TRUE)) { MPR_LOG0(RESTORE, "Worker thread will perform cleanup\n"); ASSERT(Params != NULL); // // Free up resources in preparation to return. // // // If LUID device maps are enabled and we restored drive letter // connections, then notify the shell about each restored drive // letter connection. // if( (g_LUIDDeviceMapsEnabled == TRUE) && (RestoredDrivesMask != 0) ) { CurrDriveMask = 1; DriveLetterName[1] = L':'; DriveLetterName[2] = UNICODE_NULL; for( DriveLetterName[0] = L'A'; DriveLetterName[0] <= L'Z'; DriveLetterName[0]++, CurrDriveMask <<= 1 ) { if( CurrDriveMask & RestoredDrivesMask ) { // Use the LUID broadcast mechanism MprBroadcastDriveChange( DriveLetterName, FALSE ); // not a Delete Message } } } if (!CloseHandle(Params->hDlgCreated)) status = GetLastError(); if (!CloseHandle(Params->hDlgFailed)) status = GetLastError(); if (!CloseHandle(Params->hDonePassword)) status = GetLastError(); MprFreeConnectionArray(Params->ConnectArray, numSubKeys); LocalFree(Params); } if (hDll != NULL) { // // We have an HINSTANCE, so we're running in a // separate thread // FreeLibraryAndExitThread(hDll, 0); } return; } BOOL MprUserNameMatch ( IN PUNICODE_STRING DomainName, IN PUNICODE_STRING UserName, IN LPCWSTR RememberedName, IN BOOL fMustMatchCompletely ) /*++ Routine Description: This function tests whether the user name for a remembered connection matches the default user name. Arguments: DomainName - default logon domain UserName - default logon user RememberedName - user name remembered for the connection Return Value: TRUE if the name matches, FALSE otherwise. --*/ { if (IS_EMPTY_STRING(RememberedName)) { return TRUE; } if (DomainName == NULL || UserName == NULL) { // This can happen if the LsaGetUserName call fails return FALSE; } // // If the remembered name is in the form "domain\user", we must compare // against the full user name; otherwise, it's sufficient to compare // against the unqualified user name // WCHAR * pSlash = wcschr(RememberedName, L'\\'); if (pSlash) { // Compare user name portion UNICODE_STRING RememberedUserName; RtlInitUnicodeString(&RememberedUserName, pSlash+1); if (! RtlEqualUnicodeString(&RememberedUserName, UserName, TRUE)) { return FALSE; } // Compare domain name portion *pSlash = L'\0'; UNICODE_STRING RememberedDomainName; RtlInitUnicodeString(&RememberedDomainName, RememberedName); BOOL fMatch = RtlEqualUnicodeString(&RememberedDomainName, DomainName, TRUE); *pSlash = L'\\'; return fMatch; } else if (fMustMatchCompletely) { // // A complete match is required but there's no domain in RememberedName // return FALSE; } else { UNICODE_STRING RememberedUserName; RtlInitUnicodeString(&RememberedUserName, RememberedName); return (RtlEqualUnicodeString(&RememberedUserName, UserName, TRUE)); } } DWORD MprRestoreThisConnection( HWND hWnd, PARAMETERS *Params, LPCONNECTION_INFO ConnectInfo, DWORD dwFlags ) /*++ Routine Description: This function attempts to add a single connection specified in the ConnectInfo. Arguments: Params - ConnectInfo - This is a pointer to a connection info structure which contains all the information necessary to restore a network connection. Return Value: returns whatever the providers AddConnection function returns. --*/ { DWORD status; LPTSTR password=NULL; HANDLE lpHandle; BOOL fDidCancel; TCHAR passwordBuffer[PWLEN+1] = {0}; LPWSTR UserNameForProvider; BOOLEAN BufferAllocated = FALSE; MPR_LOG3(RESTORE, "Doing MprRestoreThisConnection for %ws, username = %ws, defer = %lu...\n", ConnectInfo->NetResource.lpRemoteName, ConnectInfo->UserName, ConnectInfo->Defer); // // Pass the provider NULL as the user name on the first pass // if the original connection used default creds. // if ( ConnectInfo->DeferFlags & DEFER_DEFAULT_CRED ) { UserNameForProvider = NULL; } else { UserNameForProvider = ConnectInfo->UserName; } // // Loop until we either have a successful connection, or // until the user stops attempting to give a proper // password. // do { if (Params && Params->status == WN_CANCEL) { // // User cancelled out on reconnections -- return // WN_SUCCESS to let DoRestoreConnection finish // return WN_SUCCESS; } // // Attempt to add the connection. // NOTE: The initial password is NULL. // #if DBG == 1 DWORD ConnectTime = GetTickCount(); #endif //************************************** // Actual call to Provider //************************************** if (ConnectInfo->Defer) { ASSERT(ConnectInfo->pfAddConnection3 != NULL); status = ConnectInfo->pfAddConnection3( NULL, // hwndOwner &(ConnectInfo->NetResource), // lpNetResource password, // lpPassword UserNameForProvider, // lpUserName CONNECT_DEFERRED | (ConnectInfo->ProviderFlags << 24) ); // dwFlags } else if (ConnectInfo->pfAddConnection != NULL) { status = ConnectInfo->pfAddConnection( &(ConnectInfo->NetResource), // lpNetResource password, // lpPassword UserNameForProvider ); // lpUserName } else { status = WN_NOT_SUPPORTED; } #if DBG == 1 ConnectTime = GetTickCount() - ConnectTime; MPR_LOG2(RESTORE, "...provider took %lu ms to return status %lu\n", ConnectTime, status); if (ConnectInfo->Defer && ConnectTime > 100) { DbgPrint("[MPR] ------ %ws took %lu ms to restore a DEFERRED\n" " connection to %ws !\n", ConnectInfo->NetResource.lpProvider, ConnectTime, ConnectInfo->NetResource.lpRemoteName); } #endif if (status == WN_CONNECTED_OTHER_PASSWORD_DEFAULT) { // // Provider reconnected using default creds // status = WN_SUCCESS; } // // If that fails due to a bad password, then // loop until we either have a successful connection, or until // the user stops attempting to give a proper password. // if (CREDUI_IS_AUTHENTICATION_ERROR(status)) { // // The password needs to be cleared each time around the loop, // so that on subsequent add connections, we go back to the // logon password. // password = NULL; // // If failure was due to bad password, then attempt // to get a new password from the user. // // Changes made by congpay because of another thread. if (Params == NULL) //lpDevice != NULL, restoring ONE { if (!(dwFlags & WNRC_NOUI)) { // // We need a username buffer to prompt for a username // if ( ConnectInfo->UserName == NULL ) { ConnectInfo->UserName = (LPTSTR)LocalAlloc(LMEM_FIXED, CRED_MAX_USERNAME_LENGTH * sizeof(WCHAR)); if ( ConnectInfo->UserName == NULL ) { status = ERROR_NOT_ENOUGH_MEMORY; SetLastError (status); memset(passwordBuffer, 0, sizeof(passwordBuffer)) ; return status; } else { ConnectInfo->UserName[0] = L'\0'; BufferAllocated = TRUE; } } // // Prompt for a username // status = DoPasswordDialog(hWnd, ConnectInfo->NetResource.lpRemoteName, ConnectInfo->UserName, passwordBuffer, sizeof (passwordBuffer), &fDidCancel, status); // // Convert zero length username to NULL buffer // if ( BufferAllocated && status == NO_ERROR ) { // // If there is no user name (the length is 0), then set the // return pointer to NULL. // if (STRLEN(ConnectInfo->UserName) == 0) { LocalFree(ConnectInfo->UserName); ConnectInfo->UserName = NULL; BufferAllocated = FALSE; } } } if (status != WN_SUCCESS) { SetLastError (status); memset(passwordBuffer, 0, sizeof(passwordBuffer)) ; return status; } else { if (fDidCancel) { status = WN_CANCEL; } else { password = passwordBuffer; status = WN_ACCESS_DENIED; } } } else // restoring all { if (!(Params->dwRestoreFlags & WNRC_NOUI)) { if (Params->status == WN_CANCEL) { // // User cancelled out of restoring connections. Return // WN_SUCCESS to let DoRestoreConnection finish looping // continue; } if ( _static_pfPostMsg == NULL ) { MprEnterLoadLibCritSect(); if ( _static_hUser32 = LoadLibraryEx( USER32_DLL_NAME, NULL, LOAD_WITH_ALTERED_SEARCH_PATH ) ) { _static_pfPostMsg = (FN_PostMessage *) GetProcAddress( _static_hUser32, POST_MSG_API_NAME ); } MprLeaveLoadLibCritSect(); if ( _static_pfPostMsg == NULL ) { memset(passwordBuffer, 0, sizeof(passwordBuffer)) ; return GetLastError(); } } lpHandle = Params->hDonePassword; Params->pchResource = ConnectInfo->NetResource.lpRemoteName; Params->pchUserName = ConnectInfo->UserName; Params->dwError = status; if ((Params->hDlg == INVALID_WINDOW_HANDLE) || ((*_static_pfPostMsg) (Params->hDlg, DO_PASSWORD_DIALOG, (WPARAM) Params, 0) == 0)) { // // Either we're not using a credential dialog or the // PostMessage call failed -- bail. // MPR_LOG3(ERROR, "%ws returned %d for connection to %ws -- bailing\n", ConnectInfo->NetResource.lpProvider, status, ConnectInfo->NetResource.lpRemoteName); goto CredentialError; } WaitForSingleObject ( lpHandle, INFINITE ); if (Params->status == WN_SUCCESS) { if (Params->fDidCancel) { status = WN_CANCEL; } else { password = Params->passwordBuffer; } } else { status = Params->status; } } else { // Caller wants no UI - don't show any status = WN_CANCEL; // Remember that we had a failure to reconnect Params->fReconnectFailed = TRUE; } } } else if (status == WN_SUCCESS) { DWORD dwIgnoredStatus; MPR_LOG1(RESTORE,"MprRestoreThisConnection: Successful " "restore of connection for %ws\n", ConnectInfo->NetResource.lpRemoteName); // // Username for the connection might have changed via the // password dialog -- update it. // dwIgnoredStatus = MprSetRegValue(ConnectInfo->RegKey, USER_NAME, ConnectInfo->UserName, 0); if (dwIgnoredStatus != ERROR_SUCCESS) { MPR_LOG(ERROR, "MprSetRegValue for user name failed %lu\n", dwIgnoredStatus); } // // If the DEFER_UNKNOWN flag was set, we can clear it now. // If the default password worked, we know that we can defer the // connection in future. If we had to prompt for a password, we // can't defer the connection in future. // Note: This may not do the right thing for share-level // connections where the level of access depends on the password. // But we'll let users fix that by manually recreating the // connection. // if (ConnectInfo->DeferFlags & DEFER_UNKNOWN) { if (password == NULL) { ConnectInfo->DeferFlags &= ~(DEFER_UNKNOWN | DEFER_EXPLICIT_PASSWORD); MPR_LOG1(RESTORE,"MprRestoreThisConnection: WILL defer " "connection for %ws in future\n", ConnectInfo->NetResource.lpRemoteName); } else { ConnectInfo->DeferFlags &= ~DEFER_UNKNOWN; ConnectInfo->DeferFlags |= DEFER_EXPLICIT_PASSWORD; MPR_LOG1(RESTORE,"MprRestoreThisConnection: will NOT defer " "connection for %ws in future\n", ConnectInfo->NetResource.lpRemoteName); } dwIgnoredStatus = MprSaveDeferFlags(ConnectInfo->RegKey, ConnectInfo->DeferFlags); if (dwIgnoredStatus != ERROR_SUCCESS) { MPR_LOG(ERROR, "MprSaveDeferFlags failed %lu\n", dwIgnoredStatus); } } } else { // // An unexpected error occured. In this case, // we want to leave the loop. // MPR_LOG2(ERROR, "MprRestoreThisConnection: AddConnection for (%ws) Error %d \n", ConnectInfo->NetResource.lpProvider, status); break; } // // On subsequent iterations, // pass the provider the user name typed by the caller. // UserNameForProvider = ConnectInfo->UserName; } while (CREDUI_IS_AUTHENTICATION_ERROR(status)); CredentialError: memset(passwordBuffer, 0, sizeof(passwordBuffer)) ; return status; } DWORD MprCreateConnectionArray( LPDWORD lpNumConnections, LPCTSTR lpDevice, LPDWORD lpRegMaxWait, LPCONNECTION_INFO *ConnectArray ) /*++ Routine Description: This function creates an array of CONNECTION_INFO structures and fills each element in the array with the info that is stored in the registry for that connection. NOTE: This function allocates memory for the array. Arguments: NumConnections - This is a pointer to the place where the number of connections is to be placed. This indicates how many elements are stored in the array. lpDevice - If this is NULL, information on all remembered connections is required. Otherwise, information for only the lpDevice connection is required. lpRegMaxWait - This is a pointer to the location where the wait time read from the registry is to be placed. If this value does not exist in the registry, then the returned value is 0. ConnectArray - This is a pointer to the location where the pointer to the array is to be placed. Return Value: An error status code is returned only if something happens that will not allow us to restore even one connection. --*/ { DWORD status = WN_SUCCESS; HKEY connectHandle; HKEY providerKeyHandle; DWORD maxSubKeyLen; DWORD maxValueLen; DWORD ValueType; DWORD Temp; DWORD i; BOOL AtLeastOneSuccess = FALSE; // // init return data // *lpNumConnections = 0 ; *ConnectArray = NULL ; // // Get a handle for the connection section of the user's registry // space. // if (!MprOpenKey( HKEY_CURRENT_USER, CONNECTION_KEY_NAME, &connectHandle, DA_READ)) { MPR_LOG(ERROR,"WNetRestoreConnection: MprOpenKey Failed\n",0); return(WN_CANNOT_OPEN_PROFILE); } // // Find out the number of connections to restore (numSubKeys) and // the max lengths of subkeys and values. // if(!MprGetKeyInfo( connectHandle, NULL, lpNumConnections, &maxSubKeyLen, NULL, &maxValueLen)) { MPR_LOG(ERROR,"WNetRestoreConnection: MprGetKeyInfo Failed\n",0); *lpNumConnections = 0 ; RegCloseKey(connectHandle); return(WN_CANNOT_OPEN_PROFILE); } if (*lpNumConnections == 0) { RegCloseKey(connectHandle); return(WN_SUCCESS); } if (lpDevice != NULL) { *lpNumConnections = 1; } // // Allocate the array. // *ConnectArray = (LPCONNECTION_INFO)LocalAlloc( LPTR, *lpNumConnections * sizeof(CONNECTION_INFO)); if (*ConnectArray == NULL) { *lpNumConnections = 0 ; RegCloseKey(connectHandle); return(GetLastError()); } // // Level 1 initialization for the call to MprGetProviderIndex in the loop // if (!(GlobalInitLevel & FIRST_LEVEL)) { status = MprLevel1Init(); if (status != WN_SUCCESS) { RegCloseKey(connectHandle); return status; } } for (i=0; i < *lpNumConnections; i++) { // // Read a Connection Key and accompanying information from the // registry. // // NOTE: If successful, this function will allocate memory for // netResource.lpRemoteName, // netResource.lpProvider, // netResource.lpLocalName, and optionally.... // userName // if (!MprReadConnectionInfo( connectHandle, lpDevice, i, &((*ConnectArray)[i].ProviderFlags), &((*ConnectArray)[i].DeferFlags), &((*ConnectArray)[i].UserName), &((*ConnectArray)[i].NetResource), &((*ConnectArray)[i].RegKey), maxSubKeyLen)) { // // The read failed even though this should be a valid index. // MPR_LOG0(ERROR, "MprCreateConnectionArray: ReadConnectionInfo Failed\n"); status = WN_CANNOT_OPEN_PROFILE; } else { // // Get the Provider Index // if (MprGetProviderIndex( (*ConnectArray)[i].NetResource.lpProvider, &((*ConnectArray)[i].ProviderIndex))) { AtLeastOneSuccess = TRUE; (*ConnectArray)[i].ContinueFlag = TRUE; } else { // // The provider index could not be found. This may mean // that the provider information stored in the registry // is for a provider that is no longer in the ProviderOrder // list. (The provider has been removed). In that case, // we will just skip this provider. We will leave the // ContinueFlag set to 0 (FALSE). // MPR_LOG0(ERROR, "MprCreateConnectionArray:MprGetProviderIndex Failed\n"); status = WN_BAD_PROVIDER; (*ConnectArray)[i].Status = status; } } // endif (MprReadConnectionInfo) } // endfor (i=0; ilpLocalName); LocalFree(netResource->lpRemoteName); LocalFree(netResource->lpProvider); LocalFree(ConnectArray[i].UserName); if (ConnectArray[i].RegKey != NULL) { RegCloseKey(ConnectArray[i].RegKey); } if (ConnectArray[i].hProviderDll != NULL) { FreeLibrary(ConnectArray[i].hProviderDll); } } LocalFree(ConnectArray); return; } DWORD MprNotifyErrors( HWND hWnd, LPCONNECTION_INFO ConnectArray, DWORD NumConnections, DWORD dwFlags ) /*++ Routine Description: This function calls the error dialog for each connection that still has the continue flag set, and does not have a SUCCESS status. Arguments: hWnd - This is a window handle that will be used as owner of the error dialog. ConnectArray - This is the array of connection information. At the point when this function is called, the following fields are meaningful: ContinueFlag - If set, it means that this connection has not yet been established. StatusFlag - If this is not SUCCESS, then it contains the error status from the last call to the provider. ContinueFlag Status ---------------|--------------- | FALSE | NotSuccess | Provider will not start | FALSE | Success | Connection was successfully established | TRUE | NotSuccess | Time-out occured | TRUE | Success | This can never occur. ------------------------------- NumConnections - This is the number of entries in the array of connection information. Return Value: --*/ { DWORD i; BOOL fDisconnect = FALSE; DWORD status = WN_SUCCESS; // // If HideErrors becomes TRUE, stop displaying error dialogs // BOOL fHideErrors = (dwFlags & WNRC_NOUI) ? TRUE : FALSE; for (i=0; (ilpLocalName = NULL ; lpNetResource->lpRemoteName = lpRemoteName ; lpNetResource->lpProvider = lpProviderName ; lpNetResource->dwType = 0 ; // // null these so we dont free twice if error exit later // lpRemoteName = NULL ; lpProviderName = NULL ; status = RegEnumValue(connectHandle, i, lpNetResource->lpRemoteName, &cbRemoteName, 0, &TypeCode, (LPBYTE) lpNetResource->lpProvider, &cbProviderName) ; if (status == NO_ERROR) { (*ConnectArray)[j].UserName = lpUserName ; // // Get the Provider Index // if (MprGetProviderIndex( (*ConnectArray)[j].NetResource.lpProvider, &((*ConnectArray)[j].ProviderIndex))) { (*ConnectArray)[j].ContinueFlag = TRUE; } else { // // The provider index could not be found. This may mean // that the provider information stored in the registry // is for a provider that is no longer in the ProviderOrder // list. (The provider has been removed). In that case, // we will just skip this provider. We will leave the // ContinueFlag set to 0 (FALSE). // status = WN_BAD_PROVIDER; (*ConnectArray)[j].Status = status; } } else { // // should not happen, but if it does the array is half built, // and cannot be used, so ErrorExit (this will clean it up). // goto ErrorExit ; } } RegCloseKey(connectHandle); return(WN_SUCCESS); ErrorExit: RegCloseKey(connectHandle); LocalFree(lpProviderName) ; LocalFree(lpRemoteName) ; MprFreeConnectionArray(*ConnectArray,*lpNumConnections); *ConnectArray = NULL ; *lpNumConnections = 0 ; return status; } VOID MprNotifyShell( IN LPCWSTR pwszDevice ) /*++ Routine Description: This function sets an event that asks a trusted system component (the service controller) to discover the changed network drives and asynchronously broadcast a device change message on our behalf. CODEWORK: Replace this entire mechanism with real plug-n-play. Arguments: pwszDevice - Name of the local device (NULL for UNC connections) Return Value: None History: BruceFo 19-May-1995 Created, calls BSM directly AnirudhS 06-Jun-1996 Set event to have another component do the BSM on our behalf --*/ { // The shell is only interested in drive redirections if (pwszDevice == NULL || wcslen(pwszDevice) != 2 || pwszDevice[1] != L':') { return; } // Ask for a device change message to be broadcast HANDLE hBSMEvent = OpenEvent( EVENT_MODIFY_STATE, // desired access FALSE, // don't inherit SC_BSM_EVENT_NAME // name ); if (hBSMEvent == NULL) { MPR_LOG(ERROR, "Couldn't open event for BSM request, %lu\n", GetLastError()); } else { if (! SetEvent(hBSMEvent)) { MPR_LOG(ERROR, "Couldn't set event for BSM request, %lu\n", GetLastError()); } CloseHandle(hBSMEvent); } } BOOL MprBroadcastDriveChange( IN LPCWSTR pwszDevice, IN BOOL DeleteMessage ) /*++ Routine Description: This function asynchronously broadcasts a device change message on our behalf. Arguments: pwszDevice - Name of the local device (NULL for UNC connections) DeleteMessage - denotes where a delete/add message is needed TRUE - send a device deleted message FALSE - send a device added message Return Value: TRUE - Operations completed FALSE - Error encountered --*/ { BOOL Result; DWORD dwFlags = DDD_LUID_BROADCAST_DRIVE; // The shell is only interested in drive redirections if (pwszDevice == NULL || wcslen(pwszDevice) != 2 || pwszDevice[1] != L':') { return( FALSE ); } if( DeleteMessage == TRUE ) { dwFlags |= DDD_REMOVE_DEFINITION; } Result = DefineDosDeviceW( dwFlags, pwszDevice, NULL ); return( Result ); }