• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

savon-noir / python-libnmap / 12857916376

19 Jan 2025 10:59PM UTC coverage: 72.708% (+0.9%) from 71.843%
12857916376

push

github

savon-noir
fix: gh actions fix bllint issue

1745 of 2400 relevant lines covered (72.71%)

2.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.36
/libnmap/objects/os.py
1
# -*- coding: utf-8 -*-
2

3
import warnings
4✔
4

5
from libnmap.objects.cpe import CPE
4✔
6

7

8
class OSFPPortUsed(object):
4✔
9
    """
10
    Port used class: this enables the user of NmapOSFingerprint class
11
    to have a common and clear interface to access portused data which
12
    were collected and used during os fingerprint scan
13
    """
14

15
    def __init__(self, port_used_dict):
4✔
16
        try:
4✔
17
            self._state = port_used_dict["state"]
4✔
18
            self._proto = port_used_dict["proto"]
4✔
19
            self._portid = port_used_dict["portid"]
4✔
20
        except KeyError:
×
21
            raise Exception("Cannot create OSFPPortUsed: missing required key")
×
22

23
    @property
4✔
24
    def state(self):
3✔
25
        """
26
        Accessor for the portused state (closed, open,...)
27
        """
28
        return self._state
×
29

30
    @property
4✔
31
    def proto(self):
3✔
32
        """
33
        Accessor for the portused protocol (tcp, udp,...)
34
        """
35
        return self._proto
×
36

37
    @property
4✔
38
    def portid(self):
3✔
39
        """
40
        Accessor for the referenced port number used
41
        """
42
        return self._portid
×
43

44

45
class NmapOSMatch(object):
4✔
46
    """
47
    NmapOSMatch is an internal class used for offering results
48
    from an nmap os fingerprint. This common interfaces makes
49
    a compatibility between old nmap xml (<1.04) and new nmap
50
    xml versions (used in nmapv6 for instance).
51

52
    In previous xml version, osclass tags from nmap fingerprints
53
    were not directly mapped to a osmatch. In new xml version,
54
    osclass could be embedded in osmatch tag.
55

56
    The approach to solve this is to create a common class
57
    which will, for older xml version, match based on the accuracy
58
    osclass to an osmatch. If no match, an osmatch will be made up
59
    from a concat of os class attributes: vendor and osfamily.
60
    Unmatched osclass will have a line attribute of -1.
61

62
    More info, see issue #26 or http://seclists.org/nmap-dev/2012/q2/252
63
    """
64

65
    def __init__(self, osmatch_dict):
4✔
66
        _osmatch_dict = osmatch_dict["osmatch"]
4✔
67
        if (
4✔
68
            "name" not in _osmatch_dict
69
            or "line" not in _osmatch_dict
70
            or "accuracy" not in _osmatch_dict
71
        ):
72
            raise Exception("Cannot create NmapOSClass: missing required key")
×
73

74
        self._name = _osmatch_dict["name"]
4✔
75
        self._line = _osmatch_dict["line"]
4✔
76
        self._accuracy = _osmatch_dict["accuracy"]
4✔
77

78
        # create osclass list
79
        self._osclasses = []
4✔
80
        try:
4✔
81
            for _osclass in osmatch_dict["osclasses"]:
4✔
82
                try:
4✔
83
                    _osclassobj = NmapOSClass(_osclass)
4✔
84
                except Exception as e:
×
85
                    em = "Could not create NmapOSClass object: {0}".format(e)
×
86
                    raise Exception(em)
×
87
                self._osclasses.append(_osclassobj)
4✔
88
        except KeyError:
×
89
            pass
×
90

91
    def add_osclass(self, osclass_obj):
4✔
92
        """
93
        Add a NmapOSClass object to the OSMatch object. This method is
94
        useful to implement compatibility with older versions of NMAP
95
        by providing a common interface to access os fingerprint data.
96
        """
97
        self._osclasses.append(osclass_obj)
4✔
98

99
    @property
4✔
100
    def osclasses(self):
3✔
101
        """
102
        Accessor for all NmapOSClass objects matching with this OS Match
103
        """
104
        return self._osclasses
4✔
105

106
    @property
4✔
107
    def name(self):
3✔
108
        """
109
        Accessor for name attribute (e.g.: Linux 2.4.26 (Slackware 10.0.0))
110
        """
111
        return self._name
4✔
112

113
    @property
4✔
114
    def line(self):
3✔
115
        """
116
        Accessor for line attribute as integer. value equals -1 if this
117
        osmatch holds orphans NmapOSClass objects. This could happen with
118
        older version of nmap xml engine (<1.04 (e.g: nmapv6)).
119

120
        :return: int
121
        """
122
        return int(self._line)
4✔
123

124
    @property
4✔
125
    def accuracy(self):
3✔
126
        """
127
        Accessor for accuracy
128

129
        :return: int
130
        """
131
        return int(self._accuracy)
4✔
132

133
    def get_cpe(self):
4✔
134
        """
135
        This method return a list of cpe stings and not CPE objects as
136
        the NmapOSClass.cpelist property. This method is a helper to
137
        simplify data management.
138

139
        For more advanced handling of CPE data, use NmapOSClass.cpelist
140
        and use the methods from CPE class
141
        """
142
        _cpelist = []
×
143
        for osc in self.osclasses:
×
144
            for cpe in osc.cpelist:
×
145
                _cpelist.append(cpe.cpestring)
×
146
        return _cpelist
×
147

148
    def __repr__(self):
4✔
149
        rval = "{0}: {1}".format(self.name, self.accuracy)
×
150
        for _osclass in self._osclasses:
×
151
            rval += "\r\n  |__ os class: {0}".format(str(_osclass))
×
152
        return rval
×
153

154

155
class NmapOSClass(object):
4✔
156
    """
157
    NmapOSClass offers an unified API to access data from analysed
158
    osclass tag. As implemented in libnmap and newer version of nmap,
159
    osclass objects will always be embedded in a NmapOSMatch.
160
    Unmatched NmapOSClass will be stored in "dummy" NmapOSMatch objects
161
    which will have the particularity of have a line attribute of -1.
162
    On top of this, NmapOSClass will have optional CPE objects
163
    embedded.
164
    """
165

166
    def __init__(self, osclass_dict):
4✔
167
        _osclass = osclass_dict["osclass"]
4✔
168
        if (
4✔
169
            "vendor" not in _osclass
170
            or "osfamily" not in _osclass
171
            or "accuracy" not in _osclass
172
        ):
173
            raise Exception("Wrong osclass structure: missing required key")
×
174

175
        self._vendor = _osclass["vendor"]
4✔
176
        self._osfamily = _osclass["osfamily"]
4✔
177
        self._accuracy = _osclass["accuracy"]
4✔
178

179
        self._osgen = ""
4✔
180
        self._type = ""
4✔
181
        # optional data
182
        if "osgen" in _osclass:
4✔
183
            self._osgen = _osclass["osgen"]
4✔
184
        if "type" in _osclass:
4✔
185
            self._type = _osclass["type"]
4✔
186

187
        self._cpelist = []
4✔
188
        for _cpe in osclass_dict["cpe"]:
4✔
189
            self._cpelist.append(CPE(_cpe))
4✔
190

191
    @property
4✔
192
    def cpelist(self):
3✔
193
        """
194
        Returns a list of CPE Objects matching with this os class
195

196
        :return: list of CPE objects
197
        :rtype: Array
198
        """
199
        return self._cpelist
×
200

201
    @property
4✔
202
    def vendor(self):
3✔
203
        """
204
        Accessor for vendor information (Microsoft, Linux,...)
205

206
        :return: string
207
        """
208
        return self._vendor
4✔
209

210
    @property
4✔
211
    def osfamily(self):
3✔
212
        """
213
        Accessor for OS family information (Windows, Linux,...)
214

215
        :return: string
216
        """
217
        return self._osfamily
4✔
218

219
    @property
4✔
220
    def accuracy(self):
3✔
221
        """
222
        Accessor for OS class detection accuracy (int)
223

224
        :return: int
225
        """
226
        return int(self._accuracy)
4✔
227

228
    @property
4✔
229
    def osgen(self):
3✔
230
        """
231
        Accessor for OS class generation (7, 8, 2.4.X,...).
232

233
        :return: string
234
        """
235
        return self._osgen
4✔
236

237
    @property
4✔
238
    def type(self):
3✔
239
        """
240
        Accessor for OS class type (general purpose,...)
241

242
        :return: string
243
        """
244
        return self._type
4✔
245

246
    @property
4✔
247
    def description(self):
3✔
248
        """
249
        Accessor helper which returns a concataned string of
250
        the valuable attributes from NmapOSClass object
251

252
        :return: string
253
        """
254
        rval = "{0}: {1}, {2}".format(self.type, self.vendor, self.osfamily)
×
255
        if len(self.osgen):
×
256
            rval += "({0})".format(self.osgen)
×
257
        return rval
×
258

259
    def __repr__(self):
4✔
260
        rval = "{0}: {1}, {2}".format(self.type, self.vendor, self.osfamily)
×
261
        if len(self.osgen):
×
262
            rval += "({0})".format(self.osgen)
×
263
        for _cpe in self._cpelist:
×
264
            rval += "\r\n    |__ {0}".format(str(_cpe))
×
265
        return rval
×
266

267

268
class NmapOSFingerprint(object):
4✔
269
    """
270
    NmapOSFingerprint is a easier API for using os fingerprinting.
271
    Data for OS fingerprint (<os> tag) is instantiated from
272
    a NmapOSFingerprint which is accessible in NmapHost via NmapHost.os
273
    """
274

275
    def __init__(self, osfp_data):
4✔
276
        self.__osmatches = []
4✔
277
        self.__ports_used = []
4✔
278
        self.__fingerprints = []
4✔
279

280
        if "osmatches" in osfp_data:
4✔
281
            for _osmatch in osfp_data["osmatches"]:
4✔
282
                _osmatch_obj = NmapOSMatch(_osmatch)
4✔
283
                self.__osmatches.append(_osmatch_obj)
4✔
284
        if "osclasses" in osfp_data:
4✔
285
            for _osclass in osfp_data["osclasses"]:
4✔
286
                _osclass_obj = NmapOSClass(_osclass)
4✔
287
                _osmatched = self.get_osmatch(_osclass_obj)
4✔
288
                if _osmatched is not None:
4✔
289
                    _osmatched.add_osclass(_osclass_obj)
4✔
290
                else:
291
                    self._add_dummy_osmatch(_osclass_obj)
4✔
292
        if "osfingerprints" in osfp_data:
4✔
293
            for _osfp in osfp_data["osfingerprints"]:
4✔
294
                if "fingerprint" in _osfp:
4✔
295
                    self.__fingerprints.append(_osfp["fingerprint"])
4✔
296
        if "ports_used" in osfp_data:
4✔
297
            for _pused_dict in osfp_data["ports_used"]:
4✔
298
                _pused = OSFPPortUsed(_pused_dict)
4✔
299
                self.__ports_used.append(_pused)
4✔
300

301
    def get_osmatch(self, osclass_obj):
4✔
302
        """
303
        This function enables NmapOSFingerprint to determine if an
304
        NmapOSClass object could be attached to an existing NmapOSMatch
305
        object in order to respect the common interface for
306
        the nmap xml version < 1.04 and >= 1.04
307

308
        This method will return an NmapOSMatch object matching with
309
        the NmapOSClass provided in parameter
310
        (match is performed based on accuracy)
311

312
        :return: NmapOSMatch object
313
        """
314
        rval = None
4✔
315
        for _osmatch in self.__osmatches:
4✔
316
            if _osmatch.accuracy == osclass_obj.accuracy:
4✔
317
                rval = _osmatch
4✔
318
                break  # sorry
4✔
319
        return rval
4✔
320

321
    def _add_dummy_osmatch(self, osclass_obj):
4✔
322
        """
323
        This functions creates a dummy NmapOSMatch object in order to
324
        encapsulate an NmapOSClass object which was not matched with an
325
        existing NmapOSMatch object
326
        """
327
        _dname = "{0}:{1}:{2}".format(
4✔
328
            osclass_obj.type, osclass_obj.vendor, osclass_obj.osfamily
329
        )
330
        _dummy_dict = {
4✔
331
            "osmatch": {
332
                "name": _dname,
333
                "accuracy": osclass_obj.accuracy,
334
                "line": -1,
335
            },
336
            "osclasses": [],
337
        }
338
        _dummy_osmatch = NmapOSMatch(_dummy_dict)
4✔
339
        self.__osmatches.append(_dummy_osmatch)
4✔
340

341
    @property
4✔
342
    def osmatches(self, min_accuracy=0):
4✔
343
        _osmatches = []
4✔
344

345
        for _osmatch in self.__osmatches:
4✔
346
            if _osmatch.accuracy >= min_accuracy:
4✔
347
                _osmatches.append(_osmatch)
4✔
348

349
        return _osmatches
4✔
350

351
    @property
4✔
352
    def osclasses(self, min_accuracy=0):
4✔
353
        osc_array = []
4✔
354
        for _osm in self.osmatches:
4✔
355
            for _osc in _osm.osclasses:
4✔
356
                if _osc.accuracy >= min_accuracy:
4✔
357
                    osc_array.append(_osc)
4✔
358
        return osc_array
4✔
359

360
    @property
4✔
361
    def fingerprint(self):
3✔
362
        return "\r\n".join(self.__fingerprints)
4✔
363

364
    @property
4✔
365
    def fingerprints(self):
3✔
366
        return self.__fingerprints
4✔
367

368
    @property
4✔
369
    def ports_used(self):
3✔
370
        """
371
        Return an array of OSFPPortUsed object with the ports used to
372
        perform the os fingerprint. This dict might contain another dict
373
        embedded containing the ports_reason values.
374
        """
375
        return self.__ports_used
×
376

377
    def osmatch(self, min_accuracy=90):
4✔
378
        warnings.warn(
×
379
            "NmapOSFingerprint.osmatch is deprecated: "
380
            "use NmapOSFingerprint.osmatches",
381
            DeprecationWarning,
382
        )
383
        os_array = []
×
384
        for _osmatch in self.__osmatches:
×
385
            if _osmatch.accuracy >= min_accuracy:
×
386
                os_array.append(_osmatch.name)
×
387
        return os_array
×
388

389
    def osclass(self, min_accuracy=90):
4✔
390
        warnings.warn(
×
391
            "NmapOSFingerprint.osclass() is deprecated: "
392
            "use NmapOSFingerprint.osclasses() if applicable",
393
            DeprecationWarning,
394
        )
395
        os_array = []
×
396
        for osmatch_entry in self.osmatches:
×
397
            if osmatch_entry.accuracy >= min_accuracy:
×
398
                for oclass in osmatch_entry.osclasses:
×
399
                    _ftstr = "type:{0}|vendor:{1}|osfamily{2}".format(
×
400
                        oclass.type, oclass.vendor, oclass.osfamily
401
                    )
402
                    os_array.append(_ftstr)
×
403
        return os_array
×
404

405
    def os_cpelist(self):
4✔
406
        cpelist = []
×
407
        for _osmatch in self.osmatches:
×
408
            for oclass in _osmatch.osclasses:
×
409
                cpelist.extend(oclass.cpelist)
×
410
        return cpelist
×
411

412
    def __repr__(self):
4✔
413
        rval = ""
×
414
        for _osmatch in self.osmatches:
×
415
            rval += "\r\n{0}".format(_osmatch)
×
416
        rval += "Fingerprints: {0}".format(self.fingerprint)
×
417
        return rval
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc