Skip to content

Commit 6243848

Browse files
authored
Merge pull request #44 from bjoernricks/connection-updates
Connection updates
2 parents 3089394 + 6cfeee7 commit 6243848

File tree

2 files changed

+123
-82
lines changed

2 files changed

+123
-82
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Some notable changes are:
1010
having to select the connection when creating the gmp object.
1111
* Support for different protocols and versions has been added. Currently
1212
supported protocols are OSP v1 and GMP v7.
13-
* Full API documentation is available.
13+
* Full API documentation is available at https://python-gvm.readthedocs.io/en/latest/.
1414
* Possible arguments to protocol methods are documented.
1515
* Arguments should be passed as keywords
1616

gvm/connections.py

Lines changed: 122 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -66,39 +66,75 @@ def _feed_xml(self, data):
6666
"read {0}".format(data), e)
6767

6868

69-
class GvmConnection:
69+
class GvmConnection(XmlReader):
7070
"""
7171
Base class for establishing a connection to a remote server daemon.
72+
73+
Arguments:
74+
timeout (int, optional): Timeout in seconds for the connection.
7275
"""
7376

7477
def __init__(self, timeout=DEFAULT_TIMEOUT):
75-
"""
76-
Arguments:
77-
socket -- A socket
78-
"""
7978
self._socket = None
8079
self._timeout = timeout
8180

81+
def _read(self):
82+
return self._socket.recv(BUF_SIZE)
83+
8284
def connect(self):
83-
"""Establish a connection to gvmd
85+
"""Establish a connection to a remote server
8486
"""
8587
raise NotImplementedError
8688

8789
def send(self, data):
88-
"""Send data to gvmd
90+
"""Send data to the connected remote server
91+
92+
Arguments:
93+
data (str or bytes): Data to be send to the server. Either utf-8
94+
encoded string or bytes.
8995
"""
9096
if isinstance(data, str):
91-
self._socket.send(data.encode())
97+
self._socket.sendall(data.encode())
9298
else:
93-
self._socket.send(data)
99+
self._socket.sendall(data)
94100

95101
def read(self):
96-
"""Read data from gvmd
102+
"""Read data from the remote server
103+
104+
Returns:
105+
str: data as utf-8 encoded string
97106
"""
98-
raise NotImplementedError
107+
response = ''
108+
109+
self._start_xml()
110+
111+
now = time.time()
112+
113+
break_timeout = now + self._timeout
114+
115+
while True:
116+
data = self._read()
117+
118+
if not data:
119+
# Connection was closed by server
120+
raise GvmError('Remote closed the connection')
121+
122+
self._feed_xml(data)
123+
124+
response += data.decode('utf-8', errors='ignore')
125+
126+
if self._is_end_xml():
127+
break
128+
129+
now = time.time()
130+
131+
if now > break_timeout:
132+
raise GvmError('Timeout while reading the response')
133+
134+
return response
99135

100136
def disconnect(self):
101-
"""Close the connection to gvmd
137+
"""Disconnect and close the connection to the remote server
102138
"""
103139
try:
104140
if self._socket is not None:
@@ -107,9 +143,17 @@ def disconnect(self):
107143
logger.debug('Connection closing error: %s', e)
108144

109145

110-
class SSHConnection(GvmConnection, XmlReader):
146+
class SSHConnection(GvmConnection):
111147
"""
112148
SSH Class to connect, read and write from GVM via SSH
149+
150+
Arguments:
151+
timeout (int, optional): Timeout in seconds for the connection.
152+
hostname (str, optional): DNS name or IP address of the remote server.
153+
Default is 127.0.0.1.
154+
port (int, optional): Port of the remote SSH server.
155+
username (str, optional): Username to use for SSH login.
156+
password (str, optional): Passwort to use for SSH login.
113157
"""
114158

115159
def __init__(self, timeout=DEFAULT_TIMEOUT, hostname='127.0.0.1', port=22,
@@ -143,6 +187,9 @@ def _send_in_chunks(self, data, chunk_size):
143187
return sent_bytes
144188

145189
def connect(self):
190+
"""
191+
Connect to the SSH server and authenticate to it
192+
"""
146193
self._socket = paramiko.SSHClient()
147194
self._socket.set_missing_host_key_policy(paramiko.AutoAddPolicy())
148195

@@ -160,35 +207,16 @@ def connect(self):
160207

161208
except (paramiko.BadHostKeyException,
162209
paramiko.AuthenticationException,
163-
paramiko.SSHException, OSError) as e:
164-
logger.debug('SSH Connection failed: %s', e)
165-
raise
166-
167-
def read(self):
168-
response = ''
169-
170-
self._start_xml()
171-
172-
while True:
173-
data = self._stdout.channel.recv(BUF_SIZE)
174-
# Connection was closed by server
175-
if not data:
176-
break
210+
paramiko.SSHException,
211+
) as e:
212+
raise GvmError('SSH Connection failed', e)
177213

178-
self._feed_xml(data)
179-
180-
response += data.decode('utf-8', errors='ignore')
181-
182-
if self._is_end_xml():
183-
break
184-
185-
return response
214+
def _read(self):
215+
return self._stdout.channel.recv(BUF_SIZE)
186216

187217
def send(self, data):
188-
logger.debug('SSH:send(): %s', data)
189218
if len(data) > MAX_SSH_DATA_LENGTH:
190-
sent_bytes = self._send_in_chunks(data, MAX_SSH_DATA_LENGTH)
191-
logger.debug("SSH: %s bytes sent.", sent_bytes)
219+
self._send_in_chunks(data, MAX_SSH_DATA_LENGTH)
192220
else:
193221
self._stdin.channel.send(data)
194222

@@ -197,10 +225,28 @@ class TLSConnection(GvmConnection):
197225
"""
198226
TLS class to connect, read and write from a remote GVM daemon via TLS
199227
secured socket.
228+
229+
Arguments:
230+
timeout (int, optional): Timeout in seconds for the connection.
231+
hostname (str, optional): DNS name or IP address of the remote TLS
232+
server.
233+
port (str, optional): Port for the TLS connection. Default is 9390.
234+
certfile (str, optional): Path to PEM encoded certificate file. See
235+
`python certificates`_ for details.
236+
cafile (str, optional): Path to PEM encoded CA file. See
237+
`python certificates`_ for details.
238+
keyfile (str, optional): Path to PEM encoded private key. See
239+
`python certificates`_ for details.
240+
password (str, optional): Password for the private key. If the password
241+
argument is not specified and a password is required it will be
242+
interactively prompt the user for a password.
243+
244+
.. _python certificates:
245+
https://docs.python.org/3.5/library/ssl.html#certificates
200246
"""
201247

202248
def __init__(self, certfile=None, cafile=None, keyfile=None,
203-
hostname='127.0.0.1', port=DEFAULT_GVM_PORT,
249+
hostname='127.0.0.1', port=DEFAULT_GVM_PORT, password=None,
204250
timeout=DEFAULT_TIMEOUT):
205251
super().__init__(timeout=timeout)
206252

@@ -209,45 +255,41 @@ def __init__(self, certfile=None, cafile=None, keyfile=None,
209255
self.certfile = certfile
210256
self.cafile = cafile
211257
self.keyfile = keyfile
258+
self.password = password
212259

213260
def _new_socket(self):
261+
transport_socket = socketlib.socket(socketlib.AF_INET,
262+
socketlib.SOCK_STREAM)
263+
214264
if self.certfile and self.cafile and self.keyfile:
215265
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH,
216266
cafile=self.cafile)
217267
context.check_hostname = False
218268
context.load_cert_chain(
219-
certfile=self.certfile, keyfile=self.keyfile)
220-
new_socket = socketlib.socket(socketlib.AF_INET,
221-
socketlib.SOCK_STREAM)
222-
sock = context.wrap_socket(new_socket, server_side=False)
269+
certfile=self.certfile, keyfile=self.keyfile,
270+
password=self.password)
271+
sock = context.wrap_socket(transport_socket, server_side=False)
223272
else:
224273
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
225-
sock = context.wrap_socket(socketlib.socket(socketlib.AF_INET))
226-
return sock
274+
sock = context.wrap_socket(transport_socket)
227275

276+
sock.settimeout(self._timeout)
277+
278+
return sock
228279

229280
def connect(self):
230281
self._socket = self._new_socket()
231-
self._socket.settimeout(self._timeout)
232282
self._socket.connect((self.hostname, int(self.port)))
233283

234-
def read(self):
235-
response = ''
236284

237-
while True:
238-
data = self._socket.read(BUF_SIZE)
239-
240-
response += data.decode('utf-8', errors='ignore')
241-
if len(data) < BUF_SIZE:
242-
break
243-
244-
return response
245-
246-
247-
class UnixSocketConnection(GvmConnection, XmlReader):
285+
class UnixSocketConnection(GvmConnection):
248286
"""
249287
UNIX-Socket class to connect, read, write from a GVM server daemon via
250288
direct communicating UNIX-Socket
289+
290+
Arguments:
291+
path (str, optional): Path to the socket.
292+
timeout (int, optional): Timeout in seconds for the connection.
251293
"""
252294

253295
def __init__(self, path=DEFAULT_UNIX_SOCKET_PATH, timeout=DEFAULT_TIMEOUT,
@@ -265,34 +307,33 @@ def connect(self):
265307
self._socket.settimeout(self._timeout)
266308
self._socket.connect(self.path)
267309

310+
311+
class DebugConnection:
312+
313+
def __init__(self, connection):
314+
self._connection = connection
315+
268316
def read(self):
269-
"""Read from the UNIX socket
270-
"""
271-
response = ''
317+
data = self._connection.read()
272318

273-
break_timeout = time.time() + self.read_timeout
274-
old_timeout = self._socket.gettimeout()
275-
self._socket.settimeout(5) # in seconds
319+
logger.debug('Read %s characters. Data %s', len(data), data)
276320

277-
self._start_xml()
321+
self.last_read_data = data
322+
return data
278323

279-
while time.time() < break_timeout:
280-
data = b''
324+
def send(self, data):
325+
self.last_send_data = data
281326

282-
try:
283-
data = self._socket.recv(BUF_SIZE)
284-
except (socketlib.timeout) as exception:
285-
logger.debug('Warning: No data received '
286-
'from server: %s', exception)
287-
continue
327+
logger.debug('Sending %s characters. Data %s', len(data), data)
288328

289-
self._feed_xml(data)
329+
return self._connection.send(data)
290330

291-
response += data.decode('utf-8', errors='ignore')
331+
def connect(self):
332+
logger.debug('Connecting')
292333

293-
if len(data) < BUF_SIZE:
294-
if self._is_end_xml():
295-
break
334+
return self._connection.connect()
296335

297-
self._socket.settimeout(old_timeout)
298-
return response
336+
def disconnect(self):
337+
logger.debug('Disconnecting')
338+
339+
return self._connection.disconnect()

0 commit comments

Comments
 (0)