From 9af647ab091c3bcf274fb9f07a65f17e1c9b35ab Mon Sep 17 00:00:00 2001 From: speyrefitte Date: Thu, 31 Jul 2014 17:58:36 +0200 Subject: [PATCH] support multi target in rdp proxy --- bin/rdpy-rdpproxy | 76 ++++++++++++++++++++++++----------- rdpy/protocol/rdp/pdu/data.py | 6 +-- rdpy/ui/view.py | 65 +++++++++++++++++++++++------- 3 files changed, 105 insertions(+), 42 deletions(-) diff --git a/bin/rdpy-rdpproxy b/bin/rdpy-rdpproxy index 5059527..03c7c5b 100755 --- a/bin/rdpy-rdpproxy +++ b/bin/rdpy-rdpproxy @@ -45,7 +45,22 @@ class ProxyServer(rdp.RDPServerObserver): rdp.RDPServerObserver.__init__(self, controller) self._credentialProvider = credentialProvider self._client = None - + self._window = None + + def showSelectView(self, machines): + self._machines = dict([("%s:%s"%(ip, port), (ip, port)) for ip, port in machines]) + width, height = self._controller.getScreen() + self._window = view.Window(width, height, QtGui.QColor(24, 93, 123)) + self._window.addView(view.Anchor(width / 2 - 250, 100, view.Label("Please select following server", 500, 50, QtGui.QFont('arial', 18, QtGui.QFont.Bold), backgroundColor = QtGui.QColor(24, 93, 123)))) + self._window.addView(view.Anchor(width / 2 - 250, 150, view.List(self._machines.keys(), 500, 500, self.onSelectMachine, QtGui.QColor(24, 93, 123))), True) + self._window.update(view.RDPRenderer(self._controller, self._controller.getColorDepth()), True) + + def onSelectMachine(self, machine): + ip, port = self._machines[machine] + width, height = self._controller.getScreen() + domain, username, password = self._controller.getCredentials() + reactor.connectTCP(ip, port, ProxyClientFactory(self, width, height, domain, username, password, "%s\\%s on %s:%s"%(domain, username, ip, port))) + def clientConnected(self, client): """ Event throw by client when it's ready @@ -54,23 +69,35 @@ class ProxyServer(rdp.RDPServerObserver): self._client = client self._controller.setColorDepth(self._client._controller.getColorDepth()) + def showErrorMessage(self, message): + """ + Print a message to the client + @param message: string + """ + width, height = self._controller.getScreen() + popup = view.Window(width, height, QtGui.QColor(24, 93, 123)) + popup.addView(view.Anchor(width / 2 - 250, height / 2 - 25, view.Label(message, 500, 50, QtGui.QFont('arial', 18, QtGui.QFont.Bold), backgroundColor = QtGui.QColor(24, 93, 123)))) + popup.update(view.RDPRenderer(self._controller, self._controller.getColorDepth()), True) + def onReady(self): """ Event use to inform state of server stack Use to connect client - On ready is not launch only on connection but after a reactivation process + On ready is not launch only after connection sequence but after a reactivation sequence too """ if self._client is None: #try a connection domain, username, password = self._controller.getCredentials() - try: - ip, port = self._credentialProvider.getProxyPass(domain, username) - except error.InvalidExpectedDataException as e: - log.info(e.message) - return + machines = self._credentialProvider.getProxyPass(domain, username) - width, height = self._controller.getScreen() - reactor.connectTCP(ip, port, ProxyClientFactory(self, width, height, domain, username, password, "%s\\%s on %s:%s"%(domain, username, ip, port))) + if len(machines) == 0: + self.showErrorMessage("No servers attach to account %s\\%s"%(domain, username)) + elif len(machines) == 1: + ip, port = machines[0] + width, height = self._controller.getScreen() + reactor.connectTCP(ip, port, ProxyClientFactory(self, width, height, domain, username, password, "%s\\%s on %s:%s"%(domain, username, ip, port))) + else: + self.showSelectView(machines) else: #refresh client width, height = self._controller.getScreen() @@ -91,9 +118,12 @@ class ProxyServer(rdp.RDPServerObserver): @param isPressed: True if key is down """ #no client connected - if self._client is None: - return - self._client._controller.sendKeyEventScancode(code, isPressed) + if not self._client is None: + self._client._controller.sendKeyEventScancode(code, isPressed) + elif not self._window is None and isPressed: + self._window.keyEvent(code) + self._window.update(view.RDPRenderer(self._controller, self._controller.getColorDepth())) + def onKeyEventUnicode(self, code, isPressed): """ @@ -262,10 +292,9 @@ class ProxyAdmin(rdp.RDPServerObserver): Init GUI view """ width, height = self._controller.getScreen() - self._window = view.WindowView(width, height, QtGui.QColor(24, 93, 123)) - self._list = view.ListView(ProxyClient._CONNECTED_.keys(), 500, 500, self.onSelect, QtGui.QColor(24, 93, 123)) - self._window.addView(view.AnchorView(width / 2 - 250, height / 2 - 250, self._list)) - self._render = view.RDPRenderer(self._controller, self._controller.getColorDepth()) + self._window = view.Window(width, height, QtGui.QColor(24, 93, 123)) + self._window.addView(view.Anchor(width / 2 - 250, 100, view.Label("Please select following session", 500, 50, QtGui.QFont('arial', 18, QtGui.QFont.Bold), backgroundColor = QtGui.QColor(24, 93, 123)))) + self._window.addView(view.Anchor(width / 2 - 250, 150, view.List(ProxyClient._CONNECTED_.keys(), 500, 500, self.onSelect, QtGui.QColor(24, 93, 123))), True) def onReady(self): """ @@ -274,7 +303,7 @@ class ProxyAdmin(rdp.RDPServerObserver): """ if self._state == ProxyAdmin.State.GUI: self.initView() - self._window.update(self._render, True) + self._window.update(view.RDPRenderer(self._controller, self._controller.getColorDepth()), True) elif self._state == ProxyAdmin.State.SPY: #refresh client width, height = self._controller.getScreen() @@ -296,7 +325,7 @@ class ProxyAdmin(rdp.RDPServerObserver): if not isPressed: return self._window.keyEvent(code) - self._window.update(self._render) + self._window.update(view.RDPRenderer(self._controller, self._controller.getColorDepth())) elif code == 1: #escape button refresh GUI self._state = ProxyAdmin.State.GUI @@ -364,20 +393,19 @@ class CredentialProvider(object): def getAccount(self, domain, username): if not self._config.has_key(domain) or not self._config[domain].has_key(username): - raise error.InvalidExpectedDataException("Invalid credentials %s\\%s"%(domain, username)) - + return None return self._config[domain][username] def getProxyPass(self, domain, username): """ @param domain: domain to check @param username: username in domain - @return: (ip, port) or None if error + @return: [(ip, port)] """ account = self.getAccount(domain, username) - if not account.has_key("ip") or not account.has_key("port"): - raise error.InvalidExpectedDataException("Invalid credentials declaration %s\\%s"%(domain, username)) - return str(account['ip']), account['port'] + if account is None: + return [] + return [(str(machine["ip"]), machine["port"]) for machine in account] def help(): """ diff --git a/rdpy/protocol/rdp/pdu/data.py b/rdpy/protocol/rdp/pdu/data.py index c0ac5c6..6c62a7b 100644 --- a/rdpy/protocol/rdp/pdu/data.py +++ b/rdpy/protocol/rdp/pdu/data.py @@ -646,7 +646,7 @@ class DataPDU(CompositeType): """ Create object in accordance self.shareDataHeader.pduType2 value """ - for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU, SuppressOutputDataPDU]: + for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU, SupressOutputDataPDU]: if self.shareDataHeader.pduType2.value == c._PDUTYPE2_: return c() log.debug("unknown PDU data type : %s"%hex(self.shareDataHeader.pduType2.value)) @@ -812,13 +812,13 @@ class InclusiveRectangle(CompositeType): @see: http://msdn.microsoft.com/en-us/library/cc240643.aspx """ def __init__(self, conditional = lambda:True): - CompositeType.__init__(self) + CompositeType.__init__(self, conditional = conditional) self.left = UInt16Le() self.top = UInt16Le() self.right = UInt16Le() self.bottom = UInt16Le() -class SuppressOutputDataPDU(CompositeType): +class SupressOutputDataPDU(CompositeType): """ @see: http://msdn.microsoft.com/en-us/library/cc240648.aspx """ diff --git a/rdpy/ui/view.py b/rdpy/ui/view.py index 20fdf92..c3090ba 100644 --- a/rdpy/ui/view.py +++ b/rdpy/ui/view.py @@ -86,7 +86,7 @@ class IView(object): raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "update", "IView")) -class AnchorView(IView): +class Anchor(IView): def __init__(self, x, y, view): self._x = x self._y = y @@ -103,7 +103,7 @@ class AnchorView(IView): self._view.update(render, force) render.translate(-self._x, -self._y) -class ListView(IView): +class List(IView): """ List widget simulate by QT painter """ @@ -158,15 +158,17 @@ class ListView(IView): i += 1 render.drawImage(drawArea) -class WindowView(IView): +class Window(IView): def __init__(self, width, height, backgroundColor = QtCore.Qt.black): self._views = [] self._focusIndex = 0 self._width = width self._height = height self._backgroundColor = backgroundColor - def addView(self, view): + def addView(self, view, focus = False): self._views.append(view) + if focus: + self._focusIndex = len(self._views) - 1 def keyEvent(self, code): if self._focusIndex < len(self._views): self._views[self._focusIndex].keyEvent(code) @@ -181,6 +183,43 @@ class WindowView(IView): render.drawImage(drawArea) for view in self._views: view.update(render, force) + +class Label(IView): + def __init__(self, label, width, height, font = QtGui.QFont(), fontColor = QtCore.Qt.white, backgroundColor = QtCore.Qt.black): + self._label = label + self._width = width + self._height = height + self._font = font + self._fontColor = fontColor + self._backgroundColor = backgroundColor + + def keyEvent(self, code): + """ + Nothing to do + """ + pass + + def pointerEvent(self, x, y, button): + """ + Nothing to do + """ + pass + + def update(self, render, force = False): + """ + Update view + @param render: IRender + @param force: force update + """ + if not force: + return; + drawArea = QtGui.QImage(self._width, self._height, render.getImageFormat()) + drawArea.fill(self._backgroundColor) + with QtGui.QPainter(drawArea) as qp: + qp.setFont(self._font) + qp.setPen(self._fontColor) + qp.drawText(drawArea.rect(), QtCore.Qt.AlignCenter, self._label) + render.drawImage(drawArea) class RDPRenderer(object): def __init__(self, controller, colorDepth): @@ -192,7 +231,6 @@ class RDPRenderer(object): self._colorDepth = colorDepth self._dx = 0 self._dy = 0 - self._renderSize = 64 def getImageFormat(self): if self._colorDepth == 15: @@ -212,14 +250,11 @@ class RDPRenderer(object): """ 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._controller.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(), self._colorDepth, False, ptr.asstring()) + padding = image.width() % 4 + for i in range(0, image.height()): + tmp = image.copy(0, i, image.width() + padding, 1) + #in RDP image or bottom top encoded + ptr = tmp.bits() + ptr.setsize(tmp.byteCount()) + self._controller.sendUpdate(self._dx, i + self._dy, image.width() + self._dx - 1, i + self._dy, tmp.width(), tmp.height(), self._colorDepth, False, ptr.asstring()) \ No newline at end of file