Completed
Push — master ( 71e7de...cd3918 )
by Juan José
13s
created

ospd_openvas.db.OpenvasDB.release_db()   A

Complexity

Conditions 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 8
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nop 2
dl 8
loc 8
rs 10
c 0
b 0
f 0
1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2018 Greenbone Networks GmbH
3
#
4
# SPDX-License-Identifier: GPL-2.0-or-later
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
20
""" Access management for redis-based OpenVAS Scanner Database."""
21
22
import redis
23
import subprocess
24
25
from ospd_openvas.errors import OSPDOpenvasError
26
from ospd.ospd import logger
27
28
SOCKET_TIMEOUT = 60  # in seconds
29
LIST_FIRST_POS = 0
30
LIST_LAST_POS = -1
31
LIST_ALL = 0
32
33
# Possible positions of nvt values in cache list.
34
NVT_META_FIELDS = [
35
    "NVT_FILENAME_POS",
36
    "NVT_REQUIRED_KEYS_POS",
37
    "NVT_MANDATORY_KEYS_POS",
38
    "NVT_EXCLUDED_KEYS_POS",
39
    "NVT_REQUIRED_UDP_PORTS_POS",
40
    "NVT_REQUIRED_PORTS_POS",
41
    "NVT_DEPENDENCIES_POS",
42
    "NVT_TAGS_POS",
43
    "NVT_CVES_POS",
44
    "NVT_BIDS_POS",
45
    "NVT_XREFS_POS",
46
    "NVT_CATEGORY_POS",
47
    "NVT_TIMEOUT_POS",
48
    "NVT_FAMILY_POS",
49
    "NVT_NAME_POS",
50
]
51
52
53 View Code Duplication
class OpenvasDB(object):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
54
    """ Class to connect to redis, to perform queries, and to move
55
    from a KB to another."""
56
    # Name of the namespace usage bitmap in redis.
57
    DBINDEX_NAME = "GVM.__GlobalDBIndex"
58
59
    def __init__(self):
60
        # Path to the Redis socket.
61
        self.db_address = None
62
63
        self.max_dbindex = 0
64
        self.db_index = 0
65
        self.rediscontext = None
66
67
    @staticmethod
68
    def _parse_openvassd_db_address(result):
69
        """ Return the path to the redis socket.
70
        Arguments:
71
            result (bytes) Output of `openvassd -s`
72
        Return redis unix socket path.
73
        """
74
        path = None
75
        result = result.decode('ascii')
76
        for conf in result.split('\n'):
77
            if conf.find('db_address') == 0:
78
                path = conf.split('=')
79
                break
80
81
        if not path:
82
            raise OSPDOpenvasError('Redis Error: Not possible to '
83
                                   'find the path to the redis socket.')
84
        return path[1].strip()
85
86
    def get_db_connection(self):
87
        """ Retrieve the db address from openvassd config.
88
        """
89
        result = subprocess.check_output(
90
            ['openvassd', '-s'], stderr=subprocess.STDOUT)
91
92
        if result:
93
            path = self._parse_openvassd_db_address(result)
94
95
        self.db_address = path
0 ignored issues
show
introduced by
The variable path does not seem to be defined in case result on line 92 is False. Are you sure this can never be the case?
Loading history...
96
97
    def max_db_index(self):
98
        """Set the number of databases have been configured into kbr struct.
99
        """
100
        ctx = self.kb_connect()
101
        resp = ctx.config_get('databases')
102
103
        if len(resp) == 1:
104
            self.max_dbindex = int(resp.get('databases'))
105
        else:
106
            raise OSPDOpenvasError('Redis Error: Not possible '
107
                                   'to get max_dbindex.')
108
109
    def set_redisctx(self, ctx):
110
        """ Set the current rediscontext.
111
        """
112
        self.rediscontext = ctx
113
114
    def db_init(self):
115
        """ Set db_address and max_db_index. """
116
        self.get_db_connection()
117
        self.max_db_index()
118
119
    def try_database_index(self, ctx, kb):
120
        """ Check if it is already in use. If not set it as in use and return.
121
        """
122
        _IN_USE = 1
123
        try:
124
            resp = ctx.hsetnx(self.DBINDEX_NAME, kb, _IN_USE)
125
        except:
126
            raise OSPDOpenvasError('Redis Error: Not possible '
127
                                   'to set %s.' % self.DBINDEX_NAME)
128
129
        if resp == 1:
130
            return True
131
        return False
132
133
    def kb_connect(self, dbnum=0):
134
        """ Connect to redis to the given database or to the default db 0 .
135
        Arguments:
136
            dbnum (int): The db number to connect to.
137
138
        Return a redis context on success or 2 on error
139
        """
140
        self.get_db_connection()
141
142
        try:
143
            ctx = redis.Redis(unix_socket_path=self.db_address,
144
                              db=dbnum,
145
                              socket_timeout=SOCKET_TIMEOUT, charset="latin-1",
146
                              decode_responses=True)
147
        except ConnectionError as e:
148
            raise OSPDOpenvasError('Redis Error: Not possible '
149
                                   'to connect to the kb.') from e
150
        self.db_index = dbnum
151
        return ctx
152
153
    def db_find(self, patt):
154
        """ Search a pattern inside all kbs. When find it return it.
155
        """
156
        for i in range(0, self.max_dbindex):
157
            ctx = self.kb_connect(i)
158
            if ctx.keys(patt):
159
                return ctx
160
161
        return None
162
163
    def kb_new(self):
164
        """ Return a new kb context to an empty kb.
165
        """
166
        ctx = self.db_find(self.DBINDEX_NAME)
167
        for index in range(1, self.max_dbindex):
168
            if self.try_database_index(ctx, index):
169
                ctx = self.kb_connect(index)
170
                return ctx
171
172
        return None
173
174
    def get_kb_context(self):
175
        """ Get redis context if it is already connected or do a connection.
176
        """
177
        if self.rediscontext is not None:
178
            return self.rediscontext
179
180
        self.rediscontext = self.db_find(self.DBINDEX_NAME)
181
182
        if self.rediscontext is None:
183
            raise OSPDOpenvasError('Redis Error: Problem retrieving '
184
                                   'Redis Context')
185
186
        return self.rediscontext
187
188
    def select_kb(self, ctx, kbindex, set_global=False):
189
        """ Use an existent redis connection and select a redis kb.
190
        If needed, set the ctx as global.
191
        Parameters:
192
            ctx (redis obj): Redis context to use.
193
            newdb (str):  The new kb to select
194
            set_global (bool, optional): If should be the global context.
195
        """
196
        ctx.execute_command('SELECT ' + str(kbindex))
197
        if set_global:
198
            self.set_redisctx(ctx)
199
200
    def get_list_item(self, name):
201
        """ Get all values under a KB key list.
202
        The right rediscontext must be already set.
203
        """
204
        ctx = self.get_kb_context()
205
        return ctx.lrange(name, start=LIST_FIRST_POS, end=LIST_LAST_POS)
206
207
    def remove_list_item(self, key, value):
208
        """ Remove item from the key list.
209
        The right rediscontext must be already set.
210
        """
211
        ctx = self.get_kb_context()
212
        ctx.lrem(key, count=LIST_ALL, value=value)
213
214
    def get_single_item(self, name):
215
        """ Get a single KB element. The right rediscontext must be
216
        already set.
217
        """
218
        ctx = self.get_kb_context()
219
        return ctx.lindex(name, index=LIST_FIRST_POS)
220
221
    def add_single_item(self, name, values):
222
        """ Add a single KB element with one or more values.
223
        The right rediscontext must be already set.
224
        """
225
        ctx = self.get_kb_context()
226
        ctx.rpush(name, *set(values))
227
228
    def set_single_item(self, name, value):
229
        """ Set (replace) a new single KB element. The right
230
        rediscontext must be already set.
231
        """
232
        ctx = self.get_kb_context()
233
        pipe = ctx.pipeline()
234
        pipe.delete(name)
235
        pipe.rpush(name, *set(value))
236
        pipe.execute()
237
238
    def get_pattern(self, pattern):
239
        """ Get all items stored under a given pattern.
240
        """
241
        ctx = self.get_kb_context()
242
        items = ctx.keys(pattern)
243
244
        elem_list = []
245
        for item in items:
246
            elem_list.append([
247
                item,
248
                ctx.lrange(item, start=LIST_FIRST_POS, end=LIST_LAST_POS),
249
            ])
250
        return elem_list
251
252
    def get_elem_pattern_by_index(self, pattern, index=1):
253
        """ Get all items with index 'index', stored under
254
        a given pattern.
255
        """
256
        ctx = self.get_kb_context()
257
        items = ctx.keys(pattern)
258
259
        elem_list = []
260
        for item in items:
261
            elem_list.append([item, ctx.lindex(item, index)])
262
        return elem_list
263
264
    def release_db(self, kbindex=0):
265
        """ Connect to redis and select the db by index.
266
        Flush db and delete the index from dbindex_name list.
267
        """
268
        ctx = self.kb_connect(kbindex)
269
        ctx.flushdb()
270
        ctx = self.kb_connect()
271
        ctx.hdel(self.DBINDEX_NAME, kbindex)
272
273
    def get_result(self):
274
        """ Get and remove the oldest result from the list. """
275
        ctx = self.get_kb_context()
276
        return ctx.rpop("internal/results")
277
278
    def get_status(self):
279
        """ Get and remove the oldest host scan status from the list. """
280
        ctx = self.get_kb_context()
281
        return ctx.rpop("internal/status")
282
283
    def get_host_scan_scan_start_time(self):
284
        """ Get the timestamp of the scan start from redis. """
285
        ctx = self.get_kb_context()
286
        return ctx.rpop("internal/start_time")
287
288
    def get_host_scan_scan_end_time(self):
289
        """ Get the timestamp of the scan end from redis. """
290
        ctx = self.get_kb_context()
291
        return ctx.rpop("internal/end_time")
292