Skip to content
2 changes: 1 addition & 1 deletion guacamole/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import logging


VERSION = '0.11'
VERSION = '0.12'


logger = logging.getLogger(__name__)
Expand Down
111 changes: 56 additions & 55 deletions guacamole/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'

Expand All @@ -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
Expand All @@ -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):
Expand All @@ -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.
"""
Expand All @@ -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):
"""
Expand All @@ -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()
Expand All @@ -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
Expand All @@ -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