NAME

XML::IODEF - A module for building/parsing IODEF messages

QUICK START

Below is an example of an Incident IODEF message.

  <?xml version="1.0" encoding="UTF-8"?>
  <!DOCTYPE IODEF-Message PUBLIC "-//IETF//DTD RFC 5070 IODEF v1.0//EN" "IODEF-Document.dtd">
  <IODEF-Document>
    <Incident purpose="reporting">
      <IncidentID>
	#12345
      </IncidentID>
      <AdditionalData meaning="data2" dtype="string">value2</AdditionalData>
      <AdditionalData meaning="data1" dtype="string">value1</AdditionalData>
    </Incident>
  </IODEF-Document>

The previous IODEF message can be built with the following code snipset:

use XML::IODEF;   

my $iodef = new XML::IODEF();  

$iodef->add("Incidentpurpose", "reporting");
$iodef->add("IncidentAdditionalData", "value1", "data1"); 
$iodef->add("IncidentAdditionalData", "value2", "data2");
$iodef->add("IncidentIncidentID", "#12345");

print $iodef->out();

To automatically insert an the ReportTime class to the current time, add the line:

$iodef->create_time();

and you will get (for example):

  <?xml version="1.0" encoding="UTF-8"?>
  <!DOCTYPE IODEF-Message PUBLIC "-//IETF//DTD RFC 5070 IODEF v1.0//EN" "IODEF-Document.dtd">
  <IODEF-Document>
    <Incident purpose="reporting">
      <IncidentID>
	#12345
      </IncidentID>
	<ReportTime>2009-12-31T18:00:58Z</ReportTime>
      <AdditionalData meaning="data2" type="string">value2</AdditionalData>
      <AdditionalData meaning="data1" type="string">value1</AdditionalData>
    </Incident>
  </IODEF-Document>

WARNING

This RFC release was a complete restructuring from the draft XML::IODEF v0.06 was based on. THIS MODULE WILL PROBABLY BREAK YOUR EXISTING XML::IODEF CODE due to this restructuring. YOU SHOULD TEST AND RETEST BEFORE DEPLOYING INTO PRODUCTION!

DESCRIPTION

IODEF.pm is an interface for simply creating and parsing IODEF messages. IODEF is an XML based protocol designed mainly for representing computer security incidents (http://www.ietf.org/html.charters/inch-charter.html).

IODEF.pm is compliant with IODEF v1.0.

This API has been designed for simplifying the task of translating a key-value based format to its iodef representation. A typical session involves the creation of a new IODEF message, the initialisation of some of its fields and the addition of new IODEF tags to this message, while parsing some other native message. IODEF.pm is heavily based on XML::IDMEF. The only changes are the DTD definition, and modifications to check_xml_dtd and fill_internal_hashes to allow for limited recursion. The maximum depth of recursion can be modified by changing $MAX_ITER (default 10).

An interface to load and parse an IODEF message is also provided.

The API used in XML::IODEF is in no way standard. It does not follow any of the SAX or DOM philosophy, since it is neither based on a tree representation nor on an event oriented parser (at least as seen from the outside). It instead gives a linear approach toward the XML object, and uses inbuilt knowledge about a given XML DTD (IODEF in our case) to make proper choices when building the message. This simplifies the task of building well formed XML messages, by taking care on your behalf of tasks such as building intermediary nodes in an XML tree, or inserting nodes in the right, DTD compliant order.

This module contains a generic XML DTD parser and includes a simplified node based representation of the IODEF DTD. It can hence easily be upgraded or extended to support new XML nodes or other DTDs. For information on how to use the XML::IODEF API with other XML DTDs, read the documentation in the source code :) Yet, beware that the internal DTD representation is a *simplified* DTD, and can not translate all the subtilities that may be defined in XML DTDs. This representation is enough for representing most simple DTDs, such as IODEF, but not for more complex DTDs. In particular, it considers all attributes as of type CDATA, and does not support complex children ordering and occurence policies.

This code is distributed under the BSD license.

EXPORT

extend_dtd	
set_doctype_name
set_doctype_sysid
set_doctype_pubid

AUTHORS

John Green - johng@cpan.org (Original) 
Wes Young  - wes@barely3am.com  (2007 Update -- RFC 5070)

LICENSE

This code is released under the BSD license.

SEE ALSO

XML::DOM, XML::Parser, DateTime
http://tools.ietf.org/html/rfc5070
http://code.google.com/p/perl-xml-iodef

SYNOPSIS

In the following, function calls and function parameters are passed in a perl object-oriented fashion. Hence, some functions (object methods) are said to not take any argument, while they in fact take an IODEF object as first argument. Refer to the examples in case of confusion. The functions listed at the end (xml_encode, xml_decode, byte_to_string are on the other hand class methods, and should not be called on an IODEF object.

Rather than returning with non null error codes, these API calls will raise an exception if an error is encountered. These exceptions will either come from XML::DOM or XML::IODEF, depending on at which level they occured. Exceptions generated by XML::IODEF are normally caused by you trying to create an XML message that is not a valid IODEF message. In practice, it means these methods will croak if you try to do something that goes against the IODEF DTD. So take care of putting all your IODEF generation code inside 'eval {};' blocks.

OBJECT METHODS

new()

ARGS none.
RETURN

a new empty IODEF message.

DESC

new creates and returns a new empty but valid IODEF message, ie containing an xml and a doctype declarations. Use add() and create_time() to add fields to this message.

EXAMPLES
my $iodef = new XML::IODEF;

$iodef->in([PATH|STRING])

ARGS

PATH|STRING: either an IODEF message as a string or a path to a file containing an IODEF message.

RETURN

the IODEF object corresponding to this IODEF message.

DESC

in creates a new IODEF message from either a string STRING or a file located at the path PATH. If no argument is provided, an empty IODEF message is created and returned. If the parsed message does not contain any xml or doctype declarations, the missing declarations will be added.

EXAMPLES
my $iodef = (new XML::IODEF)->in("iodef.file");
my $iodef = $iodef->in("<IODEF-Document/>");

$iodef->out()

ARGS none.
RETURN

a string representing this IODEF object.

DESC

out returns the IODEF message as a string.

EXAMPLES
print $iodef->out;

$iodef->create_time([$epoch])

ARGS

$epoch: optional. an epoch time (time in secunds since January 1, 1970, UTC).

RETURN nothing.
DESC

create_time sets the IODEF ReportTime node to the current time (if no epoch argument is provided), or to the time corresponding to the epoch value provided. It sets both the ntpstamp and the UTC time stamps of ReportTime.

EXAMPLES
$iodef->create_time();

$iodef->get_type()

ARGS none.
RETURN

the type of this IODEF message, as a string.

DESC

get_type returns the type of this IODEF message as a string. An 'Incident' IODEF message would for example return "Incident".

EXAMPLES
$string_type = $iodef->get_type();

$iodef->add($tagpath, $value)

ARGS

$iodef: a hash representation of an IODEF message, as received from new or in.

$tagpath: a string obtained by concatenating the names of the nested XML tags, from the Incident tag down to the closest tag to value.

$value: the value (content of a tag, or value of an attribute) of the last tag given in tagpath.

RETURN

0 if the field was correctly added. Otherwise it croaks, because the node you wanted to add goes against the IODEF DTD. Use 'eval {};' blocks to catch this exceptions.

DESC

Each IODEF content/value of a given IODEF message node can be created through an appropriate add() call. A 'tagpath' is a string obtained by concatenating the names of the XML nodes from the top 'Incident' node down to the attribute or content whose value we want to set. Hence, in the example given in introduction, the tagpath for setting the value of the Incident purpose attribute is 'Incidentpurpose'.

The add call was designed for easily building a new IODEF message while parsing a log file, or any data based on a key-value format.

DISCUSSION

add is used for creating all the nodes along a given tag path, and setting the content of the last node, or one of its attributes. add can also be used to create a new empty IODEF node by calling add with the appropriate tag path and no value.

When one tag path occurs multiple times in an IODEF object, the add calls only affects the last one created.

DUPLICATED TAG PATH

add cannot be used to change the value of an already existing content or attribute. If you run add on an attribute that already exists, XML::IODEF will try to duplicate one of the parent nodes of the attribute, and set the attribute to the new node hence created. If the IODEF DTD does not allow this node path to be duplicated, XML::IODEF croaks. The same stands true when trying to add a content to a node path where the node already has a content. XML::IODEF will try to duplicate this node path.

SPECIAL CASE: AdditionalData

AdditionalData is a special tag requiring at least 2 add() calls to build a valid node. In case of multiple AdditionalData delarations, take care of building AdditionalData nodes one at a time.

As a response to this issue, the 'add("IncidentAdditionalData", "value")' call accepts an extended syntax compared with other calls:

add("IncidentAdditionalData", <value>);   
   => add the content <value> to Incident AdditionalData

add("IncidentAdditionalData", <value>, <meaning>); 
   => same as:  (type "string" is assumed by default)
      add("IncidentAdditionalData", <value>); 
      add("IncidentAdditionalDatameaning", <meaning>); 
      add("IncidentAdditionalDatadtype", "string");

add("IncidentAdditionalData", <value>, <meaning>, <dtype>); 
   => same as: 
      add("IncidentAdditionalData", <value>); 
      add("IncidentAdditionalDatameaning", <meaning>); 
      add("IncidentAdditionalDatadtype", <dtype>);

The use of add("IncidentAdditionalData", <arg1>, <arg2>, <arg3>) is prefered to the simple add call, since it creates the whole AdditionalData node at once. In the case of multiple arguments add("IncidentAdditionalData"...), the returned value is 1 if the type key was inserted, 0 otherwise.

EXAMPLES
my $iodef = new XML::IODEF();

$iodef->add("IncidentIncidentID", "<value>");     

$iodef->add($iodef, "Incidentpurpose", "<value>");

# AdditionalData case:
# DO:
$iodef->add("IncidentAdditionalData", "value");           # creating first AdditionalData node
$iodef->add("IncidentAdditionalDatadtype", "string");      
$iodef->add("IncidentAdditionalDatameaning", "meaning");  

$iodef->add("IncidentAdditionalData", "value2");          # creating second AdditionalData node
$iodef->add("IncidentAdditionalDatadtype", "string");      
$iodef->add("IncidentAdditionalDatameaning", "meaning2"); 

# or BETTER:
$iodef->add("IncidentAdditionalData", "value", "meaning", "string");  
$iodef->add("IncidentAdditionalData", "value2", "meaning2");          

$iodef->set($tagpath, $value)

ARGS

$iodef: a hash representation of an IODEF message, as received from new or in.

$tagpath: a string obtained by concatenating the names of the nested XML tags, from the Incident tag down to the closest tag to value, and leading to either a valid IODEF attribute or a valid content node.

$value: the value (content of a tag, or value of an attribute) of the last tag given in tagpath.

RETURN

0 if the field was correctly changed. Otherwise it croaks, because the node you wanted to add goes against the IODEF DTD. Use 'eval {};' blocks to catch this exceptions.

DESC

The set() call follows the first occurence of the node path described by <tagpath> and attempts at changing the corresponding content or attribute value. If the first occurence of <tagpath> does not lead to any existing node or attribute, set() croaks. Check first with contains() that the node or attribute exists. If you want to create an attribute value or a node content where there was none, use add() and not set().

RESTRICTIONS

set() only allows you to reach and change the attribute or content of the first occurence of a given tagpath (ie the last one created). If this tagpath occurs multiple time, you will not be able to modify the other occurences. Yet this should be able for most applications. Furthermore, set() cannot be used to create any new value/content.

EXAMPLES
my $iodef = new XML::IODEF();

$iodef->add("IncidentAdditionalData", "value");           # content add first
$iodef->add("IncidentAdditionalDatadtype", "string");      # ok
$iodef->add("IncidentAdditionalDatameaning", "meaning");  # ok

# change AdditionalData's content value
$iodef->set("IncidentAdditionalData", "new value");

$iodef->get($tagpath)

ARGS

$iodef: a hash representation of an IODEF message, as received from new or in.

$tagpath: a string obtained by concatenating the names of the nested XML tags, from the Incident tag down to the closest tag to value, and leading to either a valid IODEF attribute or a valid content node.

RETURN

a string: the content of the node or value of the attribute, undef if there is no such value, and croaks if error.

DESC

The get() call follows the first occurence of the node path described by $tagpath and attempts at retrieving the corresponding content or attribute value. If the first occurence of $tagpath does not lead to any existing node, get() returns undef. But this does not mean that the value does not exists in an other occurence of the pagpath.

get() only allows you to reach and retrieve the attribute or content of the first occurence of a given tagpath. If this tagpath occurs multiple time, you will not be able to retrieve the other occurences. Yet this should be able for most applications.

EXAMPLES
my $iodef = new XML::IODEF();

$iodef->add("IncidentAdditionalData", "value", "meaning"); 

# get AdditionalData's content value
$iodef->get("IncidentAdditionalData");

$iodef->contains($tagpath)

ARGS

$tagpath: a tagpath (see add).

RETURN

1 if there exists in this iodef message a value associated to the given tagpath. 0 otherwise.

DESC

contains is a test function, used to determine whether a value has already been set at a given tagpath.

$iodef->to_hash()

ARGS none.
RETURN

the IODEF message flattened inside a hash.

DESC

to_hash returns a hash enumerating all the contents and attributes of this IODEF message. Each key is a concatenated sequence of XML tags (a 'tagpath', see add()) leading to the content/attribute, and the corresponding value is an array containing the content/attribute itself. In case of multiple occurences of one 'tagpath', the corresponding values are listed as elements of the array (See the example).

EXAMPLES
<IODEF-Document version="1.0">
  <Incident purpose="mitigation">
    <IncidentID>
      #12345
    </IncidentID>
    <AdditionalData meaning="datatype1">data1</AdditionalData>
    <AdditionalData meaning="datatype2">data2</AdditionalData>
  </Incident>
</IODEF-Document>

becomes:
 
{ "version"                       => [ "1.0" ],
  "Incidentpurpose"               => [ "mitigation" ],
  "IncidentIncidentID"            => [ "#12345" ],
  "IncidentAdditionalDatameaning" => [ "datatype1", "datatype2" ],   # meaning & contents are
  "IncidentAdditionalData"        => [ "type1", "type2" ],           # listed in same order
}

CLASS METHODS

COMMENT

The following class methods are designed to access the DTD and XML engine on top of which XML::IODEF is built. These calls allows you to use the XML::IODEF API calls to generate/parse other XML formats than IODEF, by loading a given DTD representation into XML::IODEF and changing the corresponding DOCTYPE declarations. Avoid using these calls if you can, as they are little documented and subject to changes. No support will be provided on how to use them, and the documentation lies in the source code :)

set_doctype_name($string)

ARGS

$string: a DOCTYPE name

DESC

Sets the name field in the XML DOCTYPE declaration of XML messages generated by XML::IODEF. 'IODEF-Document' is the default.

set_doctype_sysid($string)

ARGS

$string: a DOCTYPE system ID

DESC

Sets the system ID field in the XML DOCTYPE declaration of XML messages generated by XML::IODEF. 'IODEF-Document.dtd' is the default.

set_doctype_pubid($string)

ARGS

$string: a DOCTYPE public ID

DESC

Sets the public ID field in the XML DOCTYPE declaration of XML messages generated by XML::IODEF. '-//IETF//DTD RFC 5070 IODEF v1.0//EN' is the default.

extend_dtd($IODEF-class, $Extended-subclass)

ARGS

$IODEF-class: a pseudo representation of an XML DTD that either extands IODEF or represent a completly different XML protocol.

$Extended-subclass: the name of the new DTD's root. 'IODEF-Document' is the default if no value provided.

RETURN nothing. croaks if the provided pseudo-DTD contains incoherencies.
DESC

extend_dtd is used to extend the IODEF DTD by changing the definition of some IODEF nodes and/or adding newnodes. It can also be used to load a completly new DTD representation in XML::IODEF's XML engine, hence making it possible to use the XML::IODEF API to generate and parse other XML formats then IODEF. Internally, the IODEF.pm module is built around a DTD parser, which reads an XML DTD (written in a proprietary but straightforward format) and provides calls to build and parse XML messages compliant with this DTD. This DTD parser and its API could be used for (almost) any other XML format than IODEF, provided that the appropriate DTD gets loaded in the module, and that the DTD can be represented in the pseudo-DTD format used internally by the module. The format of the pseudo-DTD representation is complex and subject to changes. Yet, if you really wish to use these functionalities, you will find proper documentation in the module source code.

Example: to add a new node called hexdata to the AdditionalData node, do:

my $ext_dtd = {
    "AdditionalData" => {
        ATTRIBUTES  => { 
            "dtype" => ["boolean", "byte", "character", "date-time", "integer", "portlist",
            "real", "string", "file", "frame", "packet", "ipv4-packet", "ipv6-packet",
            "path", "url", "csv", "winreg", "xml", "ext-value"],
            "ext-dtype"    => [],
            "meaning"      => [],
            "formatid"     => [],
            "restriction" => [ "public", "need-to-know", "private", "default" ],
        },
        CONTENT => 'ANY',
        CHILDREN    => [ "hexdata" ],
    },
    "hexdata"   => { CONTENT => PCDATA },
};
extend_dtd($ext_dtd, "IODEF-Document");