python-gvm 24.3.0__py3-none-any.whl → 24.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of python-gvm might be problematic. Click here for more details.

Files changed (111) hide show
  1. gvm/__version__.py +1 -1
  2. gvm/_enum.py +14 -5
  3. gvm/connections/__init__.py +32 -0
  4. gvm/connections/_connection.py +124 -0
  5. gvm/connections/_debug.py +74 -0
  6. gvm/connections/_ssh.py +352 -0
  7. gvm/connections/_tls.py +104 -0
  8. gvm/connections/_unix.py +54 -0
  9. gvm/errors.py +22 -8
  10. gvm/protocols/__init__.py +2 -7
  11. gvm/protocols/_protocol.py +136 -0
  12. gvm/protocols/core/__init__.py +15 -0
  13. gvm/protocols/core/_connection.py +201 -0
  14. gvm/protocols/core/_request.py +16 -0
  15. gvm/protocols/core/_response.py +115 -0
  16. gvm/protocols/gmp/__init__.py +30 -0
  17. gvm/protocols/gmp/_gmp.py +118 -0
  18. gvm/protocols/gmp/_gmp224.py +4300 -0
  19. gvm/protocols/gmp/_gmp225.py +95 -0
  20. gvm/protocols/gmp/requests/__init__.py +11 -0
  21. gvm/protocols/gmp/requests/_entity_id.py +8 -0
  22. gvm/protocols/gmp/requests/_version.py +15 -0
  23. gvm/protocols/gmp/requests/v224/__init__.py +124 -0
  24. gvm/protocols/{gmpv208/system/aggregates.py → gmp/requests/v224/_aggregates.py} +55 -53
  25. gvm/protocols/{gmpv208/entities/alerts.py → gmp/requests/v224/_alerts.py} +172 -213
  26. gvm/protocols/{gmpv208/entities/audits.py → gmp/requests/v224/_audits.py} +173 -244
  27. gvm/protocols/gmp/requests/v224/_auth.py +77 -0
  28. gvm/protocols/gmp/requests/v224/_cert_bund_advisories.py +67 -0
  29. gvm/protocols/gmp/requests/v224/_cpes.py +68 -0
  30. gvm/protocols/{gmpv208/entities/credentials.py → gmp/requests/v224/_credentials.py} +125 -121
  31. gvm/protocols/gmp/requests/v224/_cves.py +68 -0
  32. gvm/protocols/gmp/requests/v224/_dfn_cert_advisories.py +68 -0
  33. gvm/protocols/{gmpv208/entities/entities.py → gmp/requests/v224/_entity_type.py} +1 -2
  34. gvm/protocols/gmp/requests/v224/_feed.py +46 -0
  35. gvm/protocols/{gmpv208/entities/filter.py → gmp/requests/v224/_filters.py} +54 -69
  36. gvm/protocols/{gmpv208/entities/groups.py → gmp/requests/v224/_groups.py} +51 -58
  37. gvm/protocols/{gmpv208/system/help.py → gmp/requests/v224/_help.py} +9 -17
  38. gvm/protocols/{gmpv208/entities/hosts.py → gmp/requests/v224/_hosts.py} +41 -42
  39. gvm/protocols/gmp/requests/v224/_notes.py +230 -0
  40. gvm/protocols/gmp/requests/v224/_nvts.py +253 -0
  41. gvm/protocols/{gmpv208/entities/operating_systems.py → gmp/requests/v224/_operating_systems.py} +39 -43
  42. gvm/protocols/{gmpv208/entities/overrides.py → gmp/requests/v224/_overrides.py} +115 -174
  43. gvm/protocols/{gmpv208/entities/permissions.py → gmp/requests/v224/_permissions.py} +87 -102
  44. gvm/protocols/{gmpv208/entities/policies.py → gmp/requests/v224/_policies.py} +124 -130
  45. gvm/protocols/{gmpv208/entities/port_lists.py → gmp/requests/v224/_port_lists.py} +68 -80
  46. gvm/protocols/{gmpv208/entities/report_formats.py → gmp/requests/v224/_report_formats.py} +65 -88
  47. gvm/protocols/{gmpv208/entities/reports.py → gmp/requests/v224/_reports.py} +45 -55
  48. gvm/protocols/{gmpv208/entities/results.py → gmp/requests/v224/_results.py} +20 -21
  49. gvm/protocols/{gmpv208/entities/roles.py → gmp/requests/v224/_roles.py} +48 -57
  50. gvm/protocols/{gmpv224/entities/scan_configs.py → gmp/requests/v224/_scan_configs.py} +148 -220
  51. gvm/protocols/{gmpv208/entities/scanners.py → gmp/requests/v224/_scanners.py} +143 -162
  52. gvm/protocols/{gmpv208/entities/schedules.py → gmp/requests/v224/_schedules.py} +56 -60
  53. gvm/protocols/gmp/requests/v224/_secinfo.py +100 -0
  54. gvm/protocols/{gmpv208/system/system_reports.py → gmp/requests/v224/_system_reports.py} +15 -13
  55. gvm/protocols/{gmpv208/entities/tags.py → gmp/requests/v224/_tags.py} +75 -92
  56. gvm/protocols/{gmpv208/entities/targets.py → gmp/requests/v224/_targets.py} +178 -177
  57. gvm/protocols/{gmpv208/entities/tasks.py → gmp/requests/v224/_tasks.py} +129 -193
  58. gvm/protocols/{gmpv208/entities/tickets.py → gmp/requests/v224/_tickets.py} +65 -76
  59. gvm/protocols/{gmpv208/entities/tls_certificates.py → gmp/requests/v224/_tls_certificates.py} +50 -60
  60. gvm/protocols/gmp/requests/v224/_trashcan.py +39 -0
  61. gvm/protocols/{gmpv208/system/user_settings.py → gmp/requests/v224/_user_settings.py} +25 -27
  62. gvm/protocols/gmp/requests/v224/_users.py +235 -0
  63. gvm/protocols/gmp/requests/v224/_vulnerabilities.py +46 -0
  64. gvm/protocols/gmp/requests/v225/__init__.py +144 -0
  65. gvm/protocols/{gmpv225/entities/resourcenames.py → gmp/requests/v225/_resource_names.py} +31 -43
  66. gvm/protocols/latest.py +3 -51
  67. gvm/protocols/next.py +3 -51
  68. gvm/protocols/ospv1.py +83 -91
  69. gvm/transforms.py +10 -8
  70. gvm/utils.py +105 -24
  71. gvm/xml.py +109 -47
  72. {python_gvm-24.3.0.dist-info → python_gvm-24.7.0.dist-info}/METADATA +10 -5
  73. python_gvm-24.7.0.dist-info/RECORD +78 -0
  74. gvm/connections.py +0 -636
  75. gvm/protocols/base.py +0 -129
  76. gvm/protocols/gmp.py +0 -119
  77. gvm/protocols/gmpv208/__init__.py +0 -183
  78. gvm/protocols/gmpv208/entities/__init__.py +0 -4
  79. gvm/protocols/gmpv208/entities/notes.py +0 -264
  80. gvm/protocols/gmpv208/entities/scan_configs.py +0 -626
  81. gvm/protocols/gmpv208/entities/secinfo.py +0 -620
  82. gvm/protocols/gmpv208/entities/users.py +0 -278
  83. gvm/protocols/gmpv208/entities/vulnerabilities.py +0 -51
  84. gvm/protocols/gmpv208/system/__init__.py +0 -4
  85. gvm/protocols/gmpv208/system/authentication.py +0 -101
  86. gvm/protocols/gmpv208/system/feed.py +0 -55
  87. gvm/protocols/gmpv208/system/trashcan.py +0 -42
  88. gvm/protocols/gmpv208/system/version.py +0 -31
  89. gvm/protocols/gmpv214/__init__.py +0 -187
  90. gvm/protocols/gmpv214/entities/__init__.py +0 -4
  91. gvm/protocols/gmpv214/entities/notes.py +0 -167
  92. gvm/protocols/gmpv214/entities/overrides.py +0 -196
  93. gvm/protocols/gmpv214/entities/scanners.py +0 -204
  94. gvm/protocols/gmpv214/entities/targets.py +0 -245
  95. gvm/protocols/gmpv214/entities/users.py +0 -110
  96. gvm/protocols/gmpv214/system/__init__.py +0 -4
  97. gvm/protocols/gmpv214/system/version.py +0 -23
  98. gvm/protocols/gmpv224/__init__.py +0 -189
  99. gvm/protocols/gmpv224/entities/__init__.py +0 -4
  100. gvm/protocols/gmpv224/entities/scanners.py +0 -201
  101. gvm/protocols/gmpv224/entities/users.py +0 -176
  102. gvm/protocols/gmpv224/system/__init__.py +0 -4
  103. gvm/protocols/gmpv224/system/version.py +0 -23
  104. gvm/protocols/gmpv225/__init__.py +0 -197
  105. gvm/protocols/gmpv225/entities/__init__.py +0 -4
  106. gvm/protocols/gmpv225/system/__init__.py +0 -4
  107. gvm/protocols/gmpv225/system/version.py +0 -22
  108. python_gvm-24.3.0.dist-info/RECORD +0 -80
  109. /gvm/protocols/{gmpv208/entities/severity.py → gmp/requests/v224/_severity.py} +0 -0
  110. {python_gvm-24.3.0.dist-info → python_gvm-24.7.0.dist-info}/LICENSE +0 -0
  111. {python_gvm-24.3.0.dist-info → python_gvm-24.7.0.dist-info}/WHEEL +0 -0
gvm/__version__.py CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  # THIS IS AN AUTOGENERATED FILE. DO NOT TOUCH!
4
4
 
5
- __version__ = "24.3.0"
5
+ __version__ = "24.7.0"
gvm/_enum.py CHANGED
@@ -4,21 +4,27 @@
4
4
  #
5
5
 
6
6
  from enum import Enum as PythonEnum
7
- from typing import Optional
8
-
9
- from typing_extensions import Self
7
+ from typing import Any, Optional, Type, TypeVar
10
8
 
11
9
  from gvm.errors import InvalidArgument
12
10
 
11
+ Self = TypeVar("Self", bound="Enum")
12
+
13
13
 
14
14
  class Enum(PythonEnum):
15
15
  """
16
16
  Base class for Enums in python-gvm
17
17
  """
18
18
 
19
+ @classmethod
20
+ def _missing_(cls: Type[Self], value: Any) -> Optional[Self]:
21
+ if isinstance(value, PythonEnum):
22
+ return cls.from_string(value.name)
23
+ return cls.from_string(str(value) if value else None)
24
+
19
25
  @classmethod
20
26
  def from_string(
21
- cls,
27
+ cls: Type[Self],
22
28
  value: Optional[str],
23
29
  ) -> Optional[Self]:
24
30
  """
@@ -33,6 +39,9 @@ class Enum(PythonEnum):
33
39
  return cls[value.replace(" ", "_").upper()]
34
40
  except KeyError:
35
41
  raise InvalidArgument(
36
- f"Invalid argument {value} for {cls.__name__}.from_string. "
42
+ f"Invalid argument {value}. "
37
43
  f"Allowed values are {','.join(e.name for e in cls)}."
38
44
  ) from None
45
+
46
+ def __str__(self) -> str:
47
+ return self.value
@@ -0,0 +1,32 @@
1
+ # SPDX-FileCopyrightText: 2024 Greenbone AG
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ from ._connection import DEFAULT_TIMEOUT, GvmConnection
6
+ from ._debug import DebugConnection
7
+ from ._ssh import (
8
+ DEFAULT_HOSTNAME,
9
+ DEFAULT_KNOWN_HOSTS_FILE,
10
+ DEFAULT_SSH_PASSWORD,
11
+ DEFAULT_SSH_PORT,
12
+ DEFAULT_SSH_USERNAME,
13
+ SSHConnection,
14
+ )
15
+ from ._tls import DEFAULT_GVM_PORT, TLSConnection
16
+ from ._unix import DEFAULT_UNIX_SOCKET_PATH, UnixSocketConnection
17
+
18
+ __all__ = (
19
+ "DEFAULT_TIMEOUT",
20
+ "DEFAULT_UNIX_SOCKET_PATH",
21
+ "DEFAULT_GVM_PORT",
22
+ "DEFAULT_HOSTNAME",
23
+ "DEFAULT_KNOWN_HOSTS_FILE",
24
+ "DEFAULT_SSH_PASSWORD",
25
+ "DEFAULT_SSH_USERNAME",
26
+ "DEFAULT_SSH_PORT",
27
+ "DebugConnection",
28
+ "GvmConnection",
29
+ "SSHConnection",
30
+ "TLSConnection",
31
+ "UnixSocketConnection",
32
+ )
@@ -0,0 +1,124 @@
1
+ # SPDX-FileCopyrightText: 2024 Greenbone AG
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import logging
6
+ import socket as socketlib
7
+ from abc import ABC, abstractmethod
8
+ from time import time
9
+ from typing import Optional, Protocol, Union, runtime_checkable
10
+
11
+ from gvm.errors import GvmError
12
+
13
+ BUF_SIZE = 16 * 1024
14
+
15
+ DEFAULT_TIMEOUT = 60 # in seconds
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @runtime_checkable
21
+ class GvmConnection(Protocol):
22
+ """
23
+ Python `protocol <https://docs.python.org/3/library/typing.html#typing.Protocol>`_
24
+ for GvmConnection classes.
25
+ """
26
+
27
+ def connect(self) -> None:
28
+ """Establish a connection to a remote server"""
29
+
30
+ def disconnect(self) -> None:
31
+ """Send data to the connected remote server
32
+
33
+ Arguments:
34
+ data: Data to be send to the server. Either utf-8 encoded string or
35
+ bytes.
36
+ """
37
+
38
+ def send(self, data: bytes) -> None:
39
+ """Send data to the connected remote server
40
+
41
+ Args:
42
+ data: Data to be send to the server as bytes.
43
+ """
44
+
45
+ def read(self) -> bytes:
46
+ """Read data from the remote server
47
+
48
+ Returns:
49
+ data as bytes
50
+ """
51
+
52
+ def finish_send(self):
53
+ """Indicate to the remote server you are done with sending data"""
54
+
55
+
56
+ class AbstractGvmConnection(ABC):
57
+ """
58
+ Base class for establishing a connection to a remote server daemon.
59
+
60
+ Arguments:
61
+ timeout: Timeout in seconds for the connection. None to
62
+ wait indefinitely
63
+ """
64
+
65
+ def __init__(self, timeout: Optional[Union[int, float]] = DEFAULT_TIMEOUT):
66
+ self._socket: Optional[socketlib.SocketType] = None
67
+ self._timeout = timeout if timeout is not None else DEFAULT_TIMEOUT
68
+
69
+ def _read(self) -> bytes:
70
+ if self._socket is None:
71
+ raise GvmError("Socket is not connected")
72
+
73
+ return self._socket.recv(BUF_SIZE)
74
+
75
+ @abstractmethod
76
+ def connect(self) -> None:
77
+ """Establish a connection to a remote server"""
78
+ raise NotImplementedError
79
+
80
+ def send(self, data: bytes) -> None:
81
+ """Send data to the connected remote server
82
+
83
+ Args:
84
+ data: Data to be send to the server as bytes.
85
+ """
86
+ if self._socket is None:
87
+ raise GvmError("Socket is not connected")
88
+
89
+ self._socket.sendall(data)
90
+
91
+ def read(self) -> bytes:
92
+ """Read data from the remote server
93
+
94
+ Returns:
95
+ data as bytes
96
+ """
97
+ break_timeout = (
98
+ time() + self._timeout if self._timeout is not None else None
99
+ )
100
+
101
+ data = self._read()
102
+
103
+ if not data:
104
+ # Connection was closed by server
105
+ raise GvmError("Remote closed the connection")
106
+
107
+ if break_timeout and time() > break_timeout:
108
+ raise GvmError("Timeout while reading the response")
109
+
110
+ return data
111
+
112
+ def disconnect(self) -> None:
113
+ """Disconnect and close the connection to the remote server"""
114
+ try:
115
+ if self._socket is not None:
116
+ self._socket.close()
117
+ except OSError as e:
118
+ logger.debug("Connection closing error: %s", e)
119
+
120
+ def finish_send(self):
121
+ """Indicate to the remote server you are done with sending data"""
122
+ if self._socket is not None:
123
+ # shutdown socket for sending. only allow reading data afterwards
124
+ self._socket.shutdown(socketlib.SHUT_WR)
@@ -0,0 +1,74 @@
1
+ # SPDX-FileCopyrightText: 2024 Greenbone AG
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import logging
6
+
7
+ from ._connection import GvmConnection
8
+
9
+ logger = logging.getLogger("gvm.connections.debug")
10
+
11
+
12
+ class DebugConnection:
13
+ """Wrapper around a connection for debugging purposes
14
+
15
+ Allows to debug the connection flow including send and read data. Internally
16
+ it uses the python `logging`_ framework to create debug messages. Please
17
+ take a look at `the logging tutorial
18
+ <https://docs.python.org/3/howto/logging.html#logging-basic-tutorial>`_
19
+ for further details.
20
+
21
+ Example:
22
+
23
+ .. code-block:: python
24
+
25
+ import logging
26
+
27
+ logging.basicConfig(level=logging.DEBUG)
28
+
29
+ socket_connection = UnixSocketConnection(path='/var/run/gvm.sock')
30
+ connection = DebugConnection(socket_connection)
31
+ gmp = Gmp(connection=connection)
32
+
33
+ .. _logging:
34
+ https://docs.python.org/3/library/logging.html
35
+ """
36
+
37
+ def __init__(self, connection: GvmConnection):
38
+ """
39
+ Create a new DebugConnection instance.
40
+
41
+ Args:
42
+ connection: GvmConnection to observe
43
+ """
44
+ self._connection = connection
45
+
46
+ def read(self) -> bytes:
47
+ data = self._connection.read()
48
+
49
+ logger.debug("Read %s characters. Data %r", len(data), data)
50
+
51
+ self.last_read_data = data
52
+ return data
53
+
54
+ def send(self, data: bytes) -> None:
55
+ self.last_send_data = data
56
+
57
+ logger.debug("Sending %s characters. Data %r", len(data), data)
58
+
59
+ return self._connection.send(data)
60
+
61
+ def connect(self) -> None:
62
+ logger.debug("Connecting")
63
+
64
+ return self._connection.connect()
65
+
66
+ def disconnect(self) -> None:
67
+ logger.debug("Disconnecting")
68
+
69
+ return self._connection.disconnect()
70
+
71
+ def finish_send(self) -> None:
72
+ logger.debug("Finish send")
73
+
74
+ self._connection.finish_send()
@@ -0,0 +1,352 @@
1
+ # SPDX-FileCopyrightText: 2024 Greenbone AG
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import base64
6
+ import errno
7
+ import hashlib
8
+ import logging
9
+ import socket as socketlib
10
+ import sys
11
+ from os import PathLike
12
+ from pathlib import Path
13
+ from time import time
14
+ from typing import Any, Callable, Optional, TextIO, Union
15
+
16
+ import paramiko
17
+ import paramiko.ssh_exception
18
+ import paramiko.transport
19
+
20
+ from gvm.errors import GvmError
21
+
22
+ from ._connection import BUF_SIZE, DEFAULT_TIMEOUT
23
+
24
+ logger = logging.getLogger("gvm.connections.ssh")
25
+
26
+ DEFAULT_SSH_PORT = 22
27
+ DEFAULT_SSH_USERNAME = "gmp"
28
+ DEFAULT_SSH_PASSWORD = ""
29
+ DEFAULT_HOSTNAME = "127.0.0.1"
30
+ DEFAULT_KNOWN_HOSTS_FILE = ".ssh/known_hosts"
31
+
32
+
33
+ class SSHConnection:
34
+ """
35
+ SSH Class to connect, read and write from GVM via SSH
36
+
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ *,
42
+ timeout: Optional[Union[int, float]] = DEFAULT_TIMEOUT,
43
+ hostname: Optional[str] = DEFAULT_HOSTNAME,
44
+ port: Optional[int] = DEFAULT_SSH_PORT,
45
+ username: Optional[str] = DEFAULT_SSH_USERNAME,
46
+ password: Optional[str] = DEFAULT_SSH_PASSWORD,
47
+ known_hosts_file: Optional[Union[str, PathLike]] = None,
48
+ auto_accept_host: Optional[bool] = None,
49
+ file: TextIO = sys.stdout,
50
+ input: Callable[[], str] = input,
51
+ exit: Callable[[str], Any] = sys.exit,
52
+ ) -> None:
53
+ """
54
+ Create a new SSH connection instance.
55
+
56
+ Args:
57
+ timeout: Timeout in seconds for the connection.
58
+ hostname: DNS name or IP address of the remote server. Default is
59
+ 127.0.0.1.
60
+ port: Port of the remote SSH server. Default is port 22.
61
+ username: Username to use for SSH login. Default is "gmp".
62
+ password: Password to use for SSH login. Default is "".
63
+ """
64
+ self._client: Optional[paramiko.SSHClient] = None
65
+ self.hostname = hostname if hostname is not None else DEFAULT_HOSTNAME
66
+ self.port = int(port) if port is not None else DEFAULT_SSH_PORT
67
+ self.username = (
68
+ username if username is not None else DEFAULT_SSH_USERNAME
69
+ )
70
+ self.password = (
71
+ password if password is not None else DEFAULT_SSH_PASSWORD
72
+ )
73
+ self.known_hosts_file = (
74
+ Path(known_hosts_file)
75
+ if known_hosts_file is not None
76
+ else Path.home() / DEFAULT_KNOWN_HOSTS_FILE
77
+ )
78
+ self.auto_accept_host = auto_accept_host
79
+ self._timeout = timeout
80
+ self._file = file
81
+ self._input = input
82
+ self._exit = exit
83
+
84
+ def _send_all(self, data: bytes) -> int:
85
+ """Returns the sum of sent bytes if success"""
86
+ sent_sum = 0
87
+ while data:
88
+ sent = self._stdin.channel.send(data)
89
+
90
+ if not sent:
91
+ # Connection was closed by server
92
+ raise GvmError("Remote closed the connection")
93
+
94
+ sent_sum += sent
95
+
96
+ data = data[sent:]
97
+ return sent_sum
98
+
99
+ def _auto_accept_host(
100
+ self, hostkeys: paramiko.HostKeys, key: paramiko.PKey
101
+ ) -> None:
102
+ if self.port == DEFAULT_SSH_PORT:
103
+ hostkeys.add(self.hostname, key.get_name(), key)
104
+ elif self.port != DEFAULT_SSH_PORT:
105
+ hostkeys.add(
106
+ "[" + self.hostname + "]:" + str(self.port),
107
+ key.get_name(),
108
+ key,
109
+ )
110
+ try:
111
+ hostkeys.save(filename=str(self.known_hosts_file))
112
+ except OSError as e:
113
+ raise GvmError(
114
+ "Something went wrong with writing "
115
+ f"the known_hosts file {self.known_hosts_file.absolute()}: {e}"
116
+ ) from None
117
+
118
+ key_type = key.get_name().replace("ssh-", "").upper()
119
+
120
+ logger.info(
121
+ "Warning: Permanently added '%s' (%s) to "
122
+ "the list of known hosts.",
123
+ self.hostname,
124
+ key_type,
125
+ )
126
+
127
+ def _ssh_authentication_input_loop(
128
+ self, hostkeys: paramiko.HostKeys, key: paramiko.PKey
129
+ ) -> None:
130
+ # Ask user for permission to continue
131
+ # let it look like openssh
132
+ sha64_fingerprint = base64.b64encode(
133
+ hashlib.sha256(base64.b64decode(key.get_base64())).digest()
134
+ ).decode("utf-8")[:-1]
135
+ key_type = key.get_name().replace("ssh-", "").upper()
136
+
137
+ print(
138
+ f"The authenticity of host '{self.hostname}' can't "
139
+ "be established.",
140
+ file=self._file,
141
+ )
142
+ print(
143
+ f"{key_type} key fingerprint is {sha64_fingerprint}.",
144
+ file=self._file,
145
+ )
146
+ print(
147
+ "Are you sure you want to continue connecting (yes/no)? ",
148
+ end="",
149
+ file=self._file,
150
+ )
151
+
152
+ add = self._input()
153
+ while True:
154
+ if add == "yes":
155
+ if self.port == DEFAULT_SSH_PORT:
156
+ hostkeys.add(self.hostname, key.get_name(), key)
157
+ elif self.port != DEFAULT_SSH_PORT:
158
+ hostkeys.add(
159
+ "[" + self.hostname + "]:" + str(self.port),
160
+ key.get_name(),
161
+ key,
162
+ )
163
+
164
+ # ask user if the key should be added permanently
165
+ print(
166
+ f"Do you want to add {self.hostname} "
167
+ "to known_hosts (yes/no)? ",
168
+ end="",
169
+ file=self._file,
170
+ )
171
+
172
+ save = self._input()
173
+ while True:
174
+ if save == "yes":
175
+ try:
176
+ hostkeys.save(filename=str(self.known_hosts_file))
177
+ except OSError as e:
178
+ raise GvmError(
179
+ "Something went wrong with writing "
180
+ f"the known_hosts file: {e}"
181
+ ) from None
182
+
183
+ logger.info(
184
+ "Warning: Permanently added '%s' (%s) to "
185
+ "the list of known hosts.",
186
+ self.hostname,
187
+ key_type,
188
+ )
189
+ break
190
+ elif save == "no":
191
+ logger.info(
192
+ "Warning: Host '%s' (%s) not added to "
193
+ "the list of known hosts.",
194
+ self.hostname,
195
+ key_type,
196
+ )
197
+ break
198
+ else:
199
+ print(
200
+ "Please type 'yes' or 'no': ",
201
+ end="",
202
+ file=self._file,
203
+ )
204
+ save = self._input()
205
+ break
206
+ elif add == "no":
207
+ self._exit("User denied key. Host key verification failed.")
208
+ else:
209
+ print("Please type 'yes' or 'no': ", end="", file=self._file)
210
+ add = self._input()
211
+
212
+ def _get_remote_host_key(self) -> paramiko.PKey:
213
+ """Get the remote host key for ssh connection"""
214
+ try:
215
+ tmp_socket = socketlib.socket()
216
+ tmp_socket.settimeout(self._timeout)
217
+ tmp_socket.connect((self.hostname, self.port))
218
+ except OSError as e:
219
+ tmp_socket.close()
220
+ raise GvmError(
221
+ "Couldn't establish a connection to fetch the"
222
+ f" remote server key: {e}"
223
+ ) from None
224
+
225
+ trans = paramiko.transport.Transport(tmp_socket)
226
+ try:
227
+ trans.start_client()
228
+ except paramiko.SSHException as e:
229
+ tmp_socket.close()
230
+ raise GvmError(
231
+ f"Couldn't fetch the remote server key: {e}"
232
+ ) from None
233
+
234
+ key = trans.get_remote_server_key()
235
+ try:
236
+ trans.close()
237
+ except paramiko.SSHException as e:
238
+ raise GvmError(
239
+ f"Couldn't close the connection to the remote server key: {e}"
240
+ ) from None
241
+ finally:
242
+ tmp_socket.close()
243
+
244
+ return key
245
+
246
+ def _ssh_authentication(self) -> None:
247
+ """Search/add/save the servers key for the SSH authentication process"""
248
+
249
+ if not self._client:
250
+ raise GvmError("SSH Client not connected.")
251
+
252
+ # set to reject policy (avoid MITM attacks)
253
+ self._client.set_missing_host_key_policy(paramiko.RejectPolicy())
254
+
255
+ # openssh is posix, so this might only a posix approach
256
+ # https://stackoverflow.com/q/32945533
257
+ try:
258
+ # load the keys into paramiko and check if remote is in the list
259
+ self._client.load_host_keys(filename=str(self.known_hosts_file))
260
+ except OSError as e:
261
+ if e.errno != errno.ENOENT:
262
+ raise GvmError(
263
+ "Something went wrong with reading "
264
+ f"the known_hosts file: {e}"
265
+ ) from None
266
+
267
+ hostkeys = self._client.get_host_keys()
268
+
269
+ # Switch based on SSH Port
270
+ if self.port == DEFAULT_SSH_PORT:
271
+ hostname = self.hostname
272
+ else:
273
+ hostname = f"[{self.hostname}]:{self.port}"
274
+
275
+ if not hostkeys.lookup(hostname):
276
+ # Key not found, so connect to remote and fetch the key
277
+ # with the paramiko Transport protocol
278
+ key = self._get_remote_host_key()
279
+ if self.auto_accept_host:
280
+ self._auto_accept_host(hostkeys=hostkeys, key=key)
281
+ else:
282
+ self._ssh_authentication_input_loop(hostkeys=hostkeys, key=key)
283
+
284
+ def _read(self) -> bytes:
285
+ return self._stdout.channel.recv(BUF_SIZE)
286
+
287
+ def send(self, data: bytes) -> None:
288
+ self._send_all(data)
289
+
290
+ def read(self) -> bytes:
291
+ break_timeout = (
292
+ time() + self._timeout if self._timeout is not None else None
293
+ )
294
+
295
+ data = self._read()
296
+
297
+ if not data:
298
+ # Connection was closed by server
299
+ raise GvmError("Remote closed the connection")
300
+
301
+ if break_timeout and time() > break_timeout:
302
+ raise GvmError("Timeout while reading the response")
303
+
304
+ return data
305
+
306
+ def connect(self) -> None:
307
+ """
308
+ Connect to the SSH server and authenticate to it
309
+ """
310
+ self._client = paramiko.SSHClient()
311
+ self._ssh_authentication()
312
+
313
+ try:
314
+ self._client.connect(
315
+ hostname=self.hostname,
316
+ username=self.username,
317
+ password=self.password,
318
+ timeout=self._timeout,
319
+ port=int(self.port),
320
+ allow_agent=False,
321
+ look_for_keys=False,
322
+ )
323
+
324
+ except (
325
+ paramiko.BadHostKeyException,
326
+ paramiko.AuthenticationException,
327
+ paramiko.SSHException,
328
+ paramiko.ssh_exception.NoValidConnectionsError,
329
+ ConnectionError,
330
+ ) as e:
331
+ raise GvmError(f"SSH Connection failed: {e}") from None
332
+
333
+ self._stdin, self._stdout, self._stderr = self._client.exec_command(
334
+ "", get_pty=False
335
+ )
336
+
337
+ def disconnect(self) -> None:
338
+ """Disconnect and close the connection to the remote server"""
339
+ try:
340
+ if self._client is not None:
341
+ self._client.close()
342
+ except OSError as e:
343
+ logger.debug("Connection closing error: %s", e)
344
+ raise e
345
+
346
+ if self._client is not None:
347
+ self._client = None
348
+ del self._stdin, self._stdout, self._stderr
349
+
350
+ def finish_send(self) -> None:
351
+ # shutdown socket for sending. only allow reading data afterwards
352
+ self._stdout.channel.shutdown(socketlib.SHUT_WR)