| 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 |