Completed
Push — master ( 3b0190...8082e9 )
by Juan José
13s queued 11s
created

ospd.misc.get_tcp_port_list()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
# Copyright (C) 2014-2018 Greenbone Networks GmbH
0 ignored issues
show
coding-style introduced by
Too many lines in module (1008/1000)
Loading history...
2
#
3
# SPDX-License-Identifier: GPL-2.0-or-later
4
#
5
# This program is free software; you can redistribute it and/or
6
# modify it under the terms of the GNU General Public License
7
# as published by the Free Software Foundation; either version 2
8
# of the License, or (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18
19
""" Miscellaneous classes and functions related to OSPD.
20
"""
21
22
23
# Needed to say that when we import ospd, we mean the package and not the
24
# module in that directory.
25
from __future__ import absolute_import
26
from __future__ import print_function
27
28
import argparse
29
import binascii
30
import collections
31
import logging
32
import logging.handlers
33
import os
34
import re
35
import socket
36
import struct
37
import sys
38
import time
39
import ssl
40
import uuid
41
import multiprocessing
42
import itertools
43
from enum import Enum
44
45
LOGGER = logging.getLogger(__name__)
46
NICENESS = 10
47
48
# Default file locations as used by a OpenVAS default installation
49
KEY_FILE = "/usr/var/lib/gvm/private/CA/serverkey.pem"
50
CERT_FILE = "/usr/var/lib/gvm/CA/servercert.pem"
51
CA_FILE = "/usr/var/lib/gvm/CA/cacert.pem"
52
53
PORT = 1234
54
ADDRESS = "0.0.0.0"
55
56
class ScanStatus(Enum):
57
    """Scan status. """
58
    INIT = 0
59
    RUNNING = 1
60
    STOPPED = 2
61
    FINISHED = 3
62
63
64
65 View Code Duplication
class ScanCollection(object):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
66
67
    """ Scans collection, managing scans and results read and write, exposing
68
    only needed information.
69
70
    Each scan has meta-information such as scan ID, current progress (from 0 to
71
    100), start time, end time, scan target and options and a list of results.
72
73
    There are 4 types of results: Alarms, Logs, Errors and Host Details.
74
75
    Todo:
76
    - Better checking for Scan ID existence and handling otherwise.
77
    - More data validation.
78
    - Mutex access per table/scan_info.
79
80
    """
81
82
    def __init__(self):
83
        """ Initialize the Scan Collection. """
84
85
        self.data_manager = None
86
        self.scans_table = dict()
87
88
    def add_result(self, scan_id, result_type, host='', hostname='', name='',
89
                   value='', port='', test_id='', severity='', qod=''):
90
        """ Add a result to a scan in the table. """
91
92
        assert scan_id
93
        assert len(name) or len(value)
94
        result = dict()
95
        result['type'] = result_type
96
        result['name'] = name
97
        result['severity'] = severity
98
        result['test_id'] = test_id
99
        result['value'] = value
100
        result['host'] = host
101
        result['hostname'] = hostname
102
        result['port'] = port
103
        result['qod'] = qod
104
        results = self.scans_table[scan_id]['results']
105
        results.append(result)
106
        # Set scan_info's results to propagate results to parent process.
107
        self.scans_table[scan_id]['results'] = results
108
109
    def set_progress(self, scan_id, progress):
110
        """ Sets scan_id scan's progress. """
111
112
        if progress > 0 and progress <= 100:
113
            self.scans_table[scan_id]['progress'] = progress
114
        if progress == 100:
115
            self.scans_table[scan_id]['end_time'] = int(time.time())
116
117
    def set_target_progress(self, scan_id, target, host, progress):
118
        """ Sets scan_id scan's progress. """
119
        if progress > 0 and progress <= 100:
120
            targets =  self.scans_table[scan_id]['target_progress']
0 ignored issues
show
Coding Style introduced by
Exactly one space required after assignment
Loading history...
121
            targets[target][host] = progress
122
            # Set scan_info's target_progress to propagate progresses
123
            # to parent process.
124
            self.scans_table[scan_id]['target_progress'] = targets
125
126
    def set_host_finished(self, scan_id, target, host):
127
        """ Add the host in a list of finished hosts """
128
        finished_hosts = self.scans_table[scan_id]['finished_hosts']
129
        finished_hosts[target].append(host)
130
        self.scans_table[scan_id]['finished_hosts'] = finished_hosts
131
132
    def get_hosts_unfinished(self, scan_id):
133
        """ Get a list of unfinished hosts."""
134
135
        unfinished_hosts = list()
136
        for target in self.scans_table[scan_id]['finished_hosts']:
137
            unfinished_hosts.extend(target_str_to_list(target))
138
        for target in self.scans_table[scan_id]['finished_hosts']:
139
           for host in self.scans_table[scan_id]['finished_hosts'][target]:
0 ignored issues
show
Coding Style introduced by
The indentation here looks off. 12 spaces were expected, but 11 were found.
Loading history...
140
               unfinished_hosts.remove(host)
0 ignored issues
show
Coding Style introduced by
The indentation here looks off. 16 spaces were expected, but 15 were found.
Loading history...
141
142
        return unfinished_hosts
143
144
    def get_hosts_finished(self, scan_id):
145
        """ Get a list of finished hosts."""
146
147
        finished_hosts = list()
148
        for target in self.scans_table[scan_id]['finished_hosts']:
149
            finished_hosts.extend(
150
                self.scans_table[scan_id]['finished_hosts'].get(target))
151
152
        return finished_hosts
153
154
    def results_iterator(self, scan_id, pop_res):
155
        """ Returns an iterator over scan_id scan's results. If pop_res is True,
156
        it removed the fetched results from the list.
157
        """
158
        if pop_res:
159
            result_aux = self.scans_table[scan_id]['results']
160
            self.scans_table[scan_id]['results'] = list()
161
            return iter(result_aux)
162
163
        return iter(self.scans_table[scan_id]['results'])
164
165
    def ids_iterator(self):
166
        """ Returns an iterator over the collection's scan IDS. """
167
168
        return iter(self.scans_table.keys())
169
170
    def remove_single_result(self, scan_id, result):
171
        """Removes a single result from the result list in scan_table.
172
173
        Parameters:
174
            scan_id (uuid): Scan ID to identify the scan process to be resumed.
175
            result (dict): The result to be removed from the results list.
176
        """
177
        results = self.scans_table[scan_id]['results']
178
        results.remove(result)
179
        self.scans_table[scan_id]['results'] = results
180
181
    def del_results_for_stopped_hosts(self, scan_id):
182
        """ Remove results from the result table for those host
183
        """
184
        unfinished_hosts = self.get_hosts_unfinished(scan_id)
185
        for result in self.results_iterator(scan_id, False):
186
            if result['host'] in unfinished_hosts:
187
                self.remove_single_result(scan_id, result)
188
189
    def resume_scan(self, scan_id, options):
190
        """ Reset the scan status in the scan_table to INIT.
191
        Also, overwrite the options, because a resume task cmd
192
        can add some new option. E.g. exclude hosts list.
193
        Parameters:
194
            scan_id (uuid): Scan ID to identify the scan process to be resumed.
195
            options (dict): Options for the scan to be resumed. This options
196
                            are not added to the already existent ones.
197
                            The old ones are removed
198
199
        Return:
200
            Scan ID which identifies the current scan.
201
        """
202
        self.scans_table[scan_id]['status'] = ScanStatus.INIT
203
        if options:
204
            self.scans_table[scan_id]['options'] = options
205
206
        self.del_results_for_stopped_hosts(scan_id)
207
208
        return scan_id
209
210
    def create_scan(self, scan_id='', targets='', options=None, vts=''):
211
        """ Creates a new scan with provided scan information. """
212
213
        if self.data_manager is None:
214
            self.data_manager = multiprocessing.Manager()
215
216
        # Check if it is possible to resume task. To avoid to resume, the
217
        # scan must be deleted from the scans_table.
218
        if scan_id and self.id_exists(scan_id) and (
219
                self.get_status(scan_id) == ScanStatus.STOPPED):
220
            return self.resume_scan(scan_id, options)
221
222
        if not options:
223
            options = dict()
224
        scan_info = self.data_manager.dict()
225
        scan_info['results'] = list()
226
        scan_info['finished_hosts'] = dict(
227
            [[target, []] for target, _, _, _ in targets])
228
        scan_info['progress'] = 0
229
        scan_info['target_progress'] = dict(
230
            [[target, {}] for target, _, _, _ in targets])
231
        scan_info['targets'] = targets
232
        scan_info['vts'] = vts
233
        scan_info['options'] = options
234
        scan_info['start_time'] = int(time.time())
235
        scan_info['end_time'] = "0"
236
        scan_info['status'] = ScanStatus.INIT
237
        if scan_id is None or scan_id == '':
238
            scan_id = str(uuid.uuid4())
239
        scan_info['scan_id'] = scan_id
240
        self.scans_table[scan_id] = scan_info
241
        return scan_id
242
243
    def set_status(self, scan_id, status):
244
        """ Sets scan_id scan's status. """
245
        self.scans_table[scan_id]['status'] = status
246
247
    def get_status(self, scan_id):
248
        """ Get scan_id scans's status."""
249
250
        return self.scans_table[scan_id]['status']
251
252
    def get_options(self, scan_id):
253
        """ Get scan_id scan's options list. """
254
255
        return self.scans_table[scan_id]['options']
256
257
    def set_option(self, scan_id, name, value):
258
        """ Set a scan_id scan's name option to value. """
259
260
        self.scans_table[scan_id]['options'][name] = value
261
262
    def get_progress(self, scan_id):
263
        """ Get a scan's current progress value. """
264
265
        return self.scans_table[scan_id]['progress']
266
267
    def get_target_progress(self, scan_id, target):
268
        """ Get a target's current progress value.
269
        The value is calculated with the progress of each single host
270
        in the target."""
271
272
        total_hosts = len(target_str_to_list(target))
273
        host_progresses = self.scans_table[scan_id]['target_progress'].get(target)
274
        try:
275
            t_prog = sum(host_progresses.values()) / total_hosts
276
        except ZeroDivisionError:
277
            LOGGER.error("Zero division error in ", get_target_progress.__name__)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable get_target_progress does not seem to be defined.
Loading history...
278
            raise
279
        return t_prog
280
281
    def get_start_time(self, scan_id):
282
        """ Get a scan's start time. """
283
284
        return self.scans_table[scan_id]['start_time']
285
286
    def get_end_time(self, scan_id):
287
        """ Get a scan's end time. """
288
289
        return self.scans_table[scan_id]['end_time']
290
291
    def get_target_list(self, scan_id):
292
        """ Get a scan's target list. """
293
294
        target_list = []
295
        for target, _, _, _ in self.scans_table[scan_id]['targets']:
296
            target_list.append(target)
297
        return target_list
298
299
    def get_ports(self, scan_id, target):
300
        """ Get a scan's ports list. If a target is specified
301
        it will return the corresponding port for it. If not,
302
        it returns the port item of the first nested list in
303
        the target's list.
304
        """
305
        if target:
306
            for item in self.scans_table[scan_id]['targets']:
307
                if target == item[0]:
308
                    return item[1]
309
310
        return self.scans_table[scan_id]['targets'][0][1]
311
312
    def get_exclude_hosts(self, scan_id, target):
313
        """ Get an exclude host list for a given target.
314
        """
315
        if target:
316
            for item in self.scans_table[scan_id]['targets']:
317
                if target == item[0]:
318
                    return item[3]
319
320
    def get_credentials(self, scan_id, target):
321
        """ Get a scan's credential list. It return dictionary with
322
        the corresponding credential for a given target.
323
        """
324
        if target:
325
            for item in self.scans_table[scan_id]['targets']:
326
                if target == item[0]:
327
                    return item[2]
328
329
    def get_vts(self, scan_id):
330
        """ Get a scan's vts list. """
331
332
        return self.scans_table[scan_id]['vts']
333
334
    def id_exists(self, scan_id):
335
        """ Check whether a scan exists in the table. """
336
337
        return self.scans_table.get(scan_id) is not None
338
339
    def delete_scan(self, scan_id):
340
        """ Delete a scan if fully finished. """
341
342
        if self.get_status(scan_id) == ScanStatus.RUNNING:
343
            return False
344
        self.scans_table.pop(scan_id)
345
        if len(self.scans_table) == 0:
346
            del self.data_manager
347
            self.data_manager = None
348
        return True
349
350
351 View Code Duplication
class ResultType(object):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
352
353
    """ Various scan results types values. """
354
355
    ALARM = 0
356
    LOG = 1
357
    ERROR = 2
358
    HOST_DETAIL = 3
359
360
    @classmethod
361
    def get_str(cls, result_type):
362
        """ Return string name of a result type. """
363
        if result_type == cls.ALARM:
364
            return "Alarm"
365
        elif result_type == cls.LOG:
366
            return "Log Message"
367
        elif result_type == cls.ERROR:
368
            return "Error Message"
369
        elif result_type == cls.HOST_DETAIL:
370
            return "Host Detail"
371
        else:
372
            assert False, "Erroneous result type {0}.".format(result_type)
373
374
    @classmethod
375
    def get_type(cls, result_name):
376
        """ Return string name of a result type. """
377
        if result_name == "Alarm":
378
            return cls.ALARM
379
        elif result_name == "Log Message":
380
            return cls.LOG
381
        elif result_name == "Error Message":
382
            return cls.ERROR
383
        elif result_name == "Host Detail":
384
            return cls.HOST_DETAIL
385
        else:
386
            assert False, "Erroneous result name {0}.".format(result_name)
387
388
389
__inet_pton = None
390
391
392 View Code Duplication
def inet_pton(address_family, ip_string):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
393
    """ A platform independent version of inet_pton """
394
    global __inet_pton
395
    if __inet_pton is None:
396
        if hasattr(socket, 'inet_pton'):
397
            __inet_pton = socket.inet_pton
398
        else:
399
            from ospd import win_socket
400
            __inet_pton = win_socket.inet_pton
401
402
    return __inet_pton(address_family, ip_string)
403
404
405
__inet_ntop = None
406
407
408 View Code Duplication
def inet_ntop(address_family, packed_ip):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
409
    """ A platform independent version of inet_ntop """
410
    global __inet_ntop
411
    if __inet_ntop is None:
412
        if hasattr(socket, 'inet_ntop'):
413
            __inet_ntop = socket.inet_ntop
414
        else:
415
            from ospd import win_socket
416
            __inet_ntop = win_socket.inet_ntop
417
418
    return __inet_ntop(address_family, packed_ip)
419
420
421
def target_to_ipv4(target):
422
    """ Attempt to return a single IPv4 host list from a target string. """
423
424
    try:
425
        inet_pton(socket.AF_INET, target)
426
        return [target]
427
    except socket.error:
428
        return None
429
430
431
def target_to_ipv6(target):
432
    """ Attempt to return a single IPv6 host list from a target string. """
433
434
    try:
435
        inet_pton(socket.AF_INET6, target)
436
        return [target]
437
    except socket.error:
438
        return None
439
440
441 View Code Duplication
def ipv4_range_to_list(start_packed, end_packed):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
442
    """ Return a list of IPv4 entries from start_packed to end_packed. """
443
444
    new_list = list()
445
    start = struct.unpack('!L', start_packed)[0]
446
    end = struct.unpack('!L', end_packed)[0]
447
    for value in range(start, end + 1):
448
        new_ip = socket.inet_ntoa(struct.pack('!L', value))
449
        new_list.append(new_ip)
450
    return new_list
451
452
453 View Code Duplication
def target_to_ipv4_short(target):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
454
    """ Attempt to return a IPv4 short range list from a target string. """
455
456
    splitted = target.split('-')
457
    if len(splitted) != 2:
458
        return None
459
    try:
460
        start_packed = inet_pton(socket.AF_INET, splitted[0])
461
        end_value = int(splitted[1])
462
    except (socket.error, ValueError):
463
        return None
464
    start_value = int(binascii.hexlify(bytes(start_packed[3])), 16)
465
    if end_value < 0 or end_value > 255 or end_value < start_value:
466
        return None
467
    end_packed = start_packed[0:3] + struct.pack('B', end_value)
468
    return ipv4_range_to_list(start_packed, end_packed)
469
470
471 View Code Duplication
def target_to_ipv4_cidr(target):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
472
    """ Attempt to return a IPv4 CIDR list from a target string. """
473
474
    splitted = target.split('/')
475
    if len(splitted) != 2:
476
        return None
477
    try:
478
        start_packed = inet_pton(socket.AF_INET, splitted[0])
479
        block = int(splitted[1])
480
    except (socket.error, ValueError):
481
        return None
482
    if block <= 0 or block > 30:
483
        return None
484
    start_value = int(binascii.hexlify(start_packed), 16) >> (32 - block)
485
    start_value = (start_value << (32 - block)) + 1
486
    end_value = (start_value | (0xffffffff >> block)) - 1
487
    start_packed = struct.pack('!I', start_value)
488
    end_packed = struct.pack('!I', end_value)
489
    return ipv4_range_to_list(start_packed, end_packed)
490
491
492 View Code Duplication
def target_to_ipv6_cidr(target):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
493
    """ Attempt to return a IPv6 CIDR list from a target string. """
494
495
    splitted = target.split('/')
496
    if len(splitted) != 2:
497
        return None
498
    try:
499
        start_packed = inet_pton(socket.AF_INET6, splitted[0])
500
        block = int(splitted[1])
501
    except (socket.error, ValueError):
502
        return None
503
    if block <= 0 or block > 126:
504
        return None
505
    start_value = int(binascii.hexlify(start_packed), 16) >> (128 - block)
506
    start_value = (start_value << (128 - block)) + 1
507
    end_value = (start_value | (int('ff' * 16, 16) >> block)) - 1
508
    high = start_value >> 64
509
    low = start_value & ((1 << 64) - 1)
510
    start_packed = struct.pack('!QQ', high, low)
511
    high = end_value >> 64
512
    low = end_value & ((1 << 64) - 1)
513
    end_packed = struct.pack('!QQ', high, low)
514
    return ipv6_range_to_list(start_packed, end_packed)
515
516
517 View Code Duplication
def target_to_ipv4_long(target):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
518
    """ Attempt to return a IPv4 long-range list from a target string. """
519
520
    splitted = target.split('-')
521
    if len(splitted) != 2:
522
        return None
523
    try:
524
        start_packed = inet_pton(socket.AF_INET, splitted[0])
525
        end_packed = inet_pton(socket.AF_INET, splitted[1])
526
    except socket.error:
527
        return None
528
    if end_packed < start_packed:
529
        return None
530
    return ipv4_range_to_list(start_packed, end_packed)
531
532
533 View Code Duplication
def ipv6_range_to_list(start_packed, end_packed):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
534
    """ Return a list of IPv6 entries from start_packed to end_packed. """
535
536
    new_list = list()
537
    start = int(binascii.hexlify(start_packed), 16)
538
    end = int(binascii.hexlify(end_packed), 16)
539
    for value in range(start, end + 1):
540
        high = value >> 64
541
        low = value & ((1 << 64) - 1)
542
        new_ip = inet_ntop(socket.AF_INET6,
543
                           struct.pack('!2Q', high, low))
544
        new_list.append(new_ip)
545
    return new_list
546
547
548 View Code Duplication
def target_to_ipv6_short(target):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
549
    """ Attempt to return a IPv6 short-range list from a target string. """
550
551
    splitted = target.split('-')
552
    if len(splitted) != 2:
553
        return None
554
    try:
555
        start_packed = inet_pton(socket.AF_INET6, splitted[0])
556
        end_value = int(splitted[1], 16)
557
    except (socket.error, ValueError):
558
        return None
559
    start_value = int(binascii.hexlify(start_packed[14:]), 16)
560
    if end_value < 0 or end_value > 0xffff or end_value < start_value:
561
        return None
562
    end_packed = start_packed[:14] + struct.pack('!H', end_value)
563
    return ipv6_range_to_list(start_packed, end_packed)
564
565
566 View Code Duplication
def target_to_ipv6_long(target):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
567
    """ Attempt to return a IPv6 long-range list from a target string. """
568
569
    splitted = target.split('-')
570
    if len(splitted) != 2:
571
        return None
572
    try:
573
        start_packed = inet_pton(socket.AF_INET6, splitted[0])
574
        end_packed = inet_pton(socket.AF_INET6, splitted[1])
575
    except socket.error:
576
        return None
577
    if end_packed < start_packed:
578
        return None
579
    return ipv6_range_to_list(start_packed, end_packed)
580
581
582
def target_to_hostname(target):
583
    """ Attempt to return a single hostname list from a target string. """
584
585
    if len(target) == 0 or len(target) > 255:
586
        return None
587
    if not re.match(r'^[\w.-]+$', target):
588
        return None
589
    return [target]
590
591
592 View Code Duplication
def target_to_list(target):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
593
    """ Attempt to return a list of single hosts from a target string. """
594
595
    # Is it an IPv4 address ?
596
    new_list = target_to_ipv4(target)
597
    # Is it an IPv6 address ?
598
    if not new_list:
599
        new_list = target_to_ipv6(target)
600
    # Is it an IPv4 CIDR ?
601
    if not new_list:
602
        new_list = target_to_ipv4_cidr(target)
603
    # Is it an IPv6 CIDR ?
604
    if not new_list:
605
        new_list = target_to_ipv6_cidr(target)
606
    # Is it an IPv4 short-range ?
607
    if not new_list:
608
        new_list = target_to_ipv4_short(target)
609
    # Is it an IPv4 long-range ?
610
    if not new_list:
611
        new_list = target_to_ipv4_long(target)
612
    # Is it an IPv6 short-range ?
613
    if not new_list:
614
        new_list = target_to_ipv6_short(target)
615
    # Is it an IPv6 long-range ?
616
    if not new_list:
617
        new_list = target_to_ipv6_long(target)
618
    # Is it a hostname ?
619
    if not new_list:
620
        new_list = target_to_hostname(target)
621
    return new_list
622
623
624 View Code Duplication
def target_str_to_list(target_str):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
625
    """ Parses a targets string into a list of individual targets. """
626
    new_list = list()
627
    for target in target_str.split(','):
628
        target = target.strip()
629
        target_list = target_to_list(target)
630
        if target_list:
631
            new_list.extend(target_list)
632
        else:
633
            LOGGER.info("{0}: Invalid target value".format(target))
634
            return None
635
    return list(collections.OrderedDict.fromkeys(new_list))
636
637
638
def resolve_hostname(hostname):
639
    """ Returns IP of a hostname. """
640
641
    assert hostname
642
    try:
643
        return socket.gethostbyname(hostname)
644
    except socket.gaierror:
645
        return None
646
647
648 View Code Duplication
def get_hostname_by_address(address):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
649
    """ Returns hostname of an address. """
650
651
    if not address:
652
        return ''
653
    try:
654
        hostname = socket.getfqdn(address)
655
    except (socket.gaierror, socket.herror):
656
        return ''
657
658
    if hostname == address:
659
        return ''
660
    return hostname
661
662
663 View Code Duplication
def port_range_expand(portrange):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
664
    """
665
    Receive a port range and expands it in individual ports.
666
667
    @input Port range.
668
    e.g. "4-8"
669
670
    @return List of integers.
671
    e.g. [4, 5, 6, 7, 8]
672
    """
673
    if not portrange or '-' not in portrange:
674
        LOGGER.info("Invalid port range format")
675
        return None
676
    port_list = list()
677
    for single_port in range(int(portrange[:portrange.index('-')]),
678
                             int(portrange[portrange.index('-') + 1:]) + 1):
679
        port_list.append(single_port)
680
    return port_list
681
682
683 View Code Duplication
def port_str_arrange(ports):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
684
    """ Gives a str in the format (always tcp listed first).
685
    T:<tcp ports/portrange comma separated>U:<udp ports comma separated>
686
    """
687
    b_tcp = ports.find("T")
688
    b_udp = ports.find("U")
689
    if (b_udp != -1 and b_tcp != -1) and b_udp < b_tcp:
690
        return ports[b_tcp:] + ports[b_udp:b_tcp]
691
692
    return ports
693
694
695 View Code Duplication
def ports_str_check_failed(port_str):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
696
    """
697
    Check if the port string is well formed.
698
    Return True if fail, False other case.
699
    """
700
701
    pattern = r'[^TU:0-9, \-]'
702
    if (
703
        re.search(pattern, port_str)
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
704
        or port_str.count('T') > 1
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
705
        or port_str.count('U') > 1
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
706
        or port_str.count(':') < (port_str.count('T') + port_str.count('U'))
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
707
    ):
708
        return True
709
    return False
710
711
712 View Code Duplication
def ports_as_list(port_str):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
713
    """
714
    Parses a ports string into two list of individual tcp and udp ports.
715
716
    @input string containing a port list
717
    e.g. T:1,2,3,5-8 U:22,80,600-1024
718
719
    @return two list of sorted integers, for tcp and udp ports respectively.
720
    """
721
    if not port_str:
722
        LOGGER.info("Invalid port value")
723
        return [None, None]
724
725
    if ports_str_check_failed(port_str):
726
        LOGGER.info("{0}: Port list malformed.")
727
        return [None, None]
728
729
    tcp_list = list()
730
    udp_list = list()
731
    ports = port_str.replace(' ', '')
732
    b_tcp = ports.find("T")
733
    b_udp = ports.find("U")
734
735
    if ports[b_tcp - 1] == ',':
736
        ports = ports[:b_tcp - 1] + ports[b_tcp:]
737
    if ports[b_udp - 1] == ',':
738
        ports = ports[:b_udp - 1] + ports[b_udp:]
739
    ports = port_str_arrange(ports)
740
741
    tports = ''
742
    uports = ''
743
    # TCP ports listed first, then UDP ports
744
    if b_udp != -1 and b_tcp != -1:
745
        tports = ports[ports.index('T:') + 2:ports.index('U:')]
746
        uports = ports[ports.index('U:') + 2:]
747
    # Only UDP ports
748
    elif b_tcp == -1 and b_udp != -1:
749
        uports = ports[ports.index('U:') + 2:]
750
    # Only TCP ports
751
    elif b_udp == -1 and b_tcp != -1:
752
        tports = ports[ports.index('T:') + 2:]
753
    else:
754
        tports = ports
755
756
    if tports:
757
        for port in tports.split(','):
758
            if '-' in port:
759
                tcp_list.extend(port_range_expand(port))
760
            else:
761
                tcp_list.append(int(port))
762
        tcp_list.sort()
763
    if uports:
764
        for port in uports.split(','):
765
            if '-' in port:
766
                udp_list.extend(port_range_expand(port))
767
            else:
768
                udp_list.append(int(port))
769
        udp_list.sort()
770
771
    return (tcp_list, udp_list)
772
773
774
def get_tcp_port_list(port_str):
775
    """ Return a list with tcp ports from a given port list in string format """
776
    return ports_as_list(port_str)[0]
777
778
779
def get_udp_port_list(port_str):
780
    """ Return a list with udp ports from a given port list in string format """
781
    return ports_as_list(port_str)[1]
782
783
784 View Code Duplication
def port_list_compress(port_list):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
785
    """ Compress a port list and return a string. """
786
787
    if not port_list or len(port_list) == 0:
788
        LOGGER.info("Invalid or empty port list.")
789
        return ''
790
791
    port_list = sorted(set(port_list))
792
    compressed_list = []
793
    for key, group in itertools.groupby(enumerate(port_list),
794
                                        lambda t: t[1] - t[0]):
795
        group = list(group)
796
        if group[0][1] == group[-1][1]:
797
            compressed_list.append(str(group[0][1]))
798
        else:
799
            compressed_list.append(str(group[0][1]) + '-' + str(group[-1][1]))
800
801
    return ','.join(compressed_list)
802
803
804
def valid_uuid(value):
805
    """ Check if value is a valid UUID. """
806
807
    try:
808
        uuid.UUID(value, version=4)
809
        return True
810
    except (TypeError, ValueError, AttributeError):
811
        return False
812
813
814 View Code Duplication
def create_args_parser(description):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
815
    """ Create a command-line arguments parser for OSPD. """
816
817
    parser = argparse.ArgumentParser(description=description)
818
819
    def network_port(string):
820
        """ Check if provided string is a valid network port. """
821
822
        value = int(string)
823
        if not 0 < value <= 65535:
824
            raise argparse.ArgumentTypeError(
825
                'port must be in ]0,65535] interval')
826
        return value
827
828
    def cacert_file(cacert):
829
        """ Check if provided file is a valid CA Certificate """
830
        try:
831
            context = ssl.create_default_context(cafile=cacert)
832
        except AttributeError:
833
            # Python version < 2.7.9
834
            return cacert
835
        except IOError:
836
            raise argparse.ArgumentTypeError('CA Certificate not found')
837
        try:
838
            not_after = context.get_ca_certs()[0]['notAfter']
839
            not_after = ssl.cert_time_to_seconds(not_after)
840
            not_before = context.get_ca_certs()[0]['notBefore']
841
            not_before = ssl.cert_time_to_seconds(not_before)
842
        except (KeyError, IndexError):
843
            raise argparse.ArgumentTypeError('CA Certificate is erroneous')
844
        if not_after < int(time.time()):
845
            raise argparse.ArgumentTypeError('CA Certificate expired')
846
        if not_before > int(time.time()):
847
            raise argparse.ArgumentTypeError('CA Certificate not active yet')
848
        return cacert
849
850
    def log_level(string):
851
        """ Check if provided string is a valid log level. """
852
853
        value = getattr(logging, string.upper(), None)
854
        if not isinstance(value, int):
855
            raise argparse.ArgumentTypeError(
856
                'log level must be one of {debug,info,warning,error,critical}')
857
        return value
858
859
    def filename(string):
860
        """ Check if provided string is a valid file path. """
861
862
        if not os.path.isfile(string):
863
            raise argparse.ArgumentTypeError(
864
                '%s is not a valid file path' % string)
865
        return string
866
867
    parser.add_argument('-p', '--port', default=PORT, type=network_port,
868
                        help='TCP Port to listen on. Default: {0}'.format(PORT))
869
    parser.add_argument('-b', '--bind-address', default=ADDRESS,
870
                        help='Address to listen on. Default: {0}'
871
                        .format(ADDRESS))
872
    parser.add_argument('-u', '--unix-socket',
873
                        help='Unix file socket to listen on.')
874
    parser.add_argument('-k', '--key-file', type=filename,
875
                        help='Server key file. Default: {0}'.format(KEY_FILE))
876
    parser.add_argument('-c', '--cert-file', type=filename,
877
                        help='Server cert file. Default: {0}'.format(CERT_FILE))
878
    parser.add_argument('--ca-file', type=cacert_file,
879
                        help='CA cert file. Default: {0}'.format(CA_FILE))
880
    parser.add_argument('-L', '--log-level', default='warning', type=log_level,
881
                        help='Wished level of logging. Default: WARNING')
882
    parser.add_argument('--foreground', action='store_true',
883
                        help='Run in foreground and logs all messages to console.')
884
    parser.add_argument('-l', '--log-file', type=filename,
885
                        help='Path to the logging file.')
886
    parser.add_argument('--version', action='store_true',
887
                        help='Print version then exit.')
888
    parser.add_argument('--niceness', default=NICENESS, type=int,
889
                        help='Start the scan with the given niceness. Default %(default)s')
890
891
    return parser
892
893
894
def go_to_background():
895
    """ Daemonize the running process. """
896
    try:
897
        if os.fork():
898
            sys.exit()
899
    except OSError as errmsg:
900
        LOGGER.error('Fork failed: {0}'.format(errmsg))
901
        sys.exit('Fork failed')
902
903
904 View Code Duplication
def get_common_args(parser, args=None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
905
    """ Return list of OSPD common command-line arguments from parser, after
906
    validating provided values or setting default ones.
907
908
    """
909
910
    options = parser.parse_args(args)
911
    # TCP Port to listen on.
912
    port = options.port
913
914
    # Network address to bind listener to
915
    address = options.bind_address
916
917
    # Unix file socket to listen on
918
    unix_socket = options.unix_socket
919
920
    # Debug level.
921
    log_level = options.log_level
922
923
    # Server key path.
924
    keyfile = options.key_file or KEY_FILE
925
926
    # Server cert path.
927
    certfile = options.cert_file or CERT_FILE
928
929
    # CA cert path.
930
    cafile = options.ca_file or CA_FILE
931
932
    common_args = dict()
933
    common_args['port'] = port
934
    common_args['address'] = address
935
    common_args['unix_socket'] = unix_socket
936
    common_args['keyfile'] = keyfile
937
    common_args['certfile'] = certfile
938
    common_args['cafile'] = cafile
939
    common_args['log_level'] = log_level
940
    common_args['foreground'] = options.foreground
941
    common_args['log_file'] = options.log_file
942
    common_args['version'] = options.version
943
    common_args['niceness'] = options.niceness
944
945
    return common_args
946
947
948 View Code Duplication
def print_version(wrapper):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
949
    """ Prints the server version and license information."""
950
951
    scanner_name = wrapper.get_scanner_name()
952
    server_version = wrapper.get_server_version()
953
    print("OSP Server for {0} version {1}".format(scanner_name, server_version))
954
    protocol_version = wrapper.get_protocol_version()
955
    print("OSP Version: {0}".format(protocol_version))
956
    daemon_name = wrapper.get_daemon_name()
957
    daemon_version = wrapper.get_daemon_version()
958
    print("Using: {0} {1}".format(daemon_name, daemon_version))
959
    print("Copyright (C) 2014, 2015 Greenbone Networks GmbH\n"
960
          "License GPLv2+: GNU GPL version 2 or later\n"
961
          "This is free software: you are free to change"
962
          " and redistribute it.\n"
963
          "There is NO WARRANTY, to the extent permitted by law.")
964
965
966 View Code Duplication
def main(name, klass):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
967
    """ OSPD Main function. """
968
969
    # Common args parser.
970
    parser = create_args_parser(name)
971
972
    # Common args
973
    cargs = get_common_args(parser)
974
    logging.getLogger().setLevel(cargs['log_level'])
975
    wrapper = klass(certfile=cargs['certfile'], keyfile=cargs['keyfile'],
976
                    cafile=cargs['cafile'], niceness=cargs['niceness'])
977
978
    if cargs['version']:
979
        print_version(wrapper)
980
        sys.exit()
981
982
    if cargs['foreground']:
983
        console = logging.StreamHandler()
984
        console.setFormatter(
985
            logging.Formatter(
986
                '%(asctime)s %(name)s: %(levelname)s: %(message)s'))
987
        logging.getLogger().addHandler(console)
988
    elif cargs['log_file']:
989
        logfile = logging.handlers.WatchedFileHandler(cargs['log_file'])
990
        logfile.setFormatter(
991
            logging.Formatter(
992
                '%(asctime)s %(name)s: %(levelname)s: %(message)s'))
993
        logging.getLogger().addHandler(logfile)
994
        go_to_background()
995
    else:
996
        syslog = logging.handlers.SysLogHandler('/dev/log')
997
        syslog.setFormatter(
998
            logging.Formatter('%(name)s: %(levelname)s: %(message)s'))
999
        logging.getLogger().addHandler(syslog)
1000
        # Duplicate syslog's file descriptor to stout/stderr.
1001
        syslog_fd = syslog.socket.fileno()
1002
        os.dup2(syslog_fd, 1)
1003
        os.dup2(syslog_fd, 2)
1004
        go_to_background()
1005
1006
    if not wrapper.check():
1007
        return 1
1008
    return wrapper.run(cargs['address'], cargs['port'], cargs['unix_socket'])
1009