//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1996 - 2002. // // File: FatNot.cxx // // Contents: Downlevel notification. // // Classes: CGenericNotify // // History: 2-23-96 KyleP Lifed from DLNotify.?xx // //---------------------------------------------------------------------------- #include #pragma hdrstop #include #include #include #include #include #include #include #include #include //+--------------------------------------------------------------------------- // // Class: CRemoteNotifications // // Purpose: A class to impersonate and enable notifications for remote // shares. // // History: 7-15-96 srikants Created // // Notes: When there are multiple alternatives possible for a remote // share, we have to use the one that allows access to remote // share (if there is one). There may be some which don't allow // the required access and we should skip those. // //---------------------------------------------------------------------------- class CRemoteNotifications : public PImpersonatedWorkItem { public: CRemoteNotifications( WCHAR const * pwszPath, CGenericNotify & notify, OBJECT_ATTRIBUTES & objAttr ) : PImpersonatedWorkItem( pwszPath ), _notify(notify), _objAttr(objAttr), _status(STATUS_SUCCESS) { } NTSTATUS OpenAndStart( CImpersonateRemoteAccess & remoteAccess ); virtual BOOL DoIt(); private: CGenericNotify & _notify; OBJECT_ATTRIBUTES & _objAttr; NTSTATUS _status; }; //+--------------------------------------------------------------------------- // // Member: CRemoteNotifications::DoIt // // Synopsis: The virtual method that does the work under an impersonated // context. // // Returns: TRUE if successful; // FALSE o/w // // History: 7-15-96 srikants Created // //---------------------------------------------------------------------------- BOOL CRemoteNotifications::DoIt() { _status = _notify.OpenDirectory( _objAttr ); if ( IsRetryableError( _status ) ) { // // We should attempt the open under a different impersonation // if possible. // return FALSE; } if ( NT_ERROR(_status) ) THROW( CException( _status ) ); // // Now, enable the notifications. // _notify.StartNotification( &_status ); // already impersonated if ( NT_ERROR(_status) ) { _notify.CloseDirectory(); if ( IsRetryableError(_status) ) return FALSE; THROW( CException( _status ) ); } // // Successfully enabled notifications. // return TRUE; } //+--------------------------------------------------------------------------- // // Member: CRemoteNotifications::OpenAndStart // // Synopsis: Opens and start notifications for the remote root by trying // various impersonation contexts if necessary. // // Arguments: [remoteAccess] - The object to use for remote access. // // Returns: NTSTATUS of the whole operation. // // History: 7-15-96 srikants Created // //---------------------------------------------------------------------------- NTSTATUS CRemoteNotifications::OpenAndStart( CImpersonateRemoteAccess & remoteAccess ) { TRY { ImpersonateAndDoWork( remoteAccess ); } CATCH( CException,e ) { vqDebugOut(( DEB_ERROR, "OpenAndStart failed with error (0x%X)\n", e.GetErrorCode() )); _status = e.GetErrorCode(); } END_CATCH return _status; } //+--------------------------------------------------------------------------- // // Member: CGenericNotify::CGenericNotify // // Synopsis: Constructor of the single scope notification object CGenericNotify. // // Arguments: [wcsScope] -- Scope to watch // [cwcScope] -- Size in chars of [wcsScope] // [fDeep] -- Set to TRUE if deep notifications are enabled. // // History: 1-17-96 srikants Created // //---------------------------------------------------------------------------- CGenericNotify::CGenericNotify( PCatalog *pCat, WCHAR const * wcsScope, unsigned cwcScope, BOOL fDeep, BOOL fLogEvents ) : _refCount(1), _pCat( pCat ), _fNotifyActive(FALSE), _fRemoteDrive(FALSE), _cwcScope(cwcScope), _fDeep(fDeep), _fLogEvents(fLogEvents), _fAbort(FALSE), _hNotify(0), _pbBuffer(0) { if ( cwcScope >= MAX_PATH ) { THROW( CException( STATUS_INVALID_PARAMETER ) ); } RtlCopyMemory( _wcsScope, wcsScope, cwcScope * sizeof(WCHAR) ); _wcsScope[cwcScope] = 0; CDoubleLink::Close(); // // Bigger buffer for local scopes. // _fRemoteDrive = !IsFixedDrive( _wcsScope, _cwcScope ); if ( _fRemoteDrive ) _cbBuffer = CB_REMOTENOTIFYBUFFER; else _cbBuffer = CB_NOTIFYBUFFER; // // Client should call EnableNotification() in ctor. Delay allocating // the buffer in case this is a USN volume and no buffer is needed. // } //+--------------------------------------------------------------------------- // // Member: CGenericNotify::~CGenericNotify // // Synopsis: ~dtor . Disables further notifications and frees up // memory. // // History: 1-17-96 srikants Created // // Notes: // //---------------------------------------------------------------------------- CGenericNotify::~CGenericNotify() { Win4Assert( 0 == _refCount ); Win4Assert( IsSingle() ); Win4Assert( 0 == _hNotify ); delete [] _pbBuffer; } //+--------------------------------------------------------------------------- // // Member: CGenericNotify::OpenDirectory // // Synopsis: Opens a remote directory and uses the given object attributes. // // Arguments: [ObjectAttr] - // // Returns: STATUS of the operation. // // History: 7-15-96 srikants Created // //---------------------------------------------------------------------------- NTSTATUS CGenericNotify::OpenDirectory( OBJECT_ATTRIBUTES & ObjectAttr ) { BOOL fSuccess = TRUE; ULONG cSkip = 0; NTSTATUS Status = STATUS_SUCCESS; IO_STATUS_BLOCK IoStatus; Status = NtOpenFile( &_hNotify, // Handle FILE_LIST_DIRECTORY | SYNCHRONIZE, // Access &ObjectAttr, // Object Attributes &IoStatus, // I/O Status block FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_DIRECTORY_FILE ); // Flags if ( NT_ERROR(Status) ) _hNotify = 0; return Status; } //+--------------------------------------------------------------------------- // // Member: CGenericNotify::CloseDirectory // // Synopsis: Closes the directory handle if open and sets it to 0. // // History: 7-15-96 srikants Created // //---------------------------------------------------------------------------- void CGenericNotify::CloseDirectory() { if ( 0 != _hNotify ) { NtClose( _hNotify ); _hNotify = 0; } } //+--------------------------------------------------------------------------- // // Member: CGenericNotify::EnableNotification // // Synopsis: Enables notifications for the current scope. // // History: 1-17-96 srikants Created // //---------------------------------------------------------------------------- void CGenericNotify::EnableNotification() { vqDebugOut(( DEB_ITRACE, "Enable notification for scope %ws this=0x%x\n", _wcsScope, this )); if ( 0 == _pbBuffer ) _pbBuffer = new BYTE [_cbBuffer]; // // Open file // NTSTATUS Status; UNICODE_STRING uScope; if ( !RtlDosPathNameToNtPathName_U( _wcsScope, &uScope, 0, 0 ) ) { vqDebugOut(( DEB_ERROR, "Error converting %ws to Nt path\n", _wcsScope )); THROW( CException(STATUS_INSUFFICIENT_RESOURCES) ); } XRtlHeapMem xScopeBuf( uScope.Buffer ); OBJECT_ATTRIBUTES ObjectAttr; InitializeObjectAttributes( &ObjectAttr, // Structure &uScope, // Name OBJ_CASE_INSENSITIVE, // Attributes 0, // Root 0 ); // Security CImpersonateRemoteAccess remoteAccess( GetCatalog()->GetImpersonationTokenCache() ); CRemoteNotifications remoteNotify( _wcsScope, *this, ObjectAttr ); if ( _fRemoteDrive ) { // // Check if remote notifications are disabled. // if ( (GetCatalog()->GetRegParams())->GetCiCatalogFlags() & CI_FLAGS_NO_REMOTE_NOTIFY ) { vqDebugOut(( DEB_WARN, "Not enabling remote notifications because it is disabled in registry\n" )); return; } // // Check if the remote drive is a DFS share. If so, don't try // to enabled notifications on the share. We have to just periodically // scan for changed documents. // if ( IsDfsShare( _wcsScope, _cwcScope ) ) { vqDebugOut(( DEB_WARN, "Not enabling notifications for DFS Share (%ws) \n", _wcsScope )); LogDfsShare(); return; } Status = remoteNotify.OpenAndStart( remoteAccess ); } else { // // Check if local notifications are disabled. // if ( (GetCatalog()->GetRegParams())->GetCiCatalogFlags() & CI_FLAGS_NO_LOCAL_NOTIFY ) { vqDebugOut(( DEB_WARN, "Not enabling local notifications because it is disabled in registry\n" )); return; } Status = OpenDirectory( ObjectAttr ); if ( NT_ERROR( Status ) ) { vqDebugOut(( DEB_ERROR, "Notification disabled. NtOpenFile( %ws ) returned 0x%lx\n", _wcsScope, Status )); _hNotify = 0; if ( _fLogEvents ) LogNoNotifications( Status ); return; } StartNotification( &Status ); } if ( !_fNotifyActive && _fLogEvents ) { LogNoNotifications( Status ); } } //+--------------------------------------------------------------------------- // // Member: CGenericNotify::DisableNotification // // Synopsis: Disables further notifications for this scope. // // History: 1-17-96 srikants Created // // Notes: This must be called from within the lock of the notify manager. // //---------------------------------------------------------------------------- void CGenericNotify::DisableNotification() { vqDebugOut(( DEB_ITRACE, "Disable notification for scope %ws this=0x%x\n", _wcsScope, this )); if ( 0 != _hNotify ) { NtClose( _hNotify ); _hNotify = 0; } Release(); } //+--------------------------------------------------------------------------- // // Member: CGenericNotify::StartNotification // // Synopsis: Starts notifications by setting the APC for receiving // notifications. If successful, the object will be refcounted // and the status set to indicate that the operation is // successful. // // History: 1-17-96 srikants Created // // Notes: This must be called from within the lock of the notify manager. // If successful, the notify manager will also be // refcounted. This is because the APC depends upon the mutex // in the notify manager to be around when it is invoked. // //---------------------------------------------------------------------------- void CGenericNotify::StartNotification( NTSTATUS * pStatus ) { // // Set up query directory file. // if ( 0 == _hNotify ) { *pStatus = STATUS_TOO_LATE; return; } NTSTATUS Status = STATUS_SUCCESS; DWORD dwFlags = GetNotifyFlags(); Status = NtNotifyChangeDirectoryFile( _hNotify, 0, APC, this, &_iosNotify, _pbBuffer, _cbBuffer, dwFlags, (BYTE)_fDeep ); if ( NT_ERROR(Status) ) { vqDebugOut(( DEB_ERROR, "NtNotifyChangeDirectoryFile( %ws ) returned 0x%lx\n", _wcsScope, Status )); } else { _fNotifyActive = TRUE; AddRef(); } if ( pStatus ) *pStatus = Status; } //+--------------------------------------------------------------------------- // // Member: CGenericNotify::AddRef // // History: 1-17-96 srikants Created // //---------------------------------------------------------------------------- void CGenericNotify::AddRef() { InterlockedIncrement(&_refCount); } //+--------------------------------------------------------------------------- // // Member: CGenericNotify::Release // // Synopsis: If the refcount goes to 0, the object will be deleted. // // History: 1-17-96 srikants Created // //---------------------------------------------------------------------------- void CGenericNotify::Release() { Win4Assert( _refCount > 0 ); if ( InterlockedDecrement(&_refCount) <= 0 ) delete this; } //+--------------------------------------------------------------------------- // // Member: CGenericNotify::AdjustForOverflow // // Synopsis: Increases the size of the notification buffer if it is not // a remote drive and if the current size is < the maximum. // // History: 2-27-96 srikants Created // // Notes: // //---------------------------------------------------------------------------- void CGenericNotify::AdjustForOverflow() { if ( !_fRemoteDrive && CB_MAXSIZE > _cbBuffer ) { unsigned cbNew = min( _cbBuffer + CB_DELTAINCR, CB_MAXSIZE ); vqDebugOut(( DEB_ITRACE, "Resizing notification buffer from 0x%X to 0x%X bytes\n", _cbBuffer, cbNew )); BYTE * pbNew = new BYTE [cbNew]; delete [] _pbBuffer; _pbBuffer = pbNew; _cbBuffer = cbNew; } } //+--------------------------------------------------------------------------- // // Member: CGenericNotify::APC // // Synopsis: Asynchronous Procedure Call invoked by the system when there // is a change notification (or related error). // // Arguments: [ApcContext] - Pointer to "this" // [IoStatusBlock] - // [Reserved] - // // History: 1-17-96 srikants Created // // Notes: // //---------------------------------------------------------------------------- void CGenericNotify::APC( void * ApcContext, IO_STATUS_BLOCK * IoStatusBlock, ULONG Reserved ) { Win4Assert( 0 != ApcContext ); CGenericNotify * pthis = (CGenericNotify *)ApcContext; NTSTATUS status = STATUS_SUCCESS; TRY { pthis->_fOverflow = FALSE; Win4Assert( &pthis->_iosNotify == IoStatusBlock ); // DbgPrint( "notifications...\n" ); if ( NT_ERROR( IoStatusBlock->Status ) ) { if ( !pthis->_fAbort ) { // DbgPrint( "Async notification for scope %ws received error 0x%x\n", // pthis->_wcsScope, // IoStatusBlock->Status ); vqDebugOut(( DEB_ERROR, "Async notification for scope %ws received error 0x%x\n", pthis->_wcsScope, IoStatusBlock->Status )); vqDebugOut(( DEB_ITRACE, "CiNotification APC: ERROR 0x%x\n", pthis )); status = IoStatusBlock->Status; // // The I/O failed and it may be due to STATUS_DELETE_PENDING. // In any case, just close the handle so the directory is // freed for other apps. // pthis->CloseDirectory(); } } else if ( IoStatusBlock->Status == STATUS_NOTIFY_CLEANUP ) { vqDebugOut(( DEB_ITRACE, "CiNotification APC: CLOSE 0x%x\n", pthis )); } else { if ( IoStatusBlock->Status == STATUS_NOTIFY_ENUM_DIR ) { // DbgPrint( "***** CiNotification LOST UPDATES for scope %ws *****\n", // pthis->_wcsScope ); vqDebugOut(( DEB_WARN, "***** CiNotification LOST UPDATES for scope %ws *****\n", pthis->_wcsScope )); pthis->_fOverflow = TRUE; // // Let us adjust the size of the buffer if possible. // pthis->AdjustForOverflow(); // // But call anyway. Client is responsible for checking ::BufferOverflow. // pthis->DoIt(); } else { // .Information is the # of bytes written to the buffer. // This may be 0 even when .Status is STATUS_NOTIFY_ENUM_DIR, // and with certain builds of rdr2, STATUS_SUCCESS. if ( 0 == IoStatusBlock->Information && 0 == IoStatusBlock->Status ) { // BrianAn says NTFS won't do this, but rdr2 might vqDebugOut(( DEB_WARN, "CGenericNotify: invalid notification apc\n" )); // DbgPrint( "0 info and status block\n" ); } #if 0 if ( 0 != IoStatusBlock->Information ) #endif { if ( !pthis->_fRemoteDrive ) { pthis->DoIt(); } else { // // Get sufficient impersonation context to get attributes on // the remote share. Then process the notifications. // CImpersonateRemoteAccess remote( pthis->GetCatalog()->GetImpersonationTokenCache() ); CImpersonatedGetAttr getAttr( pthis->_wcsScope ); getAttr.DoWork( remote ); pthis->DoIt(); } } } } } CATCH(CException, e) { //DbgPrint( "caught exception in notifications\n" ); vqDebugOut(( DEB_ERROR, "CiNotification APC: CATCH 0x%x, iostatus: 0x%x, info: 0x%x\n", e.GetErrorCode(), IoStatusBlock->Status, IoStatusBlock->Information )); status = e.GetErrorCode(); } END_CATCH; if ( STATUS_SUCCESS != status ) { //DbgPrint( "clearing notify enabled\n" ); pthis->ClearNotifyEnabled(); if ( pthis->_fLogEvents ) pthis->LogNotificationsFailed( status ); } pthis->Release(); } //APC //+--------------------------------------------------------------------------- // // Member: CGenericNotify::IsFixedDrive, private // // Arguments: [wcsScope] -- Scope to check // [len] -- Length in chars of [wcsScope] // // Returns: TRUE if scope is for a fixed drive. // // History: 1-17-96 srikants Created // //---------------------------------------------------------------------------- BOOL CGenericNotify::IsFixedDrive( WCHAR const * wcsScope, ULONG len ) { CPathParser pathParser( wcsScope, len ); if ( pathParser.IsUNCName() ) return FALSE; WCHAR wDrive[MAX_PATH]; ULONG cc=sizeof(wDrive)/sizeof(WCHAR); pathParser.GetFileName( wDrive, cc ); UINT uType = GetDriveType( wDrive ); return DRIVE_FIXED == uType; } //+--------------------------------------------------------------------------- // // Function: CiNetShareGetInfo // // Synopsis: Calls NetShareGetInfo. Loads the library so we don't // link to netapi32.dll for the odd case of indexing remote // volumes. Also, don't cache the function pointer since // it's called so rarely. // // Arguments: Same as NetShareGetInfo // // Returns: Win32 / NetStatus error code // // History: 2-18-98 dlee Created // //---------------------------------------------------------------------------- typedef NET_API_STATUS (NET_API_FUNCTION * NET_SHARE_GET_INFO_FUNC)( LPTSTR servername, LPTSTR netname, DWORD level, BYTE ** bufptr ); NET_API_STATUS NET_API_FUNCTION CiNetShareGetInfo( LPTSTR servername, LPTSTR netname, DWORD level, BYTE ** bufptr ) { HINSTANCE hLib = LoadLibrary( L"netapi32.dll" ); if ( 0 == hLib ) return GetLastError(); NET_SHARE_GET_INFO_FUNC pfn = (NET_SHARE_GET_INFO_FUNC) GetProcAddress( hLib, "NetShareGetInfo" ); if ( 0 == pfn ) { FreeLibrary( hLib ); return GetLastError(); } NET_API_STATUS status = (*pfn)( servername, netname, level, bufptr ); FreeLibrary( hLib ); return status; } //CiNetShareGetInfo //+--------------------------------------------------------------------------- // // Function: IsDfsShare // // Synopsis: Determines if the given UNC share is a DFS share. // // Arguments: [wcsScope] - scope // [len] - Length // // Returns: TRUE if it is a DFS share. FALSE o/w // // History: 6-23-96 srikants Created // //---------------------------------------------------------------------------- BOOL CGenericNotify::IsDfsShare( WCHAR const * wcsScope, ULONG len ) { CPathParser pathParser( wcsScope, len ); if ( !pathParser.IsUNCName() ) return FALSE; WCHAR wDrive[MAX_PATH]; ULONG cc=sizeof(wDrive)/sizeof(WCHAR); pathParser.GetFileName( wDrive, cc ); WCHAR * pwszServerName = wDrive; WCHAR * pwszShareName = 0; // // Locate the third backslash and replace it with a NULL char. // for ( unsigned i = 2; i < cc; i++ ) { if ( wDrive[i] == L'\\' ) { wDrive[i] = 0; pwszShareName = wDrive+i+1; break; } } Win4Assert( 0 != pwszShareName ); // // Remove any trailing backslash in the share name. // i = wcslen( pwszShareName ); if ( L'\\' == pwszShareName[i-1] ) { pwszShareName[i-1] = 0; } BOOL fIsDfs = FALSE; PSHARE_INFO_1005 shi1005; NET_API_STATUS err = CiNetShareGetInfo( pwszServerName, pwszShareName, 1005, (PBYTE *) &shi1005 ); if (err == ERROR_SUCCESS) { fIsDfs = ((shi1005->shi1005_flags & SHI1005_FLAGS_DFS) != 0); // // Netapi32.dll midl_user_allocate calls LocalAlloc, so use // LocalFree to free up the stuff the stub allocated. // LocalFree( shi1005 ); } return fIsDfs; } void CGenericNotify::LogNotificationsFailed( DWORD dwError ) const { Win4Assert( 0 != dwError ); TRY { CEventLog eventLog( NULL, wcsCiEventSource ); CEventItem item( EVENTLOG_ERROR_TYPE, CI_SERVICE_CATEGORY, MSG_CI_NOTIFICATIONS_TURNED_OFF, 2 ); item.AddArg( _wcsScope ); // // When a logon fails, all the other eventlog messages have the // WIN32 error code in them. Just to keep it consistent, use the // WIN32 error code here also. // if ( STATUS_LOGON_FAILURE == dwError ) dwError = ERROR_LOGON_FAILURE; item.AddError( dwError ); eventLog.ReportEvent( item ); } CATCH( CException, e ) { vqDebugOut(( DEB_ERROR, "Exception 0x%X while writing to event log\n", e.GetErrorCode() )); } END_CATCH } void CGenericNotify::LogNoNotifications( DWORD dwError ) const { Win4Assert( 0 != dwError ); TRY { CEventLog eventLog( NULL, wcsCiEventSource ); CEventItem item( EVENTLOG_INFORMATION_TYPE, CI_SERVICE_CATEGORY, MSG_CI_NOTIFICATIONS_NOT_STARTED, 2 ); item.AddArg( _wcsScope ); // // When a logon fails, all the other eventlog messages have the // WIN32 error code in them. Just to keep it consistent, use the // WIN32 error code here also. // if ( STATUS_LOGON_FAILURE == dwError ) dwError = ERROR_LOGON_FAILURE; item.AddError( dwError ); eventLog.ReportEvent( item ); } CATCH( CException, e ) { vqDebugOut(( DEB_ERROR, "Exception 0x%X while writing to event log\n", e.GetErrorCode() )); } END_CATCH } //+--------------------------------------------------------------------------- // // Member: CGenericNotify::LogDfsShare // // Synopsis: Logs the the current share is a DFS aware share. // // History: 6-27-96 srikants Created // //---------------------------------------------------------------------------- void CGenericNotify::LogDfsShare() const { TRY { CEventLog eventLog( NULL, wcsCiEventSource ); CEventItem item( EVENTLOG_INFORMATION_TYPE, CI_SERVICE_CATEGORY, MSG_CI_DFS_SHARE_DETECTED, 1 ); item.AddArg( _wcsScope ); eventLog.ReportEvent( item ); } CATCH( CException, e ) { vqDebugOut(( DEB_ERROR, "Exception 0x%X while writing to event log\n", e.GetErrorCode() )); } END_CATCH }