//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1992 - 2000. // // File: opendoc.cxx // // Contents: Code that encapsulates an opened file on a Nt volume // //---------------------------------------------------------------------------- #include #pragma hdrstop #include #include #include #include #include #include #include #include #include //+--------------------------------------------------------------------------- // // Class: CImpersonatedOpenOpLock // // Purpose: Opens the oplock by doing any necessary impersonation. If there // are multiple alternative user-ids, it will try until one // succeeds or there are no more choices. // // History: 1-24-97 srikants Created // //---------------------------------------------------------------------------- class CImpersonatedOpenOpLock : public PImpersonatedWorkItem { public: CImpersonatedOpenOpLock( const CFunnyPath & funnyDoc, CImpersonateRemoteAccess * pRemoteAccess, BOOL fTakeOpLock ) : PImpersonatedWorkItem( funnyDoc.GetActualPath() ), _funnyDoc(funnyDoc), _fTakeOpLock( fTakeOpLock ), _pRemoteAccess( pRemoteAccess ), _fSharingViolation(FALSE) { } BOOL DoIt(); // virtual void Open() { if ( _pRemoteAccess ) { ImpersonateAndDoWork( *_pRemoteAccess ); } else { BOOL fSuccess = DoIt(); Win4Assert( fSuccess ); } } CFilterOplock * Acquire() { return _oplock.Acquire(); } BOOL IsSharingViolation() const { return _fSharingViolation; } BOOL IsOffline() const { return _oplock->IsOffline(); } BOOL IsNotContentIndexed() const { return _oplock->IsNotContentIndexed(); } private: const CFunnyPath & _funnyDoc; BOOL _fTakeOpLock; CImpersonateRemoteAccess * _pRemoteAccess; XPtr _oplock; BOOL _fSharingViolation; }; //+--------------------------------------------------------------------------- // // Member: CImpersonatedOpenOpLock::DoIt // // Synopsis: The virtual "DoIt" method that does the work under the // impersonated context (if necessary). // // Returns: TRUE if successful. // FALSE if another impersonation must be tried // THROWS if there is a failure and no impersonation to be // tried. // // History: 1-24-97 srikants Created // //---------------------------------------------------------------------------- BOOL CImpersonatedOpenOpLock::DoIt() { BOOL fStatus = TRUE; BOOL fTryAgain = TRUE; for (;;) { TRY { _oplock.Set( new CFilterOplock( _funnyDoc, _fTakeOpLock ) ); break; } CATCH( CException, e ) { NTSTATUS Status = e.GetErrorCode(); // // If the path was a net path, we may have to use a different impersonation // to gain access. We try until there are no more impersonation choices left. // if ( _fNetPath && 0 != _pRemoteAccess && IsRetryableError(Status) ) { fStatus = FALSE; ciDebugOut(( DEB_WARN, "Trying another user-id for (%ws)\n", _funnyDoc.GetPath() )); } else { if ( fTryAgain && ::IsSharingViolation( Status ) ) { _fSharingViolation = TRUE; _fTakeOpLock = FALSE; fTryAgain = FALSE; continue; } ciDebugOut(( DEB_IWARN, "Failed to filter (%ws). Error (0x%X)\n", _funnyDoc.GetPath(), e.GetErrorCode() )); RETHROW(); } break; } END_CATCH } return fStatus; } //+--------------------------------------------------------------------------- // // Construction/Destruction // //---------------------------------------------------------------------------- //+--------------------------------------------------------------------------- // // Member: CCiCOpenedDoc::CCiCOpenedDoc // // Synopsis: Default constructor. We initialize the minimal set of // members to indicate an inactive document // // Arguments: None // //---------------------------------------------------------------------------- CCiCOpenedDoc::CCiCOpenedDoc( CStorageFilterObject * pFilterObject, CClientDaemonWorker * pWorker, BOOL fTakeOpLock, BOOL fFailIfNotContentIndexed ) :_RefCount( 1 ), _TypeOfStorage( eUnknown ), _llFileSize(0), _pWorker( pWorker), _pRemoteImpersonation(0), _pFilterObject( pFilterObject ), _fTakeOpLock( fTakeOpLock ), _fSharingViolation( FALSE ), _fFailIfNotContentIndexed( fFailIfNotContentIndexed ) { if ( _pWorker ) { _pRemoteImpersonation = new CImpersonateRemoteAccess( &(_pWorker->GetImpersonationCache()) ); } } CCiCOpenedDoc::~CCiCOpenedDoc() { Win4Assert( 0 == _RefCount ); Close(); delete _pRemoteImpersonation; } //+--------------------------------------------------------------------------- // // Member: CCiCOpenedDoc::_TooBigForDefault // // Synopsis: Checks if the given size is too big for using the default // filter. // // Arguments: [ll] - Size to check // // History: 1-24-97 srikants Created // //---------------------------------------------------------------------------- inline BOOL CCiCOpenedDoc::_TooBigForDefault( LONGLONG const ll ) { Win4Assert( 0 != _pWorker ); return( ll > (_pWorker->GetRegParams().GetMaxFilesizeFiltered() * 1024) ); } //+--------------------------------------------------------------------------- // // Member: CCiCOpenedDoc::_GetFileSize // // Synopsis: Retrieves the size of the current file. // // History: 1-24-97 srikants Created // //---------------------------------------------------------------------------- LONGLONG CCiCOpenedDoc::_GetFileSize() { Win4Assert( IsOpen( ) ); IO_STATUS_BLOCK IoStatus; HANDLE FileHandle = _SafeOplock->GetFileHandle( ); Win4Assert( INVALID_HANDLE_VALUE != FileHandle ); FILE_STANDARD_INFORMATION stdInfo; NTSTATUS Status = NtQueryInformationFile( FileHandle, // File handle &IoStatus, // I/O Status &stdInfo, // Buffer sizeof( stdInfo ),// Buffer size FileStandardInformation ); Win4Assert( STATUS_DATATYPE_MISALIGNMENT != Status ); if ( !NT_SUCCESS(Status) ) { ciDebugOut(( DEB_IERROR, "Error 0x%x querying file info\n", Status )); QUIETTHROW( CException( Status )); } return stdInfo.EndOfFile.QuadPart; } //+--------------------------------------------------------------------------- // // IUnknown method implementations // //---------------------------------------------------------------------------- //+--------------------------------------------------------------------------- // // Member: CCiCOpenedDoc::QueryInterface // // Synopsis: Supports IID_IUnknown and IID_ICiCOpenedDoc // // Arguments: [riid] - desired interface id // [ppvObject] - output interface pointer // //---------------------------------------------------------------------------- STDMETHODIMP CCiCOpenedDoc::QueryInterface( REFIID riid, void **ppvObject) { Win4Assert( 0 != ppvObject ); if ( IID_ICiCOpenedDoc == riid ) *ppvObject = (void *)((ICiCOpenedDoc *)this); else if ( IID_IUnknown == riid ) *ppvObject = (void *)((IUnknown *)this); else { *ppvObject = 0; return E_NOINTERFACE; } AddRef(); return S_OK; } // QueryInterface //+--------------------------------------------------------------------------- // // Member: CCiCOpenedDoc::AddRef // //---------------------------------------------------------------------------- STDMETHODIMP_(ULONG) CCiCOpenedDoc::AddRef() { return InterlockedIncrement( &_RefCount ); } // AddRef //+--------------------------------------------------------------------------- // // Member: CCiCOpenedDoc::Release // //---------------------------------------------------------------------------- STDMETHODIMP_(ULONG) CCiCOpenedDoc::Release() { Win4Assert( _RefCount != 0 ); LONG RefCount = InterlockedDecrement( &_RefCount ); if ( RefCount <= 0 ) delete this; return (ULONG) RefCount; } // Release //+--------------------------------------------------------------------------- // // ICiCOpenedDoc method implementations // //---------------------------------------------------------------------------- //+--------------------------------------------------------------------------- // // Member: CCiCOpenedDoc::Open // // Synopsis: Opens the specified file. In general, the caller // passes an implementation-specific blob of data to the // ICiCOpenedDoc interface. The selected implementation is // free to choose how it wants to interpret it. This // implementation considers it to be a lpwstr. // // Arguments: [pbDocName] - Pointer to, presumably, lpwstr // [cbDocName] - Length in BYTES, not characters, of the name // // Returns: S_OK if successful // FILTER_E_ALREADY_OPEN if there is already an open document // FILTER_E_UNREACHABLE if there is a net failure // FILTER_E_IN_USE if the document is in use by another proess // other errors as appropriate // //---------------------------------------------------------------------------- SCODE CCiCOpenedDoc::Open ( BYTE const * pbDocName, ULONG cbDocName ) { // // We cannot open if we're already open // if ( IsOpen() ) { ciDebugOut(( DEB_IERROR, "CCiCOpenedDoc::Open - Document is already open\n" )); return FILTER_E_ALREADY_OPEN; } // // The docname (Path) is always null terminated. It is an empty string // for a deleted document. // if ( cbDocName <= 2 ) return CI_E_NOT_FOUND; SCODE sc = S_OK; TRY { // // Safely construct each piece. // XInterface LocalFileName( new CCiCDocName( (PWCHAR) pbDocName, cbDocName / sizeof( WCHAR ))); _funnyFileName.SetPath( (const WCHAR*)pbDocName, (cbDocName/sizeof(WCHAR) - 1) ); #if DBG || CIDBG // // Check that we haven't been asked to filter a file in a catalog.wci // directory. // const WCHAR * pwszComponent = (WCHAR *)pbDocName; while (pwszComponent = wcschr(pwszComponent+1, L'\\')) { Win4Assert ( _wcsnicmp( pwszComponent, L"\\catalog.wci\\", wcslen(L"\\catalog.wci\\" )) != 0 ); } #endif // DBG || CIDBG // // We have to impersonate before accessing. // CImpersonatedOpenOpLock openDoc( _funnyFileName, _pRemoteImpersonation, _fTakeOpLock ); openDoc.Open(); // // Anything bad happen? // if ( openDoc.IsSharingViolation() ) sc = FILTER_E_IN_USE; else if ( openDoc.IsOffline() ) sc = FILTER_E_OFFLINE; else if ( _fFailIfNotContentIndexed && openDoc.IsNotContentIndexed() ) { ciDebugOut(( DEB_ITRACE, "not indexing not-indexed file\n" )); sc = FILTER_E_IN_USE; } else { // // At this point, everything is correctly opened. We assign to the // members. This disconnects the objects from the safe pointers. // _FileName.Set( LocalFileName.Acquire( )); _SafeOplock.Set( openDoc.Acquire( )); _TypeOfStorage = eUnknown; } } CATCH ( CException, e ) { // // Grab status code and convert to SCODE. Convert the status, // if possible, to a known class of SCODE that the caller might, // in some way, be able to deal with. // NTSTATUS Status = e.GetErrorCode( ); if (::IsSharingViolation( Status )) { ciDebugOut(( DEB_IERROR, "CCiCOpenedDoc::Open - sharing violation - %x\n", Status )); sc = FILTER_E_IN_USE; _fSharingViolation = TRUE; } else if (IsNetDisconnect( Status )) { ciDebugOut(( DEB_ERROR, "CCiCOpenedDoc::Open - net disconnect - %x\n", Status )); sc = FILTER_E_UNREACHABLE; } else { ciDebugOut(( DEB_ITRACE, "CCiCOpenedDoc::Open - other error - %x\n", Status )); sc = Status; } } END_CATCH return sc; } //Open //+--------------------------------------------------------------------------- // // Member: CCiCOpenedDoc::Close // // Synopsis: Terminate all access to a file // // Arguments: None. // // Returns: S_OK if successful // FILTER_E_NOT_OPEN if there is no document // //---------------------------------------------------------------------------- STDMETHODIMP CCiCOpenedDoc::Close( void ) { // // If there is no document open, reject this call // if ( !IsOpen() ) return FILTER_E_NOT_OPEN; if ( 0 != _pWorker ) _SafeOplock->MaybeSetLastAccessTime( _pWorker->GetRegParams().GetStompLastAccessDelay() ); // // Release pointers // _Storage.Free(); _SafeOplock.Free( ); _FileName.Free( ); _funnyFileName.Truncate(0); _llFileSize = 0; return S_OK; } //Close //+--------------------------------------------------------------------------- // // Member: CCiCOpenedDoc::GetDocumentName // // Synopsis: Returns the interface to the document name, if open. // // Arguments: [ppIDocName] - Pointer to the returned document name // // Returns: S_OK if successful // FILTER_E_NOT_OPEN if document is not currently open // //---------------------------------------------------------------------------- STDMETHODIMP CCiCOpenedDoc::GetDocumentName( ICiCDocName ** ppIDocName ) { // // If there is no name, then we haven't opened a document yet. // if (!IsOpen( )) { return FILTER_E_NOT_OPEN; } // // Set up return pointer and reference interface // *ppIDocName = _FileName.GetPointer( ); (*ppIDocName)->AddRef( ); return S_OK; } // GetDocumentName void FunnyToNormalPath( const CFunnyPath & funnyPath, WCHAR * awcPath ); SCODE ShellBindToItemByName( WCHAR const * pszFile, REFIID riid, void ** ppv ); //+--------------------------------------------------------------------------- // // Member: CCiCOpenedDoc::GetPropertySetStorage // // Synopsis: Returns the interface to the underlying IStorage if we have // a docfile. // // Returns: IStorage* of underlying docfile. // NULL if not a docfile or not open // // History: 06-Apr-1998 KyleP Use only for property set storage // //---------------------------------------------------------------------------- IPropertySetStorage * CCiCOpenedDoc::GetPropertySetStorage() { // NTRAID#DB-NTBUG9-84131-2000/07/31-dlee Indexing Service contains workarounds for StgOpenStorage AV on > MAX_PATH paths if ( _funnyFileName.GetLength() >= MAX_PATH ) { ciDebugOut(( DEB_WARN, "Not calling StgOpenStorage in CCiCOpenedDoc::GetPropertySetStorage for paths > MAX_PATH: \n(%ws)\n", _funnyFileName.GetPath() )); _TypeOfStorage = eOther; Win4Assert( _Storage.IsNull() ); return _Storage.GetPointer( ); } // // If we are open and we don't have a storage instance and we don't know // the type of storage // if (IsOpen() && _Storage.IsNull( ) && _TypeOfStorage == eUnknown) { // // Attempt to open docfile // Win4Assert( _Storage.IsNull() ); Win4Assert( !_SafeOplock->IsOffline() ); #if 0 SCODE sc = StgOpenStorageEx( _funnyFileName.GetPath( ), // Path STGM_DIRECT | STGM_READ | STGM_SHARE_DENY_WRITE, // Flags (BChapman said use these) STGFMT_ANY, // Format 0, // Reserved 0, // Reserved 0, // Reserved IID_IPropertySetStorage, // IID _Storage.GetQIPointer() ); #else // // The shell expects the string not to change or go away until the // IPropertySetStorage is released, so make a copy of it. // FunnyToNormalPath( _funnyFileName, _awcPath ); SCODE sc = ShellBindToItemByName( _awcPath, IID_IPropertySetStorage, _Storage.GetQIPointer() ); #endif // // Set the type of storage based on what we found // if ( SUCCEEDED( sc ) ) _TypeOfStorage = eDocfile; else { _TypeOfStorage = eOther; Win4Assert( _Storage.IsNull() ); } } return _Storage.GetPointer( ); } //GetPropertySetStorage //+--------------------------------------------------------------------------- // // Member: CCiCOpenedDoc::GetStatPropertyEnum // // Synopsis: Return property storage for the "stat" (i.e., system storage) // property set. This property set is not really stored in normal // property storage but is faked out of Nt file information. // // Arguments: [ppIStatPropEnum] - pointer to the returned IPropertyStorage // interface // // Returns: S_OK -if successful // E_NOTIMPL if stat properties are not supported. This // implementation supports them. // FILTER_E_NOT_OPEN if there is no open document. // Other as appropriate // //---------------------------------------------------------------------------- STDMETHODIMP CCiCOpenedDoc::GetStatPropertyEnum( IPropertyStorage **ppIStatPropEnum ) { // // Make sure the document is open // if (!IsOpen( )) { ciDebugOut(( DEB_IERROR, "Document is not open\n" )); return FILTER_E_NOT_OPEN; } // // Return and interface // *ppIStatPropEnum = new CStatPropertyStorage( _SafeOplock->GetFileHandle( ), _funnyFileName.GetLength() ); return S_OK; } // GetStatPropertyEnum //+--------------------------------------------------------------------------- // // Member: CCiCOpenedDoc::GetPropertySetEnum // // Synopsis: Returns the docfile property set storage interface on the // current doc. // // Arguments: [ppIPropSetEnum] - returned pointer to the Docfile property // set storage // // Returns: S_OK if successful // FILTER_S_NO_PROPSETS if no property sets (beyond stat ones) // available on this document // FILTER_E_NOT_OPEN if the document is not currently open // Other as appropriate // //---------------------------------------------------------------------------- STDMETHODIMP CCiCOpenedDoc::GetPropertySetEnum( IPropertySetStorage ** ppIPropSetEnum ) { // // Make sure the document is open // if ( !IsOpen() ) return FILTER_E_NOT_OPEN; // // Make sure the extra file handle in the oplock is closed // _SafeOplock->CloseFileHandle(); // // Get the IStorage corresponding to this document // IPropertySetStorage * Storage = GetPropertySetStorage(); // // If we couldn't get the IStorage, then we certainly // couldn't get the property set storage // if ( 0 == Storage ) return FILTER_S_NO_PROPSETS; Storage->AddRef(); *ppIPropSetEnum = Storage; return S_OK; } //+--------------------------------------------------------------------------- // // Member: CCiCOpenedDoc::GetPropertyEnum // // Synopsis: Return the property storage for a particular property set // // Arguments: [GuidPropSet] - GUID of the property set whose property // storage is being requested // [ppIPropEnum] - returned pointer to property storage // // Returns: S_OK if successful // FILTER_E_NO_SUCH_PROPERTY - if property set with GUID not found // FILTER_E_NOT_OPEN if there is no open document // //---------------------------------------------------------------------------- STDMETHODIMP CCiCOpenedDoc::GetPropertyEnum( REFFMTID refGuidPropSet, IPropertyStorage **ppIPropEnum ) { // // Get the property set storage interface // IPropertySetStorage *PropSetStorage; SCODE sc = GetPropertySetEnum( &PropSetStorage ); if (!SUCCEEDED( sc )) { return sc; } // // Attempt to open the named property set. // HRESULT hr = PropSetStorage->Open( refGuidPropSet, STGM_READ | STGM_SHARE_EXCLUSIVE, // BChapman said to use these ppIPropEnum ); PropSetStorage->Release( ); if (!SUCCEEDED( hr )) { return FILTER_E_NO_SUCH_PROPERTY; } return S_OK; } // GetPropertyEnum //+--------------------------------------------------------------------------- // // Member: CCiCOpenedDoc::GetIFilter // // Synopsis: Return the appropriate filter bound to the document // // Arguments: [ppIFilter] - returned IFilter // // Returns: S_OK if successful // FILTER_E_NOT_OPEN if there is no open document // E_NOT_IMPL if not implemented // Other as appropriate // //---------------------------------------------------------------------------- STDMETHODIMP CCiCOpenedDoc::GetIFilter( IFilter ** ppIFilter ) { SCODE sc = S_OK; // // We can't do anything if the document is not opened yet. // if ( !IsOpen() || IsSharingViolation() ) return FILTER_E_NOT_OPEN; // // Make sure the extra file handle in the oplock is closed // if ( INVALID_HANDLE_VALUE != _SafeOplock->GetFileHandle() ) { _llFileSize = _GetFileSize(); _SafeOplock->CloseFileHandle(); } // // Call Ole to perform the binding // TRY { sc = CCiOle::BindIFilter( _funnyFileName.GetPath(), NULL, ppIFilter, FALSE ); if ( FAILED(sc) ) { // MK_E_CANTOPENFILE is returned by the office filter. It should be // fixed to return FILTER_E_ACCESS/FILTER_E_PASSWORD in case it is not able // to open a file for filtering. Once that is done, we can get rid // of MK_E_CANTOPENFILE condition if ( FILTER_E_ACCESS == sc || ( MK_E_CANTOPENFILE == sc && ::IsSharingViolation( GetLastError() )) ) { sc = FILTER_E_IN_USE ; } else if ( FILTER_E_PASSWORD == sc ) { sc = FILTER_E_UNREACHABLE; } else if ( _pWorker->GetRegParams().FilterFilesWithUnknownExtensions() ) { // // File with unknown extension. Does the registry say that we should // filter the file ? // ciDebugOut((DEB_IWARN, "Going to default filtering for %ws.\n", _FileName->GetPath( ) )); // TSTART(_drep->timerNoBind); // // Verify file is of appropriate size for default filtering. // if ( _TooBigForDefault( _llFileSize ) ) { ciDebugOut(( DEB_IWARN, "%ws too large for default filtering\n", _FileName->GetPath( ) )); // // When this code was in CFilterDriver::Init we returned FILTER_E_TOO_BIG. // Now we don't want to do that. Properties should still be filtered. // //sc = FILTER_E_TOO_BIG; sc = LoadBinaryFilter( _funnyFileName.GetPath(), ppIFilter ); } else { sc = LoadTextFilter( _funnyFileName.GetPath(), ppIFilter ); } if ( FAILED(sc) ) { if ( FILTER_E_ACCESS == sc ) { sc = FILTER_E_IN_USE ; } else if ( FILTER_E_PASSWORD == sc ) { sc = FILTER_E_UNREACHABLE; } } // TSTOP(_drep->timerNoBind); } } } CATCH ( CException, e ) { sc = e.GetErrorCode(); } END_CATCH if ( FAILED(sc) && 0 != _pFilterObject ) _pFilterObject->ReportFilterLoadFailure( _FileName->GetPath(), sc ); return sc; } //+--------------------------------------------------------------------------- // // Member: CCiCOpenedDoc::GetSecurity // // Synopsis: Retrieves security // // Arguments: [pbData] - pointer to returned buffer containing security descriptor // [pcbData] - input: size of buffer // output: amount of buffer used, if successful, size needed // otherwise // // Returns: S_OK if successful // FILTER_E_NOT_OPEN if document not open // HRESULT_FROM_NT( STATUS_BUFFER_TOO_SMALL ) // //---------------------------------------------------------------------------- STDMETHODIMP CCiCOpenedDoc::GetSecurity( BYTE * pbData, ULONG * pcbData ) { // // Verify that the file is open // if (!IsOpen( )) { return FILTER_E_NOT_OPEN; } // // If it is a network file, don't store security. // if ( _pRemoteImpersonation && _pRemoteImpersonation->IsImpersonated() ) { *pcbData = 0; return S_OK; } // // Attempt to get the security descriptor into the buffer // ULONG cbBuffer = *pcbData; NTSTATUS Status = NtQuerySecurityObject ( _SafeOplock->GetFileHandle(), OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, pbData, cbBuffer, pcbData ); // // If we succeeded let caller know // if (NT_SUCCESS(Status)) { *pcbData = GetSecurityDescriptorLength(pbData); return S_OK; } // // Map status for caller // if ( STATUS_BUFFER_TOO_SMALL == Status ) return CI_E_BUFFERTOOSMALL; else return HRESULT_FROM_NT( Status ); } //+--------------------------------------------------------------------------- // // Member: CCiCOpenedDoc::IsInUseByAnotherProcess // // Synopsis: Tests to see if the document is wanted by another process // // Arguments: [pfInUse] - Returned flag, TRUE => someone wants this document // // Returns: S_OK if successful // FILTER_E_NOT_OPEN if document isn't open // E_NOTIMPL // //---------------------------------------------------------------------------- STDMETHODIMP CCiCOpenedDoc::IsInUseByAnotherProcess( BOOL * pfInUse ) { // // Verify that the file is open // if (!IsOpen( ) || IsSharingViolation() ) { // If there was a sharing violation, then we never took the oplock. return FILTER_E_NOT_OPEN; } *pfInUse = _SafeOplock->IsOplockBroken( ); return S_OK; }