Completed
Push — master ( 6f2eae...4bbe71 )
by Juan José
19s queued 10s
created

ospd.misc.ScanCollection.create_scan()   B

Complexity

Conditions 5

Size

Total Lines 25
Code Lines 23

Duplication

Lines 25
Ratio 100 %

Importance

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