1#! /usr/bin/env python
2# -*- coding: utf-8 -*-
3
4"""
5This file is part of cpe package.
6
7This module is an implementation of CPE language matching
8algorithm in accordance with version 2.3 of CPE (Common Platform
9Enumeration) specification.
10
11Copyright (C) 2013  Alejandro Galindo García, Roberto Abdelkader Martínez Pérez
12
13This program is free software: you can redistribute it and/or modify
14it under the terms of the GNU Lesser General Public License as published by
15the Free Software Foundation, either version 3 of the License, or
16(at your option) any later version.
17
18This program is distributed in the hope that it will be useful,
19but WITHOUT ANY WARRANTY; without even the implied warranty of
20MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21GNU Lesser General Public License for more details.
22
23You should have received a copy of the GNU Lesser General Public License
24along with this program. If not, see <http://www.gnu.org/licenses/>.
25
26For any problems using the cpe package, or general questions and
27feedback about it, please contact:
28
29- Alejandro Galindo García: galindo.garcia.alejandro@gmail.com
30- Roberto Abdelkader Martínez Pérez: robertomartinezp@gmail.com
31"""
32
33from .cpeset2_3 import CPESet2_3
34from .cpelang import CPELanguage
35from .cpe2_3_wfn import CPE2_3_WFN
36from .cpe2_3_uri import CPE2_3_URI
37from .cpe2_3_fs import CPE2_3_FS
38
39
40class CPELanguage2_3(CPELanguage):
41    """
42    Represents an expression in the CPE Language.
43
44    This class allows match a CPE element against an expression
45    in the CPE Language, that is, a XML document format for binding
46    descriptive prose and diagnostic test to a CPE name
47    (CPE Description Format).
48    """
49
50    ###############
51    #  CONSTANTS  #
52    ###############
53
54    #: Version of CPE Language
55    VERSION = "2.3"
56
57    ###################
58    #  CLASS METHODS  #
59    ###################
60
61    @classmethod
62    def _fact_ref_eval(cls, cpeset, wfn):
63        """
64        Returns True if wfn is a non-proper superset (True superset
65        or equal to) any of the names in cpeset, otherwise False.
66
67        :param CPESet cpeset: list of CPE bound Names.
68        :param CPE2_3_WFN wfn: WFN CPE Name.
69        :returns: True if wfn is a non-proper superset any of the names in cpeset, otherwise False
70        :rtype: boolean
71        """
72
73        for n in cpeset:
74            # Need to convert each n from bound form to WFN
75            if (CPESet2_3.cpe_superset(wfn, n)):
76                return True
77
78        return False
79
80    @classmethod
81    def _check_fact_ref_eval(cls, cpel_dom):
82        """
83        Returns the result (True, False, Error) of performing the specified
84        check, unless the check isn’t supported, in which case it returns
85        False. Error is a catch-all for all results other than True and
86        False.
87
88        :param string cpel_dom: XML infoset for the check_fact_ref element.
89        :returns: result of performing the specified check
90        :rtype: boolean or error
91        """
92
93        CHECK_SYSTEM = "check-system"
94        CHECK_LOCATION = "check-location"
95        CHECK_ID = "check-id"
96
97        checksystemID = cpel_dom.getAttribute(CHECK_SYSTEM)
98        if (checksystemID == "http://oval.mitre.org/XMLSchema/ovaldefinitions-5"):
99            # Perform an OVAL check.
100            # First attribute is the URI of an OVAL definitions file.
101            # Second attribute is an OVAL definition ID.
102            return CPELanguage2_3._ovalcheck(cpel_dom.getAttribute(CHECK_LOCATION),
103                                             cpel_dom.getAttribute(CHECK_ID))
104
105        if (checksystemID == "http://scap.nist.gov/schema/ocil/2"):
106            # Perform an OCIL check.
107            # First attribute is the URI of an OCIL questionnaire file.
108            # Second attribute is OCIL questionnaire ID.
109            return CPELanguage2_3._ocilcheck(cpel_dom.getAttribute(CHECK_LOCATION),
110                                             cpel_dom.getAttribute(CHECK_ID))
111
112        # Can add additional check systems here, with each returning a
113        # True, False, or Error value
114        return False
115
116    @classmethod
117    def _ocilcheck(location, ocil_id):
118        """
119        Perform an OCIL check.
120
121        :param string location: URI of an OCIL questionnaire file
122        :param string ocil_id: OCIL questionnaire ID
123        :rtype: boolean
124        :exception: NotImplementedError - Method not implemented
125        """
126
127        errmsg = "Method not implemented"
128        raise NotImplementedError(errmsg)
129
130    @classmethod
131    def _ovalcheck(location, oval_id):
132        """
133        Perform an OVAL check.
134
135        :param string location: URI of an OVAL definitions file
136        :param string oval_id: OVAL definition ID
137        :rtype: boolean
138        :exception: NotImplementedError - Method not implemented
139        """
140
141        errmsg = "Method not implemented"
142        raise NotImplementedError(errmsg)
143
144    @classmethod
145    def _unbind(cls, boundname):
146        """
147        Unbinds a bound form to a WFN.
148
149        :param string boundname: CPE name
150        :returns: WFN object associated with boundname.
151        :rtype: CPE2_3_WFN
152        """
153
154        try:
155            fs = CPE2_3_FS(boundname)
156        except:
157            # CPE name is not formatted string
158            try:
159                uri = CPE2_3_URI(boundname)
160            except:
161                # CPE name is not URI but WFN
162                return CPE2_3_WFN(boundname)
163            else:
164                return CPE2_3_WFN(uri.as_wfn())
165        else:
166            return CPE2_3_WFN(fs.as_wfn())
167
168    ####################
169    #  OBJECT METHODS  #
170    ####################
171
172    def language_match(self, cpeset, cpel_dom=None):
173        """
174        Accepts a set of known CPE Names and an expression in the CPE language,
175        and delivers the answer True if the expression matches with the set.
176        Otherwise, it returns False.
177
178        :param CPELanguage self: An expression in the CPE Applicability
179            Language, represented as the XML infoset for the platform element.
180        :param CPESet cpeset: CPE set object to match with self expression.
181        :param string cpel_dom: An expression in the CPE Applicability
182            Language, represented as DOM tree.
183        :returns: True if self expression can be satisfied by language matching
184            against cpeset, False otherwise.
185        :rtype: boolean
186        """
187
188        # Root element tag
189        TAG_ROOT = '#document'
190        # A container for child platform definitions
191        TAG_PLATSPEC = 'cpe:platform-specification'
192
193        # Information about a platform definition
194        TAG_PLATFORM = 'cpe:platform'
195        TAG_LOGITEST = 'cpe:logical-test'
196        TAG_CPE = 'cpe:fact-ref'
197        TAG_CHECK_CPE = 'check-fact-ref'
198
199        # Tag attributes
200        ATT_NAME = 'name'
201        ATT_OP = 'operator'
202        ATT_NEGATE = 'negate'
203
204        # Attribute values
205        ATT_OP_AND = 'AND'
206        ATT_OP_OR = 'OR'
207        ATT_NEGATE_TRUE = 'TRUE'
208
209        # Constant associated with an error in language matching
210        ERROR = 2
211
212        if cpel_dom is None:
213            cpel_dom = self.document
214
215        # Identify the root element
216        if cpel_dom.nodeName == TAG_ROOT or cpel_dom.nodeName == TAG_PLATSPEC:
217            for node in cpel_dom.childNodes:
218                if node.nodeName == TAG_PLATSPEC:
219                    return self.language_match(cpeset, node)
220                if node.nodeName == TAG_PLATFORM:
221                    return self.language_match(cpeset, node)
222
223        # Identify a platform element
224        elif cpel_dom.nodeName == TAG_PLATFORM:
225            # Parse through E's elements and ignore all but logical-test
226            for node in cpel_dom.childNodes:
227                if node.nodeName == TAG_LOGITEST:
228                    # Call the function again, but with logical-test
229                    # as the root element
230                    return self.language_match(cpeset, node)
231
232        # Identify a CPE element
233        elif cpel_dom.nodeName == TAG_CPE:
234            # fact-ref's name attribute is a bound name,
235            # so we unbind it to a WFN before passing it
236            cpename = cpel_dom.getAttribute(ATT_NAME)
237            wfn = CPELanguage2_3._unbind(cpename)
238            return CPELanguage2_3._fact_ref_eval(cpeset, wfn)
239
240        # Identify a check of CPE names (OVAL, OCIL...)
241        elif cpel_dom.nodeName == TAG_CHECK_CPE:
242            return CPELanguage2_3._check_fact_ref_Eval(cpel_dom)
243
244        # Identify a logical operator element
245        elif cpel_dom.nodeName == TAG_LOGITEST:
246            count = 0
247            len = 0
248            answer = False
249
250            for node in cpel_dom.childNodes:
251                if node.nodeName.find("#") == 0:
252                    continue
253                len = len + 1
254                result = self.language_match(cpeset, node)
255                if result:
256                    count = count + 1
257                elif result == ERROR:
258                    answer = ERROR
259
260            operator = cpel_dom.getAttribute(ATT_OP).upper()
261
262            if operator == ATT_OP_AND:
263                if count == len:
264                    answer = True
265            elif operator == ATT_OP_OR:
266                if count > 0:
267                    answer = True
268
269            operator_not = cpel_dom.getAttribute(ATT_NEGATE)
270            if operator_not:
271                if ((operator_not.upper() == ATT_NEGATE_TRUE) and
272                   (answer != ERROR)):
273                    answer = not answer
274
275            return answer
276        else:
277            return False
278
279if __name__ == "__main__":
280    import doctest
281    doctest.testmod()
282    doctest.testfile("tests/testfile_cpelang2_3.txt")
283