A probe is a class written as a singleton, which is responsible for collecting items from a computer subject to the constraints expressed in an object.
An object represents something similar to a search specification. Each object contains one or more entities, which describe constraints on individual properties of the items to be collected. Since items also have entities, we will refer to those associated with objects as object entities when it would otherwise be ambiguous.
An object's entities are chosen to represent that minimal set of properties which are required to uniquely identify a particular item on a system. For example, every network interface on a unix system has a unique name. Therefore, the interface object for unix only needs one entity: a name.
An item represents the result of a search. Items also contain entities, which we will call item entities. Each item entity which corresponds to an object entity must satisfy the constraint expressed by the object. Items may have other entities as well, as specified by the OVAL language. All item entities must be constructed according to the language schema, which dictates things like legal values and the ordering of item entities within their containing item.
The task of the probe author is to write code to collect system information in whatever way is appropriate, subject to the constraints expressed by the object, and construct schema-compliant items representing this information.
All probes extend an abstract class named AbsProbe
. The relevant parts are below.
typedef vector < Item* > ItemVector; class AbsProbe { public: virtual ~AbsProbe(); protected: virtual ItemVector* CollectItems(Object* object) = 0; virtual Item* CreateItem() = 0; };
The CollectItems(Object*)
method is the primary method you would implement to perform the search.
In addition, you must implement a static method Instance()
, which returns your singleton. It is not shown in AbsProbe because static methods
cannot be virtual. And obviously, the constructor in your singleton subclass should probably be private.
The pure-virtual method CreateItem()
must be implemented in your probe. You may then call this method whenever you want to create a new item. It simply creates an item with particular XML schema details, e.g. tag name, namespace, qname prefix, etc. Here is a sample:
Item* InterfaceProbe::CreateItem() { Item* item = new Item(0, "http://oval.mitre.org/XMLSchema/oval-system-characteristics-5#unix", "unix-sc", "http://oval.mitre.org/XMLSchema/oval-system-characteristics-5#unix unix-system-characteristics-schema.xsd", OvalEnum::STATUS_ERROR, "interface_item"); return item; }
The item's ID (the first constructor parameter) is always set to 0. It will be automatically set to a unique value by the interpreter.
Constructing an item entity is straightforward; most of the time you only need to pass the entity's name and value, and maybe a datatype. But there is one important detail to remember: each item entity has within it a flag which tells the interpreter whether it corresponds to an object entity. Since an object's entities are selected to uniquely identify items, this effectively lets the interpreter determine which of an item's properties uniquely identify the item. This is necessary to compute a unique identifier which is used to cache and retrieve the item. So when creating an item, don't forget to set this flag on the appropriate entities, or the caching system won't work, and the interpreter will generate invalid results.
The OVAL language contains many symbols representing particular error and status conditions. It is important to handle errors in a way that is sensible and compliant with the language.
The interpreter automatically computes the flag for a collected object based on the status values of its items. You must be careful to set appropriate status values, but currently need not concern yourself with the flags, since the interpreter handles them automatically. The accepted policy for error handling is as follows:
ProbeException
.OvalEnum::STATUS_ERROR
. Attempt to continue to search for more items.ProbeException
.Probes are obtained via an instance of ProbeFactory
. There are different implementations of this class for different platforms. They live in subdirectories of <checkout dir>/src according to the platform, e.g. src/windows, src/linux, etc.
The probe factories have a static GetProbe(std::string)
method which gets the probe singleton instance corresponding to the given object element name. The factories we've written thus far simply check the name in a big if-then-else statement, and return the appropriate probe. So in order to integrate your probe into the interpreter, you must locate the probe factory for your platform, and then insert code to check for the corresponding object name and instantiate the probe. The code looks roughly like:
AbsProbe* ProbeFactory::GetProbe(string objectName) { if(objectName.compare("family_object") == 0) { probe = FamilyProbe::Instance(); } else if(objectName.compare("filemd5_object") == 0) { probe = FileMd5Probe::Instance(); } else if(objectName.compare("filehash_object") == 0) { etc...
To obtain the parameters of the search, first get the entities of the given object. These will be of type ObjectEntity
. This class has a method GetEntityValues(StringVector&)
(inherited from AbsEntity
), which you should use to get the entity's values. This method handles details like variable references, variable flags, etc, which probes should not handle themselves. The values will be inserted into the given vector. The return value is a flag, which gives the status of the variable evaluation, if one was done. If a variable reference was not used, OvalEnum::FLAG_COMPLETE
is always returned.
Note: Currently, flag values are ignored by probes, so you may ignore the return value of this method.
If you need to check whether an item matches your object, the best way to do it is via one of two Analyze methods. Most of the time, it is easiest to create an ItemEntity
and then call ObjectEntity::Analyze(ItemEntity*)
(inherited from AbsEntity
) on it. This method automatically handles variable references and all of the various possible operators (e.g. equals, not equals, pattern match, etc) to determine whether there is a match. If there is a match, OvalEnum::RESULT_TRUE
is returned. If any other result is returned, there was not a match.
Another alternative is to create a complete Item
, and invoke Object::Analyze(Item*)
on it. If the item doesn't match though, all the work creating it is wasted, and there may be a lot of junk to delete, so there may be a performance penalty. But it can potentially simplify probe code.
Rough pseudocode might look like:
ItemVector* YourProbe::CollectItems(Object* object) { ObjectEntity* usernameEntity = object->GetElementByName ( "username" ); // search through system data in a loop... // see if we found a match ItemEntity *nameItemEntity = new ItemEntity("username", username, OvalEnum::DATATYPE_STRING, true); if (usernameEntity->Analyze(nameItemEntity) == OvalEnum::RESULT_TRUE) { // found a match! } else delete nameItemEntity; }
The CollectItems(Object*)
method can be implemented in any way you like. But it should be made as efficient as possible. Some ideas we've used include: