fix rdp stander security layer server side bug, fix lisense automata, ready for next release
This commit is contained in:
@@ -7,7 +7,7 @@ before_install:
|
|||||||
- sudo apt-get install python-qt4
|
- sudo apt-get install python-qt4
|
||||||
- ln -s /usr/lib/python2.7/dist-packages/PyQt4/ $VIRTUAL_ENV/lib/python2.7/site-packages/
|
- ln -s /usr/lib/python2.7/dist-packages/PyQt4/ $VIRTUAL_ENV/lib/python2.7/site-packages/
|
||||||
- ln -s /usr/lib/python2.7/dist-packages/sip.so $VIRTUAL_ENV/lib/python2.7/site-packages/
|
- ln -s /usr/lib/python2.7/dist-packages/sip.so $VIRTUAL_ENV/lib/python2.7/site-packages/
|
||||||
- pip install qt4reactor pyopenssl twisted service_identity
|
- pip install qt4reactor pyopenssl twisted service_identity rsa
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- python setup.py install
|
- python setup.py install
|
||||||
|
|||||||
46
README.md
46
README.md
@@ -21,14 +21,14 @@ sudo apt-get install python-qt4
|
|||||||
|
|
||||||
x86 | x86_64
|
x86 | x86_64
|
||||||
----|-------
|
----|-------
|
||||||
[PyQt4 x86](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x32.exe) | [PyQt4 x86_64](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x64.exe/download)
|
[PyQt4](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x32.exe) | [PyQt4](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x64.exe/download)
|
||||||
[PyWin32 x86](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.7.exe/download) | [PyWin32 x86_64](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win-amd64-py2.7.exe/download)
|
[PyWin32](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.7.exe/download) | [PyWin32](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win-amd64-py2.7.exe/download)
|
||||||
|
|
||||||
### Build
|
### Build
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone https://github.com/citronneur/rdpy.git rdpy
|
$ git clone https://github.com/citronneur/rdpy.git rdpy
|
||||||
$ pip install twisted pyopenssl qt4reactor
|
$ pip install twisted pyopenssl qt4reactor service_identity rsa
|
||||||
$ python rdpy/setup.py install
|
$ python rdpy/setup.py install
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -81,36 +81,32 @@ $ rdpy-vncscreenshot.py [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:590
|
|||||||
|
|
||||||
### rdpy-rdpproxy
|
### rdpy-rdpproxy
|
||||||
|
|
||||||
rdpy-rdpproxy is a RDP proxy. It is used to manage and control access to the RDP servers as well as watch live sessions through any RDP client. It can be compared to a HTTP reverse proxy with added spy features.
|
rdpy-rdpproxy is a RDP proxy with shadow and record function.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rdpy-rdpproxy.py -f credentials_file_path -k private_key_file_path -c certificate_file_path [-i admin_ip[:admin_port]] listen_port
|
$ rdpy-rdpproxy.py -t target_ip[:target_port] [-k private_key_file_path] [-c certificate_file_path] [-i admin_ip[:admin_port]] listen_port
|
||||||
```
|
```
|
||||||
|
|
||||||
The credentials file is JSON file that must conform with the following format:
|
The target ip and port represent the target host.
|
||||||
|
|
||||||
|
The private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer. The CredSSP security layer is planned for an upcoming release. If one of both parameters are omitted, the server use standard RDP as security layer.
|
||||||
|
|
||||||
|
The IP and port admin are used in order to shadow active sessions thanks to a RDP client (rdpy-rdpclient, remina, mstsc) set username parameter like name of session printed by proxy.
|
||||||
|
|
||||||
|
Exemple :
|
||||||
```
|
```
|
||||||
{
|
$ rdpy-rdpproxy.py -t [my_computer] -i 0.0.0.0:56654 3389
|
||||||
"domain1":
|
$ INFO : Shadow listener on 0.0.0.0:56654
|
||||||
{
|
$ INFO : **************************************************
|
||||||
"username1":
|
$ INFO : Now connected
|
||||||
[
|
$ INFO : ['super-administrator']
|
||||||
{"ip":"machine1", "port":3389"},
|
$ INFO : **************************************************
|
||||||
{"ip":"machine2", "port":3389"}
|
|
||||||
],
|
|
||||||
"username2":
|
|
||||||
[
|
|
||||||
{"ip":"machine1", "port":3389"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
In this exemple domain1\username1 can access to machine1 and machine2 and domain1\username2 can only access to machine1.
|
To shadow 'super-administrator' session :
|
||||||
|
```
|
||||||
The private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer but RDPY is limited to SSL. The CredSSP security layer is planned for an upcoming release. The basic RDP security layer is not supported (windows wp sp1&2).
|
$ rdpy-rdpclient.py -u super-administrator 127.0.0.1:56654
|
||||||
|
```
|
||||||
The IP and port admin are used in order to spy active sessions thanks to a RDP client (rdpy-rdpclient, remina, mstsc). Common values are 127.0.0.1:3389 to protect from connections by unauthorized user.
|
|
||||||
|
|
||||||
## RDPY Qt Widget
|
## RDPY Qt Widget
|
||||||
|
|
||||||
|
|||||||
@@ -35,16 +35,17 @@ class RDPClientQtFactory(rdp.ClientFactory):
|
|||||||
"""
|
"""
|
||||||
@summary: Factory create a RDP GUI client
|
@summary: Factory create a RDP GUI client
|
||||||
"""
|
"""
|
||||||
def __init__(self, width, height, username, password, domain, fullscreen, keyboardLayout, optimized):
|
def __init__(self, width, height, username, password, domain, fullscreen, keyboardLayout, optimized, security):
|
||||||
"""
|
"""
|
||||||
@param width: width of client
|
@param width: {integer} width of client
|
||||||
@param heigth: heigth of client
|
@param heigth: {integer} heigth of client
|
||||||
@param username: username present to the server
|
@param username: {str} username present to the server
|
||||||
@param password: password present to the server
|
@param password: {str} password present to the server
|
||||||
@param domain: microsoft domain
|
@param domain: {str} microsoft domain
|
||||||
@param fullscreen: show widget in fullscreen mode
|
@param fullscreen: {bool} show widget in fullscreen mode
|
||||||
@param keyboardLayout: keyboard layout
|
@param keyboardLayout: {str} (fr|en) keyboard layout
|
||||||
@param optimized: enable optimized session orders
|
@param optimized: {bool} enable optimized session orders
|
||||||
|
@param security: {str} (ssl | rdp | nego)
|
||||||
"""
|
"""
|
||||||
self._width = width
|
self._width = width
|
||||||
self._height = height
|
self._height = height
|
||||||
@@ -54,8 +55,12 @@ class RDPClientQtFactory(rdp.ClientFactory):
|
|||||||
self._fullscreen = fullscreen
|
self._fullscreen = fullscreen
|
||||||
self._keyboardLayout = keyboardLayout
|
self._keyboardLayout = keyboardLayout
|
||||||
self._optimized = optimized
|
self._optimized = optimized
|
||||||
|
self._nego = security == "nego"
|
||||||
|
if self._nego:
|
||||||
|
self._security = "ssl"
|
||||||
|
else:
|
||||||
|
self._security = security
|
||||||
self._w = None
|
self._w = None
|
||||||
self._basicRDPSecurity = False
|
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
def buildObserver(self, controller, addr):
|
||||||
"""
|
"""
|
||||||
@@ -66,9 +71,9 @@ class RDPClientQtFactory(rdp.ClientFactory):
|
|||||||
@return: RDPClientQt
|
@return: RDPClientQt
|
||||||
"""
|
"""
|
||||||
#create client observer
|
#create client observer
|
||||||
client = RDPClientQt(controller, self._width, self._height)
|
self._client = RDPClientQt(controller, self._width, self._height)
|
||||||
#create qt widget
|
#create qt widget
|
||||||
self._w = client.getWidget()
|
self._w = self._client.getWidget()
|
||||||
self._w.setWindowTitle('rdpy-rdpclient')
|
self._w.setWindowTitle('rdpy-rdpclient')
|
||||||
if self._fullscreen:
|
if self._fullscreen:
|
||||||
self._w.showFullScreen()
|
self._w.showFullScreen()
|
||||||
@@ -82,14 +87,9 @@ class RDPClientQtFactory(rdp.ClientFactory):
|
|||||||
controller.setHostname(socket.gethostname())
|
controller.setHostname(socket.gethostname())
|
||||||
if self._optimized:
|
if self._optimized:
|
||||||
controller.setPerformanceSession()
|
controller.setPerformanceSession()
|
||||||
|
controller.setSecurityLevel(self._security)
|
||||||
if self._basicRDPSecurity:
|
|
||||||
controller.setRDPBasicSecurity()
|
|
||||||
|
|
||||||
return client
|
return self._client
|
||||||
|
|
||||||
def startedConnecting(self, connector):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def clientConnectionLost(self, connector, reason):
|
def clientConnectionLost(self, connector, reason):
|
||||||
"""
|
"""
|
||||||
@@ -98,8 +98,12 @@ class RDPClientQtFactory(rdp.ClientFactory):
|
|||||||
@param reason: str use to advertise reason of lost connection
|
@param reason: str use to advertise reason of lost connection
|
||||||
"""
|
"""
|
||||||
#try reconnect with basic RDP security
|
#try reconnect with basic RDP security
|
||||||
if reason.type == RDPSecurityNegoFail and not self._basicRDPSecurity:
|
if reason.type == RDPSecurityNegoFail and self._nego:
|
||||||
self._basicRDPSecurity = True
|
#stop nego
|
||||||
|
log.info("due to security nego error back to standard RDP security layer")
|
||||||
|
self._nego = False
|
||||||
|
self._security = "rdp"
|
||||||
|
self._client._widget.hide()
|
||||||
connector.connect()
|
connector.connect()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -205,12 +209,9 @@ if __name__ == '__main__':
|
|||||||
width = QtGui.QDesktopWidget().screenGeometry().width()
|
width = QtGui.QDesktopWidget().screenGeometry().width()
|
||||||
height = QtGui.QDesktopWidget().screenGeometry().height()
|
height = QtGui.QDesktopWidget().screenGeometry().height()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
log.info("keyboard layout set to %s"%keyboardLayout)
|
log.info("keyboard layout set to %s"%keyboardLayout)
|
||||||
|
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
reactor.connectTCP(ip, int(port), RDPClientQtFactory(width, height, username, password, domain, fullscreen, keyboardLayout, optimized))
|
reactor.connectTCP(ip, int(port), RDPClientQtFactory(width, height, username, password, domain, fullscreen, keyboardLayout, optimized, "nego"))
|
||||||
reactor.runReturn()
|
reactor.runReturn()
|
||||||
app.exec_()
|
app.exec_()
|
||||||
@@ -23,10 +23,10 @@ RDP proxy with spy capabilities
|
|||||||
---------------------------
|
---------------------------
|
||||||
Client RDP -> | ProxyServer | ProxyClient | -> Server RDP
|
Client RDP -> | ProxyServer | ProxyClient | -> Server RDP
|
||||||
---------------------------
|
---------------------------
|
||||||
| ProxyAdmin |
|
| ProxyShadow |
|
||||||
------------
|
--------------
|
||||||
^
|
^
|
||||||
Admin ----------------------|
|
Shadow -------------------|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys, os, getopt, json
|
import sys, os, getopt, json
|
||||||
@@ -37,77 +37,32 @@ from rdpy.ui import view
|
|||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
#log._LOG_LEVEL = log.Level.INFO
|
log._LOG_LEVEL = log.Level.INFO
|
||||||
|
|
||||||
class ProxyServer(rdp.RDPServerObserver):
|
class ProxyServer(rdp.RDPServerObserver):
|
||||||
"""
|
"""
|
||||||
@summary: Server side of proxy
|
@summary: Server side of proxy
|
||||||
"""
|
"""
|
||||||
def __init__(self, controller, credentialProvider):
|
_SESSIONS_ = {}
|
||||||
|
def __init__(self, controller, target):
|
||||||
"""
|
"""
|
||||||
@param controller: RDPServerController
|
@param controller: {RDPServerController}
|
||||||
@param credentialProvider: CredentialProvider
|
@param target: {tuple(ip, port)}
|
||||||
"""
|
"""
|
||||||
rdp.RDPServerObserver.__init__(self, controller)
|
rdp.RDPServerObserver.__init__(self, controller)
|
||||||
self._credentialProvider = credentialProvider
|
self._target = target
|
||||||
self._client = None
|
self._client = None
|
||||||
self._window = None
|
|
||||||
|
|
||||||
def showSelectView(self, machines):
|
|
||||||
"""
|
|
||||||
@summary: Show select sever view to the client
|
|
||||||
@param machines: [(ip, port)]
|
|
||||||
"""
|
|
||||||
self._machines = machines
|
|
||||||
width, height = self._controller.getScreen()
|
|
||||||
self._window = view.Window(width, height, QtGui.QColor(8, 24, 66))
|
|
||||||
|
|
||||||
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(8, 24, 66))))
|
|
||||||
|
|
||||||
self._window.addView(view.Anchor(width / 2 - 250, 150,
|
|
||||||
view.List(["%s:%s"%(ip, port) for ip, port in machines],
|
|
||||||
500, 500, self.onSelectMachine,
|
|
||||||
QtGui.QColor(8, 24, 66))), True)
|
|
||||||
|
|
||||||
self._window.update(view.RDPRenderer(self._controller), True)
|
|
||||||
|
|
||||||
def onSelectMachine(self, index):
|
|
||||||
"""
|
|
||||||
@summary: Callback of view.List in Select server view
|
|
||||||
@param: index in list
|
|
||||||
"""
|
|
||||||
ip, port = self._machines[index]
|
|
||||||
width, height = self._controller.getScreen()
|
|
||||||
domain, username, password = self._controller.getCredentials()
|
|
||||||
reactor.connectTCP(ip, port, ProxyClientFactory(self, width, height, domain, username, password))
|
|
||||||
|
|
||||||
def clientConnected(self, client):
|
def clientConnected(self, client):
|
||||||
"""
|
"""
|
||||||
@summary: Event throw by client when it's ready
|
@summary: Event throw by client when it's ready
|
||||||
@param client: ProxyClient
|
@param client: {ProxyClient}
|
||||||
"""
|
"""
|
||||||
self._client = client
|
self._client = client
|
||||||
#need to reevaluate color depth
|
#need to reevaluate color depth
|
||||||
self._controller.setColorDepth(self._client._controller.getColorDepth())
|
self._controller.setColorDepth(self._client._controller.getColorDepth())
|
||||||
|
ProxyServer._SESSIONS_[self._controller.getHostname()] = client
|
||||||
def showMessage(self, message):
|
nowConnected()
|
||||||
"""
|
|
||||||
@summary: Print a message to the client
|
|
||||||
@param message: string
|
|
||||||
"""
|
|
||||||
width, height = self._controller.getScreen()
|
|
||||||
|
|
||||||
popup = view.Window(width, height, QtGui.QColor(8, 24, 66))
|
|
||||||
|
|
||||||
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(8, 24, 66))))
|
|
||||||
|
|
||||||
popup.update(view.RDPRenderer(self._controller), True)
|
|
||||||
|
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
@@ -115,23 +70,15 @@ class ProxyServer(rdp.RDPServerObserver):
|
|||||||
First time this event is called is when human client is connected
|
First time this event is called is when human client is connected
|
||||||
Second time is after color depth nego, because color depth nego
|
Second time is after color depth nego, because color depth nego
|
||||||
restart a connection sequence
|
restart a connection sequence
|
||||||
Use to connect proxy client or show available server
|
|
||||||
@see: rdp.RDPServerObserver.onReady
|
@see: rdp.RDPServerObserver.onReady
|
||||||
"""
|
"""
|
||||||
if self._client is None:
|
if self._client is None:
|
||||||
#try a connection
|
#try a connection
|
||||||
domain, username, password = self._controller.getCredentials()
|
domain, username, password = self._controller.getCredentials()
|
||||||
machines = self._credentialProvider.getProxyPass(domain, username)
|
|
||||||
|
|
||||||
if len(machines) == 0:
|
width, height = self._controller.getScreen()
|
||||||
self.showMessage("No servers attach to account %s\\%s"%(domain, username))
|
reactor.connectTCP(self._target[0], int(self._target[1]), ProxyClientFactory(self, width, height,
|
||||||
elif len(machines) == 1:
|
domain, username, password))
|
||||||
ip, port = machines[0]
|
|
||||||
width, height = self._controller.getScreen()
|
|
||||||
reactor.connectTCP(ip, port, ProxyClientFactory(self, width, height,
|
|
||||||
domain, username, password))
|
|
||||||
else:
|
|
||||||
self.showSelectView(machines)
|
|
||||||
else:
|
else:
|
||||||
#refresh client
|
#refresh client
|
||||||
width, height = self._controller.getScreen()
|
width, height = self._controller.getScreen()
|
||||||
@@ -144,6 +91,9 @@ class ProxyServer(rdp.RDPServerObserver):
|
|||||||
"""
|
"""
|
||||||
if self._client is None:
|
if self._client is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
del ProxyServer._SESSIONS_[self._controller.getHostname()]
|
||||||
|
nowConnected()
|
||||||
#close proxy client
|
#close proxy client
|
||||||
self._client._controller.close()
|
self._client._controller.close()
|
||||||
|
|
||||||
@@ -154,13 +104,9 @@ class ProxyServer(rdp.RDPServerObserver):
|
|||||||
@param isPressed: True if key is down
|
@param isPressed: True if key is down
|
||||||
@see: rdp.RDPServerObserver.onKeyEventScancode
|
@see: rdp.RDPServerObserver.onKeyEventScancode
|
||||||
"""
|
"""
|
||||||
#no client connected
|
if self._client is None:
|
||||||
if not self._client is None:
|
return
|
||||||
self._client._controller.sendKeyEventScancode(code, isPressed)
|
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))
|
|
||||||
|
|
||||||
|
|
||||||
def onKeyEventUnicode(self, code, isPressed):
|
def onKeyEventUnicode(self, code, isPressed):
|
||||||
"""
|
"""
|
||||||
@@ -169,7 +115,6 @@ class ProxyServer(rdp.RDPServerObserver):
|
|||||||
@param isPressed: True if key is down
|
@param isPressed: True if key is down
|
||||||
@see: rdp.RDPServerObserver.onKeyEventUnicode
|
@see: rdp.RDPServerObserver.onKeyEventUnicode
|
||||||
"""
|
"""
|
||||||
#no client connected domain
|
|
||||||
if self._client is None:
|
if self._client is None:
|
||||||
return
|
return
|
||||||
self._client._controller.sendKeyEventUnicode(code, isPressed)
|
self._client._controller.sendKeyEventUnicode(code, isPressed)
|
||||||
@@ -183,7 +128,6 @@ class ProxyServer(rdp.RDPServerObserver):
|
|||||||
@param isPressed: True if mouse button is pressed
|
@param isPressed: True if mouse button is pressed
|
||||||
@see: rdp.RDPServerObserver.onPointerEvent
|
@see: rdp.RDPServerObserver.onPointerEvent
|
||||||
"""
|
"""
|
||||||
#no client connected
|
|
||||||
if self._client is None:
|
if self._client is None:
|
||||||
return
|
return
|
||||||
self._client._controller.sendPointerEvent(x, y, button, isPressed)
|
self._client._controller.sendPointerEvent(x, y, button, isPressed)
|
||||||
@@ -192,14 +136,14 @@ class ProxyServerFactory(rdp.ServerFactory):
|
|||||||
"""
|
"""
|
||||||
@summary: Factory on listening events
|
@summary: Factory on listening events
|
||||||
"""
|
"""
|
||||||
def __init__(self, credentialProvider, privateKeyFilePath, certificateFilePath):
|
def __init__(self, target, privateKeyFilePath = None, certificateFilePath = None):
|
||||||
"""
|
"""
|
||||||
@param credentialProvider: CredentialProvider
|
@param target: {tuple(ip, prt)}
|
||||||
@param privateKeyFilePath: file contain server private key
|
@param privateKeyFilePath: {str} file contain server private key (if none -> back to standard RDP security)
|
||||||
@param certificateFilePath: file contain server certificate
|
@param certificateFilePath: {str} file contain server certificate (if none -> back to standard RDP security)
|
||||||
"""
|
"""
|
||||||
rdp.ServerFactory.__init__(self, privateKeyFilePath, certificateFilePath, 16)
|
rdp.ServerFactory.__init__(self, 16, privateKeyFilePath, certificateFilePath)
|
||||||
self._credentialProvider = credentialProvider
|
self._target = target
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
def buildObserver(self, controller, addr):
|
||||||
"""
|
"""
|
||||||
@@ -207,23 +151,19 @@ class ProxyServerFactory(rdp.ServerFactory):
|
|||||||
@param addr: destination address
|
@param addr: destination address
|
||||||
@see: rdp.ServerFactory.buildObserver
|
@see: rdp.ServerFactory.buildObserver
|
||||||
"""
|
"""
|
||||||
return ProxyServer(controller, self._credentialProvider)
|
return ProxyServer(controller, self._target)
|
||||||
|
|
||||||
class ProxyClient(rdp.RDPClientObserver):
|
class ProxyClient(rdp.RDPClientObserver):
|
||||||
"""
|
"""
|
||||||
@summary: Client side of proxy
|
@summary: Client side of proxy
|
||||||
"""
|
"""
|
||||||
_CONNECTED_ = []
|
def __init__(self, controller, server):
|
||||||
def __init__(self, controller, server, name = None):
|
|
||||||
"""
|
"""
|
||||||
@param controller: rdp.RDPClientController
|
@param controller: rdp.RDPClientController
|
||||||
@param server: ProxyServer
|
@param server: ProxyServer
|
||||||
@param name: name of session None if you don't
|
|
||||||
want to spy this session
|
|
||||||
"""
|
"""
|
||||||
rdp.RDPClientObserver.__init__(self, controller)
|
rdp.RDPClientObserver.__init__(self, controller)
|
||||||
self._server = server
|
self._server = server
|
||||||
self._name = name
|
|
||||||
self._connected = False
|
self._connected = False
|
||||||
|
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
@@ -239,9 +179,7 @@ class ProxyClient(rdp.RDPClientObserver):
|
|||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self._connected = True
|
self._connected = True
|
||||||
|
|
||||||
if not self._name is None:
|
|
||||||
ProxyClient._CONNECTED_.append(self)
|
|
||||||
self._server.clientConnected(self)
|
self._server.clientConnected(self)
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
@@ -249,8 +187,6 @@ class ProxyClient(rdp.RDPClientObserver):
|
|||||||
@summary: Event inform that stack is close
|
@summary: Event inform that stack is close
|
||||||
@see: rdp.RDPClientObserver.onClose
|
@see: rdp.RDPClientObserver.onClose
|
||||||
"""
|
"""
|
||||||
if not self._name is None:
|
|
||||||
ProxyClient._CONNECTED_.remove(self)
|
|
||||||
|
|
||||||
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
||||||
"""
|
"""
|
||||||
@@ -302,57 +238,18 @@ class ProxyClientFactory(rdp.ClientFactory):
|
|||||||
controller.setDomain(self._domain)
|
controller.setDomain(self._domain)
|
||||||
controller.setUsername(self._username)
|
controller.setUsername(self._username)
|
||||||
controller.setPassword(self._password)
|
controller.setPassword(self._password)
|
||||||
return ProxyClient(controller, self._server, "%s\\%s on %s"%(self._domain, self._username, addr))
|
return ProxyClient(controller, self._server)
|
||||||
|
|
||||||
def startedConnecting(self, connector):
|
class Shadow(rdp.RDPServerObserver):
|
||||||
pass
|
|
||||||
|
|
||||||
def clientConnectionLost(self, connector, reason):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def clientConnectionFailed(self, connector, reason):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ProxyAdmin(rdp.RDPServerObserver):
|
|
||||||
"""
|
"""
|
||||||
@summary: Use to manage admin session
|
@summary: Use to manage admin session
|
||||||
Add GUI to select which session to see
|
|
||||||
Just escape key is authorized during spy session
|
|
||||||
To switch from spy state to admin state
|
|
||||||
"""
|
"""
|
||||||
class State(object):
|
|
||||||
GUI = 0 #->list of active session
|
|
||||||
SPY = 1 #->watch active session
|
|
||||||
|
|
||||||
def __init__(self, controller):
|
def __init__(self, controller):
|
||||||
"""
|
"""
|
||||||
@param server: rdp.RDPServerController
|
@param server: rdp.RDPServerController
|
||||||
"""
|
"""
|
||||||
rdp.RDPServerObserver.__init__(self, controller)
|
rdp.RDPServerObserver.__init__(self, controller)
|
||||||
self._spy = None
|
self._client = None
|
||||||
self._state = ProxyAdmin.State.GUI
|
|
||||||
|
|
||||||
def initView(self):
|
|
||||||
"""
|
|
||||||
@summary: Initialize Admin GUI view
|
|
||||||
"""
|
|
||||||
self._sessions = list(ProxyClient._CONNECTED_) #copy at t time
|
|
||||||
width, height = self._controller.getScreen()
|
|
||||||
self._window = view.Window(width, height, QtGui.QColor(8, 24, 66))
|
|
||||||
|
|
||||||
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(8, 24, 66))))
|
|
||||||
|
|
||||||
self._window.addView(view.Anchor(width / 2 - 250, 150,
|
|
||||||
view.List([p._name for p in self._sessions],
|
|
||||||
500, 500, self.onSelect,
|
|
||||||
QtGui.QColor(8, 24, 66))), True)
|
|
||||||
|
|
||||||
def clientConnected(self, client):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
@@ -360,73 +257,39 @@ class ProxyAdmin(rdp.RDPServerObserver):
|
|||||||
May be called after an setColorDepth too
|
May be called after an setColorDepth too
|
||||||
@see: rdp.RDPServerObserver.onReady
|
@see: rdp.RDPServerObserver.onReady
|
||||||
"""
|
"""
|
||||||
if self._state == ProxyAdmin.State.GUI:
|
if self._client is None:
|
||||||
self.initView()
|
username = self._controller.getUsername()
|
||||||
self._window.update(view.RDPRenderer(self._controller), True)
|
if not ProxyServer._SESSIONS_.has_key(username):
|
||||||
elif self._state == ProxyAdmin.State.SPY:
|
log.info("invalid session name [%s]"%username)
|
||||||
|
self._controller.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
self._client = ProxyClient(ProxyServer._SESSIONS_[username]._controller, self)
|
||||||
|
self._controller.setColorDepth(self._client._controller.getColorDepth())
|
||||||
|
else:
|
||||||
#refresh client
|
#refresh client
|
||||||
width, height = self._controller.getScreen()
|
width, height = self._controller.getScreen()
|
||||||
self._spy._controller.sendRefreshOrder(0, 0, width, height)
|
self._client._controller.sendRefreshOrder(0, 0, width, height)
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
@summary: Stack is close
|
@summary: Stack is close
|
||||||
@see: rdp.RDPServerObserver.onClose
|
@see: rdp.RDPServerObserver.onClose
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
def onKeyEventScancode(self, code, isPressed):
|
def onKeyEventScancode(self, code, isPressed):
|
||||||
|
""" Shadow
|
||||||
"""
|
"""
|
||||||
@summary: Event call when a keyboard event
|
|
||||||
is catch in scan code format
|
|
||||||
@param code: scan code of key
|
|
||||||
@param isPressed: True if key is down
|
|
||||||
@see: rdp.RDPServerObserver.onKeyEventScancode
|
|
||||||
"""
|
|
||||||
if self._state == ProxyAdmin.State.GUI:
|
|
||||||
if not isPressed:
|
|
||||||
return
|
|
||||||
self._window.keyEvent(code)
|
|
||||||
self._window.update(view.RDPRenderer(self._controller))
|
|
||||||
elif code == 1:
|
|
||||||
#escape button refresh GUI
|
|
||||||
self._state = ProxyAdmin.State.GUI
|
|
||||||
self._spy._controller.removeClientObserver(self._spy)
|
|
||||||
self.onReady()
|
|
||||||
|
|
||||||
def onKeyEventUnicode(self, code, isPressed):
|
def onKeyEventUnicode(self, code, isPressed):
|
||||||
|
""" Shadow
|
||||||
"""
|
"""
|
||||||
@summary: Event call when a keyboard event is catch in unicode format
|
|
||||||
Admin GUI add filter for this event
|
|
||||||
@param code: unicode of key
|
|
||||||
@param isPressed: True if key is down
|
|
||||||
@see: rdp.RDPServerObserver.onKeyEventUnicode
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onPointerEvent(self, x, y, button, isPressed):
|
def onPointerEvent(self, x, y, button, isPressed):
|
||||||
|
""" Shadow
|
||||||
"""
|
"""
|
||||||
@summary: Event call on mouse event
|
|
||||||
Admin GUI add filter for this event
|
|
||||||
@param x: x position
|
|
||||||
@param y: y position
|
|
||||||
@param button: 1, 2 or 3 button
|
|
||||||
@param isPressed: True if mouse button is pressed
|
|
||||||
@see: rdp.RDPServerObserver.onPointerEvent
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onSelect(self, index):
|
class ShadowFactory(rdp.ServerFactory):
|
||||||
"""
|
|
||||||
@summary: Callback of list view of active session
|
|
||||||
Connect to select session
|
|
||||||
@param index: index in sessions array
|
|
||||||
"""
|
|
||||||
self._state = ProxyAdmin.State.SPY
|
|
||||||
self._spy = ProxyClient(self._sessions[index]._controller, self)
|
|
||||||
self._controller.setColorDepth(self._spy._controller.getColorDepth())
|
|
||||||
|
|
||||||
class ProxyAdminFactory(rdp.ServerFactory):
|
|
||||||
"""
|
"""
|
||||||
@summary: Factory for admin session
|
@summary: Factory for admin session
|
||||||
"""
|
"""
|
||||||
@@ -435,7 +298,7 @@ class ProxyAdminFactory(rdp.ServerFactory):
|
|||||||
@param privateKeyFilePath: private key for admin session
|
@param privateKeyFilePath: private key for admin session
|
||||||
@param certificateFilePath: certificate for admin session
|
@param certificateFilePath: certificate for admin session
|
||||||
"""
|
"""
|
||||||
rdp.ServerFactory.__init__(self, privateKeyFilePath, certificateFilePath, 16)
|
rdp.ServerFactory.__init__(self, 16, privateKeyFilePath, certificateFilePath)
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
def buildObserver(self, controller, addr):
|
||||||
"""
|
"""
|
||||||
@@ -445,116 +308,58 @@ class ProxyAdminFactory(rdp.ServerFactory):
|
|||||||
@return: ProxyAdmin
|
@return: ProxyAdmin
|
||||||
@see: rdp.ServerFactory.buildObserver
|
@see: rdp.ServerFactory.buildObserver
|
||||||
"""
|
"""
|
||||||
return ProxyAdmin(controller)
|
return Shadow(controller)
|
||||||
|
|
||||||
class CredentialProvider(object):
|
|
||||||
"""
|
|
||||||
@summary: Credential provider for proxy
|
|
||||||
"""
|
|
||||||
def __init__(self, config):
|
|
||||||
"""
|
|
||||||
@param config: rdp proxy config
|
|
||||||
"""
|
|
||||||
self._config = config
|
|
||||||
|
|
||||||
def getAccount(self, domain, username):
|
|
||||||
"""
|
|
||||||
@summary: Find account that match domain::username in config file
|
|
||||||
@param domain: Windows domain
|
|
||||||
@param username: username for session
|
|
||||||
@return: [(unicode(ip), port] or None if not found
|
|
||||||
"""
|
|
||||||
if not self._config.has_key(domain) or not self._config[domain].has_key(username):
|
|
||||||
return None
|
|
||||||
return self._config[domain][username]
|
|
||||||
|
|
||||||
def getProxyPass(self, domain, username):
|
|
||||||
"""
|
|
||||||
@summary: Find list of server available for thi account
|
|
||||||
@param domain: domain to check
|
|
||||||
@param username: username in domain
|
|
||||||
@return: [(ip, port)]
|
|
||||||
"""
|
|
||||||
account = self.getAccount(domain, username)
|
|
||||||
if account is None:
|
|
||||||
return []
|
|
||||||
return [(str(machine["ip"]), machine["port"]) for machine in account]
|
|
||||||
|
|
||||||
def help():
|
def help():
|
||||||
"""
|
"""
|
||||||
@summary: Print help in console
|
@summary: Print help in console
|
||||||
"""
|
"""
|
||||||
print "Usage: rdpy-rdpproxy -f credential_file_path -k private_key_file_path -c certificate_file_path [-i admin_ip[:admin_port]] listen_port"
|
print "Usage: rdpy-rdpproxy -t target_ip[:target_port] [-k private_key_file_path (mandatory for SSL)] [-c certificate_file_path (mandatory for SSL)] [-i admin_ip[:admin_port]] listen_port"
|
||||||
|
|
||||||
|
def parseIpPort(interface, defaultPort = "3389"):
|
||||||
|
if ':' in interface:
|
||||||
|
return interface.split(':')
|
||||||
|
else:
|
||||||
|
return interface, defaultPort
|
||||||
|
|
||||||
def loadConfig(configFilePath):
|
def nowConnected():
|
||||||
"""
|
log.info("*" * 50)
|
||||||
@summary: Load and check config file
|
log.info("Now connected")
|
||||||
@param configFilePath: config file path
|
log.info(ProxyServer._SESSIONS_.keys())
|
||||||
"""
|
log.info("*" * 50)
|
||||||
if not os.path.isfile(configFilePath):
|
|
||||||
log.error("File %s doesn't exist"%configFilePath)
|
|
||||||
return None
|
|
||||||
|
|
||||||
f = open(configFilePath, 'r')
|
|
||||||
config = json.load(f)
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
return config
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
configFilePath = None
|
target = None
|
||||||
privateKeyFilePath = None
|
privateKeyFilePath = None
|
||||||
certificateFilePath = None
|
certificateFilePath = None
|
||||||
adminInterface = None
|
shadowInterface = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "hf:k:c:i:")
|
opts, args = getopt.getopt(sys.argv[1:], "ht:k:c:i:")
|
||||||
except getopt.GetoptError:
|
except getopt.GetoptError:
|
||||||
help()
|
help()
|
||||||
for opt, arg in opts:
|
for opt, arg in opts:
|
||||||
if opt == "-h":
|
if opt == "-h":
|
||||||
help()
|
help()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
elif opt == "-f":
|
elif opt == "-t":
|
||||||
configFilePath = arg
|
target = arg
|
||||||
elif opt == "-k":
|
elif opt == "-k":
|
||||||
privateKeyFilePath = arg
|
privateKeyFilePath = arg
|
||||||
elif opt == "-c":
|
elif opt == "-c":
|
||||||
certificateFilePath = arg
|
certificateFilePath = arg
|
||||||
elif opt == "-i":
|
elif opt == "-i":
|
||||||
adminInterface = arg
|
shadowInterface = arg
|
||||||
|
|
||||||
if configFilePath is None:
|
if target is None:
|
||||||
print "Config file is mandatory"
|
log.error("Target is mandatory")
|
||||||
help()
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if certificateFilePath is None:
|
|
||||||
print "Certificate file is mandatory"
|
|
||||||
help()
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if privateKeyFilePath is None:
|
|
||||||
print "Private key file is mandatory"
|
|
||||||
help()
|
help()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
#load config file
|
reactor.listenTCP(int(args[0]), ProxyServerFactory(parseIpPort(target), privateKeyFilePath, certificateFilePath))
|
||||||
config = loadConfig(configFilePath)
|
|
||||||
if config is None:
|
|
||||||
log.error("Bad configuration file")
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
#use to init font
|
if not shadowInterface is None:
|
||||||
app = QtGui.QApplication(sys.argv)
|
shadowInterface, shadowPort = parseIpPort(shadowInterface)
|
||||||
|
log.info("Shadow listener on %s:%s"%(shadowInterface, shadowPort))
|
||||||
reactor.listenTCP(int(args[0]), ProxyServerFactory(CredentialProvider(config), privateKeyFilePath, certificateFilePath))
|
reactor.listenTCP(int(shadowPort), ShadowFactory(privateKeyFilePath, certificateFilePath), interface = shadowInterface)
|
||||||
|
|
||||||
if not adminInterface is None:
|
|
||||||
if ':' in adminInterface:
|
|
||||||
adminInterface, adminPort = adminInterface.split(':')
|
|
||||||
else:
|
|
||||||
adminInterface, adminPort = adminInterface, "3390"
|
|
||||||
log.info("Admin listen on %s:%s"%(adminInterface, adminPort))
|
|
||||||
reactor.listenTCP(int(adminPort), ProxyAdminFactory(privateKeyFilePath, certificateFilePath), interface = adminInterface)
|
|
||||||
reactor.run()
|
reactor.run()
|
||||||
@@ -29,6 +29,7 @@ from PyQt4 import QtCore, QtGui
|
|||||||
from rdpy.protocol.rdp import rdp
|
from rdpy.protocol.rdp import rdp
|
||||||
from rdpy.ui.qt4 import RDPBitmapToQtImage
|
from rdpy.ui.qt4 import RDPBitmapToQtImage
|
||||||
import rdpy.core.log as log
|
import rdpy.core.log as log
|
||||||
|
from rdpy.core.error import RDPSecurityNegoFail
|
||||||
from twisted.internet import task
|
from twisted.internet import task
|
||||||
|
|
||||||
#set log level
|
#set log level
|
||||||
@@ -39,18 +40,23 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
@summary: Factory for screenshot exemple
|
@summary: Factory for screenshot exemple
|
||||||
"""
|
"""
|
||||||
__INSTANCE__ = 0
|
__INSTANCE__ = 0
|
||||||
def __init__(self, width, height, path, timeout):
|
__STATE__ = []
|
||||||
|
def __init__(self, reactor, app, width, height, path, timeout):
|
||||||
"""
|
"""
|
||||||
@param width: width of screen
|
@param reactor: twisted reactor
|
||||||
@param height: height of screen
|
@param width: {integer} width of screen
|
||||||
@param path: path of output screenshot
|
@param height: {integer} height of screen
|
||||||
@param timeout: close connection after timeout s without any updating
|
@param path: {str} path of output screenshot
|
||||||
|
@param timeout: {float} close connection after timeout s without any updating
|
||||||
"""
|
"""
|
||||||
RDPScreenShotFactory.__INSTANCE__ += 1
|
RDPScreenShotFactory.__INSTANCE__ += 1
|
||||||
|
self._reactor = reactor
|
||||||
|
self._app = app
|
||||||
self._width = width
|
self._width = width
|
||||||
self._height = height
|
self._height = height
|
||||||
self._path = path
|
self._path = path
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
|
self._security = "ssl"
|
||||||
|
|
||||||
def clientConnectionLost(self, connector, reason):
|
def clientConnectionLost(self, connector, reason):
|
||||||
"""
|
"""
|
||||||
@@ -58,11 +64,18 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
|
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
|
||||||
@param reason: str use to advertise reason of lost connection
|
@param reason: str use to advertise reason of lost connection
|
||||||
"""
|
"""
|
||||||
|
if reason.type == RDPSecurityNegoFail and self._security != "rdp":
|
||||||
|
log.info("due to RDPSecurityNegoFail try standard security layer")
|
||||||
|
self._security = "rdp"
|
||||||
|
connector.connect()
|
||||||
|
return
|
||||||
|
|
||||||
log.info("connection lost : %s"%reason)
|
log.info("connection lost : %s"%reason)
|
||||||
|
RDPScreenShotFactory.__STATE__.append((connector.host, connector.port, reason))
|
||||||
RDPScreenShotFactory.__INSTANCE__ -= 1
|
RDPScreenShotFactory.__INSTANCE__ -= 1
|
||||||
if(RDPScreenShotFactory.__INSTANCE__ == 0):
|
if(RDPScreenShotFactory.__INSTANCE__ == 0):
|
||||||
reactor.stop()
|
self._reactor.stop()
|
||||||
app.exit()
|
self._app.exit()
|
||||||
|
|
||||||
def clientConnectionFailed(self, connector, reason):
|
def clientConnectionFailed(self, connector, reason):
|
||||||
"""
|
"""
|
||||||
@@ -71,10 +84,11 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
@param reason: str use to advertise reason of lost connection
|
@param reason: str use to advertise reason of lost connection
|
||||||
"""
|
"""
|
||||||
log.info("connection failed : %s"%reason)
|
log.info("connection failed : %s"%reason)
|
||||||
|
RDPScreenShotFactory.__STATE__.append((connector.host, connector.port, reason))
|
||||||
RDPScreenShotFactory.__INSTANCE__ -= 1
|
RDPScreenShotFactory.__INSTANCE__ -= 1
|
||||||
if(RDPScreenShotFactory.__INSTANCE__ == 0):
|
if(RDPScreenShotFactory.__INSTANCE__ == 0):
|
||||||
reactor.stop()
|
self._reactor.stop()
|
||||||
app.exit()
|
self._app.exit()
|
||||||
|
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
def buildObserver(self, controller, addr):
|
||||||
@@ -87,20 +101,24 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
"""
|
"""
|
||||||
@summary: observer that connect, cache every image received and save at deconnection
|
@summary: observer that connect, cache every image received and save at deconnection
|
||||||
"""
|
"""
|
||||||
def __init__(self, controller, width, height, path, timeout):
|
def __init__(self, controller, width, height, security, path, timeout, reactor):
|
||||||
"""
|
"""
|
||||||
@param controller: RDPClientController
|
@param controller: {RDPClientController}
|
||||||
@param width: width of screen
|
@param width: {integer} width of screen
|
||||||
@param height: height of screen
|
@param height: {integer} height of screen
|
||||||
@param path: path of output screenshot
|
@param security: {str} (ssl | rdp) security level
|
||||||
@param timeout: close connection after timeout s without any updating
|
@param path: {str} path of output screenshot
|
||||||
|
@param timeout: {float} close connection after timeout s without any updating
|
||||||
|
@param reactor: twisted reactor
|
||||||
"""
|
"""
|
||||||
rdp.RDPClientObserver.__init__(self, controller)
|
rdp.RDPClientObserver.__init__(self, controller)
|
||||||
controller.setScreen(width, height);
|
controller.setScreen(width, height);
|
||||||
|
controller.setSecurityLevel(security)
|
||||||
self._buffer = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
|
self._buffer = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
|
||||||
self._path = path
|
self._path = path
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
self._startTimeout = False
|
self._startTimeout = False
|
||||||
|
self._reactor = reactor
|
||||||
|
|
||||||
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
||||||
"""
|
"""
|
||||||
@@ -112,7 +130,7 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
qp.drawImage(destLeft, destTop, image, 0, 0, destRight - destLeft + 1, destBottom - destTop + 1)
|
qp.drawImage(destLeft, destTop, image, 0, 0, destRight - destLeft + 1, destBottom - destTop + 1)
|
||||||
if not self._startTimeout:
|
if not self._startTimeout:
|
||||||
self._startTimeout = False
|
self._startTimeout = False
|
||||||
reactor.callLater(self._timeout, self.checkUpdate)
|
self._reactor.callLater(self._timeout, self.checkUpdate)
|
||||||
|
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
@@ -130,7 +148,37 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
def checkUpdate(self):
|
def checkUpdate(self):
|
||||||
self._controller.close();
|
self._controller.close();
|
||||||
|
|
||||||
return ScreenShotObserver(controller, self._width, self._height, self._path, self._timeout)
|
return ScreenShotObserver(controller, self._width, self._height, self._security, self._path, self._timeout, self._reactor)
|
||||||
|
|
||||||
|
def main(width, height, path, timeout, hosts):
|
||||||
|
"""
|
||||||
|
@summary: main algorithm
|
||||||
|
@param height: {integer} height of screenshot
|
||||||
|
@param width: {integer} width of screenshot
|
||||||
|
@param timeout: {float} in sec
|
||||||
|
@param hosts: {list(str(ip[:port]))}
|
||||||
|
@return: {list(tuple(ip, port, Failure instance)} list of connection state
|
||||||
|
"""
|
||||||
|
#create application
|
||||||
|
app = QtGui.QApplication(sys.argv)
|
||||||
|
|
||||||
|
#add qt4 reactor
|
||||||
|
import qt4reactor
|
||||||
|
qt4reactor.install()
|
||||||
|
|
||||||
|
from twisted.internet import reactor
|
||||||
|
|
||||||
|
for host in hosts:
|
||||||
|
if ':' in host:
|
||||||
|
ip, port = host.split(':')
|
||||||
|
else:
|
||||||
|
ip, port = host, "3389"
|
||||||
|
|
||||||
|
reactor.connectTCP(ip, int(port), RDPScreenShotFactory(reactor, app, width, height, path + "%s.jpg"%ip, timeout))
|
||||||
|
|
||||||
|
reactor.runReturn()
|
||||||
|
app.exec_()
|
||||||
|
return RDPScreenShotFactory.__STATE__
|
||||||
|
|
||||||
def help():
|
def help():
|
||||||
print "Usage: rdpy-rdpscreenshot [options] ip[:port]"
|
print "Usage: rdpy-rdpscreenshot [options] ip[:port]"
|
||||||
@@ -163,22 +211,4 @@ if __name__ == '__main__':
|
|||||||
elif opt == "-t":
|
elif opt == "-t":
|
||||||
timeout = float(arg)
|
timeout = float(arg)
|
||||||
|
|
||||||
#create application
|
main(width, height, path, timeout, args)
|
||||||
app = QtGui.QApplication(sys.argv)
|
|
||||||
|
|
||||||
#add qt4 reactor
|
|
||||||
import qt4reactor
|
|
||||||
qt4reactor.install()
|
|
||||||
|
|
||||||
from twisted.internet import reactor
|
|
||||||
|
|
||||||
for arg in args:
|
|
||||||
if ':' in arg:
|
|
||||||
ip, port = arg.split(':')
|
|
||||||
else:
|
|
||||||
ip, port = arg, "3389"
|
|
||||||
|
|
||||||
reactor.connectTCP(ip, int(port), RDPScreenShotFactory(width, height, path + "%s.jpg"%ip, timeout))
|
|
||||||
|
|
||||||
reactor.runReturn()
|
|
||||||
app.exec_()
|
|
||||||
@@ -30,6 +30,7 @@ class Level(object):
|
|||||||
INFO = 1
|
INFO = 1
|
||||||
WARNING = 2
|
WARNING = 2
|
||||||
ERROR = 3
|
ERROR = 3
|
||||||
|
NONE = 4
|
||||||
|
|
||||||
_LOG_LEVEL = Level.DEBUG
|
_LOG_LEVEL = Level.DEBUG
|
||||||
|
|
||||||
|
|||||||
@@ -142,10 +142,10 @@ class EncryptionLevel(object):
|
|||||||
@see: http://msdn.microsoft.com/en-us/library/cc240518.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240518.aspx
|
||||||
"""
|
"""
|
||||||
ENCRYPTION_LEVEL_NONE = 0x00000000
|
ENCRYPTION_LEVEL_NONE = 0x00000000
|
||||||
ENCRYPTION_LEVEL_LOW = 0x00000000
|
ENCRYPTION_LEVEL_LOW = 0x00000001
|
||||||
ENCRYPTION_LEVEL_CLIENT_COMPATIBLE = 0x00000000
|
ENCRYPTION_LEVEL_CLIENT_COMPATIBLE = 0x00000002
|
||||||
ENCRYPTION_LEVEL_HIGH = 0x00000000
|
ENCRYPTION_LEVEL_HIGH = 0x00000003
|
||||||
ENCRYPTION_LEVEL_FIPS = 0x00000000
|
ENCRYPTION_LEVEL_FIPS = 0x00000004
|
||||||
|
|
||||||
class ChannelOptions(object):
|
class ChannelOptions(object):
|
||||||
"""
|
"""
|
||||||
@@ -355,7 +355,7 @@ class ProprietaryServerCertificate(CompositeType):
|
|||||||
self.wSignatureBlobType = UInt16Le(0x0008, constant = True)
|
self.wSignatureBlobType = UInt16Le(0x0008, constant = True)
|
||||||
self.wSignatureBlobLen = UInt16Le(lambda:(sizeof(self.SignatureBlob) - 8))
|
self.wSignatureBlobLen = UInt16Le(lambda:(sizeof(self.SignatureBlob) - 8))
|
||||||
self.SignatureBlob = String(readLen = self.wSignatureBlobLen)
|
self.SignatureBlob = String(readLen = self.wSignatureBlobLen)
|
||||||
self.padding = String("\x00" * 8, readLen = UInt8(8))
|
self.padding = String(b"\x00" * 8, readLen = UInt8(8))
|
||||||
|
|
||||||
def getPublicKey(self):
|
def getPublicKey(self):
|
||||||
"""
|
"""
|
||||||
@@ -380,7 +380,7 @@ class ProprietaryServerCertificate(CompositeType):
|
|||||||
md5Digest = md5.new()
|
md5Digest = md5.new()
|
||||||
md5Digest.update(s.getvalue())
|
md5Digest.update(s.getvalue())
|
||||||
|
|
||||||
return md5Digest.digest() + "\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"
|
return md5Digest.digest() + "\x00" + "\xff" * 46 + "\x01"
|
||||||
|
|
||||||
def sign(self):
|
def sign(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -25,8 +25,9 @@
|
|||||||
from rdpy.core.type import CompositeType, UInt8, UInt16Le, UInt32Le, String, sizeof, FactoryType, ArrayType, Stream
|
from rdpy.core.type import CompositeType, UInt8, UInt16Le, UInt32Le, String, sizeof, FactoryType, ArrayType, Stream
|
||||||
from rdpy.core.error import InvalidExpectedDataException
|
from rdpy.core.error import InvalidExpectedDataException
|
||||||
import rdpy.core.log as log
|
import rdpy.core.log as log
|
||||||
import sec
|
import sec, gcc
|
||||||
from rdpy.security import rc4
|
from rdpy.security import rc4
|
||||||
|
from rdpy.security import rsa_wrapper as rsa
|
||||||
|
|
||||||
class MessageType(object):
|
class MessageType(object):
|
||||||
"""
|
"""
|
||||||
@@ -253,35 +254,31 @@ def createValidClientLicensingErrorMessage():
|
|||||||
|
|
||||||
class LicenseManager(object):
|
class LicenseManager(object):
|
||||||
"""
|
"""
|
||||||
@summary: handle license automata
|
@summary: handle license automata (client side)
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc241890.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc241890.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self, transport):
|
def __init__(self, transport):
|
||||||
"""
|
"""
|
||||||
@param transport: layer use to send packet
|
@param transport: layer use to send packet
|
||||||
"""
|
"""
|
||||||
self._preMasterSecret = "\x00" * 64
|
|
||||||
self._clientRandom = "\x00" * 32
|
|
||||||
self._serverRandom = None
|
|
||||||
self._serverEncryptedChallenge = None
|
|
||||||
self._transport = transport
|
self._transport = transport
|
||||||
self._username = ""
|
self._username = ""
|
||||||
self._hostname = ""
|
self._hostname = ""
|
||||||
|
|
||||||
def generateKeys(self):
|
def generateKeys(self):
|
||||||
"""
|
"""
|
||||||
@summary: generate key for license session
|
@summary: generate keys for license session
|
||||||
"""
|
"""
|
||||||
masterSecret = sec.masterSecret(self._preMasterSecret, self._clientRandom, self._serverRandom)
|
|
||||||
sessionKeyBlob = sec.masterSecret(masterSecret, self._serverRandom, self._clientRandom)
|
|
||||||
self._macSalt = sessionKeyBlob[:16]
|
|
||||||
self._licenseKey = sec.finalHash(sessionKeyBlob[16:32], self._clientRandom, self._serverRandom)
|
|
||||||
|
|
||||||
def recv(self, s):
|
def recv(self, s):
|
||||||
"""
|
"""
|
||||||
@summary: receive license packet from PDU layer
|
@summary: receive license packet from PDU layer
|
||||||
@return true when license automata is finish
|
@return true when license automata is finish
|
||||||
"""
|
"""
|
||||||
|
with open("/tmp/toto", "wb") as f:
|
||||||
|
f.write(s.getvalue()[s.pos:].encode('base64'))
|
||||||
|
|
||||||
licPacket = LicPacket()
|
licPacket = LicPacket()
|
||||||
s.readType(licPacket)
|
s.readType(licPacket)
|
||||||
|
|
||||||
@@ -290,14 +287,11 @@ class LicenseManager(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
elif licPacket.bMsgtype.value == MessageType.LICENSE_REQUEST:
|
elif licPacket.bMsgtype.value == MessageType.LICENSE_REQUEST:
|
||||||
self._serverRandom = licPacket.licensingMessage.serverRandom.value
|
self.sendClientNewLicenseRequest(licPacket.licensingMessage)
|
||||||
self.generateKeys()
|
|
||||||
self.sendClientNewLicenseRequest()
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
elif licPacket.bMsgtype.value == MessageType.PLATFORM_CHALLENGE:
|
elif licPacket.bMsgtype.value == MessageType.PLATFORM_CHALLENGE:
|
||||||
self._serverEncryptedChallenge = licPacket.licensingMessage.encryptedPlatformChallenge.blobData.value
|
self.sendClientChallengeResponse(licPacket.licensingMessage)
|
||||||
self.sendClientChallengeResponse()
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
#yes get a new license
|
#yes get a new license
|
||||||
@@ -308,34 +302,52 @@ class LicenseManager(object):
|
|||||||
raise InvalidExpectedDataException("Not a valid license packet")
|
raise InvalidExpectedDataException("Not a valid license packet")
|
||||||
|
|
||||||
|
|
||||||
def sendClientNewLicenseRequest(self):
|
def sendClientNewLicenseRequest(self, licenseRequest):
|
||||||
"""
|
"""
|
||||||
@summary: Create new license request in response to server license request
|
@summary: Create new license request in response to server license request
|
||||||
|
@param licenseRequest: {ServerLicenseRequest}
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc241989.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc241989.aspx
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc241918.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc241918.aspx
|
||||||
"""
|
"""
|
||||||
|
#get server information
|
||||||
|
serverRandom = licenseRequest.serverRandom.value
|
||||||
|
s = Stream(licenseRequest.serverCertificate.blobData.value)
|
||||||
|
serverCertificate = gcc.ServerCertificate()
|
||||||
|
s.readType(serverCertificate)
|
||||||
|
|
||||||
|
#generate crypto values
|
||||||
|
clientRandom = rsa.random(256)
|
||||||
|
preMasterSecret = rsa.random(384)
|
||||||
|
masterSecret = sec.masterSecret(preMasterSecret, clientRandom, serverRandom)
|
||||||
|
sessionKeyBlob = sec.masterSecret(masterSecret, serverRandom, clientRandom)
|
||||||
|
self._macSalt = sessionKeyBlob[:16]
|
||||||
|
self._licenseKey = sec.finalHash(sessionKeyBlob[16:32], clientRandom, serverRandom)
|
||||||
|
|
||||||
|
#format message
|
||||||
message = ClientNewLicenseRequest()
|
message = ClientNewLicenseRequest()
|
||||||
message.clientRandom.value = self._clientRandom
|
message.clientRandom.value = clientRandom
|
||||||
message.encryptedPreMasterSecret.blobData = String(self._preMasterSecret + "\x00" * 8)
|
message.encryptedPreMasterSecret.blobData.value = rsa.encrypt(preMasterSecret[::-1], serverCertificate.certData.getPublicKey())[::-1] + "\x00" * 8
|
||||||
message.ClientMachineName.blobData = String(self._hostname + "\x00")
|
message.ClientMachineName.blobData.value = self._hostname + "\x00"
|
||||||
message.ClientUserName.blobData = String(self._username + "\x00")
|
message.ClientUserName.blobData.value = self._username + "\x00"
|
||||||
self._transport.sendFlagged(sec.SecurityFlag.SEC_LICENSE_PKT, LicPacket(message))
|
self._transport.sendFlagged(sec.SecurityFlag.SEC_LICENSE_PKT, LicPacket(message))
|
||||||
|
|
||||||
def sendClientChallengeResponse(self):
|
def sendClientChallengeResponse(self, platformChallenge):
|
||||||
"""
|
"""
|
||||||
@summary: generate valid challenge response
|
@summary: generate valid challenge response
|
||||||
|
@param platformChallenge: {ServerPlatformChallenge}
|
||||||
"""
|
"""
|
||||||
|
serverEncryptedChallenge = platformChallenge.encryptedPlatformChallenge.blobData.value
|
||||||
#decrypt server challenge
|
#decrypt server challenge
|
||||||
#it should be TEST word in unicode format
|
#it should be TEST word in unicode format
|
||||||
serverChallenge = rc4.crypt(rc4.RC4Key(self._licenseKey), self._serverEncryptedChallenge)
|
serverChallenge = rc4.crypt(rc4.RC4Key(self._licenseKey), serverEncryptedChallenge)
|
||||||
|
|
||||||
#generate hwid
|
#generate hwid
|
||||||
s = Stream()
|
s = Stream()
|
||||||
s.writeType((UInt32Le(2), String(self._hostname + "\x00" * 16)))
|
s.writeType((UInt32Le(2), String(self._hostname + self._username + "\x00" * 16)))
|
||||||
hwid = s.getvalue()[:20]
|
hwid = s.getvalue()[:20]
|
||||||
|
|
||||||
message = ClientPLatformChallengeResponse()
|
message = ClientPLatformChallengeResponse()
|
||||||
message.encryptedPlatformChallengeResponse.blobData.value = self._serverEncryptedChallenge
|
message.encryptedPlatformChallengeResponse.blobData.value = serverEncryptedChallenge
|
||||||
message.encryptedHWID.blobData.value = rc4.crypt(rc4.RC4Key(self._licenseKey), hwid)
|
message.encryptedHWID.blobData.value = rc4.crypt(rc4.RC4Key(self._licenseKey), hwid)
|
||||||
message.MACData.value = sec.macData(self._macSalt, serverChallenge + hwid)
|
message.MACData.value = sec.macData(self._macSalt, serverChallenge + hwid)
|
||||||
|
|
||||||
|
|||||||
@@ -539,4 +539,4 @@ class MultiFragmentUpdate(CompositeType):
|
|||||||
|
|
||||||
def __init__(self, readLen = None):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.MaxRequestSize = UInt32Le(0xffffffff)
|
self.MaxRequestSize = UInt32Le(0)
|
||||||
@@ -29,7 +29,7 @@ import caps, order
|
|||||||
|
|
||||||
class PDUType(object):
|
class PDUType(object):
|
||||||
"""
|
"""
|
||||||
Data PDU type primary index
|
@summary: Data PDU type primary index
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240576.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240576.aspx
|
||||||
"""
|
"""
|
||||||
PDUTYPE_DEMANDACTIVEPDU = 0x11
|
PDUTYPE_DEMANDACTIVEPDU = 0x11
|
||||||
@@ -40,7 +40,7 @@ class PDUType(object):
|
|||||||
|
|
||||||
class PDUType2(object):
|
class PDUType2(object):
|
||||||
"""
|
"""
|
||||||
Data PDU type secondary index
|
@summary: Data PDU type secondary index
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
||||||
"""
|
"""
|
||||||
PDUTYPE2_UPDATE = 0x02
|
PDUTYPE2_UPDATE = 0x02
|
||||||
@@ -70,7 +70,7 @@ class PDUType2(object):
|
|||||||
|
|
||||||
class StreamId(object):
|
class StreamId(object):
|
||||||
"""
|
"""
|
||||||
Stream priority
|
@summary: Stream priority
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
||||||
"""
|
"""
|
||||||
STREAM_UNDEFINED = 0x00
|
STREAM_UNDEFINED = 0x00
|
||||||
@@ -80,7 +80,7 @@ class StreamId(object):
|
|||||||
|
|
||||||
class CompressionOrder(object):
|
class CompressionOrder(object):
|
||||||
"""
|
"""
|
||||||
PDU compression order
|
@summary: PDU compression order
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
||||||
"""
|
"""
|
||||||
CompressionTypeMask = 0x0F
|
CompressionTypeMask = 0x0F
|
||||||
@@ -90,7 +90,7 @@ class CompressionOrder(object):
|
|||||||
|
|
||||||
class CompressionType(object):
|
class CompressionType(object):
|
||||||
"""
|
"""
|
||||||
PDU compression type
|
@summary: PDU compression type
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
||||||
"""
|
"""
|
||||||
PACKET_COMPR_TYPE_8K = 0x0
|
PACKET_COMPR_TYPE_8K = 0x0
|
||||||
@@ -100,7 +100,7 @@ class CompressionType(object):
|
|||||||
|
|
||||||
class Action(object):
|
class Action(object):
|
||||||
"""
|
"""
|
||||||
Action flag use in Control PDU packet
|
@summary: Action flag use in Control PDU packet
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240492.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240492.aspx
|
||||||
"""
|
"""
|
||||||
CTRLACTION_REQUEST_CONTROL = 0x0001
|
CTRLACTION_REQUEST_CONTROL = 0x0001
|
||||||
@@ -110,7 +110,7 @@ class Action(object):
|
|||||||
|
|
||||||
class PersistentKeyListFlag(object):
|
class PersistentKeyListFlag(object):
|
||||||
"""
|
"""
|
||||||
Use to determine the number of persistent key packet
|
@summary: Use to determine the number of persistent key packet
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240495.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240495.aspx
|
||||||
"""
|
"""
|
||||||
PERSIST_FIRST_PDU = 0x01
|
PERSIST_FIRST_PDU = 0x01
|
||||||
@@ -118,7 +118,7 @@ class PersistentKeyListFlag(object):
|
|||||||
|
|
||||||
class BitmapFlag(object):
|
class BitmapFlag(object):
|
||||||
"""
|
"""
|
||||||
Use in bitmap update PDU
|
@summary: Use in bitmap update PDU
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240612.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240612.aspx
|
||||||
"""
|
"""
|
||||||
BITMAP_COMPRESSION = 0x0001
|
BITMAP_COMPRESSION = 0x0001
|
||||||
@@ -126,7 +126,7 @@ class BitmapFlag(object):
|
|||||||
|
|
||||||
class UpdateType(object):
|
class UpdateType(object):
|
||||||
"""
|
"""
|
||||||
Use in update PDU to determine which type of update
|
@summary: Use in update PDU to determine which type of update
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240608.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240608.aspx
|
||||||
"""
|
"""
|
||||||
UPDATETYPE_ORDERS = 0x0000
|
UPDATETYPE_ORDERS = 0x0000
|
||||||
@@ -136,7 +136,7 @@ class UpdateType(object):
|
|||||||
|
|
||||||
class InputMessageType(object):
|
class InputMessageType(object):
|
||||||
"""
|
"""
|
||||||
Use in slow-path input PDU
|
@summary: Use in slow-path input PDU
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240583.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240583.aspx
|
||||||
"""
|
"""
|
||||||
INPUT_EVENT_SYNC = 0x0000
|
INPUT_EVENT_SYNC = 0x0000
|
||||||
@@ -148,7 +148,7 @@ class InputMessageType(object):
|
|||||||
|
|
||||||
class PointerFlag(object):
|
class PointerFlag(object):
|
||||||
"""
|
"""
|
||||||
Use in Pointer event
|
@summary: Use in Pointer event
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240586.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240586.aspx
|
||||||
"""
|
"""
|
||||||
PTRFLAGS_HWHEEL = 0x0400
|
PTRFLAGS_HWHEEL = 0x0400
|
||||||
@@ -163,7 +163,7 @@ class PointerFlag(object):
|
|||||||
|
|
||||||
class KeyboardFlag(object):
|
class KeyboardFlag(object):
|
||||||
"""
|
"""
|
||||||
Use in scan code key event
|
@summary: Use in scan code key event
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240584.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240584.aspx
|
||||||
"""
|
"""
|
||||||
KBDFLAGS_EXTENDED = 0x0100
|
KBDFLAGS_EXTENDED = 0x0100
|
||||||
@@ -172,7 +172,7 @@ class KeyboardFlag(object):
|
|||||||
|
|
||||||
class FastPathUpdateType(object):
|
class FastPathUpdateType(object):
|
||||||
"""
|
"""
|
||||||
Use in Fast Path update packet
|
@summary: Use in Fast Path update packet
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
|
||||||
"""
|
"""
|
||||||
FASTPATH_UPDATETYPE_ORDERS = 0x0
|
FASTPATH_UPDATETYPE_ORDERS = 0x0
|
||||||
@@ -189,14 +189,14 @@ class FastPathUpdateType(object):
|
|||||||
|
|
||||||
class FastPathOutputCompression(object):
|
class FastPathOutputCompression(object):
|
||||||
"""
|
"""
|
||||||
Flag for compression
|
@summary: Flag for compression
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
|
||||||
"""
|
"""
|
||||||
FASTPATH_OUTPUT_COMPRESSION_USED = 0x2
|
FASTPATH_OUTPUT_COMPRESSION_USED = 0x2
|
||||||
|
|
||||||
class Display(object):
|
class Display(object):
|
||||||
"""
|
"""
|
||||||
Use in supress output PDU
|
@summary: Use in supress output PDU
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240648.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240648.aspx
|
||||||
"""
|
"""
|
||||||
SUPPRESS_DISPLAY_UPDATES = 0x00
|
SUPPRESS_DISPLAY_UPDATES = 0x00
|
||||||
@@ -204,7 +204,7 @@ class Display(object):
|
|||||||
|
|
||||||
class ErrorInfo(object):
|
class ErrorInfo(object):
|
||||||
"""
|
"""
|
||||||
Error code use in Error info PDU
|
@summary: Error code use in Error info PDU
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240544.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240544.aspx
|
||||||
"""
|
"""
|
||||||
ERRINFO_RPC_INITIATED_DISCONNECT = 0x00000001
|
ERRINFO_RPC_INITIATED_DISCONNECT = 0x00000001
|
||||||
@@ -417,12 +417,12 @@ class ErrorInfo(object):
|
|||||||
|
|
||||||
class ShareControlHeader(CompositeType):
|
class ShareControlHeader(CompositeType):
|
||||||
"""
|
"""
|
||||||
PDU share control header
|
@summary: PDU share control header
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240576.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240576.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self, totalLength, pduType, userId):
|
def __init__(self, totalLength, pduType, userId):
|
||||||
"""
|
"""
|
||||||
Set pduType as constant
|
@summary: Set pduType as constant
|
||||||
@param totalLength: total length of PDU packet
|
@param totalLength: total length of PDU packet
|
||||||
"""
|
"""
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self)
|
||||||
@@ -433,7 +433,7 @@ class ShareControlHeader(CompositeType):
|
|||||||
|
|
||||||
class ShareDataHeader(CompositeType):
|
class ShareDataHeader(CompositeType):
|
||||||
"""
|
"""
|
||||||
PDU share data header
|
@summary: PDU share data header
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self, size, pduType2 = 0, shareId = 0):
|
def __init__(self, size, pduType2 = 0, shareId = 0):
|
||||||
@@ -448,7 +448,7 @@ class ShareDataHeader(CompositeType):
|
|||||||
|
|
||||||
class PDU(CompositeType):
|
class PDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Main PDU message
|
@summary: Main PDU message
|
||||||
"""
|
"""
|
||||||
def __init__(self, userId = 0, pduMessage = None):
|
def __init__(self, userId = 0, pduMessage = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self)
|
||||||
@@ -456,7 +456,7 @@ class PDU(CompositeType):
|
|||||||
|
|
||||||
def PDUMessageFactory():
|
def PDUMessageFactory():
|
||||||
"""
|
"""
|
||||||
build message in accordance of type self.shareControlHeader.pduType.value
|
@summary: build message in accordance of type self.shareControlHeader.pduType.value
|
||||||
"""
|
"""
|
||||||
for c in [DemandActivePDU, ConfirmActivePDU, DataPDU, DeactiveAllPDU]:
|
for c in [DemandActivePDU, ConfirmActivePDU, DataPDU, DeactiveAllPDU]:
|
||||||
if self.shareControlHeader.pduType.value == c._PDUTYPE_:
|
if self.shareControlHeader.pduType.value == c._PDUTYPE_:
|
||||||
@@ -475,7 +475,7 @@ class PDU(CompositeType):
|
|||||||
class DemandActivePDU(CompositeType):
|
class DemandActivePDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240485.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240485.aspx
|
||||||
Main use for capabilities exchange server -> client
|
@summary: Main use for capabilities exchange server -> client
|
||||||
"""
|
"""
|
||||||
#may declare the PDU type
|
#may declare the PDU type
|
||||||
_PDUTYPE_ = PDUType.PDUTYPE_DEMANDACTIVEPDU
|
_PDUTYPE_ = PDUType.PDUTYPE_DEMANDACTIVEPDU
|
||||||
@@ -494,7 +494,7 @@ class DemandActivePDU(CompositeType):
|
|||||||
class ConfirmActivePDU(CompositeType):
|
class ConfirmActivePDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240488.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240488.aspx
|
||||||
Main use for capabilities confirm client -> sever
|
@summary: Main use for capabilities confirm client -> sever
|
||||||
"""
|
"""
|
||||||
#may declare the PDU type
|
#may declare the PDU type
|
||||||
_PDUTYPE_ = PDUType.PDUTYPE_CONFIRMACTIVEPDU
|
_PDUTYPE_ = PDUType.PDUTYPE_CONFIRMACTIVEPDU
|
||||||
@@ -512,7 +512,7 @@ class ConfirmActivePDU(CompositeType):
|
|||||||
|
|
||||||
class DeactiveAllPDU(CompositeType):
|
class DeactiveAllPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to signal already connected session
|
@summary: Use to signal already connected session
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240536.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240536.aspx
|
||||||
"""
|
"""
|
||||||
#may declare the PDU type
|
#may declare the PDU type
|
||||||
@@ -526,7 +526,7 @@ class DeactiveAllPDU(CompositeType):
|
|||||||
|
|
||||||
class DataPDU(CompositeType):
|
class DataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Generic PDU packet use after connection sequence
|
@summary: Generic PDU packet use after connection sequence
|
||||||
"""
|
"""
|
||||||
#may declare the PDU type
|
#may declare the PDU type
|
||||||
_PDUTYPE_ = PDUType.PDUTYPE_DATAPDU
|
_PDUTYPE_ = PDUType.PDUTYPE_DATAPDU
|
||||||
@@ -537,7 +537,7 @@ class DataPDU(CompositeType):
|
|||||||
|
|
||||||
def PDUDataFactory():
|
def PDUDataFactory():
|
||||||
"""
|
"""
|
||||||
Create object in accordance self.shareDataHeader.pduType2 value
|
@summary: Create object in accordance self.shareDataHeader.pduType2 value
|
||||||
"""
|
"""
|
||||||
for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU, SupressOutputDataPDU]:
|
for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU, SupressOutputDataPDU]:
|
||||||
if self.shareDataHeader.pduType2.value == c._PDUTYPE2_:
|
if self.shareDataHeader.pduType2.value == c._PDUTYPE2_:
|
||||||
@@ -584,7 +584,7 @@ class ControlDataPDU(CompositeType):
|
|||||||
|
|
||||||
class ErrorInfoDataPDU(CompositeType):
|
class ErrorInfoDataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to inform error in PDU layer
|
@summary: Use to inform error in PDU layer
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240544.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240544.aspx
|
||||||
"""
|
"""
|
||||||
_PDUTYPE2_ = PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU
|
||||||
@@ -600,7 +600,7 @@ class ErrorInfoDataPDU(CompositeType):
|
|||||||
|
|
||||||
class FontListDataPDU(CompositeType):
|
class FontListDataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to indicate list of font. Deprecated packet
|
@summary: Use to indicate list of font. Deprecated packet
|
||||||
client -> server
|
client -> server
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240498.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240498.aspx
|
||||||
"""
|
"""
|
||||||
@@ -618,7 +618,7 @@ class FontListDataPDU(CompositeType):
|
|||||||
|
|
||||||
class FontMapDataPDU(CompositeType):
|
class FontMapDataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to indicate map of font. Deprecated packet (maybe the same as FontListDataPDU)
|
@summary: Use to indicate map of font. Deprecated packet (maybe the same as FontListDataPDU)
|
||||||
server -> client
|
server -> client
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240498.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240498.aspx
|
||||||
"""
|
"""
|
||||||
@@ -636,7 +636,7 @@ class FontMapDataPDU(CompositeType):
|
|||||||
|
|
||||||
class PersistentListEntry(CompositeType):
|
class PersistentListEntry(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to record persistent key in PersistentListPDU
|
@summary: Use to record persistent key in PersistentListPDU
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240496.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240496.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -646,7 +646,7 @@ class PersistentListEntry(CompositeType):
|
|||||||
|
|
||||||
class PersistentListPDU(CompositeType):
|
class PersistentListPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to indicate that bitmap cache was already
|
@summary: Use to indicate that bitmap cache was already
|
||||||
Fill with some keys from previous session
|
Fill with some keys from previous session
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240495.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240495.aspx
|
||||||
"""
|
"""
|
||||||
@@ -671,7 +671,7 @@ class PersistentListPDU(CompositeType):
|
|||||||
|
|
||||||
class ClientInputEventPDU(CompositeType):
|
class ClientInputEventPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
PDU use to send client inputs in slow path mode
|
@summary: PDU use to send client inputs in slow path mode
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc746160.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc746160.aspx
|
||||||
"""
|
"""
|
||||||
_PDUTYPE2_ = PDUType2.PDUTYPE2_INPUT
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_INPUT
|
||||||
@@ -684,7 +684,7 @@ class ClientInputEventPDU(CompositeType):
|
|||||||
|
|
||||||
class ShutdownRequestPDU(CompositeType):
|
class ShutdownRequestPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
PDU use to signal that the session will be closed
|
@summary: PDU use to signal that the session will be closed
|
||||||
client -> server
|
client -> server
|
||||||
"""
|
"""
|
||||||
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_REQUEST
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_REQUEST
|
||||||
@@ -693,7 +693,7 @@ class ShutdownRequestPDU(CompositeType):
|
|||||||
|
|
||||||
class ShutdownDeniedPDU(CompositeType):
|
class ShutdownDeniedPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
PDU use to signal which the session will be closed is connected
|
@summary: PDU use to signal which the session will be closed is connected
|
||||||
server -> client
|
server -> client
|
||||||
"""
|
"""
|
||||||
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_DENIED
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_DENIED
|
||||||
@@ -737,7 +737,7 @@ class RefreshRectPDU(CompositeType):
|
|||||||
|
|
||||||
class UpdateDataPDU(CompositeType):
|
class UpdateDataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Update data PDU use by server to inform update image or palet
|
@summary: Update data PDU use by server to inform update image or palet
|
||||||
for example
|
for example
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240608.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240608.aspx
|
||||||
"""
|
"""
|
||||||
@@ -754,7 +754,7 @@ class UpdateDataPDU(CompositeType):
|
|||||||
|
|
||||||
def UpdateDataFactory():
|
def UpdateDataFactory():
|
||||||
"""
|
"""
|
||||||
Create object in accordance self.updateType value
|
@summary: Create object in accordance self.updateType value
|
||||||
"""
|
"""
|
||||||
for c in [BitmapUpdateDataPDU]:
|
for c in [BitmapUpdateDataPDU]:
|
||||||
if self.updateType.value == c._UPDATE_TYPE_:
|
if self.updateType.value == c._UPDATE_TYPE_:
|
||||||
@@ -771,7 +771,7 @@ class UpdateDataPDU(CompositeType):
|
|||||||
|
|
||||||
class FastPathUpdatePDU(CompositeType):
|
class FastPathUpdatePDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Fast path update PDU packet
|
@summary: Fast path update PDU packet
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self, updateData = None):
|
def __init__(self, updateData = None):
|
||||||
@@ -782,7 +782,7 @@ class FastPathUpdatePDU(CompositeType):
|
|||||||
|
|
||||||
def UpdateDataFactory():
|
def UpdateDataFactory():
|
||||||
"""
|
"""
|
||||||
Create correct object in accordance to self.updateHeader field
|
@summary: Create correct object in accordance to self.updateHeader field
|
||||||
"""
|
"""
|
||||||
for c in [FastPathBitmapUpdateDataPDU]:
|
for c in [FastPathBitmapUpdateDataPDU]:
|
||||||
if (self.updateHeader.value & 0xf) == c._FASTPATH_UPDATE_TYPE_:
|
if (self.updateHeader.value & 0xf) == c._FASTPATH_UPDATE_TYPE_:
|
||||||
@@ -799,7 +799,7 @@ class FastPathUpdatePDU(CompositeType):
|
|||||||
|
|
||||||
class BitmapUpdateDataPDU(CompositeType):
|
class BitmapUpdateDataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
PDU use to send raw bitmap compressed or not
|
@summary: PDU use to send raw bitmap compressed or not
|
||||||
@see: http://msdn.microsoft.com/en-us/library/dd306368.aspx
|
@see: http://msdn.microsoft.com/en-us/library/dd306368.aspx
|
||||||
"""
|
"""
|
||||||
_UPDATE_TYPE_ = UpdateType.UPDATETYPE_BITMAP
|
_UPDATE_TYPE_ = UpdateType.UPDATETYPE_BITMAP
|
||||||
@@ -814,7 +814,7 @@ class BitmapUpdateDataPDU(CompositeType):
|
|||||||
|
|
||||||
class OrderUpdateDataPDU(CompositeType):
|
class OrderUpdateDataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
PDU type use to communicate Accelerated order (GDI)
|
@summary: PDU type use to communicate Accelerated order (GDI)
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc241571.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc241571.aspx
|
||||||
@todo: not implemented yet but need it
|
@todo: not implemented yet but need it
|
||||||
"""
|
"""
|
||||||
@@ -827,7 +827,7 @@ class OrderUpdateDataPDU(CompositeType):
|
|||||||
|
|
||||||
class BitmapCompressedDataHeader(CompositeType):
|
class BitmapCompressedDataHeader(CompositeType):
|
||||||
"""
|
"""
|
||||||
Compressed header of bitmap
|
@summary: Compressed header of bitmap
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240644.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240644.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self, bodySize = 0, scanWidth = 0, uncompressedSize = 0, conditional = lambda:True):
|
def __init__(self, bodySize = 0, scanWidth = 0, uncompressedSize = 0, conditional = lambda:True):
|
||||||
@@ -846,7 +846,7 @@ class BitmapCompressedDataHeader(CompositeType):
|
|||||||
|
|
||||||
class BitmapData(CompositeType):
|
class BitmapData(CompositeType):
|
||||||
"""
|
"""
|
||||||
Bitmap data here the screen capture
|
@summary: Bitmap data here the screen capture
|
||||||
"""
|
"""
|
||||||
def __init__(self, destLeft = 0, destTop = 0, destRight = 0, destBottom = 0, width = 0, height = 0, bitsPerPixel = 0, bitmapDataStream = ""):
|
def __init__(self, destLeft = 0, destTop = 0, destRight = 0, destBottom = 0, width = 0, height = 0, bitsPerPixel = 0, bitmapDataStream = ""):
|
||||||
"""
|
"""
|
||||||
@@ -874,7 +874,7 @@ class BitmapData(CompositeType):
|
|||||||
|
|
||||||
class FastPathBitmapUpdateDataPDU(CompositeType):
|
class FastPathBitmapUpdateDataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Fast path version of bitmap update PDU
|
@summary: Fast path version of bitmap update PDU
|
||||||
@see: http://msdn.microsoft.com/en-us/library/dd306368.aspx
|
@see: http://msdn.microsoft.com/en-us/library/dd306368.aspx
|
||||||
"""
|
"""
|
||||||
_FASTPATH_UPDATE_TYPE_ = FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP
|
_FASTPATH_UPDATE_TYPE_ = FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP
|
||||||
@@ -887,7 +887,7 @@ class FastPathBitmapUpdateDataPDU(CompositeType):
|
|||||||
|
|
||||||
class SlowPathInputEvent(CompositeType):
|
class SlowPathInputEvent(CompositeType):
|
||||||
"""
|
"""
|
||||||
PDU use in slow-path sending client inputs
|
@summary: PDU use in slow-path sending client inputs
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240583.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240583.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self, messageData = None):
|
def __init__(self, messageData = None):
|
||||||
@@ -911,7 +911,7 @@ class SlowPathInputEvent(CompositeType):
|
|||||||
|
|
||||||
class PointerEvent(CompositeType):
|
class PointerEvent(CompositeType):
|
||||||
"""
|
"""
|
||||||
Event use to communicate mouse position
|
@summary: Event use to communicate mouse position
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240586.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240586.aspx
|
||||||
"""
|
"""
|
||||||
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_MOUSE
|
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_MOUSE
|
||||||
@@ -924,7 +924,7 @@ class PointerEvent(CompositeType):
|
|||||||
|
|
||||||
class ScancodeKeyEvent(CompositeType):
|
class ScancodeKeyEvent(CompositeType):
|
||||||
"""
|
"""
|
||||||
Event use to communicate keyboard informations
|
@summary: Event use to communicate keyboard informations
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240584.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240584.aspx
|
||||||
"""
|
"""
|
||||||
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_SCANCODE
|
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_SCANCODE
|
||||||
@@ -937,7 +937,7 @@ class ScancodeKeyEvent(CompositeType):
|
|||||||
|
|
||||||
class UnicodeKeyEvent(CompositeType):
|
class UnicodeKeyEvent(CompositeType):
|
||||||
"""
|
"""
|
||||||
Event use to communicate keyboard informations
|
@summary: Event use to communicate keyboard informations
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240585.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240585.aspx
|
||||||
"""
|
"""
|
||||||
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_UNICODE
|
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_UNICODE
|
||||||
|
|||||||
@@ -411,7 +411,7 @@ class Server(PDULayer):
|
|||||||
self._clientCapabilities[cap.capabilitySetType] = cap
|
self._clientCapabilities[cap.capabilitySetType] = cap
|
||||||
|
|
||||||
#find use full flag
|
#find use full flag
|
||||||
self._clientFastPathSupported = bool(self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags.value & (caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED | caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED))
|
self._clientFastPathSupported = bool(self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags.value & caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED)
|
||||||
|
|
||||||
self.setNextState(self.recvClientSynchronizePDU)
|
self.setNextState(self.recvClientSynchronizePDU)
|
||||||
|
|
||||||
|
|||||||
@@ -123,22 +123,26 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
@param layout: us | fr
|
@param layout: us | fr
|
||||||
"""
|
"""
|
||||||
if layout == "fr":
|
if layout == "fr":
|
||||||
self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).kbdLayout.value = gcc.KeyboardLayout.FRENCH
|
self._mcsLayer._clientSettings.CS_CORE.kbdLayout.value = gcc.KeyboardLayout.FRENCH
|
||||||
elif layout == "us":
|
elif layout == "us":
|
||||||
self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).kbdLayout.value = gcc.KeyboardLayout.US
|
self._mcsLayer._clientSettings.CS_CORE.kbdLayout.value = gcc.KeyboardLayout.US
|
||||||
|
|
||||||
def setHostname(self, hostname):
|
def setHostname(self, hostname):
|
||||||
"""
|
"""
|
||||||
@summary: set hostname of machine
|
@summary: set hostname of machine
|
||||||
"""
|
"""
|
||||||
self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).clientName.value = hostname[:15] + "\x00" * (15 - len(hostname))
|
self._mcsLayer._clientSettings.CS_CORE.clientName.value = hostname[:15] + "\x00" * (15 - len(hostname))
|
||||||
self._secLayer._licenceManager._hostname = hostname
|
self._secLayer._licenceManager._hostname = hostname
|
||||||
|
|
||||||
def setRDPBasicSecurity(self):
|
def setSecurityLevel(self, level):
|
||||||
"""
|
"""
|
||||||
@summary: Request basic security
|
@summary: Request basic security
|
||||||
|
@param level: {str} (ssl | rdp)
|
||||||
"""
|
"""
|
||||||
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_RDP
|
if level == "rdp":
|
||||||
|
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_RDP
|
||||||
|
elif level == "ssl":
|
||||||
|
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_SSL
|
||||||
|
|
||||||
def addClientObserver(self, observer):
|
def addClientObserver(self, observer):
|
||||||
"""
|
"""
|
||||||
@@ -362,6 +366,12 @@ class RDPServerController(pdu.layer.PDUServerListener):
|
|||||||
"""
|
"""
|
||||||
return self._tpktLayer
|
return self._tpktLayer
|
||||||
|
|
||||||
|
def getHostname(self):
|
||||||
|
"""
|
||||||
|
@return: name of client (information done by RDP)
|
||||||
|
"""
|
||||||
|
return self._mcsLayer._clientSettings.CS_CORE.clientName.value.strip('\x00')
|
||||||
|
|
||||||
def getUsername(self):
|
def getUsername(self):
|
||||||
"""
|
"""
|
||||||
@summary: Must be call after on ready event else always empty string
|
@summary: Must be call after on ready event else always empty string
|
||||||
@@ -414,7 +424,8 @@ class RDPServerController(pdu.layer.PDUServerListener):
|
|||||||
"""
|
"""
|
||||||
@summary: Set color depth of session
|
@summary: Set color depth of session
|
||||||
if PDU stack is already connected send a deactive-reactive sequence
|
if PDU stack is already connected send a deactive-reactive sequence
|
||||||
@param colorDepth: depth of session (15, 16, 24)
|
and an onReady message is re send when client is ready
|
||||||
|
@param colorDepth: {integer} depth of session (15, 16, 24)
|
||||||
"""
|
"""
|
||||||
self._colorDepth = colorDepth
|
self._colorDepth = colorDepth
|
||||||
self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_BITMAP].capability.preferredBitsPerPixel.value = colorDepth
|
self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_BITMAP].capability.preferredBitsPerPixel.value = colorDepth
|
||||||
@@ -526,15 +537,15 @@ class ServerFactory(layer.RawLayerServerFactory):
|
|||||||
"""
|
"""
|
||||||
@summary: Factory of Server RDP protocol
|
@summary: Factory of Server RDP protocol
|
||||||
"""
|
"""
|
||||||
def __init__(self, privateKeyFileName, certificateFileName, colorDepth):
|
def __init__(self, colorDepth, privateKeyFileName = None, certificateFileName = None):
|
||||||
"""
|
"""
|
||||||
@param privateKeyFileName: file contain server private key
|
|
||||||
@param certficiateFileName: file that contain public key
|
|
||||||
@param colorDepth: color depth of session
|
@param colorDepth: color depth of session
|
||||||
|
@param privateKeyFileName: file contain server private key (if none -> back to standard RDP security)
|
||||||
|
@param certficiateFileName: file that contain public key (if none -> back to standard RDP security)
|
||||||
"""
|
"""
|
||||||
|
self._colorDepth = colorDepth
|
||||||
self._privateKeyFileName = privateKeyFileName
|
self._privateKeyFileName = privateKeyFileName
|
||||||
self._certificateFileName = certificateFileName
|
self._certificateFileName = certificateFileName
|
||||||
self._colorDepth = colorDepth
|
|
||||||
|
|
||||||
def connectionLost(self, tpktLayer, reason):
|
def connectionLost(self, tpktLayer, reason):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ class Client(X224Layer):
|
|||||||
raise InvalidExpectedDataException("RDPY doesn't support NLA security Layer")
|
raise InvalidExpectedDataException("RDPY doesn't support NLA security Layer")
|
||||||
|
|
||||||
if self._selectedProtocol == Protocols.PROTOCOL_SSL:
|
if self._selectedProtocol == Protocols.PROTOCOL_SSL:
|
||||||
log.debug("*" * 10 + " select SSL layer" + "*" * 10)
|
log.debug("*" * 10 + " select SSL layer " + "*" * 10)
|
||||||
#_transport is TPKT and transport is TCP layer of twisted
|
#_transport is TPKT and transport is TCP layer of twisted
|
||||||
self._transport.transport.startTLS(ClientTLSContext())
|
self._transport.transport.startTLS(ClientTLSContext())
|
||||||
|
|
||||||
@@ -287,7 +287,7 @@ class Server(X224Layer):
|
|||||||
message.protocolNeg.selectedProtocol.value = self._selectedProtocol
|
message.protocolNeg.selectedProtocol.value = self._selectedProtocol
|
||||||
self._transport.send(message)
|
self._transport.send(message)
|
||||||
if self._selectedProtocol == Protocols.PROTOCOL_SSL:
|
if self._selectedProtocol == Protocols.PROTOCOL_SSL:
|
||||||
log.debug("*" * 5 + " select SSL layer")
|
log.debug("*" * 10 + " select SSL layer " + "*" * 10)
|
||||||
#_transport is TPKT and transport is TCP layer of twisted
|
#_transport is TPKT and transport is TCP layer of twisted
|
||||||
self._transport.transport.startTLS(ServerTLSContext(self._serverPrivateKeyFileName, self._serverCertificateFileName))
|
self._transport.transport.startTLS(ServerTLSContext(self._serverPrivateKeyFileName, self._serverCertificateFileName))
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ def int2bytes(i, fill_size=None):
|
|||||||
def random(size):
|
def random(size):
|
||||||
"""
|
"""
|
||||||
@summary: wrapper around rsa.randnum.read_random_bits function
|
@summary: wrapper around rsa.randnum.read_random_bits function
|
||||||
|
@param size: {integer] size in bits
|
||||||
@return: {str} random bytes array
|
@return: {str} random bytes array
|
||||||
"""
|
"""
|
||||||
return rsa.randnum.read_random_bits(size)
|
return rsa.randnum.read_random_bits(size)
|
||||||
|
|||||||
@@ -29,6 +29,48 @@ import unittest
|
|||||||
from rdpy.protocol.rdp import lic, sec
|
from rdpy.protocol.rdp import lic, sec
|
||||||
import rdpy.core.type as type
|
import rdpy.core.type as type
|
||||||
|
|
||||||
|
#dump of server request
|
||||||
|
SERVERREQUEST = """
|
||||||
|
AQNfCBkr6c1CVLRPx7PPYVgzW5uMQ1pSvtzs9XlTt74jwjslAAAGACwAAABNAGkAYwByAG8AcwBv
|
||||||
|
AGYAdAAgAEMAbwByAHAAbwByAGEAdABpAG8AbgAAAAgAAABBADAAMgAAAA0ABAABAAAAAwDZBwIA
|
||||||
|
AAACAAAAWAMAADCCA1QwggJAoAMCAQICCAGemF/kFo3QMAkGBSsOAwIdBQAwODE2MBUGA1UEBx4O
|
||||||
|
AFMASQBSAEEARABFAEwwHQYDVQQDHhYAVgBTAEkALQBTAFIAVgAtADAAMAA0MB4XDTcwMTAyMTA4
|
||||||
|
MDMxN1oXDTQ5MTAyMTA4MDMxN1owODE2MBUGA1UEBx4OAFMASQBSAEEARABFAEwwHQYDVQQDHhYA
|
||||||
|
VgBTAEkALQBTAFIAVgAtADAAMAA0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0mh+
|
||||||
|
rgsNk7WCR8Ck46jTV6OgnMgsFmdt0RqBJqtFwk+uqH8cBKFohtD9CeHTeQYkk+8m1C5GDpjQ/5BK
|
||||||
|
jaBfZHW+g5lZQITKbTi2n8TZfDg76wHvfl71JzwB84AG736KOZSOkBg8mJk3xf8LX5hbYHPQCRc5
|
||||||
|
y+06CWOyPiBYCEElXPOqsZYwum72N/NIsrKe98QHLn+X6MSbZ9Qzw6jnoBFgjk65bzpcChaOhnlW
|
||||||
|
875D6+8KZXgFmHT6MSJX7eabKw1zbKXszYpPvPfMDybp+6z125AJ+GxJ847AQ10xOV2X13ZVfSvS
|
||||||
|
Np62ogDD23duWHvcwLmHJ+45Jdgt7y4C0wIDAQABo2owaDAPBgNVHRMECDAGAQH/AgEAMFUGCSsG
|
||||||
|
AQQBgjcSCARIQwBDAFIATQBWAEoANgBWAEMAVABUAFcAUgBXAEcATQA4AEgASwBIAFkAMwBKAEsA
|
||||||
|
OABXADMAVgBNAEIARABXAFQAUQBGAAAAMAkGBSsOAwIdBQADggEBAD2NQPpBREuD+CZI9T4bUF9s
|
||||||
|
2yyQ8HidxJ+7NMcDyW4Ozid/SrAT9JP42RQrBosgq8S8teWNubrsNMsJjGaeueeQisLP1OigXQH1
|
||||||
|
8DcnLUc5TZKIsKjBqxJ40tssR5KaOfjGHXX5VoADsthnp3wO4bRuaXyhpLQzN1bUNKQk0Fok6Fzt
|
||||||
|
zo/rNEiAQv+M/Y1vmCoTuulXwxymZ+UdbxHPr/IvmxD0Gcmz8Qm45Lpypt2t0lGwNiuV2/3dm13C
|
||||||
|
spjIGxJFf/26QiRuZi5cps2YsOaQfW3xByklE5EbZWYIlsTCQpht+owKqnoPy7aD2On8z0IyGgY3
|
||||||
|
YdsOzHDvRZ+S0hxhBAAAMIIEXTCCA0mgAwIBAgIFAQAAABgwCQYFKw4DAh0FADA4MTYwFQYDVQQH
|
||||||
|
Hg4AUwBJAFIAQQBEAEUATDAdBgNVBAMeFgBWAFMASQAtAFMAUgBWAC0AMAAwADQwHhcNMTMxMTI3
|
||||||
|
MTQyNDIyWhcNMzgwMTE5MDMxNDA3WjCBqDGBpTAnBgNVBAMeIABuAGMAYQBjAG4AXwBuAHAAOgAx
|
||||||
|
ADkAMgAuADEANgA4MDUGA1UEBx4uAG4AYwBhAGMAbgBfAG4AcAA6ADEAOQAyAC4AMQA2ADgALgAx
|
||||||
|
ADMANQAuADMAMDBDBgNVBAUePAAxAEIAYwBLAGUAYQBiADcAWABPAGoAbgBXAEUATQBuAHgAMQBw
|
||||||
|
AFMAaQA2AEoAUgBBAHUAMAA9AA0ACjBYMAkGBSsOAwIPBQADSwAwSAJBANGDkkicUIVwIX2apm1b
|
||||||
|
U9WXVMnVT+S081PVP87vGp6VtzYpT9cMNKv70Qi2U3MQoQ4MuKS1XN2uc9SrNC7RWoUCAwEAAaOC
|
||||||
|
Ac8wggHLMBQGCSsGAQQBgjcSBAEB/wQEAQAFADA8BgkrBgEEAYI3EgIBAf8ELE0AaQBjAHIAbwBz
|
||||||
|
AG8AZgB0ACAAQwBvAHIAcABvAHIAYQB0AGkAbwBuAAAAMIHNBgkrBgEEAYI3EgUBAf8EgbwAMAAA
|
||||||
|
AQAAAAIAAAAJBAAAHABKAGYASgCwAAEAMwBkADIANgA3ADkANQA0AC0AZQBlAGIANwAtADEAMQBk
|
||||||
|
ADEALQBiADkANABlAC0AMAAwAGMAMAA0AGYAYQAzADAAOAAwAGQAAAAzAGQAMgA2ADcAOQA1ADQA
|
||||||
|
LQBlAGUAYgA3AC0AMQAxAGQAMQAtAGIAOQA0AGUALQAwADAAYwAwADQAZgBhADMAMAA4ADAAZAAA
|
||||||
|
AAAAABAAgOQAAAAAADB0BgkrBgEEAYI3EgYBAf8EZAAwAAAAABgASABWAFMASQAtAFMAUgBWAC0A
|
||||||
|
MAAwADQAAAA1ADUAMAA0ADEALQAwADAAOAAtADIAOQAxADMAMwA5ADEALQA4ADQANgAwADkAAABT
|
||||||
|
AEkAUgBBAEQARQBMAAAAAAAwLwYDVR0jAQH/BCUwI6EapBhWAFMASQAtAFMAUgBWAC0AMAAwADQA
|
||||||
|
AACCBQEAAAAYMAkGBSsOAwIdBQADggEBAGFI8teU2iypPG2BbpNLa+zZeb87MNjtzHgQlC0RfoK0
|
||||||
|
dY91t3OXA5pdyD5IRRkvGUVKBf/5G/OVZZvKTnyh3daopD6InPy1X6Wlx5QveCL8ydd/H+ezm0zl
|
||||||
|
KlMg0pImG0V2NX50q4b9R4I5xiaA7mUeww6rz6iAh1iB2CYHOc/uGAS1/PPtHqnfss5bY+wZR+tW
|
||||||
|
dvyMFvn6DbvzAN9wT2KIcv1LtMGgxv28v+dnqGhcxrLRE4nRjMIV/AKpBJXhgH7DaUEc1NJNDPUt
|
||||||
|
5Pdz4qy8WM2d8JO0yNXQVqykJahgt5TJxCoP46SPFUU5XBbNTZuKv7CtPkSxgtrdrzwFTtcAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAQAAAA4ADgBtaWNyb3NvZnQuY29tAA==
|
||||||
|
"""
|
||||||
|
|
||||||
class TestLic(unittest.TestCase):
|
class TestLic(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
@summary: Test case for MCS automata
|
@summary: Test case for MCS automata
|
||||||
@@ -71,9 +113,6 @@ class TestLic(unittest.TestCase):
|
|||||||
t = Transport()
|
t = Transport()
|
||||||
l = lic.LicenseManager(t)
|
l = lic.LicenseManager(t)
|
||||||
|
|
||||||
s = type.Stream()
|
s = type.Stream(SERVERREQUEST.decode("base64"))
|
||||||
s.writeType(lic.LicPacket(lic.ServerLicenseRequest()))
|
|
||||||
#reinit position
|
|
||||||
s.pos = 0
|
|
||||||
|
|
||||||
self.assertFalse(l.recv(s) and t._state, "Bad message after license request")
|
self.assertFalse(l.recv(s) and t._state, "Bad message after license request")
|
||||||
@@ -26,7 +26,7 @@ import os, sys
|
|||||||
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import rdpy.core.rc4 as rc4
|
import rdpy.security.rc4 as rc4
|
||||||
|
|
||||||
|
|
||||||
class RC4Test(unittest.TestCase):
|
class RC4Test(unittest.TestCase):
|
||||||
|
|||||||
Reference in New Issue
Block a user