File Coverage

blib/lib/XML/IDMEF.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             # $Id: IDMEF.pm,v 1.16 2004/11/29 13:53:35 erwan Exp $
2              
3             package XML::IDMEF;
4              
5             # syntax cerbere
6 2     2   62425 use 5.006;
  2         10  
  2         81  
7 2     2   15 use strict;
  2         4  
  2         74  
8 2     2   10 use warnings;
  2         10  
  2         84  
9              
10             # various includes
11 2     2   11 use Carp;
  2         3  
  2         190  
12 2     2   2800 use XML::DOM;
  0            
  0            
13             use Data::Dumper;
14              
15             # export, version, inheritance
16             require Exporter;
17              
18             our @ISA = qw(Exporter);
19              
20             our %EXPORT_TAGS = ( 'all' => [ qw() ] );
21              
22             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
23              
24             our @EXPORT = qw(xml_encode
25             xml_decode
26             byte_to_string
27             extend_idmef
28             extend_dtd
29             set_doctype_name
30             set_doctype_sysid
31             set_doctype_pubid
32             );
33              
34             our $VERSION = '0.11';
35              
36              
37              
38             ##----------------------------------------------------------------------------------------
39             ##
40             ## IDMEF - An XML wrapper for building/parsing IDMEF messages
41             ##
42             ## Erwan Lemonnier - Proact Defcom - 2002/05
43             ##
44             ## DESC:
45             ##
46             ## IDMEF.pm is an interface for simply creating and parsing IDMEF messages.
47             ## It is compliant with IDMEF v1.0, and hence provides calls for building Alert,
48             ## ToolAlert, CorrelationAlert, OverflowAlert and Heartbeat IDMEF messages.
49             ##
50             ## This interface has been designed for simplifying the task of translating a
51             ## key-value based format to its idmef representation. A typical session involves
52             ## the creation of a new IDMEF message, the initialisation of some of it's fields
53             ## and its conversion into an IDMEF string, as illustrated below:
54             ##
55             ## use XML::IDMEF;
56             ##
57             ## my $idmef = new XML::IDMEF();
58             ## $idmef->create_ident();
59             ## $idmef->create_time();
60             ## $idmef->add("AlertAdditionalData", "myvalue", "mymeaning");
61             ## $idmef->add("AlertAdditionalData", byte_to_string($bytes), "binary-data", "byte");
62             ## $idmef->add("AlertAnalyzermodel", "myids");
63             ## print $idmef->out();
64             ##
65             ## An interface to load and parse an IDMEF message is also provided (with the
66             ## 'to_hash' function), but is quite limited.
67             ##
68             ## This module is based on XML::DOM and contains a simplified version of the latest
69             ## IDMEF DTD. It is hence DTD aware and perform some validity checks on the IDMEF
70             ## message treated, in an attempt at easying the process of producing valid IDMEF
71             ## messages.
72             ##
73             ## This simplified internal DTD representation can easily be upgraded or extended to
74             ## support new XML node. For information on how to extend IDMEF with IDMEF.pm, read
75             ## the documentation in the source code.
76             ##
77             ##
78             ## REM: to extract the api documentation, do 'cat IDMEF.pm | grep "##" | sed -e "s/##//"'
79             ##
80             ##
81             ## BSD LICENSE:
82             ##
83             ## All rights reserved.
84             ##
85             ## Redistribution and use in source and binary forms, with or without modification, are permitted
86             ## provided that the following conditions are met:
87             ##
88             ## Redistributions of source code must retain the above coopyright notice, this list
89             ## of conditions and the following disclaimer.
90             ## Redistributions in binary form must reproduce the above copyright notice, this list of
91             ## conditions and the following disclaimer in the documentation and/or other materials
92             ## provided with the distribution.
93             ## Neither the name of the nor the names of its contributors may be used
94             ## to endorse or promote products derived from this software without specific prior written
95             ## permission.
96             ##
97             ## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
98             ## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
99             ## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
100             ## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
101             ## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
102             ## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
103             ## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
104             ## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
105             ## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
106             ## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
107             ## POSSIBILITY OF SUCH DAMAGE.
108             ##
109             ##----------------------------------------------------------------------------------------
110             ##
111             ## LIST OF FUNCTIONS
112             ##
113             ## new # create new IDMEF message
114             ## in # load new IDMEF message from string/file
115             ## out # write IDMEF message to string/file
116             ##
117             ## to_hash # convert IDMEF message to hash for easy parsing
118             ## add # add a field to IDMEF message
119             ## get_type # return type of IDMEF message
120             ##
121             ## create_ident # initialyze the Alertident field witha unique id string
122             ## create_time # initialize the CreateTime field with the current time
123             ##
124             ## EXPORTS:
125             ##
126             ## xml_encode # encode data (not binary) into an IDMEF compliant string
127             ## xml_decode # and the other way round
128             ## byte_to_string # encode binary data into an IDMEF compliant string
129             ##
130             ##
131             ##
132              
133              
134              
135              
136             #
137             # IDMEF DTD REPRESENTATION
138             # ------------------------
139             #
140             # The IDMEF DTD, as all DTDs, can be represented as a class hierarchy in which
141             # each class corresponds to one node level. There can be 2 kind of relations between
142             # these node classes: inheritance (ex: a ToolAlert is an Alert) and composition
143             # (Alert contains Analyzer, Source, Target...).
144             #
145             # Below is a hash structure, called 'IDMEF_DTD', which defines the whole IDMEF DTD
146             # as in version 0.5. Each key is the name of the root tag of an IDMEF node, and its
147             # value is a structure representing the attributes, tags and subnodes allowed for
148             # this node, as well as the node's subclasses if there are some. If on attribute can
149             # take only a limited set of values, this is also specified. One class element (tag,
150             # attribute or node) may appear more than once, in which case it is specified.
151             #
152             # This IDMEF DTD is then parsed by the 'load_xml_dtd' function when the IDMEF.pm
153             # module is loaded, which in turn builds two internal and more convenient
154             # representations: $EXPAND_PATH & $CHECK_VALUE. These 2 hashes are used by the add()
155             # call, and faster to use than the DTD class construction.
156             #
157             # The main advantage of prefering a DTD representation of IDMEF is its flexibility:
158             # if the IDMEF rfc happens to change, the DTD hash is the only part of this module
159             # which will need an upgrade. Beside, it gets easy to extend IDMEF by adding to the
160             # DTD some home-defined root class, and extend IDMEF.pm. The extension module only
161             # needs to contain a DTD hash extending the one of IDMEF, and call 'extend_idmef'.
162             # All other functions ('in', 'out', 'add', 'to_hash'...) are then inherited from IDMEF.
163             #
164             # This code is actually build in a very generic way and could be used with whatever
165             # other XML format.
166             #
167             # DTD hash:
168             # ---------
169             #
170             # A DTD is represented as a hash where each key is the name of a node, and each value
171             # a hash encoding the corresponding DTD definition of this node.
172             # This hash describes the attributes, children and content type of this node,
173             # and can be deduced directly from the corresponding ELEMENT and ATTRIBUTE definitions
174             # in the DTD. Yet, some subtilities from the DTD, such as complex combinations
175             # of allowed children order and occurence, can not be represented in this model.
176             # That's why this DTD representation only is a pseudo-DTD, and will not be able
177             # to comply to some case of complex DTDs.
178             #
179             # A node has a name, which is its tag string. This name is the node's key in the DTD
180             # hash.
181             #
182             # A node may has children nodes. These children are listed in an anonymous array
183             # associated to the CHILDREN key. Each element of this children array is a string
184             # made of the name of the child node preceded by a one letter prefix representing
185             # the allowed occurencies of this child node. This prefix should be one of:
186             #
187             # prefix meaning
188             # ------ -------
189             #
190             # ? 0 or 1 occurences
191             # + 1 or more
192             # * 0 or more
193             # 1 exactly one
194             # # unknown (in practice, same as *)
195             #
196             # The order of the children names in the children array reflects the order of
197             # children nodes in the DTD. As a result, the XML::IDMEF API allows only to
198             # create XML messages with one given order of children in each node. If the DTD
199             # allows other combinations, it can not be encoded in XML::IDMEF, and you will
200             # have to choose one of the poccible combinations when writting the pseudo-DTD.
201             # In some cases, this won't be possible. That's why this API can not yet be
202             # generalised to any generating any XML format.
203             #
204             # A node can also have attributes, which are represented as keys of the ATTRIBUTES
205             # hash. The value associated with each key is an array of the values allowed for this
206             # attributes, or an empty array if there are no restrictions on the value.
207             #
208             # Finally, a node can have a content, declared under the CONTENT key. That key can
209             # accept 3 values: ANY, PCDATA, EMPTY. In practice, all are treated as PCDATA internaly.
210             #
211             # ex: DTD entity definition
212             #
213             # "EntityName" = {
214             # ATTRIBUTES => { "attribute1" => [ list of values ],
215             # "attribute2" => [],
216             # ...
217             # },
218             # CHILDREN => [ "elem1", "elem2"... ],
219             # CONTENT => ANY | PCDATA | EMPTY
220             # }
221             #
222              
223              
224             #
225             # CONTENT:
226             # --------
227             #
228             # the official xml contents supported by this simplified DTD representation
229              
230             use constant ANY => "ANY";
231             use constant PCDATA => "PCDATA";
232             use constant EMPTY => "EMPTY";
233              
234              
235              
236             #
237             # IDMEF_DTD:
238             # ----------
239             #
240             # A hash encoding all the xml entities defined in the IDMEF DTD, as
241             # specified in the version $IDMEF_VERSION of the IDMEF draft.
242             #
243             # REM: this is a simplified DTD representation and does not reflect
244             # exactly the content of the IDMEF DTD.
245             # In particular, this representation does not properly represent
246             # for each entity the allowed number and occurences of its children.
247              
248             # version of the IDMEF draft used for this DTD
249             my $IDMEF_VERSION = "1.0";
250              
251             my $IDMEF_DTD = {
252              
253             # each children of an entity should have a 1 letter code prefixed
254             # to its name, reflecting the occurences, as allowed by the DTD, and
255             # according to the list below:
256            
257             "IDMEF-Message" => {
258             ATTRIBUTES => { "version" => ["1.0"] },
259             CHILDREN => [ "#Alert", "#Heartbeat" ],
260             },
261              
262             "Alert" => {
263             ATTRIBUTES => { "messageid" => [] },
264             CHILDREN => [ "1Analyzer", "1CreateTime", "?DetectTime", "?AnalyzerTime",
265             "*Source", "*Target", "1Classification", "?Assessment",
266             "#ToolAlert", "#CorrelationAlert", "#OverflowAlert", "*AdditionalData" ],
267             },
268              
269             "Heartbeat" => {
270             ATTRIBUTES => { "messageid" => [] },
271             CHILDREN => [ "1Analyzer", "1CreateTime", "?AnalyzerTime", "*AdditionalData" ],
272             },
273              
274             "CorrelationAlert" => {
275             CHILDREN => [ "1name", "*alertident" ],
276             },
277              
278             "OverflowAlert" => {
279             CHILDREN => [ "1program", "?size", "?buffer" ],
280             },
281              
282             "ToolAlert" => {
283             NODES => [ "1name", "?command", "+alertident" ],
284             },
285              
286             #
287             # Additional Data
288             #
289              
290             "AdditionalData" => {
291             ATTRIBUTES => { "type" => ["string", "boolean", "byte", "character", "date-time",
292             "integer", "ntpstamp", "portlist", "real", "xml"],
293             "meaning" => [],
294             },
295             CONTENT => ANY,
296             },
297              
298             #
299             # Elements related to identifying entities
300             #
301              
302             "Analyzer" => {
303             ATTRIBUTES => { "analyzerid" => [], "name" => [], "manufacturer" => [], "model" => [],
304             "version" => [], "class" => [], "ostype" => [],
305             "osversion" => [],
306             },
307             CHILDREN => [ "?Node", "?Process" ],
308             # ALERT! ignore ?Analyzer node: the DTD parser does not support recursive nodes
309             },
310            
311             "Source" => {
312             ATTRIBUTES => { "ident" => [], "interface" => [],
313             "spoofed" => ["unknown", "yes", "no"] },
314             CHILDREN => [ "?Node", "?User", "?Process", "?Service" ],
315             },
316              
317             "Target" => {
318             ATTRIBUTES => { "ident" => [], "decoy" => ["unknown","yes","no"], "interface" => [] },
319             CHILDREN => [ "?Node", "?User", "?Process", "?Service", "?FileList" ],
320             },
321              
322             #
323             # Support elements used for providing info about entities
324             #
325              
326             "Address" => {
327             ATTRIBUTES => { "ident" => [], "vlan-num" => [], "vlan-name" => [],
328             "category" => [ "unknown", "atm", "e-mail", "lotus-notes", "mac", "sna",
329             "vm", "ipv4-addr", "ipv4-addr-hex", "ipv4-net", "ipv4-net-mask",
330             "ipv6-addr", "ipv6-addr-hex", "ipv6-net", "ipv6-net-mask" ],
331             },
332             CHILDREN => [ "1address", "?netmask" ],
333             },
334              
335             "Assessment" => {
336             CHILDREN => [ "?Impact", "*Action", "?Confidence" ],
337             },
338              
339             "Classification" => {
340             ATTRIBUTES => { "ident" => [], "text" => [] },
341             CHILDREN => [ "*Reference" ],
342             },
343              
344             "Reference" => {
345             ATTRIBUTES => { "origin" => ["unknown", "vendor-specific", "user-specific", "bugtraqid", "cve", "osvdb" ], "meaning" => [] },
346             CHILDREN => [ "1name", "1url" ],
347             },
348            
349             "File" => {
350             ATTRIBUTES => { "ident" => [], "category" => ["current","original"], "fstype" => ["ufs", "efs", "nfs", "afs" ,"ntfs" ,"fat16", "fat32", "pcfs", "joliet", "iso9660" ] },
351             CHILDREN => [ "1name", "1path", "?create-time", "?modify-time", "?access-time", "?data-size",
352             "?disk-size", "*FileAccess", "*Linkage", "?Inode", "*Checksum" ],
353             },
354              
355             "Checksum" => {
356             ATTRIBUTES => { "algorithm" => [ "MD4", "MD5", "SHA1", "SHA2-256", "SHA2-384", "SHA2-512", "CRC-32", "Haval", "Tiger", "Gost" ] },
357             CHILDREN => [ "1value", "?key" ],
358             },
359            
360             "FileAccess" => {
361             CHILDREN => [ "1UserId", "+permission" ],
362             },
363            
364             "FileList" => {
365             CHILDREN => [ "+File" ],
366             },
367              
368             "Inode" => {
369             CHILDREN => ["?change-time", "?number", "?major-device", "?minor-device", "?c-major-device",
370             "?c-minor-device"],
371             },
372            
373             "Linkage" => {
374             ATTRIBUTES => { "category" => ["hard-link", "mount-point", "reparse-point", "shortcut",
375             "stream", "symbolic-link"] },
376             CHILDREN => [ "1name", "1path" ],
377             # ALERT! ignore File node: the DTD parser does not support recursive nodes
378             },
379              
380             "Node" => {
381             ATTRIBUTES => { "category" => [ "unknown", "ads", "afs", "coda", "dfs", "dns", "hosts",
382             "kerberos", "nds", "nis", "nisplus", "nt", "wfw"],
383             "ident" => [],
384             },
385             CHILDREN => [ "?location", "?name", "*Address" ],
386             },
387              
388             "Process" => {
389             ATTRIBUTES => { "ident" => [] },
390             CHILDREN => [ "1name", "?pid", "?path", "*arg", "*env" ],
391             },
392            
393             "Service" => {
394             ATTRIBUTES => { "ident" => [], "ip_version" => [], "iana_protocol_number" => [], "iana_protocol_name" => [] },
395             CHILDREN => [ "?name", "?port", "?portlist", "?protocol", "?SNMPService", "?WebService" ],
396             },
397              
398             "SNMPService" => {
399             CHILDREN => [ "?oid", "?community", "?securityName", "?contextName",
400             "?contextEngineID", "?command" ],
401             },
402            
403             "User" => {
404             ATTRIBUTES => { "ident" => [], "category" => ["unknown", "application", "os-device"] },
405             CHILDREN => [ "+UserId" ],
406             },
407              
408             "UserId" => {
409             ATTRIBUTES => { "ident" => [],
410             "type" => [ "current-user", "original-user", "target-user", "user-privs",
411             "current-group", "group-privs", "other-privs" ],
412             },
413             CHILDREN => [ "?name", "?number" ],
414             },
415            
416             "WebService" => {
417             CHILDREN => [ "1url", "?cgi", "?http-method", "*arg" ],
418             },
419              
420             #
421             # Simple elements with sub elements or attributes of a special nature
422             #
423              
424             "Action" => {
425             ATTRIBUTES => { "category" => ["block-installed", "notification-sent", "taken-offline", "other"] },
426             CONTENT => PCDATA,
427             },
428            
429             "AnalyzerTime" => {
430             ATTRIBUTES => { "ntpstamp" => [] },
431             CONTENT => PCDATA,
432             },
433            
434             "Confidence" => {
435             ATTRIBUTES => { "rating" => ["low", "medium", "high", "numeric"] },
436             CONTENT => PCDATA,
437             },
438            
439             "CreateTime" => {
440             ATTRIBUTES => { "ntpstamp" => [] },
441             CONTENT => PCDATA,
442             },
443            
444             "DetectTime" => {
445             ATTRIBUTES => { "ntpstamp" => [] },
446             CONTENT => PCDATA,
447             },
448              
449             "Impact" => {
450             ATTRIBUTES => { "severity" => ["info", "low", "medium", "high"],
451             "completion" => ["failed", "succeeded"],
452             "type" => ["admin", "dos", "file", "recon", "user", "other"],
453             },
454             CONTENT => PCDATA,
455             },
456              
457             "alertident" => {
458             ATTRIBUTES => { "analyzerid" => [] },
459             CONTENT => PCDATA,
460             },
461              
462             #
463             # Simple elements with no sub-elements and no attributes
464             #
465            
466             "access-time" => { CONTENT => PCDATA },
467             "address" => { CONTENT => PCDATA },
468             "arg" => { CONTENT => PCDATA },
469             "buffer" => { CONTENT => PCDATA },
470             "c-major-device" => { CONTENT => PCDATA },
471             "c-minor-device" => { CONTENT => PCDATA },
472             "cgi" => { CONTENT => PCDATA },
473             "change-time" => { CONTENT => PCDATA },
474             "command" => { CONTENT => PCDATA },
475             "community" => { CONTENT => PCDATA },
476             "create-time" => { CONTENT => PCDATA },
477             "data-size" => { CONTENT => PCDATA },
478             "disk-size" => { CONTENT => PCDATA },
479             "env" => { CONTENT => PCDATA },
480             "http-method" => { CONTENT => PCDATA },
481             "location" => { CONTENT => PCDATA },
482             "major-device" => { CONTENT => PCDATA },
483             "minor-device" => { CONTENT => PCDATA },
484             "modify-time" => { CONTENT => PCDATA },
485             "name" => { CONTENT => PCDATA },
486             "netmask" => { CONTENT => PCDATA },
487             "number" => { CONTENT => PCDATA },
488             "oid" => { CONTENT => PCDATA },
489             "path" => { CONTENT => PCDATA },
490             "permission" => { CONTENT => PCDATA },
491             "pid" => { CONTENT => PCDATA },
492             "port" => { CONTENT => PCDATA },
493             "portlist" => { CONTENT => PCDATA },
494             "program" => { CONTENT => PCDATA },
495             "protocol" => { CONTENT => PCDATA },
496             "size" => { CONTENT => PCDATA },
497             "url" => { CONTENT => PCDATA },
498             "value" => { CONTENT => PCDATA },
499             "key" => { CONTENT => PCDATA },
500              
501             #
502             # Not defined in IDMEF DTD
503             #
504              
505             "securityName" => { CONTENT => PCDATA },
506             "contextName" => { CONTENT => PCDATA },
507             "contextEngineID" => { CONTENT => PCDATA },
508             };
509              
510              
511              
512              
513             ##--------------------------------------------------------------------------------
514             ##
515             ##
516             ## CLASS METHODS:
517             ## --------------
518             ##
519             ##
520             ##--------------------------------------------------------------------------------
521              
522              
523              
524              
525             ##================================================================================
526             ##
527             ## XML PSEUDO DTD LOADER
528             ##
529             ##================================================================================
530             ##
531             ## Below is the generic code for loading a pseudo DTD representation of an XML
532             ## DTD into structures optimised for internal usage.
533             ##
534              
535              
536              
537             # $EXPAND_PATH is a hash table linking an idmef tag path to the corresponding list
538             # of arguments needed to add a value at this path with the add() call.
539             # each key is a tagpath to a given IDMEF field, as given to the 'add()' call.
540             # each corresponding value is an array containing the list of tags in the
541             # tagpath, preceded by 2 values. The first one is 'A' if the pointed field is
542             # an attribute, 'C' if it is a content, 'N' if it is just a node. Notice that
543             # a C path is a N path.
544             # ex:
545             # 'AlertTargetUserUserIdident' => [ A, "Alert", "Target", "User", "UserId", "ident"],
546             # 'AlertTargetUserUserIdtype' => [ A, "Alert", "Target", "User", "UserId", "type"],
547             # 'AlertTargetUserUserIdname' => [ C, "Alert", "Target", "User", "UserId", "name"],
548             # 'AlertTargetUserUserIdnumber' => [ C, "Alert", "Target", "User", "UserId", "number"],
549             # 'AlertTargetUserUserId' => [ N, "Alert", "Target", "User", "UserId"],
550              
551             my $EXPAND_PATH = {};
552              
553              
554             # hash of the tagpaths for which the values can only take a limited set of values
555             # which can be checked with check_allowed. each key is a tagpath, each value is
556             # an array of the corresponding allowed values.
557             #
558             # ex:
559             # 'OverflowAlertAssessmentImpactcompletion' => [ 'failed', 'succeeded' ],
560             #
561              
562             my $CHECK_VALUE = {};
563              
564              
565             # a counter used by create_ident's unique id generator
566             #
567              
568             my $ID_COUNT = 0;
569              
570              
571              
572              
573              
574             #
575             # Internal variables describing the DTD in use
576             # --------------------------------------------
577             #
578             # This variables are to be initiated by a serie
579             # of api calls, listed below.
580              
581             my $DTD = undef;
582             my $ROOT = undef;
583              
584              
585             #
586             # xml declaration
587             #
588              
589             my $XML_DECL_VER = "1.0";
590             my $XML_DECL_ENC = "UTF-8";
591              
592              
593             #
594             # IDMEF DTD declaration
595             #
596              
597             my $DOCTYPE_NAME = "IDMEF-Message";
598             my $DOCTYPE_SYSID = "idmef-message.dtd";
599             my $DOCTYPE_PUBID = "-//IETF//DTD RFC XXXX IDMEF v1.0//EN";
600              
601              
602              
603             ##----------------------------------------------------------------------------------------
604             ##
605             ## set_doctype_name()
606             ## set_doctype_sysid()
607             ## set_doctype_pubid()
608             ##
609              
610             sub set_doctype_name { $DOCTYPE_NAME = shift; }
611             sub set_doctype_sysid { $DOCTYPE_SYSID = shift; }
612             sub set_doctype_pubid { $DOCTYPE_PUBID = shift; }
613              
614              
615              
616             ##----------------------------------------------------------------------------------------
617             ##
618             ## extend_dtd($DTD_extension, "new_root_class")
619             ##
620             ## ARGS:
621             ## $DTD_extension a DTD hash, as described in the source doc above.
622             ## "new_root_class" the name of a new root class
623             ##
624             ## RETURN:
625             ## This function can be used to extend IDMEF by adding a new
626             ## root class definition to the original IDMEF DTD.
627             ## $DTD_extension is a DTD hash, as defined above, providing definitions
628             ## for all the new IDMEF classes introduced by the extension, including
629             ## the one for the new root class.
630             ## "new_root_class" is the name of the root node of the IDMEF extension.
631             ## From now on, the usual IDMEF calls ('in', 'add', 'to_hash'...) can be
632             ## used to create/parse extended messages as well.
633             ##
634             ## To extend IDMEF, use extend_dtd(, "IDMEF-Message")
635             ## To load a new DTD, extend_dtd(, "new root") + call set_doctype_*
636             ##
637              
638             sub extend_dtd {
639             my($dtd, $name) = @_;
640              
641             $name = "IDMEF-Message" if (!defined($name));
642            
643             foreach my $k (keys(%{$dtd})) {
644             $IDMEF_DTD->{$k} = $dtd->{$k};
645             }
646              
647             load_xml_dtd($IDMEF_DTD, $name);
648             }
649              
650              
651              
652             ##----------------------------------------------------------------------------------------
653             ##
654             ## load_xml_dtd(, )
655             ##
656             ## ARGS:
657             ## a DTD hash
658             ## the name (string) of the DTD's root class
659             ##
660             ## RETURN:
661             ## This is the DTD parser used to load the IDMEF DTD in the DTD
662             ## engine at startup.
663             ## This function parses the DTD entity list as defined
664             ## through the hash and builds the xml class tree of
665             ## the root node .
666             ##
667             ## EX:
668             ## # load the IDMEF DTD at startup
669             ## load_xml_dtd($IDMEF_DTD, "IDMEF-Message");
670             ##
671              
672             sub load_xml_dtd {
673             my($dtd, $root) = @_;
674              
675             defined($dtd)
676             || croak "XML::IDMEF - load_xml_dtd: received a null ref in place of DTD hash.";
677             defined($root)
678             || croak "XML::IDMEF - load_xml_dtd: received a null ref in place of DTD root name.";
679             exists($dtd->{$root})
680             || croak "XML::IDMEF - load_xml_dtd: the root entity \'$root\' is not defined in the DTD hash.";
681              
682             my $err = check_xml_dtd($dtd, $root);
683             croak "XML::IDMEF - load_xml_dtd: $err errors in the pseudo DTD. dying."
684             if ($err > 0);
685              
686             # everything fine, accept DTD
687             $DTD = $dtd;
688             $ROOT = $root;
689            
690             fill_internal_hashes($DTD, "1".$ROOT);
691              
692             return 0;
693             }
694              
695              
696              
697             #----------------------------------------------------------------------------------------
698             #
699             # fill_internal_hashes(, [, @path])
700             #
701             # build the EXPAND_PATH and CHECK_VALUE hashes.
702             # it works recursively, and @path is the path of tags
703             # of where we currently are in the xml tree.
704             #
705              
706             sub fill_internal_hashes {
707             my($dtd, $name, @path) = @_;
708             my($node, $k, $v, $type, $att, $kid, $vals);
709              
710             $node = $dtd->{substr($name,1)};
711             $k = join '', map({substr $_, 1} @path, $name);
712              
713             # add node too EXPAND_PATH, as a node or content
714             if (exists($node->{CONTENT})) {
715             $EXPAND_PATH->{$k} = ['C', @path, $name];
716             } else {
717             $EXPAND_PATH->{$k} = ['N', @path, $name];
718             }
719              
720             # does it have attributes? if so, add them.
721             if (exists($node->{ATTRIBUTES})) {
722             foreach $att (keys %{$node->{ATTRIBUTES}}) {
723             $EXPAND_PATH->{$k.$att} = ['A', @path, $name, $att];
724            
725             # fill CHECK_VALUE hash
726             $vals = $node->{ATTRIBUTES}->{$att};
727             $CHECK_VALUE->{$k.$att} = $vals
728             if ((scalar @{$vals}) > 0);
729             }
730             }
731              
732             # does it have children elements? if so, add them.
733             if (exists($node->{CHILDREN})) {
734             foreach $kid (@{$node->{CHILDREN}}) {
735             fill_internal_hashes($dtd, $kid, @path, $name);
736             }
737             }
738              
739             return 0;
740             }
741              
742              
743              
744             #----------------------------------------------------------------------------------------
745             #
746             # check_xml_dtd(, )
747             #
748             # internal function, called by load_xml_dtd to validate the pseudo DTD's
749             # syntax. recursive function. log errors to stdout.
750             #
751             # return 0 if no error found, a positive number (error count) if errors found.
752             # If error found, the module should croak.
753             #
754              
755             sub check_xml_dtd {
756             my($dtd, $name) = @_;
757             my($ent, $code, $child);
758             my $ret = 0;
759              
760             # check if entity is defined in pseudo-dtd
761             if (!exists($dtd->{$name})) {
762             print "XML::IDMEF - check_xml_dtd: entity \'$name\' is not defined in the pseudo DTD.\n";
763             return 1;
764             }
765              
766             $ent = $dtd->{$name};
767              
768             # check entity content code
769             if (exists($ent->{CONTENT})) {
770             $code = $ent->{CONTENT};
771             if ($code ne PCDATA && $code ne ANY && $code ne EMPTY) {
772             print "XML::IDMEF - check_xml_dtd: entity \'$name\' does not have a valid content.\n";
773             $ret++;
774             }
775             }
776              
777             # check each child of this entity
778             if (exists($ent->{CHILDREN})) {
779             $code = $ent->{CHILDREN};
780             foreach $child (@{$code}) {
781            
782             # check that children starts with occurence code
783             if (index("?*+1#", substr($child,0,1)) == -1) {
784             print "XML::IDMEF - check_xml_dtd: children \'$child\' of entity \'$name\' does not have a proper occurence code.\n";
785             $ret++;
786             } else {
787             # check children's validity
788             $ret += check_xml_dtd($dtd, substr($child,1));
789             }
790             }
791             }
792            
793             return $ret;
794             }
795              
796              
797              
798             ##--------------------------------------------------------------------------------
799             ##
800             ## MODULE LOAD TIME INITIALISATION
801             ##
802             ##--------------------------------------------------------------------------------
803              
804             # DTD engine initialization:
805             # load the IDMEF root classes: Alert & Heartbeat, and build the intermediary
806             # structures representing the DTD (EXPAND_PATH & CHECK_VALUE) used by API calls
807             # such as add().
808             load_xml_dtd($IDMEF_DTD, "IDMEF-Message");
809              
810              
811              
812             # return true to package loader
813             1;
814              
815              
816              
817              
818              
819             ##--------------------------------------------------------------------------------
820             ##
821             ##
822             ## OBJECT METHODS:
823             ## ---------------
824             ##
825             ##
826             ##--------------------------------------------------------------------------------
827              
828              
829              
830             ##----------------------------------------------------------------------------------------
831             ##
832             ## new IDMEF()
833             ##
834             ## RETURN
835             ## a new empty IDMEF message, with initiated doctype and xml declaration
836             ## as well as root element and IDMEF version tag.
837             ##
838             ## DESC
839             ## create a new empty idmef message
840             ##
841             ## EXAMPLES:
842             ## $idmef = new XML::IDMEF();
843             ##
844              
845             sub new {
846             my($idmef, $doc, $x);
847              
848             $idmef = {};
849             bless($idmef, "XML::IDMEF");
850              
851             $doc = new XML::DOM::Document();
852              
853             $x = $doc->createDocumentType($DOCTYPE_NAME, $DOCTYPE_SYSID, $DOCTYPE_PUBID);
854             $doc->setDoctype($x);
855            
856             $x = $doc->createXMLDecl($XML_DECL_VER, $XML_DECL_ENC);
857             $doc->setXMLDecl($x);
858            
859             $idmef->{"DOM"} = $doc;
860              
861             # $idmef->add("", $IDMEF_VERSION);
862              
863             return $idmef;
864             }
865              
866              
867              
868             ##----------------------------------------------------------------------------------------
869             ##
870             ## in(, )
871             ##
872             ## ARGS:
873             ## idmef object
874             ## can be either a path to an IDMEF file to load, or an IDMEF string.
875             ## if it is an empty string, a new empty IDMEF message is created.
876             ## RETURN:
877             ## a hash to the loaded IDMEF message
878             ##
879             ## DESC:
880             ## loads an idmef message into an IDMEF container (a hash with XML::Simple syntax)
881             ## the input can either be a string, a file or an empty string. if the parsed IDMEF
882             ## message does not include an XML or DOCTYPE declaration, it will be added, assuming
883             ## IDMEF v1.0 as the default.
884             ##
885             ## EXAMPLES:
886             ## my $idmef = (new XML::IDMEF)->in("/home/user/idmef.xml");
887             ## $idmef = $idmef->in("");
888             ##
889              
890             sub in {
891             my($idmef, $arg) = @_;
892             my($doc, $parser, $x);
893              
894             # if no param, create empty XML::IDMEF doc
895             return new XML::IDMEF if (!defined($idmef));
896             return new XML::IDMEF if (!defined($arg));
897              
898             # parse IDMEF string or file
899             $parser = XML::DOM::Parser->new;
900            
901             # is $arg an idmef string or a filepath? test if it starts with <
902             $arg =~ / *(.)/;
903             if ($1 eq "<") {
904             $doc = $parser->parse($arg);
905             } else {
906             $doc = $parser->parsefile($arg);
907             }
908              
909             # check that the document has a DOCTYPE and an XML declaration
910             if (!defined($doc->getDoctype())) {
911             $x = $doc->createDocumentType($DOCTYPE_NAME, $DOCTYPE_SYSID, $DOCTYPE_PUBID);
912             $doc->setDoctype($x);
913             }
914            
915             if (!defined($doc->getXMLDecl())) {
916             $x = $doc->createXMLDecl($XML_DECL_VER, $XML_DECL_ENC);
917             $doc->setXMLDecl($x);
918             }
919              
920             $idmef->{"DOM"} = $doc;
921              
922             return $idmef;
923             }
924              
925              
926              
927             ##----------------------------------------------------------------------------------------
928             ##
929             ## out()
930             ##
931             ## ARGS:
932             ## an XML::IDMEF object
933             ##
934             ## RETURN:
935             ## a string containing the corresponding IDMEF message
936             ##
937             ## EXAMPLES:
938             ## $string = $idmef->out();
939             ##
940              
941             sub out {
942             my $idmef = shift;
943             return $idmef->{"DOM"}->toString;
944             }
945              
946              
947              
948             ##----------------------------------------------------------------------------------------
949             ##
950             ## get_root()
951             ##
952             ## ARGS:
953             ## an XML::IDMEF object
954             ##
955             ## RETURN:
956             ## a string representing the name of the root element of the IDMEF message,
957             ## normally "IDMEF-Message", or undef if no root element defined.
958             ##
959             ## EXAMPLES:
960             ## $idmef = new XML::IDMEF();
961             ## $idmef->add("Alertimpact", "7");
962             ## $root = $idmef->get_root(); # $type now contains the string "IDMEF-Message"
963             ##
964              
965             sub get_root {
966             my $idmef = shift;
967            
968             my $c = $idmef->{"DOM"}->getDocumentElement();
969             return $c->getTagName()
970             if (defined($c));
971              
972             return undef;
973             }
974              
975              
976              
977             ##----------------------------------------------------------------------------------------
978             ##
979             ## get_type()
980             ##
981             ## ARGS:
982             ## an XML::IDMEF object
983             ##
984             ## RETURN:
985             ## a string representing the type of IDMEF message ("Alert", "Heartbeat"...)
986             ## or undef if this message does not have a type yet.
987             ##
988             ## EXAMPLES:
989             ## $idmef = new XML::IDMEF();
990             ## $idmef->add("Alertimpact", "7");
991             ## $type = $idmef->get_type(); # $type now contains the string "Alert"
992             ##
993              
994             sub get_type {
995             my $idmef = shift;
996            
997             my $c = $idmef->{"DOM"}->getDocumentElement();
998             return undef
999             if (!defined($c));
1000              
1001             foreach my $n ($c->getChildNodes()) {
1002             return $n->getTagName()
1003             if ($n->getNodeType() == ELEMENT_NODE);
1004             }
1005              
1006             return undef;
1007             }
1008              
1009              
1010              
1011             ##----------------------------------------------------------------------------------------
1012             ##
1013             ## contains(, )
1014             ##
1015             ## ARGS:
1016             ## idmef: a hash representation of an IDMEF message, as received from new or in
1017             ## tagpath: a string obtained by concatenating the names of the nested tags, from the
1018             ## Alert tag down to the closest tag to value.
1019             ##
1020             ## RETURN:
1021             ## 1 if there is at least one value set to the particular tagpath.
1022             ## 0 otherwise.
1023             ##
1024              
1025             sub contains {
1026             my($idmef, $path) = @_;
1027             my($type, @tagpath, $dom, $att, $n);
1028              
1029             $path = $ROOT.$path;
1030             $dom = $idmef->{"DOM"}->getDocumentElement;
1031              
1032             return 0 if (!defined $dom);
1033              
1034             return 0 if (!exists($EXPAND_PATH->{$path}));
1035              
1036             ($type, @tagpath) = @{$EXPAND_PATH->{$path}};
1037              
1038             $att = pop @tagpath
1039             if ($type eq 'A');
1040              
1041             if ($type eq 'N' or $type eq 'C') {
1042             defined(find_node($dom, @tagpath)) ? return 1 : return 0;
1043              
1044             } elsif ($type eq 'A') {
1045             $n = find_node($dom, @tagpath);
1046             return 0 if (!defined($n));
1047             ($n->getAttribute($att) ne "") ? return 1 : return 0;
1048             }
1049              
1050             croak "contains: internal error. found element of type $type.";
1051             }
1052              
1053              
1054              
1055             #----------------------------------------------------------------------------------------
1056             #
1057             # find_node($node, @tagpath)
1058             #
1059             # return the last node in @tagpath if @tagpath exists in $dom,
1060             # return undef otherwise
1061             # @tagpath are the name of DOM::Elements inside $dom. no attribute.
1062             # tagpath starts at the root (IDMEF-Message)
1063             # if the tagpath occurs multiple times, return the first occurence of it.
1064             #
1065              
1066             sub find_node {
1067             my($node, @tagpath) = @_;
1068             my($name, $n, $m);
1069            
1070             $name = substr(shift(@tagpath), 1);
1071              
1072             if ($node->getTagName() eq $name) {
1073              
1074             return $node
1075             if ((scalar @tagpath) == 0);
1076            
1077             foreach $n ($node->getChildNodes()) {
1078             if ($n->getNodeType() == ELEMENT_NODE) {
1079             $m = find_node($n, @tagpath);
1080             if (defined($m)) {
1081             return $m;
1082             }
1083             }
1084             }
1085             }
1086            
1087             return undef;
1088             }
1089              
1090              
1091              
1092              
1093             #----------------------------------------------------------------------------------------
1094             #
1095             # find_node_in_first_path($node, @tagpath)
1096             #
1097             # similar to find_node(), but look only through the first
1098             # occurence of the tagpath. the node may hence exists somewhere else.
1099             # return the last node in @tagpath if @tagpath exists in $dom,
1100             # return undef otherwise
1101             #
1102              
1103             sub find_node_in_first_path {
1104             my($node, @tagpath) = @_;
1105             my($tag, $name, $n, $next);
1106              
1107             $name = substr(shift @tagpath, 1);
1108              
1109             return undef
1110             if ($node->getTagName() ne $name);
1111              
1112             foreach $tag (@tagpath) {
1113             $name = substr($tag, 1);
1114              
1115             # find a child with right name
1116             $next = undef;
1117             foreach $n ($node->getChildNodes()) {
1118             if ($n->getNodeType() == ELEMENT_NODE and $n->getTagName() eq $name) {
1119             $next = $n;
1120             last;
1121             }
1122             }
1123            
1124             # next child not found
1125             return undef
1126             if (!defined($next));
1127            
1128             $node = $next;
1129             }
1130              
1131             return $node;
1132             }
1133              
1134              
1135              
1136             ##----------------------------------------------------------------------------------------
1137             ##
1138             ## add(hash, tagpath, value)
1139             ##
1140             ## ARGS:
1141             ## hash: a hash representation of an IDMEF message, as received from new or in
1142             ## tagpath: a string obtained by concatenating the names of the nested tags, from the
1143             ## Alert tag down to the closest tag to value.
1144             ## value: the value (content of a tag, or value of an attribute) of the last tag
1145             ## given in tagpath
1146             ##
1147             ## RETURN:
1148             ## 0 if the field was correctly added, and croak otherwise (if you did
1149             ## something that goes against the DTD).
1150             ##
1151             ## DESC:
1152             ## Each IDMEF field of a given IDMEF message can be created through a corresponding add()
1153             ## call. These interfaces are designed for easily building a new IDMEF message while
1154             ## parsing a log file. The 'tagpath' is the same as returned by the 'to_hash' call.
1155             ##
1156             ## RESTRICTIONS:
1157             ## You cannot change an attribute value with add(). An attempt to run add() on an attribute
1158             ## that already exists will just be ignored. Contents cannot be changed either, but a new
1159             ## tag can be created if you are adding an idmef content that can occur multiple time (ex:
1160             ## UserIdname, AdditionalData...).
1161             ##
1162             ## SPECIAL CASE: AdditionalData
1163             ## AdditionalData is a special tag requiring at least 2 add() calls to build a valid node. In
1164             ## case of multiple AdditionalData delaration, take care of building AdditionalData nodes one
1165             ## at a time, and always begin by adding the "AddtitionalData" field (ie the tag's content).
1166             ## Otherwise, the idmef key insertion engine will get lost, and you'll get scrap.
1167             ##
1168             ## As a response to this issue, the 'add("AlertAdditionalData", "value")' call accepts an
1169             ## extended syntax compared with other calls:
1170             ##
1171             ## add("AlertAdditionalData", );
1172             ## => add the content to Alert/AdditionalData
1173             ##
1174             ## add("AlertAdditionalData", , );
1175             ## => same as: (type string is assumed by default)
1176             ## add("AlertAdditionalData", );
1177             ## add("AlertAdditionalDatameaning", );
1178             ## add("AlertAdditionalDatatype", "string");
1179             ##
1180             ## add("AlertAdditionalData", , , );
1181             ## => same as:
1182             ## add("AlertAdditionalData", );
1183             ## add("AlertAdditionalDatameaning", );
1184             ## add("AlertAdditionalDatatype", );
1185             ##
1186             ## The use of add("AlertAdditionalData", , , ); is prefered to the simple
1187             ## add call, since it creates the whole AdditionalData node at once. In the case of
1188             ## multiple arguments add("AlertAdditionalData"...), the returned value is 1 if the type key
1189             ## was inserted, 0 otherwise.
1190             ##
1191             ##
1192             ## EXAMPLES:
1193             ##
1194             ## my $idmef = new XML::IDMEF();
1195             ##
1196             ## $idmef->add("Alertimpact", "");
1197             ##
1198             ## $idmef->add($idmef, "AlertTargetUserUserIdname", "");
1199             ##
1200             ## # AdditionalData case:
1201             ## # DO:
1202             ## $idmef->add("AlertAdditionalData", "value"); # content add first
1203             ## $idmef->add("AlertAdditionalDatatype", "string"); # ok
1204             ## $idmef->add("AlertAdditionalDatameaning", "meaning"); # ok
1205             ##
1206             ## $idmef->add("AlertAdditionalData", "value2"); # content add first
1207             ## $idmef->add("AlertAdditionalDatatype", "string"); # ok
1208             ## $idmef->add("AlertAdditionalDatameaning", "meaning2"); # ok
1209             ##
1210             ## # or BETTER:
1211             ##
1212             ## $idmef->add("AlertAdditionalData", "value", "meaning", "string"); # VERY GOOD
1213             ## $idmef->add("AlertAdditionalData", "value2", "meaning2"); # VERY GOOD (string type is default)
1214             ##
1215             ##
1216             ## # DO NOT DO:
1217             ## $idmef->add("AlertAdditionalData", "value"); # BAD!! content should be declared first
1218             ## $idmef->add("AlertAdditionalDatameaning", "meaning2"); # BAD!! content first!
1219             ##
1220             ## # DO NOT DO:
1221             ## $idmef->add("AlertAdditionalData", "value"); # BAD!!!!! mixing node declarations
1222             ## $idmef->add("AlertAdditionalData", "value2"); # BAD!!!!! for value & value2
1223             ## $idmef->add("AlertAdditionalDatatype", "string"); # BAD!!!!!
1224             ## $idmef->add("AlertAdditionalDatatype", "string"); # BAD!!!!!
1225             ##
1226             ##
1227              
1228             sub add {
1229             my ($tag, $root, $dom, $c);
1230             my ($idmef, $path, $value, @tail) = @_;
1231              
1232             $path = $ROOT.$path;
1233             $dom = $idmef->{"DOM"};
1234             $root = $dom->getDocumentElement;
1235              
1236             # create a root element if none exists
1237             if (!defined $root) {
1238             $root = $dom->createElement($ROOT);
1239             $dom->appendChild($root);
1240             }
1241              
1242             # is this a known tagpath?
1243             if (!exists($EXPAND_PATH->{$path})) {
1244             croak "add: $path is not a known IDMEF tag path (IDMEF v$IDMEF_VERSION).";
1245             }
1246              
1247             # if it is an attribute or a content, did we get a value?
1248             $c = ${$EXPAND_PATH->{$path}}[0];
1249             croak "add: $path is an attribute or a content and requires a value (which you did not give)."
1250             if (($c eq 'A' or $c eq 'C') and !defined($value));
1251              
1252             # check if value is valid
1253             if (exists($CHECK_VALUE->{$path})) {
1254             check_allowed($path, $value, @{$CHECK_VALUE->{$path}});
1255             }
1256              
1257             # add key to path
1258             $tag = @{$EXPAND_PATH->{$path}}[3];
1259              
1260             # check if it is AdditionalData
1261             if (defined($tag) && substr($tag,1) eq "AdditionalData") {
1262            
1263             if (scalar(@tail) == 0) {
1264             add_in_dom($dom, $root, $path, $value);
1265             } elsif (scalar(@tail) == 1) {
1266             add_in_dom($dom, $root, $path, $value);
1267             add_in_dom($dom, $root, $path."meaning", $tail[0]);
1268             add_in_dom($dom, $root, $path."type", "string");
1269             } elsif (scalar(@tail) == 2) {
1270             check_allowed($path."type", $tail[1], @{$CHECK_VALUE->{$path."type"}});
1271             add_in_dom($dom, $root, $path, $value);
1272             add_in_dom($dom, $root, $path."meaning", $tail[0]);
1273             add_in_dom($dom, $root, $path."type", $tail[1]);
1274             } else {
1275             croak "add: wrong number of arguments given to add(\"$path\")";
1276             }
1277             }
1278             else
1279             {
1280             add_in_dom($dom, $root, $path, $value);
1281             }
1282              
1283             return 0;
1284             }
1285              
1286              
1287              
1288             #----------------------------------------------------------------------------------------
1289             #
1290             # add_in_dom($root, $tagpath [, $value])
1291             #
1292             # if their is a value, add this value to the tagpath, otherwise add the
1293             # node pointed by tagpath. return the changed node.
1294             #
1295              
1296             sub add_in_dom {
1297             my($dom, $root, $path, $val) = @_;
1298             my($type, @tagpath, $att, $node, $text, $n);
1299              
1300             # find the tagpath corresponding to $path
1301             ($type, @tagpath) = @{$EXPAND_PATH->{$path}};
1302              
1303             if ($type eq 'N') {
1304             # we want to add a node
1305             $node = find_node_in_first_path($root, @tagpath);
1306              
1307             if (defined $node) {
1308             return duplicate_node_path($dom, $root, @tagpath);
1309             } else {
1310             return create_node_path($dom, $root, @tagpath);
1311             }
1312              
1313             } elsif ($type eq 'A') {
1314             # we want to add an attribute
1315             $att = pop @tagpath;
1316             $node = find_node_in_first_path($root, @tagpath);
1317              
1318             if (!defined $node) {
1319             $node = create_node_path($dom, $root, @tagpath);
1320             } else {
1321             # if attribute already set, try to duplicate node
1322             if ($node->getAttribute($att) ne "") {
1323             $node = duplicate_node_path($dom, $root, @tagpath);
1324             }
1325             }
1326              
1327             # add attribute
1328             $node->setAttribute($att, $val);
1329              
1330             return $node;
1331              
1332             } elsif ($type eq 'C') {
1333             # we want to add a content
1334             $node = find_node_in_first_path($root, @tagpath);
1335              
1336             # if node does not exists, create it
1337             if (!defined $node) {
1338             $node = create_node_path($dom, $root, @tagpath);
1339             }
1340              
1341            
1342             # find this node's Text node
1343             foreach $n ($node->getChildNodes()) {
1344             if ($n->getNodeType() == TEXT_NODE) {
1345             # node already has text child. duplicate node
1346             $n = duplicate_node_path($dom, $root, @tagpath);
1347             $node = $n;
1348             last;
1349             }
1350             }
1351              
1352             # found a node that does not have any text element. create text.
1353             $n = $dom->createTextNode($val);
1354             $node->appendChild($n);
1355              
1356             return $node;
1357             }
1358              
1359             croak "add_in_dom: internal error. found element of type $type.";
1360             }
1361              
1362              
1363              
1364             #----------------------------------------------------------------------------------------
1365             #
1366             # create_node_path($root, @tagpath)
1367             #
1368             # create all nodes in @tagpath, and return the last node in tagpath.
1369             # all nodes in @tagpath are elements.
1370             # create_node assumes that $root is a non null element, which usually
1371             # implies that the idmef dom document should have a root.
1372             #
1373              
1374             sub create_node_path {
1375             my($dom, $root, @tagpath) = @_;
1376             @tagpath = map({substr($_,1)} @tagpath);
1377             return create_node_internal($dom, $root, @tagpath);
1378             }
1379              
1380             sub create_node_internal {
1381             my($dom, $node, @tagpath) = @_;
1382             my($name_node, $name_next, @child_order, $i, $pos, $next_child, $pos2, $name, $new, @a, $n);
1383              
1384             $name_node = shift @tagpath;
1385             $name_next = shift @tagpath;
1386              
1387             croak "create_node: got empty tagpath."
1388             if (!defined $name_node);
1389              
1390             return undef
1391             if ($node->getTagName() ne $name_node);
1392              
1393             return $node
1394             if (!defined $name_next);
1395              
1396             # lookup children order for $name_node in DTD
1397             @child_order = @{$DTD->{$name_node}->{CHILDREN}};
1398             @child_order = map({substr $_, 1} @child_order);
1399              
1400             # this expression finds the offset in @children of the last occurence of $name_next
1401             for($pos=0, $i=0; $i < scalar(@child_order); $i++) {
1402             $pos = $i if ($child_order[$i] eq $name_next);
1403             }
1404              
1405             # go through all children, and insert new node before first following kid
1406             $next_child = undef;
1407              
1408             foreach $n ($node->getChildNodes()) {
1409              
1410             if ($n->getNodeType() == ELEMENT_NODE) {
1411             $name = $n->getTagName;
1412            
1413             # if we found the node we searched, loop in it
1414             if ($name eq $name_next) {
1415             return create_node_internal($dom, $n, $name_next, @tagpath);
1416             }
1417              
1418             # check if we found a node that should occur after the one to be inserted
1419             # if so, break the loop and create a new node before it
1420             for($pos2=0, $i=0; $i < scalar(@child_order); $i++) {
1421             if ($child_order[$i] eq $name) {
1422             $pos2 = $i;
1423             last;
1424             }
1425             }
1426              
1427             if ($pos2 > $pos) {
1428             $next_child = $n;
1429             last;
1430             }
1431             }
1432             }
1433              
1434             # create a new node and insert it at the right place
1435             $new = $dom->createElement($name_next);
1436             $node->insertBefore($new, $next_child);
1437              
1438             return create_node_internal($dom, $new, $name_next, @tagpath);
1439             }
1440              
1441              
1442              
1443             #----------------------------------------------------------------------------------------
1444             #
1445             # duplicate_node_path($dom, $root, @tagpath)
1446             #
1447             # duplicate the last node in @tagpath, ie
1448             # find the closest parent to that node that accepts multiple occurences
1449             # of node path, create a new instance of the node, and call create_node
1450             # to recreate all elements down to the node. return the duplicated node
1451             #
1452              
1453             sub duplicate_node_path {
1454             my($dom, $root, @node_path) = @_;
1455             my($name, $node, $new, $next, $array, @tail, $i, $c, @array);
1456              
1457             # find the closest parent of last node, having multiple occurences
1458             for ($i = (scalar @node_path) - 1; $i > 0; $i--) {
1459             last if ($node_path[$i] =~ /^[\+\#\*]/);
1460             }
1461              
1462             croak "add - duplicate_node: could not duplicate node".(pop @node_path).". no duplicable parent."
1463             if ($i == 0);
1464            
1465             # duplicate the node at $i-2 in @node_path
1466             @tail = splice(@node_path, $i+1);
1467             $name = pop @node_path;
1468              
1469             # try to find the node to duplicate
1470             $node = find_node($root, @node_path, $name) ||
1471             croak "duplicate_node_path: did not find node to duplicate. impossible.";
1472              
1473             # create new instance of 'name' and insert before $node
1474             $new = $dom->createElement(substr($name, 1));
1475             $node->getParentNode()->insertBefore($new, $node);
1476              
1477             # build all node path in the original @node_path, and return the last
1478             return create_node_path($dom, $root, @node_path, $name, @tail);
1479             }
1480              
1481              
1482              
1483             #----------------------------------------------------------------------------------------
1484             #
1485             # check_allowed(path, key, @list);
1486             #
1487             # check that key is one element of list.
1488             # returns 1 if it is, 0 if key is not in and
1489             # croak
1490             #
1491              
1492             sub check_allowed {
1493             my($path, $key, $v, @vals);
1494             ($path, $key, @vals)= @_;
1495              
1496             foreach $v (@vals) {
1497             return 1 if ($v eq $key);
1498             }
1499              
1500             croak "add: $key is not an allowed value for attribute $path (IDMEF v$IDMEF_VERSION).";
1501             return 0;
1502             }
1503              
1504              
1505              
1506             ##----------------------------------------------------------------------------------------
1507             ##
1508             ## set(hash, tagpath, value)
1509             ##
1510             ## ARGS:
1511             ## hash: a hash representation of an IDMEF message, as received from new or in
1512             ## tagpath: a string obtained by concatenating the names of the nested tags, from the
1513             ## Alert tag down to the closest tag to value.
1514             ## value: the value (content of a tag, or value of an attribute) of the last tag
1515             ## given in tagpath
1516             ##
1517             ## RETURN:
1518             ## 0 if the field was correctly changed, croaks otherwise.
1519             ##
1520             ## DESC:
1521             ## The set() call follows the first occurence of the node path described by
1522             ## and attempts at changing the corresponding content or attribute value.
1523             ## If the first occurence of does not lead to any existing node, set()
1524             ## croaks. Check that the node or attribute exists with contains() first.
1525             ## If you want to create an attribute value or a node content where there was none,
1526             ## use add() instead.
1527             ##
1528             ## RESTRICTIONS:
1529             ## set() only allows you to reach and change the attribute or content of the first
1530             ## occurence of a given tagpath. If this tagpath occurs multiple time, you will
1531             ## not be able to modify the other occurences. Yet this should be able for most
1532             ## applications. Furthermore, set() cannot be used to create any new value/content.
1533             ##
1534             ## EXAMPLES:
1535             ##
1536             ## my $idmef = new XML::IDMEF();
1537             ##
1538             ## $idmef->add("AlertAdditionalData", "value"); # content add first
1539             ## $idmef->add("AlertAdditionalDatatype", "string"); # ok
1540             ## $idmef->add("AlertAdditionalDatameaning", "meaning"); # ok
1541             ##
1542             ## # change AdditionalData's content value
1543             ## $idmef->set("AlertAdditionalData", "new value");
1544             ##
1545              
1546             sub set {
1547             my($idmef, $path, $value) = @_;
1548             my($root, $type, $att, @tagpath, $node, $n);
1549              
1550             # did we get a path?
1551             croak "set: you did not give any path."
1552             if (!defined($path));
1553              
1554             $path = $ROOT.$path;
1555             $root = $idmef->{"DOM"}->getDocumentElement;
1556              
1557             # is this a known tagpath?
1558             croak "set: $path is not a known IDMEF tag path (IDMEF v$IDMEF_VERSION)."
1559             if (!exists($EXPAND_PATH->{$path}));
1560            
1561             # is it a content or attribute?
1562             ($type, @tagpath) = @{$EXPAND_PATH->{$path}};
1563              
1564             croak "set: $path does not lead to an attribute nor to an authorized node content."
1565             if ($type eq 'N');
1566            
1567             # did we get a value?
1568             croak "set: you did not provide any value."
1569             if (!defined($value));
1570            
1571             # check if value is valid
1572             if (exists($CHECK_VALUE->{$path})) {
1573             check_allowed($path, $value, @{$CHECK_VALUE->{$path}});
1574             }
1575            
1576             $att = pop @tagpath
1577             if ($type eq 'A');
1578              
1579             $node = find_node($root, @tagpath);
1580              
1581             # if node does not exists, croaks
1582             croak "set: there is no node at path $path. use add() first."
1583             if (!defined($node));
1584              
1585             # let's change the content or attribute
1586             if ($type eq 'A') {
1587              
1588             # does the attribute exists?
1589             croak "set: the attribute at path $path has no value. use add() first."
1590             if ($node->getAttribute($att) eq "");
1591              
1592             # set its value
1593             $node->setAttribute($att, $value);
1594             return 0;
1595              
1596             } elsif ($type eq 'C') {
1597              
1598             # does this node has a text node?
1599             foreach $n ($node->getChildNodes()) {
1600             if ($n->getNodeType() == TEXT_NODE) {
1601             $n->setData($value);
1602             return 0;
1603             }
1604             }
1605              
1606             croak "set: the node at path $path has no content. use add() first.";
1607             }
1608            
1609             # should never reach here
1610             croak "set: internal error.";
1611             }
1612              
1613              
1614              
1615             ##----------------------------------------------------------------------------------------
1616             ##
1617             ## get(hash, tagpath, value)
1618             ##
1619             ## ARGS:
1620             ## hash: a hash representation of an IDMEF message, as received from new or in
1621             ## tagpath: a string obtained by concatenating the names of the nested tags, from the
1622             ## Alert tag down to the closest tag to value.
1623             ## value: the value (content of a tag, or value of an attribute) of the last tag
1624             ## given in tagpath
1625             ##
1626             ## RETURN:
1627             ## a string: the content of the node or value of the attribute, undef if there is
1628             ## no such value, and croaks if error.
1629             ##
1630             ## DESC:
1631             ## The get() call follows the first occurence of the node path described by
1632             ## and attempts at retrieving the corresponding content or attribute value.
1633             ## If the first occurence of does not lead to any existing node, get()
1634             ## returns undef. But this does not mean that the value does not exists in an other
1635             ## occurence of the pagpath.
1636             ##
1637             ## RESTRICTIONS:
1638             ## get() only allows you to reach and retrieve the attribute or content of the first
1639             ## occurence of a given tagpath. If this tagpath occurs multiple time, you will
1640             ## not be able to retrieve the other occurences. Yet this should be able for most
1641             ## applications.
1642             ##
1643             ## EXAMPLES:
1644             ##
1645             ## my $idmef = new XML::IDMEF();
1646             ##
1647             ## $idmef->add("AlertAdditionalData", "value"); # content add first
1648             ## $idmef->add("AlertAdditionalDatatype", "string"); # ok
1649             ## $idmef->add("AlertAdditionalDatameaning", "meaning"); # ok
1650             ##
1651             ## # change AdditionalData's content value
1652             ## $idmef->get("AlertAdditionalData");
1653             ##
1654              
1655             sub get {
1656             my($idmef, $path, $value) = @_;
1657             my($root, $type, $att, @tagpath, $node, $n);
1658              
1659             # did we get a path?
1660             croak "get: you did not give any path."
1661             if (!defined($path));
1662              
1663             $path = $ROOT.$path;
1664             $root = $idmef->{"DOM"}->getDocumentElement;
1665              
1666             # is this a known tagpath?
1667             croak "get: $path is not a known IDMEF tag path (IDMEF v$IDMEF_VERSION)."
1668             if (!exists($EXPAND_PATH->{$path}));
1669            
1670             # is it a content or attribute?
1671             ($type, @tagpath) = @{$EXPAND_PATH->{$path}};
1672              
1673             croak "get: $path does not lead to an attribute nor to an authorized node content."
1674             if ($type eq 'N');
1675            
1676             $att = pop @tagpath
1677             if ($type eq 'A');
1678              
1679             $node = find_node($root, @tagpath);
1680              
1681             # if node does not exists, return undef
1682             return undef
1683             if (!defined($node));
1684              
1685             # let's fetch the content or attribute
1686             if ($type eq 'A') {
1687             return $node->getAttribute($att);
1688              
1689             } elsif ($type eq 'C') {
1690              
1691             # does this node has a text node?
1692             foreach $n ($node->getChildNodes()) {
1693             if ($n->getNodeType() == TEXT_NODE) {
1694             return $n->getData;
1695             }
1696             }
1697              
1698             return undef;
1699             }
1700            
1701             # no content in this node
1702             return undef;
1703             }
1704              
1705              
1706              
1707             ##----------------------------------------------------------------------------------------
1708             ##
1709             ## create_ident()
1710             ##
1711             ## ARGS:
1712             ## idmef message object
1713             ##
1714             ## RETURN:
1715             ## nothing.
1716             ##
1717             ## DESC:
1718             ## Set the root ident attribute field of this IDMEF message with a unique,
1719             ## randomly generated ID number. The code for the ID number generator is actually
1720             ## inspired from Sys::UniqueID. If no IDMEF type is given, "Alert" is assumed as default.
1721             ##
1722              
1723             sub create_ident {
1724             my($id, $idmef, $name, $netaddr);
1725             $idmef = shift;
1726              
1727             $name = $idmef->get_type();
1728             $name = "Alert" if (!defined $name);
1729              
1730             # code cut n paste from Sys::UniqueID. replaced IP with random number.
1731             # absolutely ensure that id is unique: < 0x10000/second
1732             $netaddr = int(rand 10000000);
1733              
1734             unless(++$ID_COUNT < 0x10000) { sleep 1; $ID_COUNT= 0; }
1735             $id = sprintf '%012X%s%08X%04X', time, $netaddr, $$, $ID_COUNT;
1736              
1737             $idmef->add($name."messageid", $id);
1738             }
1739              
1740              
1741              
1742             ##----------------------------------------------------------------------------------------
1743             ##
1744             ## create_time(, [])
1745             ##
1746             ## ARGS:
1747             ## idmef message object
1748             ## optional. epoch time (time since January 1, 1970, UTC).
1749             ##
1750             ## RETURN:
1751             ## nothing.
1752             ##
1753             ## DESC:
1754             ## Set the CreateTime field of this idmef message with the current time
1755             ## (if no epoch argument if provided), or to the time corresponding to
1756             ## the epoch value provided, in both the content and ntpstamp fields.
1757             ## If the IDMEF message does not yet have a type, "Alert" is assumed by
1758             ## default.
1759             ##
1760              
1761             sub create_time {
1762             my $idmef = shift;
1763             my $utc = shift || time();
1764              
1765             my $name = $idmef->get_type();
1766             $name = "Alert" if (!defined $name);
1767              
1768             # add time stamp
1769             my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($utc);
1770             $year += 1900;
1771             $mon += 1;
1772              
1773             add($idmef,
1774             $name."CreateTime",
1775             sprintf("%04d-%02d-%02d-T%02d:%02d:%02dZ",
1776             $year, $mon, $mday, $hour, $min, $sec));
1777              
1778             # seconds between 1900-01-01 and 1970-01-01
1779             $utc = $utc + 2208988800;
1780             # translate utc to hex
1781             $utc = sprintf "%x", $utc;
1782              
1783             add($idmef, $name."CreateTimentpstamp", "0x$utc.0x0");
1784             }
1785              
1786              
1787              
1788             ##----------------------------------------------------------------------------------------
1789             ##
1790             ## to_hash()
1791             ##
1792             ## ARGS:
1793             ## hash containing an IDMEF message in XML::Simple representation
1794             ##
1795             ## RETURN:
1796             ## a hash enumerating all the contents and attributes of this IDMEF message.
1797             ## each key is a concatenated sequence of tags leading to the content/attribute,
1798             ## and the corresponding value is the content/attribute itself.
1799             ## all IDMEF contents and values are converted from IDMEF format (STRING or BYTE)
1800             ## back to the original ascii string.
1801             ##
1802             ## EXAMPLES:
1803             ##
1804             ##
1805             ##
1806             ##
1807             ##
1808             ## node2
1809             ##
1810             ##
1811             ## data1
1812             ## data2
1813             ##
1814             ##
1815             ##
1816             ## becomes:
1817             ##
1818             ## { "version" => [ "0.5" ],
1819             ## "Alertident" => [ "myalertidentity" ],
1820             ## "AlertTargetNodecategory" => [ "dns" ],
1821             ## "AlertTargetNodename" => [ "node2" ],
1822             ## "AlertAdditionalDatameaning" => [ "datatype1", "datatype2" ], #meaning & contents are
1823             ## "AlertAdditionalData" => [ "type1", "type2" ], #listed in same order
1824             ## }
1825             ##
1826             ##
1827              
1828             sub to_hash {
1829             my $idmef = shift;
1830             my $result = {};
1831             my $root = $idmef->{"DOM"}->getDocumentElement;
1832              
1833             dom_to_hash($root, $result, "");
1834              
1835             return $result;
1836             }
1837            
1838              
1839             #----------------------------------------------------------------------------------------
1840             #
1841             # dom_to_hash($node, $result, $path)
1842             #
1843             # explore node and add its attributes and content to $result, and
1844             # explore recursively each of node's children.
1845             #
1846              
1847             sub dom_to_hash {
1848             my($node, $result, $path) = @_;
1849             my($n, $type);
1850              
1851             return if (!defined($node));
1852              
1853             # explore node's attributes
1854             foreach $n ($node->getAttributes->getValues) {
1855             add_to_result($result, $path.$n->getName, $n->getValue);
1856             }
1857              
1858             # explore node's children
1859             foreach $n ($node->getChildNodes()) {
1860              
1861             $type = $n->getNodeType();
1862              
1863             if ($type == TEXT_NODE) {
1864             # first check if the DTD accepts content for this node
1865             # this is to avoid all the '\n' that DOM::Parser consider
1866             # as content.
1867             if (@{$EXPAND_PATH->{$ROOT.$path}}[0] eq 'C') {
1868             add_to_result($result, $path, $n->getData);
1869             }
1870             } elsif ($type == ELEMENT_NODE) {
1871             dom_to_hash($n, $result, $path.$n->getTagName);
1872             }
1873             }
1874             }
1875              
1876             sub add_to_result {
1877             my($result, $path, $val) = @_;
1878              
1879             if (exists($result->{$path})) {
1880             push @{$result->{$path}}, $val;
1881             } else {
1882             $result->{$path} = [ $val ];
1883             }
1884             }
1885              
1886              
1887              
1888             ##=========================================================================================
1889             ##
1890             ## BACKWARD COMPATIBILIY FUNCTIONS
1891             ##
1892             ##=========================================================================================
1893              
1894             ##
1895             ##
1896             ## CLASS FUNCTIONS:
1897             ## ----------------
1898             ##
1899              
1900             # wrapper for contains()
1901             sub at_least_one {
1902             return contains(@_);
1903             }
1904              
1905             ##
1906             ##
1907             ## EXPORTED FUNCTIONS:
1908             ## -------------------
1909             ##
1910              
1911             # wrapper for extend_dtd()
1912             sub extend_idmef { extend_dtd(@_); }
1913              
1914             ##----------------------------------------------------------------------------------------
1915             ##
1916             ## = byte_to_string()
1917             ##
1918             ## ARGS:
1919             ## a binary string
1920             ##
1921             ## RETURN:
1922             ## : the string obtained by converting into its IDMEF representation,
1923             ## refered to as type BYTE[] in the IDMEF rfc.
1924             ##
1925              
1926             sub byte_to_string {
1927             return join '', map( { "&\#$_;" } unpack("C*", $_[0]) );
1928             }
1929              
1930             ##----------------------------------------------------------------------------------------
1931             ##
1932             ## = xml_encode()
1933             ##
1934             ## ARGS:
1935             ## a usual string
1936             ##
1937             ## RETURN:
1938             ## : the xml encoded string equivalent to .
1939             ##
1940             ## DESC:
1941             ## You don't need this function if you are using add() calls (which already calls it).
1942             ## To convert a string into an idmef STRING, xml_encode basically replaces
1943             ## characters: with:
1944             ## & &
1945             ## < <
1946             ## > >
1947             ## " "
1948             ## ' '
1949             ## and all non printable characters (ie charcodes >126 or <32 except 10) into
1950             ## the corresponding �XX; form.
1951             ##
1952              
1953             # create a lookup array, start with filling it with xml encoded chars
1954             my @xml_enc = map { sprintf("&\#x00%.2x;", $_) } 0..255;
1955            
1956             # map the printable characters to themselves
1957             # NOTE: XML standard says encode all chars < 32 except 10, and all > 126
1958             for (10,32..126) {
1959             $xml_enc[$_] = chr($_);
1960             }
1961              
1962             # the special xml characters maps to their own encodings
1963             $xml_enc[ord('&')] = "&";
1964             $xml_enc[ord('<')] = "<";
1965             $xml_enc[ord('>')] = ">";
1966             $xml_enc[ord('"')] = """;
1967             $xml_enc[ord('\'')] = "'";
1968              
1969             sub xml_encode {
1970             my ($st) = @_;
1971             return join('', map { $xml_enc[ord($_)]} ($st =~ /(.)/gs));
1972             }
1973              
1974             ##----------------------------------------------------------------------------------------
1975             ##
1976             ## = xml_decode()
1977             ##
1978             ## ARGS:
1979             ## a xml encoded IDMEF STRING
1980             ##
1981             ## RETURN:
1982             ## the corresponding decoded string
1983             ##
1984             ## DESC:
1985             ## You don't need this function with 'to_hash' (which already calls it).
1986             ## It decodes into a string, ie replace the following
1987             ## characters: with:
1988             ## & &
1989             ## < <
1990             ## > >
1991             ## " "
1992             ## &apos '
1993             ## &#XX; XX in base 10
1994             ## &#xXXXX; XXXX in base 16
1995             ## It also decodes strings encoded with 'byte_to_string'
1996             ##
1997              
1998             sub xml_decode {
1999             my ($st) = @_;
2000              
2001             if (defined $st) {
2002            
2003             $st =~ s/&\;/&/gs;
2004             $st =~ s/<\;/
2005             $st =~ s/>\;/>/gs;
2006             $st =~ s/"\;/\"/gs;
2007             $st =~ s/&apos\;/\'/gs;
2008            
2009             $st =~ s/&\#x(.{4});/chr(hex $1)/ges;
2010             $st =~ s/&\#(.{2,3});/chr($1)/ges;
2011             }
2012              
2013             return $st;
2014             }
2015              
2016              
2017              
2018             #----------------------------------------------------------------------------------------
2019             #
2020             # END OF CODE - START OF POD DOC
2021             #
2022             #----------------------------------------------------------------------------------------
2023              
2024              
2025             1;
2026              
2027             __END__