diff --git a/guacamole/__init__.py b/guacamole/__init__.py index 10e28ab..0d0b1a0 100644 --- a/guacamole/__init__.py +++ b/guacamole/__init__.py @@ -7,7 +7,7 @@ import logging -VERSION = '0.11' +VERSION = '0.12' logger = logging.getLogger(__name__) diff --git a/guacamole/client.py b/guacamole/client.py index 7198c57..9d1ec48 100644 --- a/guacamole/client.py +++ b/guacamole/client.py @@ -4,8 +4,7 @@ Copyright (c) 2014 rescale 2014 - 2016 Mohab Usama """ - -import socket +import asyncio import logging from guacamole import logger as guac_logger @@ -16,7 +15,7 @@ from guacamole.instruction import GuacamoleInstruction as Instruction # supported protocols -PROTOCOLS = ('vnc', 'rdp', 'ssh') +PROTOCOLS = ('vnc', 'rdp', 'ssh', 'telnet', 'kubernetes') PROTOCOL_NAME = 'guacamole' @@ -43,7 +42,8 @@ def __init__(self, host, port, timeout=20, debug=False, logger=None): self.port = port self.timeout = timeout - self._client = None + self._reader = None + self._writer = None # handshake established? self.connected = False @@ -66,13 +66,11 @@ def client(self): """ Socket connection. """ - if not self._client: - self._client = socket.create_connection( - (self.host, self.port), self.timeout) - self.logger.info('Client connected with guacd server (%s, %s, %s)' - % (self.host, self.port, self.timeout)) + if not self._reader and self._writer: + self._reader, self._writer = asyncio.open_connection(self.host, self.port) + #self.logger.info(f'Client connected with guacd server {self.host} {self.port} {self.timeout}') - return self._client + return True @property def id(self): @@ -83,12 +81,14 @@ def close(self): """ Terminate connection with Guacamole guacd server. """ - self.client.close() - self._client = None + if self._writer: + self._writer.close() + self._writer.wait_closed() + self.connected = False - self.logger.info('Connection closed.') + #self.logger.info('Connection closed.') - def receive(self): + async def receive(self): """ Receive instructions from Guacamole guacd server. """ @@ -100,42 +100,44 @@ def receive(self): # instruction was fully received! line = self._buffer[:idx + 1].decode() self._buffer = self._buffer[idx + 1:] - self.logger.debug('Received instruction: %s' % line) + # self.logger.debug('Received instruction: %s' % line) return line else: start = len(self._buffer) # we are still waiting for instruction termination - buf = self.client.recv(BUF_LEN) + buf = await self._reader.read(BUF_LEN) if not buf: # No data recieved, connection lost?! self.close() - self.logger.warn( - 'Failed to receive instruction. Closing.') - return None + msg = 'Connection closed by remote server' + self.logger.warn(msg) + raise GuacamoleError(msg) + self._buffer.extend(buf) - def send(self, data): + async def send(self, data): """ Send encoded instructions to Guacamole guacd server. """ - self.logger.debug('Sending data: %s' % data) - self.client.sendall(data.encode()) + #self.logger.debug('Sending data: %s' % data) + self._writer.write(data.encode()) + await self._writer.drain() - def read_instruction(self): + async def read_instruction(self): """ Read and decode instruction. """ - self.logger.debug('Reading instruction.') - return Instruction.load(self.receive()) + #self.logger.debug('Reading instruction.') + return Instruction.load(await self.receive()) - def send_instruction(self, instruction): + async def send_instruction(self, instruction): """ Send instruction after encoding. """ - self.logger.debug('Sending instruction: %s' % str(instruction)) - return self.send(instruction.encode()) + #self.logger.debug('Sending instruction: %s' % str(instruction)) + return await self.send(instruction.encode()) - def handshake(self, protocol='vnc', width=1024, height=768, dpi=96, + async def handshake(self, protocol='vnc', width=1024, height=768, dpi=96, audio=None, video=None, image=None, width_override=None, height_override=None, dpi_override=None, **kwargs): """ @@ -158,19 +160,18 @@ def handshake(self, protocol='vnc', width=1024, height=768, dpi=96, image = list() # 1. Send 'select' instruction - self.logger.debug('Send `select` instruction.') + #self.logger.debug('Send `select` instruction.') # if connectionid is provided - connect to existing connectionid if 'connectionid' in kwargs: - self.send_instruction(Instruction('select', - kwargs.get('connectionid'))) + await self.send_instruction(Instruction('select', kwargs.get('connectionid'))) else: - self.send_instruction(Instruction('select', protocol)) + await self.send_instruction(Instruction('select', protocol)) # 2. Receive `args` instruction - instruction = self.read_instruction() - self.logger.debug('Expecting `args` instruction, received: %s' - % str(instruction)) + instruction = await self.read_instruction() + #self.logger.debug('Expecting `args` instruction, received: %s' + # % str(instruction)) if not instruction: self.close() @@ -182,20 +183,24 @@ def handshake(self, protocol='vnc', width=1024, height=768, dpi=96, raise GuacamoleError( 'Cannot establish Handshake. Expected opcode `args`, ' 'received `%s` instead.' % instruction.opcode) + #self.logger.debug(f"ARGS: {instruction}") # 3. Respond with size, audio & video support - self.logger.debug('Send `size` instruction (%s, %s, %s)' - % (width, height, dpi)) - self.send_instruction(Instruction('size', width, height, dpi)) + #self.logger.debug('Send `size` instruction {width} {height} {dpi}') + await self.send_instruction(Instruction('size', width, height, dpi)) - self.logger.debug('Send `audio` instruction (%s)' % audio) - self.send_instruction(Instruction('audio', *audio)) + #self.logger.debug('Send `audio` instruction (%s)' % audio) + await self.send_instruction(Instruction('audio', *audio)) self.logger.debug('Send `video` instruction (%s)' % video) - self.send_instruction(Instruction('video', *video)) + await self.send_instruction(Instruction('video', *video)) self.logger.debug('Send `image` instruction (%s)' % image) - self.send_instruction(Instruction('image', *image)) + await self.send_instruction(Instruction('image', *image)) + + if timezone := kwargs.get("timezone", None): + #self.logger.debug('Send `timezone` instruction (%s)' % timezone) + await self.send_instruction(Instruction('timezone', timezone)) if width_override: kwargs["width"] = width_override @@ -205,26 +210,22 @@ def handshake(self, protocol='vnc', width=1024, height=768, dpi=96, kwargs["dpi"] = dpi_override # 4. Send `connect` instruction with proper values + #self.logger.debug(instruction.args) connection_args = [ kwargs.get(arg.replace('-', '_'), '') for arg in instruction.args ] - self.logger.debug('Send `connect` instruction (%s)' % connection_args) - self.send_instruction(Instruction('connect', *connection_args)) + #self.logger.debug(f'Send `connect` instruction ({connection_args}') + await self.send_instruction(Instruction('connect', *connection_args)) # 5. Receive ``ready`` instruction, with client ID. - instruction = self.read_instruction() - self.logger.debug('Expecting `ready` instruction, received: %s' - % str(instruction)) + instruction = await self.read_instruction() - if instruction.opcode != 'ready': - self.logger.warning( - 'Expected `ready` instruction, received: %s instead') + #if instruction.opcode != 'ready': + # self.logger.warning(f'Expected `ready` instruction, received: {instruction} instead') if instruction.args: self._id = instruction.args[0] - self.logger.debug( - 'Established connection with client id: %s' % self.id) - - self.logger.debug('Handshake completed.') + #self.logger.debug('Established connection with client id: {self.id}') + #self.logger.debug('Handshake completed.') self.connected = True