// // The developer of the original code and/or files is Tripwire, Inc. // Portions created by Tripwire, Inc. are copyright (C) 2000-2018 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(); TW_UNIQUE_PTR 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()) { TW_UNIQUE_PTR 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_t 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); if (pIter) { d.TraceDetail("Examining 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 \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_t flags) { mFlags = flags; // create the data source iterator // TW_UNIQUE_PTR 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); } if (flags & FLAG_DIRECT_IO) { mpPropCalc->SetCalcFlags(mpPropCalc->GetCalcFlags() | iFCOPropCalc::DIRECT_IO); } // // 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& fcoNames, uint32_t flags) { iFCONameTranslator* pTrans = iTWFactory::GetInstance()->GetNameTranslator(); // // create the data source iterator // TW_UNIQUE_PTR 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); } if (flags & FLAG_DIRECT_IO) { mpPropCalc->SetCalcFlags(mpPropCalc->GetCalcFlags() | iFCOPropCalc::DIRECT_IO); } // // iterate over all the objects to integrity check.. // std::list::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); } }