From a518963253dab16270022449f810554e8188427e Mon Sep 17 00:00:00 2001 From: speyrefitte Date: Mon, 28 Jul 2014 18:25:40 +0200 Subject: [PATCH] add view system + bug fix --- bin/rdpy-rdpproxy | 62 ++++++++++----- rdpy/network/type.py | 16 +++- rdpy/protocol/rdp/pdu/layer.py | 2 +- rdpy/protocol/rdp/rdp.py | 5 +- rdpy/protocol/rdp/tpdu.py | 42 ++++++---- rdpy/ui/qt4.py | 13 ++-- rdpy/ui/view.py | 137 +++++++++++++++++++++++++++++++++ 7 files changed, 228 insertions(+), 49 deletions(-) create mode 100644 rdpy/ui/view.py diff --git a/bin/rdpy-rdpproxy b/bin/rdpy-rdpproxy index 03d4e62..85444cf 100755 --- a/bin/rdpy-rdpproxy +++ b/bin/rdpy-rdpproxy @@ -29,7 +29,7 @@ sys.path.insert(1, os.path.join(sys.path[0], '..')) from rdpy.base import log, error from rdpy.protocol.rdp import rdp -from rdpy.ui import widget +from rdpy.ui import view from twisted.internet import reactor from PyQt4 import QtCore, QtGui @@ -42,6 +42,8 @@ class IProxyClient(object): pass def sendPointerEvent(self, x, y, button, isPressed): pass + def sendRefreshOrder(self, left, top, right, bottom): + pass class ProxyServer(rdp.RDPServerObserver): """ @@ -69,21 +71,27 @@ class ProxyServer(rdp.RDPServerObserver): Event use to inform state of server stack Use to connect client """ - domain, username, password = self._controller.getCredentials() - - if self._credentialProvider.isAdmin(domain, username, password): - self.clientConnected(ProxyAdmin(self)) - return - - try: - dstIp, dstPort, dstDomain, dstUsername, dstPassword = self._credentialProvider.getCredentials(domain, username, password) - except error.InvalidExpectedDataException as e: - log.info(e.message) - #self._controller.close() - return - - width, height = self._controller.getScreen() - reactor.connectTCP(dstIp, dstPort, ProxyClientFactory(self, width, height, dstDomain, dstUsername, dstPassword, "%s/%s -> %s %s/%s"%(domain, username, dstIp, dstDomain, dstUsername))) + if self._client is None: + #try a connection + domain, username, password = self._controller.getCredentials() + + if self._credentialProvider.isAdmin(domain, username, password): + self.clientConnected(ProxyAdmin(self)) + return + + try: + dstIp, dstPort, dstDomain, dstUsername, dstPassword = self._credentialProvider.getCredentials(domain, username, password) + except error.InvalidExpectedDataException as e: + log.info(e.message) + #self._controller.close() + return + + width, height = self._controller.getScreen() + reactor.connectTCP(dstIp, dstPort, ProxyClientFactory(self, width, height, dstDomain, dstUsername, dstPassword, "%s/%s -> %s %s/%s"%(domain, username, dstIp, dstDomain, dstUsername))) + else: + #refresh client + width, height = self._controller.getScreen() + self._client.sendRefreshOrder(0, 0, width, height) def onKeyEventScancode(self, code, isPressed): """ @@ -159,14 +167,21 @@ class ProxyClient(rdp.RDPClientObserver, IProxyClient): @param data: bitmap data """ self._server._controller.sendUpdate(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data) + def getColorDepth(self): return self._controller.getColorDepth() + def sendKeyEventScancode(self, code, isPressed): self._controller.sendKeyEventScancode(code, isPressed) + def sendKeyEventUnicode(self, code, isPressed): self._controller.sendKeyEventUnicode(code, isPressed) + def sendPointerEvent(self, x, y, button, isPressed): self._controller.sendPointerEvent(x, y, button, isPressed) + + def sendRefreshOrder(self, left, top, right, bottom): + self._controller.sendRefreshOrder(left, top, right, bottom) class ProxyServerFactory(rdp.ServerFactory): """ @@ -177,7 +192,7 @@ class ProxyServerFactory(rdp.ServerFactory): @param config: rdp-proxy configuration @param credentialProvider: CredentialProvider """ - rdp.ServerFactory.__init__(self, "/home/sylvain/dev/certificate/rdpy.key", "/home/sylvain/dev/certificate/rdpy.crt", 16) + rdp.ServerFactory.__init__(self, "/home/speyrefitte/dev/certificate/rdpy.key", "/home/speyrefitte/dev/certificate/rdpy.crt", 16) self._credentialProvider = credentialProvider def buildObserver(self, controller): @@ -277,19 +292,28 @@ class CredentialProvider(object): return self._config['admin']['domain'] == domain and self._config['admin']['username'] == username and self._config['admin']['password'] == password class ProxyAdmin(IProxyClient): + """ + Use to manage client side of admin session + Add GUI to select wich session to see + """ def __init__(self, server): self._server = server - #self._list = widget.List(ProxyClientFactory._CLIENT_PROXY_.keys(), 100, 100, self.onSelect, widget.Anchor(0, 0, widget.RDPWidgetListener(self._server._controller))) - self._list = widget.List(["salut les copains"], 300, 300, self.onSelect, widget.Anchor(0, 0, widget.RDPWidgetListener(self._server._controller))) + self._list = view.ListView(ProxyClientFactory._CLIENT_PROXY_.keys(), 300, 300, self.onSelect) + self._render = view.RDPRenderer(self._server._controller) + self._list.update(self._render) def getColorDepth(self): return 16 def sendKeyEventScancode(self, code, isPressed): if isPressed: self._list.keyEvent(code) + self._list.update(self._render) def sendKeyEventUnicode(self, code, isPressed): pass def sendPointerEvent(self, x, y, button, isPressed): pass + def sendRefreshOrder(self, left, top, right, bottom): + self._list.keyEvent(code) + self._list.update(self._render) def onSelect(self, name): if ProxyClientFactory._CLIENT_PROXY_.has_key(name): self._server.clientConnected(ProxyClient(ProxyClientFactory._CLIENT_PROXY_[name]._controller, self._server)) diff --git a/rdpy/network/type.py b/rdpy/network/type.py index 8e6d27f..7251094 100644 --- a/rdpy/network/type.py +++ b/rdpy/network/type.py @@ -668,7 +668,7 @@ class String(Type, CallableValue): """ String network type """ - def __init__(self, value = "", readLen = None, conditional = lambda:True, optional = False, constant = False, unicode = False): + def __init__(self, value = "", readLen = None, conditional = lambda:True, optional = False, constant = False, unicode = False, until = None): """ @param value: python string use for inner value @param readLen: length use to read in stream (SimpleType) if 0 read entire stream @@ -676,12 +676,14 @@ class String(Type, CallableValue): @param optional: boolean check before read if there is still data in stream @param constant: if true check any changement of object during reading @param unicode: Encode and decode value as unicode + @param until: read until sequence is readed or write sequence at the end of string """ Type.__init__(self, conditional = conditional, optional = optional, constant = constant) CallableValue.__init__(self, value) #type use to know read length self._readLen = readLen self._unicode = unicode + self._until = until def __eq__(self, other): ''' @@ -710,6 +712,11 @@ class String(Type, CallableValue): Write the entire raw value @param s: Stream """ + toWrite = self.value + + if not self._until is None: + toWrite += self._until + if self._unicode: s.write(encodeUnicode(self.value)) else: @@ -722,7 +729,12 @@ class String(Type, CallableValue): @param s: Stream """ if self._readLen is None: - self.value = s.getvalue() + if self._until is None: + self.value = s.getvalue() + else: + self.value = "" + while self.value[-len(self._until):] != self._until or s.dataLen() == 0: + self.value += s.read(1) else: self.value = s.read(self._readLen.value) diff --git a/rdpy/protocol/rdp/pdu/layer.py b/rdpy/protocol/rdp/pdu/layer.py index 8dfae6b..295350c 100644 --- a/rdpy/protocol/rdp/pdu/layer.py +++ b/rdpy/protocol/rdp/pdu/layer.py @@ -576,7 +576,7 @@ class Server(PDULayer, tpkt.IFastPathListener): self._transport.close() def recvFastPath(self, fastPathS): - """ + """r Implement IFastPathListener interface Fast path is needed by RDP 8.0 @param fastPathS: Stream that contain fast path data diff --git a/rdpy/protocol/rdp/rdp.py b/rdpy/protocol/rdp/rdp.py index 1961611..a0e34ca 100644 --- a/rdpy/protocol/rdp/rdp.py +++ b/rdpy/protocol/rdp/rdp.py @@ -247,7 +247,6 @@ class RDPServerController(pdu.layer.PDUServerListener): @param colorDepth: 15, 16, 24 """ self._isReady = False - self._sendReady = False #list of observer self._serverObserver = [] #build RDP protocol stack @@ -342,9 +341,6 @@ class RDPServerController(pdu.layer.PDUServerListener): RDP stack is now ready """ self._isReady = True - if self._sendReady: - return - self._sendReady = True for observer in self._serverObserver: observer.onReady() @@ -490,6 +486,7 @@ class RDPServerObserver(object): def onReady(self): """ Stack is ready and connected + May be called after an setColorDepth too """ raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPServerObserver")) diff --git a/rdpy/protocol/rdp/tpdu.py b/rdpy/protocol/rdp/tpdu.py index 7dda76f..e4dc2dc 100644 --- a/rdpy/protocol/rdp/tpdu.py +++ b/rdpy/protocol/rdp/tpdu.py @@ -25,7 +25,7 @@ RDP basic security is not supported by RDPY (because is not a true security laye """ from rdpy.network.layer import LayerAutomata, IStreamSender -from rdpy.network.type import UInt8, UInt16Le, UInt16Be, UInt32Le, CompositeType, sizeof +from rdpy.network.type import UInt8, UInt16Le, UInt16Be, UInt32Le, CompositeType, sizeof, String from rdpy.base.error import InvalidExpectedDataException class MessageType(object): @@ -66,17 +66,30 @@ class NegotiationFailureCode(object): HYBRID_REQUIRED_BY_SERVER = 0x00000005 SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 0x00000006 -class TPDUConnectMessage(CompositeType): +class ClientConnectionRequestPDU(CompositeType): """ - Header of TPDU connection messages + Connection request + client -> server + @see: http://msdn.microsoft.com/en-us/library/cc240470.aspx """ - def __init__(self, code): - """ - @param code: MessageType - """ + def __init__(self): CompositeType.__init__(self) self.len = UInt8(lambda:sizeof(self) - 1) - self.code = UInt8(code, constant = True) + self.code = UInt8(MessageType.X224_TPDU_CONNECTION_REQUEST, constant = True) + self.padding = (UInt16Be(), UInt16Be(), UInt8()) + self.cookie = String(until = "\x0d\x0a", conditional = lambda:(self.len._is_readed and self.len.value > 14)) + #read if there is enough data + self.protocolNeg = Negotiation(optional = True) + +class ServerConnectionConfirm(CompositeType): + """ + Server response + @see: http://msdn.microsoft.com/en-us/library/cc240506.aspx + """ + def __init__(self): + CompositeType.__init__(self) + self.len = UInt8(lambda:sizeof(self) - 1) + self.code = UInt8(MessageType.X224_TPDU_CONNECTION_CONFIRM, constant = True) self.padding = (UInt16Be(), UInt16Be(), UInt8()) #read if there is enough data self.protocolNeg = Negotiation(optional = True) @@ -103,7 +116,7 @@ class Negotiation(CompositeType): self.code = UInt8() self.flag = UInt8(0) #always 8 - self.len = UInt16Le(0x0008)#not constant because freerdp send me random value... + self.len = UInt16Le(0x0008, constant = True)#not constant because freerdp send me random value... self.selectedProtocol = UInt32Le(conditional = lambda: (self.code.value != NegociationType.TYPE_RDP_NEG_FAILURE)) self.failureCode = UInt32Le(conditional = lambda: (self.code.value == NegociationType.TYPE_RDP_NEG_FAILURE)) @@ -164,7 +177,7 @@ class Client(TPDULayer): Next state is recvConnectionConfirm @see: http://msdn.microsoft.com/en-us/library/cc240500.aspx """ - message = TPDUConnectMessage(MessageType.X224_TPDU_CONNECTION_REQUEST) + message = ClientConnectionRequestPDU() message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_REQ message.protocolNeg.selectedProtocol.value = self._requestedProtocol self._transport.send(message) @@ -179,7 +192,7 @@ class Client(TPDULayer): @see: response -> http://msdn.microsoft.com/en-us/library/cc240506.aspx @see: failure ->http://msdn.microsoft.com/en-us/library/cc240507.aspx """ - message = TPDUConnectMessage(MessageType.X224_TPDU_CONNECTION_CONFIRM) + message = ServerConnectionConfirm() data.readType(message) #check presence of negotiation response @@ -231,7 +244,7 @@ class Server(TPDULayer): @param data: Stream @see : http://msdn.microsoft.com/en-us/library/cc240470.aspx """ - message = TPDUConnectMessage(MessageType.X224_TPDU_CONNECTION_REQUEST) + message = ClientConnectionRequestPDU() data.readType(message) if not message.protocolNeg._is_readed or message.protocolNeg.failureCode._is_readed: @@ -241,8 +254,7 @@ class Server(TPDULayer): if not self._requestedProtocol & Protocols.PROTOCOL_SSL: #send error message and quit - message = TPDUConnectMessage() - message.code.value = MessageType.X224_TPDU_CONNECTION_CONFIRM + message = ServerConnectionConfirm() message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_FAILURE message.protocolNeg.failureCode.value = NegotiationFailureCode.SSL_REQUIRED_BY_SERVER self._transport.send(message) @@ -258,7 +270,7 @@ class Server(TPDULayer): Next state is recvData @see : http://msdn.microsoft.com/en-us/library/cc240501.aspx """ - message = TPDUConnectMessage(MessageType.X224_TPDU_CONNECTION_CONFIRM) + message = ServerConnectionConfirm() message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_RSP message.protocolNeg.selectedProtocol.value = self._selectedProtocol self._transport.send(message) diff --git a/rdpy/ui/qt4.py b/rdpy/ui/qt4.py index 135d8de..7f27098 100644 --- a/rdpy/ui/qt4.py +++ b/rdpy/ui/qt4.py @@ -179,7 +179,7 @@ class RDPClientQt(RDPClientObserver, QAdaptor): @param e: QKeyEvent @param isPressed: event come from press or release action """ - self._controller.sendKeyEventUnicode(ord(unicode(e.text().toUtf8(), encoding="UTF-8")), isPressed) + self._controller.sendKeyEventScancode(e.nativeScanCode(), isPressed) def closeEvent(self, e): """ @@ -207,28 +207,28 @@ class RDPClientQt(RDPClientObserver, QAdaptor): image = QtGui.QImage(width, height, QtGui.QImage.Format_RGB555) data = rle.bitmap_decompress(image.bits(), width, height, data, len(data), 2) else: - image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB555) + image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB555).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) elif bitsPerPixel == 16: if isCompress: image = QtGui.QImage(width, height, QtGui.QImage.Format_RGB16) data = rle.bitmap_decompress(image.bits(), width, height, data, len(data), 2) else: - image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB16) + image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB16).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) elif bitsPerPixel == 24: if isCompress: image = QtGui.QImage(width, height, QtGui.QImage.Format_RGB24) data = rle.bitmap_decompress(image.bits(), width, height, data, len(data), 3) else: - image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB24) + image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB24).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) elif bitsPerPixel == 32: if isCompress: image = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32) data = rle.bitmap_decompress(image.bits(), width, height, data, len(data), 4) else: - image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB32) + image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB32).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) else: log.error("Receive image in bad format") return @@ -288,9 +288,6 @@ class QRemoteDesktop(QtGui.QWidget): Call when Qt renderer engine estimate that is needed @param e: QEvent """ - #if there is no refresh -> done - if self._refresh == []: - return #fill buffer image with QtGui.QPainter(self._buffer) as qp: #draw image diff --git a/rdpy/ui/view.py b/rdpy/ui/view.py new file mode 100644 index 0000000..a92b02a --- /dev/null +++ b/rdpy/ui/view.py @@ -0,0 +1,137 @@ +# +# Copyright (c) 2014 Sylvain Peyrefitte +# +# This file is part of rdpy. +# +# rdpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +""" +Fake widget +""" +from rdpy.base.error import CallPureVirtualFuntion +from PyQt4 import QtGui, QtCore + + +class KeyCode(object): + ENTER = 28 + UP = 328 + DOWN = 336 + +class IRender(object): + def translate(self, dx, dy): + pass + def drawImage(self, image): + pass + +class IView(object): + def keyEvent(self, code): + pass + def pointerEvent(self, x, y, button): + pass + def update(self, render): + pass + +class AnchorView(IView): + def __init__(self, x, y, view): + self._x = x + self._y = y + self._view = view + def keyEvent(self, code): + self._view.keyEvent(code) + def pointerEvent(self, x, y, button): + self._view.pointerEvent(x - self._x, y - self._y) + def update(self, render): + render.translate(self._x, self._y) + self._view.update(self._view, render) + render.translate(- self._x, - self._y) + + +class ListView(IView): + """ + List widget simulate by QT painter + """ + def __init__(self, labels, width, height, callback): + self._labels = labels + self._width = width + self._height = height + self._cellHeight = 25 + self._backGroudColor = QtGui.QColor(24, 93, 123) + self._fontSize = 14 + self._current = 0 + self._callback = callback + + def keyEvent(self, code): + #enter key + if len(self._labels) == 0: + return + if code == KeyCode.ENTER: + self._callback(self._labels[self._current]) + elif code == KeyCode.DOWN: + self._current = min(len(self._labels) - 1, self._current + 1) + elif code == KeyCode.UP: + self._current = max(0, self._current - 1) + + def pointerEvent(self, x, y, button): + pass + + def update(self, render): + """ + Draw GUI that list active session + """ + i = 0 + drawArea = QtGui.QImage(self._width, self._height, QtGui.QImage.Format_RGB16) + #fill with background Color + drawArea.fill(self._backGroudColor) + with QtGui.QPainter(drawArea) as qp: + for label in self._labels: + rect = QtCore.QRect(0, i * self._cellHeight, self._width, self._cellHeight) + if i == self._current: + qp.setPen(QtCore.Qt.darkGreen) + qp.drawRoundedRect(rect, 0.2, 0.2) + qp.setPen(QtCore.Qt.white) + qp.setFont(QtGui.QFont('arial', self._fontSize, QtGui.QFont.Bold)) + qp.drawText(rect, QtCore.Qt.AlignCenter, label) + i += 1 + render.drawImage(drawArea) + +class RDPRenderer(object): + def __init__(self, server): + """ + @param server: RDPServerController + """ + self._server = server + self._dx = 0 + self._dy = 0 + self._renderSize = 64 + + def translate(self, dx, dy): + self._dx += dx + self._dy += dy + + def drawImage(self, image): + """ + Render of widget + """ + nbWidth = image.width() / self._renderSize + 1 + nbHeight = image.height() / self._renderSize + 1 + for i in range(0, nbWidth): + for j in range(0, nbHeight): + tmp = image.copy(i * self._renderSize, j * self._renderSize, self._renderSize, self._renderSize) + #in RDP image or bottom top encoded + tmp = tmp.transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) + ptr = tmp.bits() + ptr.setsize(tmp.byteCount()) + self._server.sendUpdate(i * self._renderSize + self._dx, j * self._renderSize + self._dy, min((i + 1) * self._renderSize, image.width()) + self._dx - 1, min((j + 1) * self._renderSize, image.height()) + self._dy - 1, tmp.width(), tmp.height(), 16, False, ptr.asstring()) + \ No newline at end of file