Total Complexity | 41 |
Total Lines | 292 |
Duplicated Lines | 81.85 % |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like ospd_openvas.db often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
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): |
|
|
|||
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 |
||
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 |