//
// The developer of the original code and/or files is Tripwire, Inc.
// Portions created by Tripwire, Inc. are copyright (C) 2000 Tripwire,
// Inc. Tripwire is a registered trademark of Tripwire, Inc.  All rights
// reserved.
// 
// This program is free software.  The contents of this file are subject
// to the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your
// option) any later version.  You may redistribute it and/or modify it
// only in compliance with the GNU General Public License.
// 
// This program is distributed in the hope that it will be useful.
// However, this program is distributed AS-IS WITHOUT ANY
// WARRANTY; INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS
// FOR A PARTICULAR PURPOSE.  Please see the GNU General Public License
// for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
// 
// Nothing in the GNU General Public License or any other license to use
// the code or files shall permit you to use Tripwire's trademarks,
// service marks, or other intellectual property without Tripwire's
// prior written consent.
// 
// If you have any questions, please contact Tripwire, Inc. at either
// info@tripwire.org or www.tripwire.org.
//
// integritycheck.cpp
#include "stdtripwire.h"
#include "tripwirestrings.h"
#include "integritycheck.h"
#include "fco/twfactory.h"
#include "tw/fcoreport.h"
#include "core/error.h"
#include "core/debug.h"
#include "fco/fcocompare.h"
#include "tripwireutil.h"
#include "fco/fcopropset.h"
#include "fco/fcospec.h"
#include "fco/fcopropcalc.h"
#include "fco/fcospeclist.h"
#include "core/errorbucket.h"
#include "fco/fcodatasourceiter.h"

// for verbose output
#include "core/usernotify.h"
#include "fco/fconametranslator.h"

//
// TODO -- change the report interface so that it takes an error bucket instead of an error queue and then 
//		pass our error bucket to it.
//
// TODO -- we need to pass in the prop displayer and do ??? with it at the appropriate times...
//

///////////////////////////////////////////////////////////////////////////////
// ProcessAddedFCO
///////////////////////////////////////////////////////////////////////////////
void cIntegrityCheck::ProcessAddedFCO( cDbDataSourceIter dbIter, iFCODataSourceIter* pIter, bool bRecurse )
{
	ASSERT( pIter && (! pIter->Done()) );
	//
	// don't do anything if this FCO is not a part of the spec...
	//
	if( mpCurSpec->ShouldStopDescent( pIter->GetName() ) )
		return;
	//
	// note that pIter points to the added fco
	//
	iFCO* pFCO = pIter->CreateFCO();
    mnObjectsScanned++;
    mReportIter.SetObjectsScanned( mReportIter.GetObjectsScanned() + 1 );
	if( pFCO )
	{
		cTripwireUtil::CalcProps( pFCO, mpCurSpec, mpPropCalc, 0 );	//TODO -- a property displayer should be passed in here.
		mReportIter.GetAddedSet()->Insert( pFCO );
		pFCO->Release();
		//
		// descend here, if we can...
		//
		if( bRecurse )
		{
			if( pIter->CanDescend() )
			{
				// note that we don't want to descend into the dbIter, so we will seek it
				// to done...
				//
				while( ! dbIter.Done() ) dbIter.Next();
				std::auto_ptr<iFCODataSourceIter> pCopy( pIter->CreateCopy() );
				ProcessDir( dbIter, pCopy.get() );
			}
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
// ProcessRemovedFCO
///////////////////////////////////////////////////////////////////////////////
void cIntegrityCheck::ProcessRemovedFCO( cDbDataSourceIter dbIter, iFCODataSourceIter* pIter, bool bRecurse )
{
	ASSERT( ! dbIter.Done() );
	//
	// don't do anything if this FCO is not a part of the spec...
	//
	if( mpCurSpec->ShouldStopDescent( dbIter.GetName() ) )
		return;
	//
	// we have to be careful here, since not all db nodes are guarenteed to have fco data associated with them.
	//
	if( dbIter.HasFCOData() )
	{
		// add this to the "removed" set
		//
		iFCO* pFCO = dbIter.CreateFCO();
		mReportIter.GetRemovedSet()->Insert( pFCO );
		pFCO->Release();
	}
	// descend if we can...
	//
	if( bRecurse )
	{
		if( dbIter.CanDescend() )
		{
			// we don't want pIter to be descended into, so we will pass null as the second param...
			//
			ProcessDir( dbIter, 0 );
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
// ProcessChangedFCO
///////////////////////////////////////////////////////////////////////////////
void cIntegrityCheck::ProcessChangedFCO( cDbDataSourceIter dbIter, iFCODataSourceIter* pIter, bool bRecurse ) 
{
	ASSERT( pIter && (! pIter->Done()) );
	ASSERT( ! dbIter.Done() );
	cDebug d("cIntegrityCheck::ProcessChangedFCO");
	//
	// don't do anything if this FCO is not a part of the spec...
	//
	if( mpCurSpec->ShouldStopDescent( pIter->GetName() ) )
		return;

	// if the database doesn't have this data, then process this as an add
	//
	if( ! dbIter.HasFCOData() )
	{
		ProcessAddedFCO( dbIter, pIter, false );	// false means not to recurse
	}
	else
	{
		// here we will actually compare the two FCOs
		//
		TW_NOTIFY_VERBOSE(	_T("--- %s%s\n"), 
							TSS_GetString( cTripwire, tripwire::STR_NOTIFY_CHECKING ).c_str(),
							iTWFactory::GetInstance()->GetNameTranslator()->ToStringDisplay
								( pIter->GetName() ).c_str() );

		iFCO* pNewFCO = pIter->CreateFCO();
		iFCO* pOldFCO = dbIter.CreateFCO();
        
        mnObjectsScanned++;
        mReportIter.SetObjectsScanned( mReportIter.GetObjectsScanned() + 1 );
		//
		// if we can't create the operating system object, treat this as a removal; we will
		// increment the os iterator...
		//
		if( ! pNewFCO )
		{
			pIter->Next();
			ProcessRemovedFCO( dbIter, pIter, bRecurse );
			return;
		}

		CompareFCOs( pOldFCO, pNewFCO );

		//-------------------------------------------------------------------------
		// Begin functionality necessary for policy update
		//
		bool bDbFCODirty = false;
		// change the valid vector for the db's fco, if the appropriate flag is set
		//
		if( mFlags & FLAG_INVALIDATE_EXTRA_DB_PROPS )
		{
			cFCOPropVector propsToInvalidate	=  pOldFCO->GetPropSet()->GetValidVector() & mpCurSpec->GetPropVector( pOldFCO );
			propsToInvalidate					^= pOldFCO->GetPropSet()->GetValidVector();
			pOldFCO->GetPropSet()->InvalidateProps( propsToInvalidate );

			bDbFCODirty = true;
		}
		// update the old fco with the new properties...
		//
		if( mFlags & FLAG_SET_NEW_PROPS )
		{
			cFCOPropVector propsToCopy	=  pOldFCO->GetPropSet()->GetValidVector() & pNewFCO->GetPropSet()->GetValidVector();
			propsToCopy					^= pNewFCO->GetPropSet()->GetValidVector();

			pOldFCO->GetPropSet()->CopyProps( pNewFCO->GetPropSet(), propsToCopy );

			bDbFCODirty = true;
		}
		// rewrite the fco to the database, if necessary
		//
		if( bDbFCODirty )
		{
			dbIter.RemoveFCOData();
			dbIter.SetFCOData( pOldFCO );

			TW_NOTIFY_VERBOSE(	_T("%s%s\n"), 
								TSS_GetString( cTripwire, tripwire::STR_NOTIFY_DB_CHANGING ).c_str(),
								iTWFactory::GetInstance()->GetNameTranslator()->ToStringDisplay
									( pOldFCO->GetName() ).c_str() );

		}
		//
		// end policy update specific code
		//-------------------------------------------------------------------------

		pNewFCO->Release();
		pOldFCO->Release();
	}
	//
	// finally, we need to recurse into our child directories...
	//
	if( bRecurse )
	{
		if( pIter->CanDescend() || dbIter.CanDescend() )
		{
			std::auto_ptr<iFCODataSourceIter> pCopy( pIter->CreateCopy() );
			ProcessDir( dbIter, pCopy.get() );
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
// CompareFCOs
///////////////////////////////////////////////////////////////////////////////
void cIntegrityCheck::CompareFCOs( iFCO* pOldFCO, iFCO* pNewFCO )
{
	cTripwireUtil::CalcProps( pNewFCO, mpCurSpec, mpPropCalc, 0 );	//TODO -- a property displayer should be passed in here.
	//
	// figure out what properties we are comparing...for an explanation of the propsToCheck derivation, see tripwire.cpp line 250.
	//
	cFCOPropVector propsToCheck;
	if( mFlags & FLAG_COMPARE_VALID_PROPS_ONLY )
	{
		propsToCheck =	pOldFCO->GetPropSet()->GetValidVector() & 
						pNewFCO->GetPropSet()->GetValidVector();
	}
	else
	{
		propsToCheck =	pOldFCO->GetPropSet()->GetValidVector() | 
						pNewFCO->GetPropSet()->GetValidVector();
	}
	propsToCheck &= mpCurSpec->GetPropVector( pNewFCO );
	//
	// trim off the loose dir stuff...
	//
	if( (pOldFCO->GetCaps() & iFCO::CAP_CAN_HAVE_CHILDREN) &&
		(pNewFCO->GetCaps() & iFCO::CAP_CAN_HAVE_CHILDREN) )
	{
		cFCOPropVector tmp	= (propsToCheck ^ mLooseDirProps);
		propsToCheck		&= tmp;
	}
	//
	// construct the compare object and actually do the compare
	//
	cFCOCompare compareObj( propsToCheck );
	uint32 result = compareObj.Compare(pOldFCO, pNewFCO);

	if( (result & cFCOCompare::PROPS_UNEQUAL) || (result & cFCOCompare::PROPS_NOT_ALL_VALID) )
	{
		// this is a violation...
		//
		mReport.AddChangedFCO(mReportIter, pOldFCO, pNewFCO, compareObj.GetUnequalProps() | compareObj.GetInvalidProps());
	}
}

///////////////////////////////////////////////////////////////////////////////
// ProcessDir
///////////////////////////////////////////////////////////////////////////////
void cIntegrityCheck::ProcessDir( cDbDataSourceIter dbIter, iFCODataSourceIter* pIter )
{
	cDebug d("cIntegrityCheck::ProcessDir");

	// if either iterator is done, or if the data source iter is NULL, that indicates that the tree 
	// has stopped for that iterator, and all FCOs encountered by the other iter are all adds or removals,
	// as appropriate.
	//
	// the first thing we will do is descend, if possible, and if not possible, set them to done().
	//
	if( pIter )
	{
		if( pIter->CanDescend() )
		{
			pIter->Descend();
		}
		else
		{
			pIter = 0;
		}
	}
	if( ! dbIter.Done() )
	{
		if( dbIter.CanDescend() )
		{
			dbIter.Descend();
		}
		else
		{
			while( ! dbIter.Done() ) dbIter.Next();
		}
	}
	// if they are both done, then we are done dealin'!
	//
	if( ((! pIter) || (pIter->Done())) && ( dbIter.Done() ) )
	{
		//TODO -- what else do I need to do here?
		return;
	}

#ifdef _DEBUG
	if( dbIter.Done() )
	{
		d.TraceDebug( "Processing directory %s\n", pIter->GetParentName().AsString().c_str() );
	}
	else
	{
		d.TraceDebug( "Processing directory %s\n", dbIter.GetParentName().AsString().c_str() );
	}
#endif

	//
	// now, process the entries in this directory...
	//
	while( true )
	{
		// this just cleans up some logic statements below...
		//
		if( pIter && pIter->Done() )
			pIter = NULL;

		// if they are both done, then we are finished...
		//
		if( dbIter.Done() && (! pIter ) )
			break;

		if( dbIter.Done() )
		{
			ASSERT( pIter != 0);
			d.TraceDetail( "Examining <done> and %s\n", pIter->GetName().AsString().c_str() );
			//
			// these are all new entries, add them to the "Added" set...
			//
			ProcessAddedFCO( dbIter, pIter );
			pIter->Next();
		}
		else if( ! pIter )
		{
			ASSERT( ! dbIter.Done() );
			d.TraceDetail( _T("--Examining %s and <done>\n"), dbIter.GetName().AsString().c_str() );
			//
			// these are all removed objects. 
			//
			ProcessRemovedFCO( dbIter, pIter );
			dbIter.Next();
		}
		else
		{
			d.TraceDetail( _T("--Examining %s and %s\n"), dbIter.GetName().AsString().c_str(), pIter->GetName().AsString().c_str() );

			ASSERT( pIter->GetParentName() == dbIter.GetParentName() );
			//
			// get the relationship between the current nodes of the two iterators...
			//
			iFCODataSourceIter::Relationship rel = dbIter.GetRelationship( *pIter );
			//
			// if rel == LT, then the old has something the new doesn't (ie -- a removal!)
			// if rel == GT, then there is an addition
			//
			if( rel == iFCODataSourceIter::REL_LT )
			{
				ProcessRemovedFCO( dbIter, pIter );
				dbIter.Next();
			}
			else if( rel == iFCODataSourceIter::REL_GT )
			{
				ProcessAddedFCO( dbIter, pIter );
				pIter->Next();

			}
			else
			{
				// we actually have to compare the FCOs at this point...
				// NOTE -- if the db iter has no data, then this is an add again.
				//		
				ProcessChangedFCO( dbIter, pIter );

				dbIter.Next();
				pIter->Next();
			}

			ASSERT( pIter->GetParentName() == dbIter.GetParentName() );
		}
	}
	d.TraceDebug( "Done Processing Directory\n");
}

///////////////////////////////////////////////////////////////////////////////
// ctor
///////////////////////////////////////////////////////////////////////////////
cIntegrityCheck::cIntegrityCheck(cGenre::Genre genreNum,  const cFCOSpecList& specList, cHierDatabase& db, cFCOReport& report, cErrorBucket* pBucket )
:
	mGenre			( genreNum ), 
	mSpecList		( specList ),
	mDb				( db ),
	mReport			( report ),
	mpPropCalc		( iTWFactory::GetInstance()->CreatePropCalc() ),
	mpCurSpec		( 0 ),
    mReportIter		( report, genreNum ),
	mFlags			( 0 ),
    mnObjectsScanned( 0 )
{
	// mBucket is a pass-thru bucket; its only job is to pass the errors onto its child.
	//
	mBucket.SetChild			( pBucket );
	mpPropCalc->SetErrorBucket	( &mBucket );
}


///////////////////////////////////////////////////////////////////////////////
// dtor
///////////////////////////////////////////////////////////////////////////////
cIntegrityCheck::~cIntegrityCheck()
{
	delete mpPropCalc;
}


///////////////////////////////////////////////////////////////////////////////
// Execute
///////////////////////////////////////////////////////////////////////////////
void cIntegrityCheck::Execute( uint32 flags )
{
	mFlags = flags;
	// create the data source iterator
	//
	std::auto_ptr<iFCODataSourceIter>	pDSIter(iTWFactory::GetInstance()->CreateDataSourceIter());
	//
	// set up the database's iterator...
	// I assume the current genre is correct...
	//
	cDbDataSourceIter dbIter( &mDb );	

	//
	// set the iterator's error bucket...
	//
	pDSIter->SetErrorBucket(&mBucket);
    dbIter.SetErrorBucket(&mBucket);

	//
	// set up loose directories; note that mLooseDirProps represents properties that will
	// be removed from the ones used to compare, so if loose dirs is not specified, we can set this
	// to the empty vector.
	//
	if( flags & FLAG_LOOSE_DIR )
	{
		mLooseDirProps = iTWFactory::GetInstance()->GetLooseDirMask();
	}

	//
	// set up flags for the property calculator and iterators
	//    
	if( flags & FLAG_ERASE_FOOTPRINTS_IC )
	{
        mpPropCalc->SetCalcFlags( iFCOPropCalc::DO_NOT_MODIFY_PROPERTIES );  
        dbIter.SetIterFlags     ( iFCODataSourceIter::DO_NOT_MODIFY_OBJECTS );
        pDSIter->SetIterFlags   ( iFCODataSourceIter::DO_NOT_MODIFY_OBJECTS );
	}
	
	//
	// iterate over all of the specs...
	//
	cFCOSpecListCanonicalIter	specIter(mSpecList);
	for(specIter.SeekBegin(); ! specIter.Done(); specIter.Next())
	{
		mpCurSpec = specIter.Spec();
	
		// verbose output
		TW_NOTIFY_VERBOSE(	_T("%s%s\n"), 
							TSS_GetString( cTripwire, tripwire::STR_NOTIFY_CHECKING_RULE ).c_str(),
							iTWFactory::GetInstance()->GetNameTranslator()->ToStringDisplay
								( mpCurSpec->GetStartPoint() ).c_str() );
		// 	
		// add the spec to the report...
		//
        mReport.AddSpec(mGenre, mpCurSpec, specIter.Attr(), &mReportIter);
		ASSERT(! mReportIter.Done());
		//
		// associate the error bucket to this current spec in the report
		//
		mReportIter.GetErrorQueue()->SetChild( mBucket.GetChild() );
		mBucket.SetChild( mReportIter.GetErrorQueue() );
		//
		// seek each iterator to the appropriate starting point...
		//
		dbIter.SeekToFCO	( mpCurSpec->GetStartPoint(), false ); // false means not to create my peers
		pDSIter->SeekToFCO	( mpCurSpec->GetStartPoint(), false );
		//
		// integrity check the start point; note that the ProcessXXX functions will
		//		handle recursing into the subdirectories and stopping when the spec says to
		//
		if( dbIter.Done() && pDSIter->Done() )
		{
			// nothing to do here... 
			;
		}
		else if( pDSIter->Done() )
		{
			// removed object...
			ProcessRemovedFCO( dbIter, pDSIter.get() );
		}
		else if( dbIter.Done() )
		{
			// added object...
			ProcessAddedFCO( dbIter, pDSIter.get() );
		}
		else
		{
			// possible changed fco
			ProcessChangedFCO( dbIter, pDSIter.get() );
		}

		// dissociate the report error bucket and mine...
		//
		mBucket.SetChild( mReportIter.GetErrorQueue()->GetChild() );
		mReportIter.GetErrorQueue()->SetChild( 0 );
	}
}

///////////////////////////////////////////////////////////////////////////////
// ExecuteOnObjectList
///////////////////////////////////////////////////////////////////////////////
void cIntegrityCheck::ExecuteOnObjectList( const std::list<cFCOName>& fcoNames, uint32 flags )
{
	iFCONameTranslator* pTrans = iTWFactory::GetInstance()->GetNameTranslator();
	//
	// create the data source iterator
	//
	std::auto_ptr<iFCODataSourceIter>	pDSIter(iTWFactory::GetInstance()->CreateDataSourceIter());
	//
	// set up the database's iterator...
	// I assume the current genre is correct...
	//
	cDbDataSourceIter			dbIter	( &mDb );	
	cFCOSpecListCanonicalIter	specIter(mSpecList);
    
	//
	// set the iterators' error bucket...
	//
	pDSIter->SetErrorBucket(&mBucket);
    dbIter.SetErrorBucket(&mBucket);

	//
	// set up flags for the property calculator and iterators
	//    
	if( flags & FLAG_ERASE_FOOTPRINTS_IC )
	{
        mpPropCalc->SetCalcFlags( iFCOPropCalc::DO_NOT_MODIFY_PROPERTIES );  
        dbIter.SetIterFlags     ( iFCODataSourceIter::DO_NOT_MODIFY_OBJECTS );
        pDSIter->SetIterFlags   ( iFCODataSourceIter::DO_NOT_MODIFY_OBJECTS );
	}

	//
	// iterate over all the objects to integrity check..
	//
	std::list<cFCOName>::const_iterator it;
	for( it = fcoNames.begin(); it != fcoNames.end(); it++ )
	{
		TW_NOTIFY_VERBOSE(	_T("%s%s\n"), 
							TSS_GetString( cTripwire, tripwire::STR_NOTIFY_CHECKING ).c_str(),
							pTrans->ToStringDisplay( *it ).c_str() );
		//
		// figure out which spec this fco belongs in...
		//
		for( specIter.SeekBegin(); ! specIter.Done(); specIter.Next() )
		{
			if( specIter.Spec()->SpecContainsFCO( *it ) )
				break;
		}
		// error if we found no spec.
		//
		if( specIter.Done() )
		{
			mBucket.AddError								( eICFCONotInSpec( pTrans->ToStringDisplay( *it ) ) );
			mReport.GetErrorQueue()->cErrorBucket::AddError	( eICFCONotInSpec( pTrans->ToStringDisplay( *it ) ) );
			continue;
		}
		mpCurSpec = specIter.Spec();
		//
		// add this spec to the report if it is not there
		// and seek to the spec
		//
	    if( ! mReportIter.SeekToSpec( mpCurSpec ) )
        {
            mReport.AddSpec(mGenre, mpCurSpec, specIter.Attr(), &mReportIter);
		}
		// insert the report error bucket between mpErrorBucket and its children...
		//
		mReportIter.GetErrorQueue()->SetChild( mBucket.GetChild() );
		mBucket.SetChild( mReportIter.GetErrorQueue() );
		//
		// create the fco in the database...
		//
		dbIter.SeekToFCO( *it, false );
		if( dbIter.Done() || (! dbIter.HasFCOData()) )
		{
			// fco not found in the db!
			//
			mBucket.AddError( eICFCONotInDb( pTrans->ToStringDisplay( *it ) ) );
		}
		else
		{
			iFCO* pOldFCO = dbIter.CreateFCO();
			//
			// create the fco from the data source
			//
			pDSIter->SeekToFCO	( *it, false );
			if( ! pDSIter->Done() )
			{
				iFCO* pNewFCO = pDSIter->CreateFCO();
                mnObjectsScanned++;                    
                mReportIter.SetObjectsScanned( mReportIter.GetObjectsScanned() + 1 );
					
				if( pNewFCO )
				{
					CompareFCOs( pOldFCO, pNewFCO );
					pNewFCO->Release();
				}
				else
				{
					mBucket.AddError( eICFCOCreate( pTrans->ToStringDisplay( *it ) ) );
				}
			}
			pOldFCO->Release();
		}
		// dissociate the report error bucket and mine...
		//
		mBucket.SetChild( mReportIter.GetErrorQueue()->GetChild() );
		mReportIter.GetErrorQueue()->SetChild( 0 );
	}
	
}

