17 Commits

Author SHA1 Message Date
speyrefitte
894dc44a52 add service_identity in depends 2014-12-03 09:43:16 +01:00
citronneur
47dba5bb6e bug fix on auto detect keybord for windows system 2014-12-03 09:38:14 +01:00
citronneur
c4d071a2e0 Bug fix in screenshot script 2014-12-02 21:32:52 +01:00
speyrefitte
44d99eaca4 Eyewitness integration 2014-12-02 17:00:42 +01:00
speyrefitte
9419bea9b0 fix issue 7 2014-12-02 16:07:33 +01:00
speyrefitte
bd91093617 add 64 bit pre build 2014-12-02 10:12:49 +01:00
speyrefitte
15fadfe3dd Merge branch 'dev' 2014-12-01 18:13:20 +01:00
speyrefitte
fa25a40721 add hostname support + finish license automata 2014-12-01 18:06:08 +01:00
citronneur
99198321a4 add keyboard layout detection or force 2014-11-30 11:22:59 +01:00
citronneur
3c3d7423a5 add fullscreen mode for client 2014-11-29 19:00:14 +01:00
speyrefitte
5af9f0708a bug fix on gui + add license neg (almost) 2014-11-28 17:54:49 +01:00
citronneur
4e7ea06906 code refactoring + gitignore updtae 2014-11-20 22:14:16 +01:00
citronneur
7fc25e35f1 Merge branch 'master' of https://github.com/citronneur/rdpy into dev 2014-11-20 20:29:57 +01:00
speyrefitte
078aaa13f6 Merge branch 'dev' of https://github.com/citronneur/rdpy into dev 2014-11-17 18:38:22 +01:00
speyrefitte
7e98bc373a license automata update 2014-11-17 18:37:48 +01:00
speyrefitte
0d9b766a03 Merge branch 'dev' of https://github.com/citronneur/rdpy 2014-11-17 11:09:06 +01:00
citronneur
c755148a3c strange bug on proxy 2014-11-16 09:36:20 +01:00
19 changed files with 734 additions and 222 deletions

5
.gitignore vendored
View File

@@ -2,7 +2,8 @@
.project .project
.pydevproject .pydevproject
README.md~ README.md~
rdpy/core/tmp/*
*.so *.so
*.os *.os
.sconsign.dblite dist/*
build/*
rdpy.egg-info/*

148
README.md
View File

@@ -1,10 +1,8 @@
# RDPY [![Build Status](https://travis-ci.org/citronneur/rdpy.svg?branch=master)](https://travis-ci.org/citronneur/rdpy) # RDPY [![Build Status](https://travis-ci.org/citronneur/rdpy.svg?branch=dev)](https://travis-ci.org/citronneur/rdpy)
Remote Desktop Protocol in twisted PYthon. Remote Desktop Protocol in twisted PYthon.
RDPY is still under development. RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol. RDPY is built over the event driven network engine Twisted.
RDPY is a pure Python implementation ot the Microsoft RDP (Remote Desktop Protocol) protocol. RDPY is built over the event driven network engine Twisted.
## Build ## Build
@@ -21,10 +19,12 @@ sudo apt-get install python-qt4
#### Windows #### Windows
[PyQt4](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x32.exe) x86 | x86_64
[PyWin32](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.7.exe/download) ----|-------
[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)
[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)
### Make ### Build
``` ```
$ git clone https://github.com/citronneur/rdpy.git rdpy $ git clone https://github.com/citronneur/rdpy.git rdpy
@@ -52,7 +52,7 @@ RDPY comes with some very useful binaries; These binaries are linux and windows
rdpy-rdpclient is a simple RDP Qt4 client . rdpy-rdpclient is a simple RDP Qt4 client .
``` ```
$ rdpy/bin/rdpy-rdpclient [-u username] [-p password] [-d domain] [...] XXX.XXX.XXX.XXX[:3389] $ rdpy-rdpclient.py [-u username] [-p password] [-d domain] [...] XXX.XXX.XXX.XXX[:3389]
``` ```
### rdpy-vncclient ### rdpy-vncclient
@@ -60,7 +60,7 @@ $ rdpy/bin/rdpy-rdpclient [-u username] [-p password] [-d domain] [...] XXX.XXX.
rdpy-vncclient is a simple VNC Qt4 client . rdpy-vncclient is a simple VNC Qt4 client .
``` ```
$ rdpy/bin/rdpy-vncclient [-p password] XXX.XXX.XXX.XXX[:5900] $ rdpy-vncclient.py [-p password] XXX.XXX.XXX.XXX[:5900]
``` ```
### rdpy-rdpscreenshot ### rdpy-rdpscreenshot
@@ -68,7 +68,7 @@ $ rdpy/bin/rdpy-vncclient [-p password] XXX.XXX.XXX.XXX[:5900]
rdpy-rdpscreenshot save login screen in file. rdpy-rdpscreenshot save login screen in file.
``` ```
$ rdpy/bin/rdpy-rdpscreenshot [-w width] [-l height] [-o output_file_path] XXX.XXX.XXX.XXX[:3389] $ rdpy-rdpscreenshot.py [-w width] [-l height] [-o output_file_path] XXX.XXX.XXX.XXX[:3389]
``` ```
### rdpy-vncscreenshot ### rdpy-vncscreenshot
@@ -76,7 +76,7 @@ $ rdpy/bin/rdpy-rdpscreenshot [-w width] [-l height] [-o output_file_path] XXX.X
rdpy-vncscreenshot save first screen update in file. rdpy-vncscreenshot save first screen update in file.
``` ```
$ rdpy/bin/rdpy-vncscreenshot [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:5900] $ rdpy-vncscreenshot.py [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:5900]
``` ```
### rdpy-rdpproxy ### rdpy-rdpproxy
@@ -84,7 +84,7 @@ $ rdpy/bin/rdpy-vncscreenshot [-p password] [-o output_file_path] XXX.XXX.XXX.XX
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. 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/bin/rdpy-rdpproxy -f credentials_file_path -k private_key_file_path -c certificate_file_path [-i admin_ip[:admin_port]] listen_port $ rdpy-rdpproxy.py -f credentials_file_path -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 credentials file is JSON file that must conform with the following format:
@@ -120,9 +120,9 @@ RDPY can also be used as Qt widget throw rdpy.ui.qt4.QRemoteDesktop class. It ca
In a nutshell the RDPY can be used as a protocol library with a twisted engine. In a nutshell the RDPY can be used as a protocol library with a twisted engine.
The RDP client code looks like this: ### Simple RDP Client
``` ```python
from rdpy.protocol.rdp import rdp from rdpy.protocol.rdp import rdp
class MyRDPFactory(rdp.ClientFactory): class MyRDPFactory(rdp.ClientFactory):
@@ -134,20 +134,36 @@ class MyRDPFactory(rdp.ClientFactory):
reactor.stop() reactor.stop()
def buildObserver(self, controller, addr): def buildObserver(self, controller, addr):
class MyObserver(rdp.RDPClientObserver) class MyObserver(rdp.RDPClientObserver)
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): def onReady(self):
#here code handle bitmap """
pass @summary: Call when stack is ready
"""
def onReady(self):
#send 'r' key #send 'r' key
self._controller.sendKeyEventUnicode(ord(unicode("r".toUtf8(), encoding="UTF-8")), True) self._controller.sendKeyEventUnicode(ord(unicode("r".toUtf8(), encoding="UTF-8")), True)
#mouse move and click at pixel 200x200 #mouse move and click at pixel 200x200
self._controller.sendPointerEvent(200, 200, 1, true) self._controller.sendPointerEvent(200, 200, 1, true)
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
@summary: Notify bitmap update
@param destLeft: xmin position
@param destTop: ymin position
@param destRight: xmax position because RDP can send bitmap with padding
@param destBottom: ymax position because RDP can send bitmap with padding
@param width: width of bitmap
@param height: height of bitmap
@param bitsPerPixel: number of bit per pixel
@param isCompress: use RLE compression
@param data: bitmap data
"""
def onClose(self): def onClose(self):
pass """
@summary: Call when stack is close
"""
return MyObserver(controller) return MyObserver(controller)
@@ -156,11 +172,66 @@ reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRDPFactory())
reactor.run() reactor.run()
``` ```
The VNC client code looks like this: ### Simple RDP Server
```python
from rdpy.protocol.rdp import rdp
class MyRDPFactory(rdp.ServerFactory):
def buildObserver(self, controller, addr):
class MyObserver(rdp.RDPServerObserver)
def onReady(self):
"""
@summary: Call when server is ready
to send and receive messages
"""
def onKeyEventScancode(self, code, isPressed):
"""
@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
"""
def onKeyEventUnicode(self, code, isPressed):
"""
@summary: Event call when a keyboard event is catch in unicode format
@param code: unicode of key
@param isPressed: True if key is down
@see: rdp.RDPServerObserver.onKeyEventUnicode
"""
def onPointerEvent(self, x, y, button, isPressed):
"""
@summary: Event call on mouse 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
"""
def onClose(self):
"""
@summary: Call when human client close connection
@see: rdp.RDPServerObserver.onClose
"""
return MyObserver(controller)
from twisted.internet import reactor
reactor.listenTCP(3389, MyRDPFactory())
reactor.run()
``` ```
### Simple VNC Client
```python
from rdpy.protocol.rfb import rdp from rdpy.protocol.rfb import rdp
class MyRDPFactory(rfb.ClientFactory): class MyRFBFactory(rfb.ClientFactory):
def clientConnectionLost(self, connector, reason): def clientConnectionLost(self, connector, reason):
reactor.stop() reactor.stop()
@@ -172,18 +243,41 @@ class MyRDPFactory(rfb.ClientFactory):
class MyObserver(rfb.RFBClientObserver) class MyObserver(rfb.RFBClientObserver)
def onReady(self): def onReady(self):
pass """
@summary: Event when network stack is ready to receive or send event
"""
def onUpdate(self, width, height, x, y, pixelFormat, encoding, data): def onUpdate(self, width, height, x, y, pixelFormat, encoding, data):
#here code handle bitmap """
pass @summary: Implement RFBClientObserver interface
@param width: width of new image
@param height: height of new image
@param x: x position of new image
@param y: y position of new image
@param pixelFormat: pixefFormat structure in rfb.message.PixelFormat
@param encoding: encoding type rfb.message.Encoding
@param data: image data in accordance with pixel format and encoding
"""
def onCutText(self, text):
"""
@summary: event when server send cut text event
@param text: text received
"""
def onBell(self):
"""
@summary: event when server send biiip
"""
def onClose(self): def onClose(self):
pass """
@summary: Call when stack is close
"""
return MyObserver(controller) return MyObserver(controller)
from twisted.internet import reactor from twisted.internet import reactor
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRDPFactory()) reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRFBFactory())
reactor.run() reactor.run()
``` ```

View File

@@ -17,14 +17,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
""" """
example of use rdpy as rdp client example of use rdpy as rdp client
""" """
import sys, os, getopt import sys, os, getopt, socket
from PyQt4 import QtGui from PyQt4 import QtGui, QtCore
from rdpy.ui.qt4 import RDPClientQt from rdpy.ui.qt4 import RDPClientQt
from rdpy.protocol.rdp import rdp from rdpy.protocol.rdp import rdp
@@ -35,19 +34,25 @@ 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): def __init__(self, width, height, username, password, domain, fullscreen, keyboardLayout, optimized):
""" """
@param width: width of client @param width: width of client
@param heigth: heigth of client @param heigth: heigth of client
@param username: username present to the server @param username: username present to the server
@param password: password present to the server @param password: password present to the server
@param domain: microsoft domain @param domain: microsoft domain
@param fullscreen: show widget in fullscreen mode
@param keyboardLayout: keyboard layout
@param optimized: enable optimized session orders
""" """
self._width = width self._width = width
self._height = height self._height = height
self._username = username self._username = username
self._passwod = password self._passwod = password
self._domain = domain self._domain = domain
self._fullscreen = fullscreen
self._keyboardLayout = keyboardLayout
self._optimized = optimized
self._w = None self._w = None
def buildObserver(self, controller, addr): def buildObserver(self, controller, addr):
@@ -63,12 +68,18 @@ class RDPClientQtFactory(rdp.ClientFactory):
#create qt widget #create qt widget
self._w = client.getWidget() self._w = client.getWidget()
self._w.setWindowTitle('rdpy-rdpclient') self._w.setWindowTitle('rdpy-rdpclient')
self._w.show() if self._fullscreen:
self._w.showFullScreen()
else:
self._w.show()
controller.setUsername(self._username) controller.setUsername(self._username)
controller.setPassword(self._passwod) controller.setPassword(self._passwod)
controller.setDomain(self._domain) controller.setDomain(self._domain)
controller.setPerformanceSession() controller.setKeyboardLayout(self._keyboardLayout)
controller.setHostname(socket.gethostname())
if self._optimized:
controller.setPerformanceSession()
return client return client
@@ -95,24 +106,55 @@ class RDPClientQtFactory(rdp.ClientFactory):
reactor.stop() reactor.stop()
app.exit() app.exit()
def autoDetectKeyboardLayout():
"""
@summary: try to auto detect keyboard layout
"""
try:
if os.name == 'posix':
from subprocess import check_output
result = check_output(["setxkbmap", "-print"])
if "azerty" in result:
return "fr"
elif os.name == 'nt':
import win32api, win32con, win32process
from ctypes import windll
w = windll.user32.GetForegroundWindow()
tid = windll.user32.GetWindowThreadProcessId(w, 0)
result = windll.user32.GetKeyboardLayout(tid)
log.info(result)
if result == 0x40c040c:
return "fr"
except Exception as e:
log.info("failed to auto detect keyboard layout " + str(e))
pass
return "en"
def help(): def help():
print "Usage: rdpy-rdpclient [options] ip[:port]" print "Usage: rdpy-rdpclient [options] ip[:port]"
print "\t-u: user name" print "\t-u: user name"
print "\t-p: password" print "\t-p: password"
print "\t-d: domain" print "\t-d: domain"
print "\t-w: width of screen default value is 1024" print "\t-w: width of screen [default : 1024]"
print "\t-l: height of screen default value is 800" print "\t-l: height of screen [default : 800]"
print "\t-f: enable full screen mode [default : False]"
print "\t-k: keyboard layout [en|fr] [default : en]"
print "\t-o: optimized session (disable costly effect) [default : False]"
if __name__ == '__main__': if __name__ == '__main__':
#default script argument #default script argument
username = "" username = ""
password = "" password = ""
domain = "" domain = ""
width = 1024 width = 1024
height = 800 height = 800
fullscreen = False
optimized = False
keyboardLayout = autoDetectKeyboardLayout()
try: try:
opts, args = getopt.getopt(sys.argv[1:], "hu:p:d:w:l:") opts, args = getopt.getopt(sys.argv[1:], "hfou:p:d:w:l:k:")
except getopt.GetoptError: except getopt.GetoptError:
help() help()
for opt, arg in opts: for opt, arg in opts:
@@ -129,12 +171,18 @@ if __name__ == '__main__':
width = int(arg) width = int(arg)
elif opt == "-l": elif opt == "-l":
height = int(arg) height = int(arg)
elif opt == "-f":
fullscreen = True
elif opt == "-o":
optimized = True
elif opt == "-k":
keyboardLayout = arg
if ':' in args[0]: if ':' in args[0]:
ip, port = args[0].split(':') ip, port = args[0].split(':')
else: else:
ip, port = args[0], "3389" ip, port = args[0], "3389"
#create application #create application
app = QtGui.QApplication(sys.argv) app = QtGui.QApplication(sys.argv)
@@ -142,7 +190,16 @@ if __name__ == '__main__':
import qt4reactor import qt4reactor
qt4reactor.install() qt4reactor.install()
if fullscreen:
width = QtGui.QDesktopWidget().screenGeometry().width()
height = QtGui.QDesktopWidget().screenGeometry().height()
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)) reactor.connectTCP(ip, int(port), RDPClientQtFactory(width, height, username, password, domain, fullscreen, keyboardLayout, optimized))
reactor.runReturn() reactor.runReturn()
app.exec_() app.exec_()

View File

@@ -37,7 +37,7 @@ 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):
""" """

View File

@@ -38,6 +38,7 @@ class RDPScreenShotFactory(rdp.ClientFactory):
""" """
@summary: Factory for screenshot exemple @summary: Factory for screenshot exemple
""" """
__INSTANCE__ = 0
def __init__(self, width, height, path, timeout): def __init__(self, width, height, path, timeout):
""" """
@param width: width of screen @param width: width of screen
@@ -45,6 +46,7 @@ class RDPScreenShotFactory(rdp.ClientFactory):
@param path: path of output screenshot @param path: path of output screenshot
@param timeout: close connection after timeout s without any updating @param timeout: close connection after timeout s without any updating
""" """
RDPScreenShotFactory.__INSTANCE__ += 1
self._width = width self._width = width
self._height = height self._height = height
self._path = path self._path = path
@@ -57,8 +59,10 @@ 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 lost : %s"%reason) log.info("connection lost : %s"%reason)
reactor.stop() RDPScreenShotFactory.__INSTANCE__ -= 1
app.exit() if(RDPScreenShotFactory.__INSTANCE__ == 0):
reactor.stop()
app.exit()
def clientConnectionFailed(self, connector, reason): def clientConnectionFailed(self, connector, reason):
""" """
@@ -66,9 +70,11 @@ 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
""" """
log.info("connection failes : %s"%reason) log.info("connection failed : %s"%reason)
reactor.stop() RDPScreenShotFactory.__INSTANCE__ -= 1
app.exit() if(RDPScreenShotFactory.__INSTANCE__ == 0):
reactor.stop()
app.exit()
def buildObserver(self, controller, addr): def buildObserver(self, controller, addr):
@@ -93,19 +99,20 @@ class RDPScreenShotFactory(rdp.ClientFactory):
controller.setScreen(width, height); controller.setScreen(width, height);
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._hasUpdated = True self._timeout = timeout
self._brandWidthTask = task.LoopingCall(self.checkUpdate) self._startTimeout = False
self._brandWidthTask.start(timeout) # call every second
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
""" """
@summary: callback use when bitmap is received @summary: callback use when bitmap is received
""" """
self._hasUpdated = True
image = RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data); image = RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data);
with QtGui.QPainter(self._buffer) as qp: with QtGui.QPainter(self._buffer) as qp:
#draw image #draw image
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:
self._startTimeout = False
reactor.callLater(self._timeout, self.checkUpdate)
def onReady(self): def onReady(self):
""" """
@@ -121,11 +128,7 @@ class RDPScreenShotFactory(rdp.ClientFactory):
self._buffer.save(self._path) self._buffer.save(self._path)
def checkUpdate(self): def checkUpdate(self):
if not self._hasUpdated: self._controller.close();
log.info("close connection on timeout without updating orders")
self._controller.close();
return
self._hasUpdated = False
return ScreenShotObserver(controller, self._width, self._height, self._path, self._timeout) return ScreenShotObserver(controller, self._width, self._height, self._path, self._timeout)
@@ -140,8 +143,8 @@ if __name__ == '__main__':
#default script argument #default script argument
width = 1024 width = 1024
height = 800 height = 800
path = "/tmp/rdpy-rdpscreenshot.jpg" path = "/tmp/"
timeout = 2.0 timeout = 5.0
try: try:
opts, args = getopt.getopt(sys.argv[1:], "hw:l:o:t:") opts, args = getopt.getopt(sys.argv[1:], "hw:l:o:t:")
@@ -159,12 +162,7 @@ if __name__ == '__main__':
path = arg path = arg
elif opt == "-t": elif opt == "-t":
timeout = float(arg) timeout = float(arg)
if ':' in args[0]:
ip, port = args[0].split(':')
else:
ip, port = args[0], "3389"
#create application #create application
app = QtGui.QApplication(sys.argv) app = QtGui.QApplication(sys.argv)
@@ -173,6 +171,14 @@ if __name__ == '__main__':
qt4reactor.install() qt4reactor.install()
from twisted.internet import reactor from twisted.internet import reactor
reactor.connectTCP(ip, int(port), RDPScreenShotFactory(width, height, path, timeout))
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() reactor.runReturn()
app.exec_() app.exec_()

View File

@@ -37,11 +37,13 @@ class RFBScreenShotFactory(rfb.ClientFactory):
""" """
@summary: Factory for screenshot exemple @summary: Factory for screenshot exemple
""" """
__INSTANCE__ = 0
def __init__(self, password, path): def __init__(self, password, path):
""" """
@param password: password for VNC authentication @param password: password for VNC authentication
@param path: path of output screenshot @param path: path of output screenshot
""" """
RFBScreenShotFactory.__INSTANCE__ += 1
self._path = path self._path = path
self._password = password self._password = password
@@ -52,8 +54,10 @@ class RFBScreenShotFactory(rfb.ClientFactory):
@param reason: str use to advertise reason of lost connection @param reason: str use to advertise reason of lost connection
""" """
log.info("connection lost : %s"%reason) log.info("connection lost : %s"%reason)
reactor.stop() RFBScreenShotFactory.__INSTANCE__ -= 1
app.exit() if(RFBScreenShotFactory.__INSTANCE__ == 0):
reactor.stop()
app.exit()
def clientConnectionFailed(self, connector, reason): def clientConnectionFailed(self, connector, reason):
""" """
@@ -62,8 +66,10 @@ class RFBScreenShotFactory(rfb.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)
reactor.stop() RFBScreenShotFactory.__INSTANCE__ -= 1
app.exit() if(RFBScreenShotFactory.__INSTANCE__ == 0):
reactor.stop()
app.exit()
def buildObserver(self, controller, addr): def buildObserver(self, controller, addr):
@@ -132,7 +138,7 @@ def help():
if __name__ == '__main__': if __name__ == '__main__':
#default script argument #default script argument
path = "/tmp/rdpy-vncscreenshot.jpg" path = "/tmp/"
password = "" password = ""
try: try:
@@ -148,20 +154,23 @@ if __name__ == '__main__':
elif opt == "-p": elif opt == "-p":
password = arg password = arg
if ':' in args[0]:
ip, port = args[0].split(':')
else:
ip, port = args[0], "5900"
#create application #create application
app = QtGui.QApplication(sys.argv) app = QtGui.QApplication(sys.argv)
#add qt4 reactor #add qt4 reactor
import qt4reactor import qt4reactor
qt4reactor.install() qt4reactor.install()
from twisted.internet import reactor from twisted.internet import reactor
reactor.connectTCP(ip, int(port), RFBScreenShotFactory(password, path))
for arg in args:
if ':' in arg:
ip, port = arg.split(':')
else:
ip, port = arg, "5900"
reactor.connectTCP(ip, int(port), RFBScreenShotFactory(password, path + "%s.jpg"%ip))
reactor.runReturn() reactor.runReturn()
app.exec_() app.exec_()

View File

@@ -463,8 +463,9 @@ class CompositeType(Type):
if not self._readLen is None and readLen > self._readLen.value: if not self._readLen is None and readLen > self._readLen.value:
#roll back #roll back
s.pos -= sizeof(self.__dict__[name]) s.pos -= sizeof(self.__dict__[name])
#and notify #and notify if not optional
raise InvalidSize("Impossible to read type %s : read length is too small"%(self.__class__)) if not self.__dict__[name]._optional:
raise InvalidSize("Impossible to read type %s : read length is too small"%(self.__class__))
except Exception as e: except Exception as e:
log.error("Error during read %s::%s"%(self.__class__, name)) log.error("Error during read %s::%s"%(self.__class__, name))

View File

@@ -230,7 +230,7 @@ class ClientCoreData(CompositeType):
self.desktopHeight = UInt16Le(800) self.desktopHeight = UInt16Le(800)
self.colorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP) self.colorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP)
self.sasSequence = UInt16Le(Sequence.RNS_UD_SAS_DEL) self.sasSequence = UInt16Le(Sequence.RNS_UD_SAS_DEL)
self.kbdLayout = UInt32Le(KeyboardLayout.FRENCH) self.kbdLayout = UInt32Le(KeyboardLayout.US)
self.clientBuild = UInt32Le(3790) self.clientBuild = UInt32Le(3790)
self.clientName = String("rdpy" + "\x00"*11, readLen = UInt8(32), unicode = True) self.clientName = String("rdpy" + "\x00"*11, readLen = UInt8(32), unicode = True)
self.keyboardType = UInt32Le(KeyboardType.IBM_101_102_KEYS) self.keyboardType = UInt32Le(KeyboardType.IBM_101_102_KEYS)

View File

@@ -930,7 +930,7 @@ class OrderUpdateDataPDU(CompositeType):
self.pad2OctetsA = UInt16Le() self.pad2OctetsA = UInt16Le()
self.numberOrders = UInt16Le(lambda:len(self.orderData._array)) self.numberOrders = UInt16Le(lambda:len(self.orderData._array))
self.pad2OctetsB = UInt16Le() self.pad2OctetsB = UInt16Le()
self.orderData = ArrayType(order.DrawingOrder, readLen = self.numberOrders) self.orderData = ArrayType(order.PrimaryDrawingOrder, readLen = self.numberOrders)
class BitmapCompressedDataHeader(CompositeType): class BitmapCompressedDataHeader(CompositeType):
""" """

View File

@@ -136,6 +136,7 @@ class Client(PDULayer, tpkt.IFastPathListener):
self._listener = listener self._listener = listener
#enable or not fast path #enable or not fast path
self._fastPathSender = None self._fastPathSender = None
self._licenceManager = lic.LicenseManager(self)
def connect(self): def connect(self):
""" """
@@ -177,18 +178,9 @@ class Client(PDULayer, tpkt.IFastPathListener):
if not (securityFlag.value & data.SecurityFlag.SEC_LICENSE_PKT): if not (securityFlag.value & data.SecurityFlag.SEC_LICENSE_PKT):
raise InvalidExpectedDataException("Waiting license packet") raise InvalidExpectedDataException("Waiting license packet")
validClientPdu = lic.LicPacket() if self._licenceManager.recv(s):
s.readType(validClientPdu)
if validClientPdu.bMsgtype.value == lic.MessageType.ERROR_ALERT and validClientPdu.licensingMessage.dwErrorCode.value == lic.ErrorCode.STATUS_VALID_CLIENT and validClientPdu.licensingMessage.dwStateTransition.value == lic.StateTransition.ST_NO_TRANSITION:
self.setNextState(self.recvDemandActivePDU) self.setNextState(self.recvDemandActivePDU)
#not tested because i can't buy RDP license server
elif validClientPdu.bMsgtype.value == lic.MessageType.LICENSE_REQUEST:
newLicenseReq = lic.createNewLicenseRequest(validClientPdu.licensingMessage)
self._transport.send((UInt16Le(data.SecurityFlag.SEC_LICENSE_PKT), UInt16Le(), newLicenseReq))
else:
raise InvalidExpectedDataException("Not a valid license packet")
def recvDemandActivePDU(self, s): def recvDemandActivePDU(self, s):
""" """
Receive demand active PDU which contains Receive demand active PDU which contains
@@ -343,6 +335,13 @@ class Client(PDULayer, tpkt.IFastPathListener):
""" """
self._transport.send((UInt16Le(data.SecurityFlag.SEC_INFO_PKT), UInt16Le(), self._info)) self._transport.send((UInt16Le(data.SecurityFlag.SEC_INFO_PKT), UInt16Le(), self._info))
def sendLicensePacket(self, licPkt):
"""
@summary: send license packet
@param licPktr: license packet
"""
self._transport.send((UInt16Le(data.SecurityFlag.SEC_LICENSE_PKT), UInt16Le(), licPkt))
def sendConfirmActivePDU(self): def sendConfirmActivePDU(self):
""" """
Send all client capabilities Send all client capabilities
@@ -592,8 +591,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
def sendDemandActivePDU(self): def sendDemandActivePDU(self):
""" """
Send server capabilities @summary: Send server capabilities server automata PDU
server automata PDU
""" """
#init general capability #init general capability
generalCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability generalCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability

View File

@@ -18,17 +18,20 @@
# #
""" """
RDP extended license @summary: RDP extended license
@see: http://msdn.microsoft.com/en-us/library/cc241880.aspx @see: http://msdn.microsoft.com/en-us/library/cc241880.aspx
""" """
from rdpy.network.type import CompositeType, UInt8, UInt16Le, UInt32Le, String, sizeof, FactoryType, ArrayType from rdpy.network.type import CompositeType, UInt8, UInt16Le, UInt32Le, String, sizeof, FactoryType, ArrayType,\
Stream
from rdpy.base.error import InvalidExpectedDataException from rdpy.base.error import InvalidExpectedDataException
import rdpy.base.log as log import rdpy.base.log as log
import rdpy.protocol.rdp.sec as sec
import rdpy.protocol.rdp.rc4 as rc4
class MessageType(object): class MessageType(object):
""" """
License packet message type @summary: License packet message type
""" """
LICENSE_REQUEST = 0x01 LICENSE_REQUEST = 0x01
PLATFORM_CHALLENGE = 0x02 PLATFORM_CHALLENGE = 0x02
@@ -42,7 +45,7 @@ class MessageType(object):
class ErrorCode(object): class ErrorCode(object):
""" """
License error message code @summary: License error message code
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx @see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
""" """
ERR_INVALID_SERVER_CERTIFICATE = 0x00000001 ERR_INVALID_SERVER_CERTIFICATE = 0x00000001
@@ -57,7 +60,7 @@ class ErrorCode(object):
class StateTransition(object): class StateTransition(object):
""" """
Automata state transition @summary: Automata state transition
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx @see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
""" """
ST_TOTAL_ABORT = 0x00000001 ST_TOTAL_ABORT = 0x00000001
@@ -67,9 +70,10 @@ class StateTransition(object):
class BinaryBlobType(object): class BinaryBlobType(object):
""" """
Binary blob data type @summary: Binary blob data type
@see: http://msdn.microsoft.com/en-us/library/cc240481.aspx @see: http://msdn.microsoft.com/en-us/library/cc240481.aspx
""" """
BB_ANY_BLOB = 0x0000
BB_DATA_BLOB = 0x0001 BB_DATA_BLOB = 0x0001
BB_RANDOM_BLOB = 0x0002 BB_RANDOM_BLOB = 0x0002
BB_CERTIFICATE_BLOB = 0x0003 BB_CERTIFICATE_BLOB = 0x0003
@@ -82,25 +86,26 @@ class BinaryBlobType(object):
class Preambule(object): class Preambule(object):
""" """
Preambule version @summary: Preambule version
""" """
PREAMBLE_VERSION_2_0 = 0x2 PREAMBLE_VERSION_2_0 = 0x2
PREAMBLE_VERSION_3_0 = 0x3 PREAMBLE_VERSION_3_0 = 0x3
EXTENDED_ERROR_MSG_SUPPORTED = 0x80
class LicenseBinaryBlob(CompositeType): class LicenseBinaryBlob(CompositeType):
""" """
Blob use by license manager to exchange security data @summary: Blob use by license manager to exchange security data
@see: http://msdn.microsoft.com/en-us/library/cc240481.aspx @see: http://msdn.microsoft.com/en-us/library/cc240481.aspx
""" """
def __init__(self, blobType = 0): def __init__(self, blobType = BinaryBlobType.BB_ANY_BLOB):
CompositeType.__init__(self) CompositeType.__init__(self)
self.wBlobType = UInt16Le(blobType, constant = True) self.wBlobType = UInt16Le(blobType, constant = True if blobType != BinaryBlobType.BB_ANY_BLOB else False)
self.wBlobLen = UInt16Le(lambda:sizeof(self.blobData)) self.wBlobLen = UInt16Le(lambda:sizeof(self.blobData))
self.blobData = String(readLen = self.wBlobLen) self.blobData = String(readLen = self.wBlobLen)
class LicensingErrorMessage(CompositeType): class LicensingErrorMessage(CompositeType):
""" """
License error message @summary: License error message
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx @see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
""" """
_MESSAGE_TYPE_ = MessageType.ERROR_ALERT _MESSAGE_TYPE_ = MessageType.ERROR_ALERT
@@ -113,7 +118,7 @@ class LicensingErrorMessage(CompositeType):
class ProductInformation(CompositeType): class ProductInformation(CompositeType):
""" """
License server product information @summary: License server product information
@see: http://msdn.microsoft.com/en-us/library/cc241915.aspx @see: http://msdn.microsoft.com/en-us/library/cc241915.aspx
""" """
def __init__(self): def __init__(self):
@@ -121,15 +126,15 @@ class ProductInformation(CompositeType):
self.dwVersion = UInt32Le() self.dwVersion = UInt32Le()
self.cbCompanyName = UInt32Le(lambda:sizeof(self.pbCompanyName)) self.cbCompanyName = UInt32Le(lambda:sizeof(self.pbCompanyName))
#may contain "Microsoft Corporation" from server microsoft #may contain "Microsoft Corporation" from server microsoft
self.pbCompanyName = String(readLen = self.cbCompanyName) self.pbCompanyName = String(readLen = self.cbCompanyName, unicode = True)
self.cbProductId = UInt32Le(lambda:sizeof(self.pbProductId)) self.cbProductId = UInt32Le(lambda:sizeof(self.pbProductId))
#may contain "A02" from microsoft license server #may contain "A02" from microsoft license server
self.pbProductId = String(readLen = self.cbProductId) self.pbProductId = String(readLen = self.cbProductId, unicode = True)
class Scope(CompositeType): class Scope(CompositeType):
""" """
Use in license nego @summary: Use in license nego
@see: http://msdn.microsoft.com/en-us/library/cc241917.aspx @see: http://msdn.microsoft.com/en-us/library/cc241917.aspx
""" """
def __init__(self): def __init__(self):
@@ -138,7 +143,7 @@ class Scope(CompositeType):
class ScopeList(CompositeType): class ScopeList(CompositeType):
""" """
Use in license nego @summary: Use in license nego
@see: http://msdn.microsoft.com/en-us/library/cc241916.aspx @see: http://msdn.microsoft.com/en-us/library/cc241916.aspx
""" """
def __init__(self): def __init__(self):
@@ -148,8 +153,8 @@ class ScopeList(CompositeType):
class ServerLicenseRequest(CompositeType): class ServerLicenseRequest(CompositeType):
""" """
Send by server to signal license request @summary: Send by server to signal license request
server -> client server -> client
@see: http://msdn.microsoft.com/en-us/library/cc241914.aspx @see: http://msdn.microsoft.com/en-us/library/cc241914.aspx
""" """
_MESSAGE_TYPE_ = MessageType.LICENSE_REQUEST _MESSAGE_TYPE_ = MessageType.LICENSE_REQUEST
@@ -164,8 +169,8 @@ class ServerLicenseRequest(CompositeType):
class ClientNewLicenseRequest(CompositeType): class ClientNewLicenseRequest(CompositeType):
""" """
Send by client to ask new license for client. @summary: Send by client to ask new license for client.
RDPY doesn'support license reuse, need it in futur version RDPY doesn'support license reuse, need it in futur version
@see: http://msdn.microsoft.com/en-us/library/cc241918.aspx @see: http://msdn.microsoft.com/en-us/library/cc241918.aspx
""" """
_MESSAGE_TYPE_ = MessageType.NEW_LICENSE_REQUEST _MESSAGE_TYPE_ = MessageType.NEW_LICENSE_REQUEST
@@ -181,10 +186,36 @@ class ClientNewLicenseRequest(CompositeType):
self.encryptedPreMasterSecret = LicenseBinaryBlob(BinaryBlobType.BB_RANDOM_BLOB) self.encryptedPreMasterSecret = LicenseBinaryBlob(BinaryBlobType.BB_RANDOM_BLOB)
self.ClientUserName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_USER_NAME_BLOB) self.ClientUserName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_USER_NAME_BLOB)
self.ClientMachineName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_MACHINE_NAME_BLOB) self.ClientMachineName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_MACHINE_NAME_BLOB)
class ServerPlatformChallenge(CompositeType):
"""
@summary: challenge send from server to client
@see: http://msdn.microsoft.com/en-us/library/cc241921.aspx
"""
_MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE
def __init__(self):
CompositeType.__init__(self)
self.connectFlags = UInt32Le()
self.encryptedPlatformChallenge = LicenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB)
self.MACData = String(readLen = UInt8(16))
class ClientPLatformChallengeResponse(CompositeType):
"""
@summary: client challenge response
@see: http://msdn.microsoft.com/en-us/library/cc241922.aspx
"""
_MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE_RESPONSE
def __init__(self):
CompositeType.__init__(self)
self.encryptedPlatformChallengeResponse = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB)
self.encryptedHWID = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB)
self.MACData = String(readLen = UInt8(16))
class LicPacket(CompositeType): class LicPacket(CompositeType):
""" """
A license packet @summary: A license packet
""" """
def __init__(self, message = None): def __init__(self, message = None):
CompositeType.__init__(self) CompositeType.__init__(self)
@@ -195,10 +226,10 @@ class LicPacket(CompositeType):
def LicensingMessageFactory(): def LicensingMessageFactory():
""" """
factory for message nego @summary: factory for message nego
Use in read mode Use in read mode
""" """
for c in [LicensingErrorMessage, ServerLicenseRequest, ClientNewLicenseRequest]: for c in [LicensingErrorMessage, ServerLicenseRequest, ClientNewLicenseRequest, ServerPlatformChallenge, ClientPLatformChallengeResponse]:
if self.bMsgtype.value == c._MESSAGE_TYPE_: if self.bMsgtype.value == c._MESSAGE_TYPE_:
return c() return c()
log.debug("unknown license message : %s"%self.bMsgtype.value) log.debug("unknown license message : %s"%self.bMsgtype.value)
@@ -210,22 +241,101 @@ class LicPacket(CompositeType):
raise InvalidExpectedDataException("Try to send an invalid license message") raise InvalidExpectedDataException("Try to send an invalid license message")
self.licensingMessage = message self.licensingMessage = message
def createValidClientLicensingErrorMessage(): def createValidClientLicensingErrorMessage():
"""
@summary: Create a licensing error message that accept client
server automata message
"""
message = LicensingErrorMessage()
message.dwErrorCode.value = ErrorCode.STATUS_VALID_CLIENT
message.dwStateTransition.value = StateTransition.ST_NO_TRANSITION
return LicPacket(message)
class LicenseManager(object):
""" """
Create a licensing error message that accept client @summary: handle license automata
server automata message @see: http://msdn.microsoft.com/en-us/library/cc241890.aspx
""" """
message = LicensingErrorMessage() def __init__(self, transport):
message.dwErrorCode.value = ErrorCode.STATUS_VALID_CLIENT """
message.dwStateTransition.value = StateTransition.ST_NO_TRANSITION @param transport: layer use to send packet
return LicPacket(message = message) """
self._preMasterSecret = "\x00" * 64
def createNewLicenseRequest(serverLicenseRequest): self._clientRandom = "\x00" * 32
""" self._serverRandom = None
Create new license request in response to server license request self._serverEncryptedChallenge = None
@see: http://msdn.microsoft.com/en-us/library/cc241989.aspx self._transport = transport
@see: http://msdn.microsoft.com/en-us/library/cc241918.aspx self._username = ""
@todo: need RDP license server self._hostname = ""
"""
return LicPacket(message = ClientNewLicenseRequest()) def generateKeys(self):
"""
@summary: generate key for license session
"""
masterSecret = sec.generateMicrosoftKey(self._preMasterSecret, self._clientRandom, self._serverRandom)
sessionKeyBlob = sec.generateMicrosoftKey(masterSecret, self._serverRandom, self._clientRandom)
self._macSalt = sessionKeyBlob[:16]
self._licenseKey = sec.finalHash(sessionKeyBlob[16:32], self._clientRandom, self._serverRandom)
def recv(self, s):
"""
@summary: receive license packet from PDU layer
@return true when license automata is finish
"""
licPacket = LicPacket()
s.readType(licPacket)
#end of automata
if licPacket.bMsgtype.value == MessageType.ERROR_ALERT and licPacket.licensingMessage.dwErrorCode.value == ErrorCode.STATUS_VALID_CLIENT and licPacket.licensingMessage.dwStateTransition.value == StateTransition.ST_NO_TRANSITION:
return True
elif licPacket.bMsgtype.value == MessageType.LICENSE_REQUEST:
self._serverRandom = licPacket.licensingMessage.serverRandom.value
self.generateKeys()
self.sendClientNewLicenseRequest()
elif licPacket.bMsgtype.value == MessageType.PLATFORM_CHALLENGE:
self._serverEncryptedChallenge = licPacket.licensingMessage.encryptedPlatformChallenge.blobData.value
self.sendClientChallengeResponse()
#yes get a new license
elif licPacket.bMsgtype.value == MessageType.NEW_LICENSE:
return True
else:
raise InvalidExpectedDataException("Not a valid license packet")
def sendClientNewLicenseRequest(self):
"""
@summary: Create new license request in response to server license request
@see: http://msdn.microsoft.com/en-us/library/cc241989.aspx
@see: http://msdn.microsoft.com/en-us/library/cc241918.aspx
"""
message = ClientNewLicenseRequest()
message.clientRandom.value = self._clientRandom
message.encryptedPreMasterSecret.blobData = String(self._preMasterSecret + "\x00" * 8)
message.ClientMachineName.blobData = String(self._hostname + "\x00")
message.ClientUserName.blobData = String(self._username + "\x00")
self._transport.sendLicensePacket(LicPacket(message))
def sendClientChallengeResponse(self):
"""
@summary: generate valid challenge response
"""
#decrypt server challenge
#it should be TEST word in unicode format
serverChallenge = rc4.crypt(self._licenseKey, self._serverEncryptedChallenge)
#generate hwid
s = Stream()
s.writeType((UInt32Le(2), String(self._hostname + "\x00" * 16)))
hwid = s.getvalue()[:20]
message = ClientPLatformChallengeResponse()
message.encryptedPlatformChallengeResponse.blobData.value = self._serverEncryptedChallenge
message.encryptedHWID.blobData.value = rc4.crypt(self._licenseKey, hwid)
message.MACData.value = sec.macData(self._macSalt, serverChallenge + hwid)
self._transport.sendLicensePacket(LicPacket(message))

56
rdpy/protocol/rdp/rc4.py Normal file
View File

@@ -0,0 +1,56 @@
"""
Copyright (C) 2012 Bo Zhu http://about.bozhu.me
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
def KSA(key):
keylength = len(key)
S = range(256)
j = 0
for i in range(256):
j = (j + S[i] + key[i % keylength]) % 256
S[i], S[j] = S[j], S[i] # swap
return S
def PRGA(S):
i = 0
j = 0
while True:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i] # swap
K = S[(S[i] + S[j]) % 256]
yield K
def RC4(key):
S = KSA(key)
return PRGA(S)
def crypt(key, plaintext):
keystream = RC4([ord(c) for c in key])
return "".join([chr(ord(c) ^ keystream.next()) for c in plaintext])

View File

@@ -68,13 +68,13 @@ class RDPClientController(pdu.layer.PDUClientListener):
def setPerformanceSession(self): def setPerformanceSession(self):
""" """
Set particular flag in RDP stack to avoid wall-paper, theme, menu animation etc... @summary: Set particular flag in RDP stack to avoid wall-paper, theme, menu animation etc...
""" """
self._pduLayer._info.extendedInfo.performanceFlags.value = pdu.data.PerfFlag.PERF_DISABLE_WALLPAPER | pdu.data.PerfFlag.PERF_DISABLE_MENUANIMATIONS | pdu.data.PerfFlag.PERF_DISABLE_CURSOR_SHADOW | pdu.data.PerfFlag.PERF_DISABLE_THEMING | pdu.data.PerfFlag.PERF_DISABLE_FULLWINDOWDRAG self._pduLayer._info.extendedInfo.performanceFlags.value = pdu.data.PerfFlag.PERF_DISABLE_WALLPAPER | pdu.data.PerfFlag.PERF_DISABLE_MENUANIMATIONS | pdu.data.PerfFlag.PERF_DISABLE_CURSOR_SHADOW | pdu.data.PerfFlag.PERF_DISABLE_THEMING | pdu.data.PerfFlag.PERF_DISABLE_FULLWINDOWDRAG
def setScreen(self, width, height): def setScreen(self, width, height):
""" """
Set screen dim of session @summary: Set screen dim of session
@param width: width in pixel of screen @param width: width in pixel of screen
@param height: height in pixel of screen @param height: height in pixel of screen
""" """
@@ -84,15 +84,16 @@ class RDPClientController(pdu.layer.PDUClientListener):
def setUsername(self, username): def setUsername(self, username):
""" """
Set the username for session @summary: Set the username for session
@param username: username of session @param username: username of session
""" """
#username in PDU info packet #username in PDU info packet
self._pduLayer._info.userName.value = username self._pduLayer._info.userName.value = username
self._pduLayer._licenceManager._username = username
def setPassword(self, password): def setPassword(self, password):
""" """
Set password for session @summary: Set password for session
@param password: password of session @param password: password of session
""" """
self.setAutologon() self.setAutologon()
@@ -100,7 +101,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def setDomain(self, domain): def setDomain(self, domain):
""" """
Set the windows domain of session @summary: Set the windows domain of session
@param domain: domain of session @param domain: domain of session
""" """
self._pduLayer._info.domain.value = domain self._pduLayer._info.domain.value = domain
@@ -111,16 +112,33 @@ class RDPClientController(pdu.layer.PDUClientListener):
""" """
self._pduLayer._info.flag |= pdu.data.InfoFlag.INFO_AUTOLOGON self._pduLayer._info.flag |= pdu.data.InfoFlag.INFO_AUTOLOGON
def setKeyboardLayout(self, layout):
"""
@summary: keyboard layout
@param layout: us | fr
"""
if layout == "fr":
self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).kbdLayout.value = gcc.KeyboardLayout.FRENCH
elif layout == "us":
self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).kbdLayout.value = gcc.KeyboardLayout.US
def setHostname(self, hostname):
"""
@summary: set hostname of machine
"""
self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).clientName.value = hostname[:15] + "\x00" * (15 - len(hostname))
self._pduLayer._licenceManager._hostname = hostname
def addClientObserver(self, observer): def addClientObserver(self, observer):
""" """
Add observer to RDP protocol @summary: Add observer to RDP protocol
@param observer: new observer to add @param observer: new observer to add
""" """
self._clientObserver.append(observer) self._clientObserver.append(observer)
def removeClientObserver(self, observer): def removeClientObserver(self, observer):
""" """
Remove observer to RDP protocol stack @summary: Remove observer to RDP protocol stack
@param observer: observer to remove @param observer: observer to remove
""" """
for i in range(0, len(self._clientObserver)): for i in range(0, len(self._clientObserver)):
@@ -130,7 +148,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def onUpdate(self, rectangles): def onUpdate(self, rectangles):
""" """
Call when a bitmap data is received from update PDU @summary: Call when a bitmap data is received from update PDU
@param rectangles: [pdu.BitmapData] struct @param rectangles: [pdu.BitmapData] struct
""" """
for observer in self._clientObserver: for observer in self._clientObserver:
@@ -140,7 +158,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def onReady(self): def onReady(self):
""" """
Call when PDU layer is connected @summary: Call when PDU layer is connected
""" """
self._isReady = True self._isReady = True
#signal all listener #signal all listener
@@ -149,7 +167,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def onClose(self): def onClose(self):
""" """
Event call when RDP stack is closed @summary: Event call when RDP stack is closed
""" """
self._isReady = False self._isReady = False
for observer in self._clientObserver: for observer in self._clientObserver:
@@ -157,7 +175,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def sendPointerEvent(self, x, y, button, isPressed): def sendPointerEvent(self, x, y, button, isPressed):
""" """
send pointer events @summary: send pointer events
@param x: x position of pointer @param x: x position of pointer
@param y: y position of pointer @param y: y position of pointer
@param button: 1 or 2 or 3 @param button: 1 or 2 or 3
@@ -189,10 +207,44 @@ class RDPClientController(pdu.layer.PDUClientListener):
except InvalidValue: except InvalidValue:
log.info("try send pointer event with incorrect position") log.info("try send pointer event with incorrect position")
def sendWheelEvent(self, x, y, step, isNegative = False, isHorizontal = False):
"""
@summary: Send a mouse wheel event
@param x: x position of pointer
@param y: y position of pointer
@param step: number of step rolled
@param isHorizontal: horizontal wheel (default is vertical)
@param isNegative: is upper (default down)
"""
if not self._isReady:
return
try:
event = pdu.data.PointerEvent()
if isHorizontal:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_HWHEEL
else:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_WHEEL
if isNegative:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_WHEEL_NEGATIVE
event.pointerFlags.value |= (step & pdu.data.PointerFlag.WheelRotationMask)
#position
event.xPos.value = x
event.yPos.value = y
#send proper event
self._pduLayer.sendInputEvents([event])
except InvalidValue:
log.info("try send wheel event with incorrect position")
def sendKeyEventScancode(self, code, isPressed): def sendKeyEventScancode(self, code, isPressed):
""" """
Send a scan code to RDP stack @summary: Send a scan code to RDP stack
@param code: scan code @param code: scan code
@param isPressed: True if key is pressed and false if it's released @param isPressed: True if key is pressed and false if it's released
""" """
@@ -215,7 +267,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def sendKeyEventUnicode(self, code, isPressed): def sendKeyEventUnicode(self, code, isPressed):
""" """
Send a scan code to RDP stack @summary: Send a scan code to RDP stack
@param code: unicode @param code: unicode
@param isPressed: True if key is pressed and false if it's released @param isPressed: True if key is pressed and false if it's released
""" """
@@ -236,7 +288,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def sendRefreshOrder(self, left, top, right, bottom): def sendRefreshOrder(self, left, top, right, bottom):
""" """
Force server to resend a particular zone @summary: Force server to resend a particular zone
@param left: left coordinate @param left: left coordinate
@param top: top coordinate @param top: top coordinate
@param right: right coordinate @param right: right coordinate
@@ -253,13 +305,13 @@ class RDPClientController(pdu.layer.PDUClientListener):
def close(self): def close(self):
""" """
Close protocol stack @summary: Close protocol stack
""" """
self._pduLayer.close() self._pduLayer.close()
class RDPServerController(pdu.layer.PDUServerListener): class RDPServerController(pdu.layer.PDUServerListener):
""" """
Controller use in server side mode @summary: Controller use in server side mode
""" """
def __init__(self, privateKeyFileName, certificateFileName, colorDepth): def __init__(self, privateKeyFileName, certificateFileName, colorDepth):
""" """
@@ -283,7 +335,7 @@ class RDPServerController(pdu.layer.PDUServerListener):
def close(self): def close(self):
""" """
Close protocol stack @summary: Close protocol stack
""" """
self._pduLayer.close() self._pduLayer.close()
@@ -296,28 +348,28 @@ class RDPServerController(pdu.layer.PDUServerListener):
def getUsername(self): def getUsername(self):
""" """
Must be call after on ready event else always empty string @summary: Must be call after on ready event else always empty string
@return: username send by client may be an empty string @return: username send by client may be an empty string
""" """
return self._pduLayer._info.userName.value return self._pduLayer._info.userName.value
def getPassword(self): def getPassword(self):
""" """
Must be call after on ready event else always empty string @summary: Must be call after on ready event else always empty string
@return: password send by client may be an empty string @return: password send by client may be an empty string
""" """
return self._pduLayer._info.password.value return self._pduLayer._info.password.value
def getDomain(self): def getDomain(self):
""" """
Must be call after on ready event else always empty string @summary: Must be call after on ready event else always empty string
@return: domain send by client may be an empty string @return: domain send by client may be an empty string
""" """
return self._pduLayer._info.domain.value return self._pduLayer._info.domain.value
def getCredentials(self): def getCredentials(self):
""" """
Must be call after on ready event else always empty string @summary: Must be call after on ready event else always empty string
@return: tuple(domain, username, password) @return: tuple(domain, username, password)
""" """
return (self.getDomain(), self.getUsername(), self.getPassword()) return (self.getDomain(), self.getUsername(), self.getPassword())
@@ -337,15 +389,15 @@ class RDPServerController(pdu.layer.PDUServerListener):
def addServerObserver(self, observer): def addServerObserver(self, observer):
""" """
Add observer to RDP protocol @summary: Add observer to RDP protocol
@param observer: new observer to add @param observer: new observer to add
""" """
self._serverObserver.append(observer) self._serverObserver.append(observer)
def setColorDepth(self, colorDepth): def setColorDepth(self, colorDepth):
""" """
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) @param colorDepth: depth of session (15, 16, 24)
""" """
self._colorDepth = colorDepth self._colorDepth = colorDepth
@@ -357,13 +409,13 @@ class RDPServerController(pdu.layer.PDUServerListener):
def setKeyEventUnicodeSupport(self): def setKeyEventUnicodeSupport(self):
""" """
Enable key event in unicode format @summary: Enable key event in unicode format
""" """
self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_INPUT].capability.inputFlags.value |= pdu.caps.InputFlags.INPUT_FLAG_UNICODE self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_INPUT].capability.inputFlags.value |= pdu.caps.InputFlags.INPUT_FLAG_UNICODE
def onReady(self): def onReady(self):
""" """
RDP stack is now ready @summary: RDP stack is now ready
""" """
self._isReady = True self._isReady = True
for observer in self._serverObserver: for observer in self._serverObserver:
@@ -371,7 +423,7 @@ class RDPServerController(pdu.layer.PDUServerListener):
def onClose(self): def onClose(self):
""" """
Event call when RDP stack is closed @summary: Event call when RDP stack is closed
""" """
self._isReady = False self._isReady = False
for observer in self._serverObserver: for observer in self._serverObserver:
@@ -379,7 +431,7 @@ class RDPServerController(pdu.layer.PDUServerListener):
def onSlowPathInput(self, slowPathInputEvents): def onSlowPathInput(self, slowPathInputEvents):
""" """
Event call when slow path input are available @summary: Event call when slow path input are available
@param slowPathInputEvents: [data.SlowPathInputEvent] @param slowPathInputEvents: [data.SlowPathInputEvent]
""" """
for observer in self._serverObserver: for observer in self._serverObserver:
@@ -404,7 +456,7 @@ class RDPServerController(pdu.layer.PDUServerListener):
def sendUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): def sendUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
""" """
send bitmap update @summary: send bitmap update
@param destLeft: xmin position @param destLeft: xmin position
@param destTop: ymin position @param destTop: ymin position
@param destRight: xmax position because RDP can send bitmap with padding @param destRight: xmax position because RDP can send bitmap with padding
@@ -454,7 +506,7 @@ class ClientFactory(layer.RawLayerClientFactory):
class ServerFactory(layer.RawLayerServerFactory): class ServerFactory(layer.RawLayerServerFactory):
""" """
Factory of Server RDP protocol @summary: Factory of Server RDP protocol
""" """
def __init__(self, privateKeyFileName, certificateFileName, colorDepth): def __init__(self, privateKeyFileName, certificateFileName, colorDepth):
""" """
@@ -476,7 +528,7 @@ class ServerFactory(layer.RawLayerServerFactory):
def buildRawLayer(self, addr): def buildRawLayer(self, addr):
""" """
Function call from twisted and build rdp protocol stack @summary: Function call from twisted and build rdp protocol stack
@param addr: destination address @param addr: destination address
""" """
controller = RDPServerController(self._privateKeyFileName, self._certificateFileName, self._colorDepth) controller = RDPServerController(self._privateKeyFileName, self._certificateFileName, self._colorDepth)
@@ -485,7 +537,7 @@ class ServerFactory(layer.RawLayerServerFactory):
def buildObserver(self, controller, addr): def buildObserver(self, controller, addr):
""" """
Build observer use for connection @summary: Build observer use for connection
@param controller: RDP stack controller @param controller: RDP stack controller
@param addr: destination address @param addr: destination address
""" """
@@ -493,7 +545,7 @@ class ServerFactory(layer.RawLayerServerFactory):
class RDPClientObserver(object): class RDPClientObserver(object):
""" """
Class use to inform all RDP event handle by RDPY @summary: Class use to inform all RDP event handle by RDPY
""" """
def __init__(self, controller): def __init__(self, controller):
""" """
@@ -504,19 +556,19 @@ class RDPClientObserver(object):
def onReady(self): def onReady(self):
""" """
Stack is ready and connected @summary: Stack is ready and connected
""" """
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPClientObserver")) raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPClientObserver"))
def onClose(self): def onClose(self):
""" """
Stack is closes @summary: Stack is closes
""" """
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver")) raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver"))
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
""" """
Notify bitmap update @summary: Notify bitmap update
@param destLeft: xmin position @param destLeft: xmin position
@param destTop: ymin position @param destTop: ymin position
@param destRight: xmax position because RDP can send bitmap with padding @param destRight: xmax position because RDP can send bitmap with padding
@@ -531,7 +583,7 @@ class RDPClientObserver(object):
class RDPServerObserver(object): class RDPServerObserver(object):
""" """
Class use to inform all RDP event handle by RDPY @summary: Class use to inform all RDP event handle by RDPY
""" """
def __init__(self, controller): def __init__(self, controller):
""" """
@@ -542,20 +594,20 @@ class RDPServerObserver(object):
def onReady(self): def onReady(self):
""" """
Stack is ready and connected @summary: Stack is ready and connected
May be called after an setColorDepth too May be called after an setColorDepth too
""" """
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPServerObserver")) raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPServerObserver"))
def onClose(self): def onClose(self):
""" """
Stack is closes @summary: Stack is closes
""" """
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver")) raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver"))
def onKeyEventScancode(self, code, isPressed): def onKeyEventScancode(self, code, isPressed):
""" """
Event call when a keyboard event is catch in scan code format @summary: Event call when a keyboard event is catch in scan code format
@param code: scan code of key @param code: scan code of key
@param isPressed: True if key is down @param isPressed: True if key is down
""" """
@@ -563,7 +615,7 @@ class RDPServerObserver(object):
def onKeyEventUnicode(self, code, isPressed): def onKeyEventUnicode(self, code, isPressed):
""" """
Event call when a keyboard event is catch in unicode format @summary: Event call when a keyboard event is catch in unicode format
@param code: unicode of key @param code: unicode of key
@param isPressed: True if key is down @param isPressed: True if key is down
""" """
@@ -571,7 +623,7 @@ class RDPServerObserver(object):
def onPointerEvent(self, x, y, button, isPressed): def onPointerEvent(self, x, y, button, isPressed):
""" """
Event call on mouse event @summary: Event call on mouse event
@param x: x position @param x: x position
@param y: y position @param y: y position
@param button: 1, 2 or 3 button @param button: 1, 2 or 3 button

96
rdpy/protocol/rdp/sec.py Normal file
View File

@@ -0,0 +1,96 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Some use full methods for security in RDP
"""
import sha, md5
from rdpy.network.type import Stream, UInt32Le
def saltedHash(inputData, salt, salt1, salt2):
"""
@summary: Generate particular signature from combination of sha1 and md5
@see: http://msdn.microsoft.com/en-us/library/cc241992.aspx
@param inputData: strange input (see doc)
@param salt: salt for context call
@param salt1: another salt (ex : client random)
@param salt2: another another salt (ex: server random)
@return : MD5(Salt + SHA1(Input + Salt + Salt1 + Salt2))
"""
sha1Digest = sha.new()
md5Digest = md5.new()
sha1Digest.update(inputData)
sha1Digest.update(salt[:48])
sha1Digest.update(salt1)
sha1Digest.update(salt2)
sha1Sig = sha1Digest.digest()
md5Digest.update(salt[:48])
md5Digest.update(sha1Sig)
return md5Digest.digest()
def finalHash(key, random1, random2):
"""
@summary: MD5(in0[:16] + in1[:32] + in2[:32])
@param key: in 16
@param random1: in 32
@param random2: in 32
@return MD5(in0[:16] + in1[:32] + in2[:32])
"""
md5Digest = md5.new()
md5Digest.update(key)
md5Digest.update(random1)
md5Digest.update(random2)
return md5Digest.digest()
def generateMicrosoftKey(secret, random1, random2):
"""
@summary: Generate master secret
@param secret: secret
@param clientRandom : client random
@param serverRandom : server random
"""
return saltedHash("A", secret, random1, random2) + saltedHash("BB", secret, random1, random2) + saltedHash("CCC", secret, random1, random2)
def macData(macSaltKey, data):
"""
@see: http://msdn.microsoft.com/en-us/library/cc241995.aspx
"""
sha1Digest = sha.new()
md5Digest = md5.new()
#encode length
s = Stream()
s.writeType(UInt32Le(len(data)))
sha1Digest.update(macSaltKey)
sha1Digest.update("\x36" * 40)
sha1Digest.update(s.getvalue())
sha1Digest.update(data)
sha1Sig = sha1Digest.digest()
md5Digest.update(macSaltKey)
md5Digest.update("\x5c" * 48)
md5Digest.update(sha1Sig)
return md5Digest.digest()

View File

@@ -76,8 +76,6 @@ class TPKT(RawLayer, IFastPathSender):
@param fastPathListener: IFastPathListener @param fastPathListener: IFastPathListener
""" """
RawLayer.__init__(self, presentation) RawLayer.__init__(self, presentation)
#last packet version read from header
self._lastPacketVersion = UInt8()
#length may be coded on more than 1 bytes #length may be coded on more than 1 bytes
self._lastShortLength = UInt8() self._lastShortLength = UInt8()
#fast path listener #fast path listener
@@ -104,9 +102,10 @@ class TPKT(RawLayer, IFastPathSender):
@param data: Stream received from twisted layer @param data: Stream received from twisted layer
""" """
#first read packet version #first read packet version
data.readType(self._lastPacketVersion) version = UInt8()
data.readType(version)
#classic packet #classic packet
if self._lastPacketVersion.value == Action.FASTPATH_ACTION_X224: if version.value == Action.FASTPATH_ACTION_X224:
#padding #padding
data.readType(UInt8()) data.readType(UInt8())
#read end header #read end header

View File

@@ -303,7 +303,7 @@ class ServerTLSContext(ssl.DefaultOpenSSLContextFactory):
def __init__(self, privateKeyFileName, certificateFileName): def __init__(self, privateKeyFileName, certificateFileName):
class TPDUSSLContext(SSL.Context): class TPDUSSLContext(SSL.Context):
def __init__(self, method): def __init__(self, method):
SSL.Context.__init__(method) SSL.Context.__init__(self, method)
self.set_options(SSL.OP_DONT_INSERT_EMPTY_FRAGMENTS) self.set_options(SSL.OP_DONT_INSERT_EMPTY_FRAGMENTS)
self.set_options(SSL.OP_TLS_BLOCK_PADDING_BUG) self.set_options(SSL.OP_TLS_BLOCK_PADDING_BUG)

View File

@@ -34,12 +34,12 @@ import rle
class QAdaptor(object): class QAdaptor(object):
""" """
Adaptor model with link between protocol @summary: Adaptor model with link between protocol
And Qt widget And Qt widget
""" """
def sendMouseEvent(self, e, isPressed): def sendMouseEvent(self, e, isPressed):
""" """
Interface to send mouse event to protocol stack @summary: Interface to send mouse event to protocol stack
@param e: QMouseEvent @param e: QMouseEvent
@param isPressed: event come from press or release action @param isPressed: event come from press or release action
""" """
@@ -47,11 +47,18 @@ class QAdaptor(object):
def sendKeyEvent(self, e, isPressed): def sendKeyEvent(self, e, isPressed):
""" """
Interface to send key event to protocol stack @summary: Interface to send key event to protocol stack
@param e: QEvent @param e: QEvent
@param isPressed: event come from press or release action @param isPressed: event come from press or release action
""" """
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendKeyEvent", "QAdaptor")) raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendKeyEvent", "QAdaptor"))
def sendWheelEvent(self, e):
"""
@summary: Interface to send wheel event to protocol stack
@param e: QWheelEvent
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendWheelEvent", "QAdaptor"))
def getWidget(self): def getWidget(self):
""" """
@@ -61,7 +68,7 @@ class QAdaptor(object):
def closeEvent(self, e): def closeEvent(self, e):
""" """
Call when you want to close connection @summary: Call when you want to close connection
@param: QCloseEvent @param: QCloseEvent
""" """
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "closeEvent", "QAdaptor")) raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "closeEvent", "QAdaptor"))
@@ -77,7 +84,7 @@ def qtImageFormatFromRFBPixelFormat(pixelFormat):
class RFBClientQt(RFBClientObserver, QAdaptor): class RFBClientQt(RFBClientObserver, QAdaptor):
""" """
QAdaptor for specific RFB protocol stack @summary: QAdaptor for specific RFB protocol stack
is to an RFB observer is to an RFB observer
""" """
def __init__(self, controller): def __init__(self, controller):
@@ -97,7 +104,7 @@ class RFBClientQt(RFBClientObserver, QAdaptor):
def onUpdate(self, width, height, x, y, pixelFormat, encoding, data): def onUpdate(self, width, height, x, y, pixelFormat, encoding, data):
""" """
Implement RFBClientObserver interface @summary: Implement RFBClientObserver interface
@param width: width of new image @param width: width of new image
@param height: height of new image @param height: height of new image
@param x: x position of new image @param x: x position of new image
@@ -119,13 +126,11 @@ class RFBClientQt(RFBClientObserver, QAdaptor):
@summary: event when server send cut text event @summary: event when server send cut text event
@param text: text received @param text: text received
""" """
pass
def onBell(self): def onBell(self):
""" """
@summary: event when server send biiip @summary: event when server send biiip
""" """
pass
def onReady(self): def onReady(self):
""" """
@@ -136,7 +141,7 @@ class RFBClientQt(RFBClientObserver, QAdaptor):
def sendMouseEvent(self, e, isPressed): def sendMouseEvent(self, e, isPressed):
""" """
Convert Qt mouse event to RFB mouse event @summary: Convert Qt mouse event to RFB mouse event
@param e: qMouseEvent @param e: qMouseEvent
@param isPressed: event come from press or release action @param isPressed: event come from press or release action
""" """
@@ -152,29 +157,37 @@ class RFBClientQt(RFBClientObserver, QAdaptor):
def sendKeyEvent(self, e, isPressed): def sendKeyEvent(self, e, isPressed):
""" """
Convert Qt key press event to RFB press event @summary: Convert Qt key press event to RFB press event
@param e: qKeyEvent @param e: qKeyEvent
@param isPressed: event come from press or release action @param isPressed: event come from press or release action
""" """
self.keyEvent(isPressed, e.nativeVirtualKey()) self.keyEvent(isPressed, e.nativeVirtualKey())
def sendWheelEvent(self, e):
"""
@summary: Convert Qt wheel event to RFB Wheel event
@param e: QKeyEvent
@param isPressed: event come from press or release action
"""
pass
def closeEvent(self, e): def closeEvent(self, e):
""" """
Call when you want to close connection @summary: Call when you want to close connection
@param: QCloseEvent @param: QCloseEvent
""" """
self._controller.close() self._controller.close()
def onClose(self): def onClose(self):
""" """
Call when stack is close @summary: Call when stack is close
""" """
#do something maybe a message #do something maybe a message
pass pass
def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data): def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data):
""" """
Bitmap transformation to Qt object @summary: Bitmap transformation to Qt object
@param width: width of bitmap @param width: width of bitmap
@param height: height of bitmap @param height: height of bitmap
@param bitsPerPixel: number of bit per pixel @param bitsPerPixel: number of bit per pixel
@@ -204,15 +217,15 @@ def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data):
if isCompress: if isCompress:
buf = bytearray(width * height * 3) buf = bytearray(width * height * 3)
rle.bitmap_decompress(buf, width, height, data, 3) rle.bitmap_decompress(buf, width, height, data, 3)
image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB24) image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB888)
else: else:
image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB24).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB888).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
elif bitsPerPixel == 32: elif bitsPerPixel == 32:
if isCompress: if isCompress:
buf = bytearray(width * height * 4) buf = bytearray(width * height * 4)
rle.bitmap_decompress(buf, width, height, data, 4) rle.bitmap_decompress(buf, width, height, data, 4)
image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB24) image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB32)
else: else:
image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB32).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB32).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
else: else:
@@ -222,7 +235,7 @@ def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data):
class RDPClientQt(RDPClientObserver, QAdaptor): class RDPClientQt(RDPClientObserver, QAdaptor):
""" """
Adaptor for RDP client @summary: Adaptor for RDP client
""" """
def __init__(self, controller, width, height): def __init__(self, controller, width, height):
""" """
@@ -243,7 +256,7 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
def sendMouseEvent(self, e, isPressed): def sendMouseEvent(self, e, isPressed):
""" """
Convert Qt mouse event to RDP mouse event @summary: Convert Qt mouse event to RDP mouse event
@param e: qMouseEvent @param e: qMouseEvent
@param isPressed: event come from press(true) or release(false) action @param isPressed: event come from press(true) or release(false) action
""" """
@@ -251,15 +264,15 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
buttonNumber = 0 buttonNumber = 0
if button == QtCore.Qt.LeftButton: if button == QtCore.Qt.LeftButton:
buttonNumber = 1 buttonNumber = 1
elif button == QtCore.Qt.MidButton:
buttonNumber = 2
elif button == QtCore.Qt.RightButton: elif button == QtCore.Qt.RightButton:
buttonNumber = 2
elif button == QtCore.Qt.MidButton:
buttonNumber = 3 buttonNumber = 3
self._controller.sendPointerEvent(e.pos().x(), e.pos().y(), buttonNumber, isPressed) self._controller.sendPointerEvent(e.pos().x(), e.pos().y(), buttonNumber, isPressed)
def sendKeyEvent(self, e, isPressed): def sendKeyEvent(self, e, isPressed):
""" """
Convert Qt key press event to RFB press event @summary: Convert Qt key press event to RDP press event
@param e: QKeyEvent @param e: QKeyEvent
@param isPressed: event come from press or release action @param isPressed: event come from press or release action
""" """
@@ -267,17 +280,25 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
if sys.platform == "linux2": if sys.platform == "linux2":
code -= 8 code -= 8
self._controller.sendKeyEventScancode(code, isPressed) self._controller.sendKeyEventScancode(code, isPressed)
def sendWheelEvent(self, e):
"""
@summary: Convert Qt wheel event to RDP Wheel event
@param e: QKeyEvent
@param isPressed: event come from press or release action
"""
self._controller.sendWheelEvent(e.pos().x(), e.pos().y(), (abs(e.delta()) / 8) / 15, e.delta() < 0, e.orientation() == QtCore.Qt.Horizontal)
def closeEvent(self, e): def closeEvent(self, e):
""" """
Convert Qt close widget event into close stack event @summary: Convert Qt close widget event into close stack event
@param e: QCloseEvent @param e: QCloseEvent
""" """
self._controller.close() self._controller.close()
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
""" """
Notify bitmap update @summary: Notify bitmap update
@param destLeft: xmin position @param destLeft: xmin position
@param destTop: ymin position @param destTop: ymin position
@param destRight: xmax position because RDP can send bitmap with padding @param destRight: xmax position because RDP can send bitmap with padding
@@ -295,14 +316,14 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
def onReady(self): def onReady(self):
""" """
Call when stack is ready @summary: Call when stack is ready
""" """
#do something maybe a loader #do something maybe a loader
pass pass
def onClose(self): def onClose(self):
""" """
Call when stack is close @summary: Call when stack is close
""" """
#do something maybe a message #do something maybe a message
pass pass
@@ -310,7 +331,7 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
class QRemoteDesktop(QtGui.QWidget): class QRemoteDesktop(QtGui.QWidget):
""" """
Qt display widget @summary: Qt display widget
""" """
def __init__(self, adaptor, width, height): def __init__(self, adaptor, width, height):
""" """
@@ -334,7 +355,7 @@ class QRemoteDesktop(QtGui.QWidget):
def notifyImage(self, x, y, qimage, width, height): def notifyImage(self, x, y, qimage, width, height):
""" """
Function call from QAdaptor @summary: Function call from QAdaptor
@param x: x position of new image @param x: x position of new image
@param y: y position of new image @param y: y position of new image
@param qimage: new QImage @param qimage: new QImage
@@ -346,7 +367,7 @@ class QRemoteDesktop(QtGui.QWidget):
def paintEvent(self, e): def paintEvent(self, e):
""" """
Call when Qt renderer engine estimate that is needed @summary: Call when Qt renderer engine estimate that is needed
@param e: QEvent @param e: QEvent
""" """
#fill buffer image #fill buffer image
@@ -362,42 +383,49 @@ class QRemoteDesktop(QtGui.QWidget):
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
""" """
Call when mouse move @summary: Call when mouse move
@param event: QMouseEvent @param event: QMouseEvent
""" """
self._adaptor.sendMouseEvent(event, False) self._adaptor.sendMouseEvent(event, False)
def mousePressEvent(self, event): def mousePressEvent(self, event):
""" """
Call when button mouse is pressed @summary: Call when button mouse is pressed
@param event: QMouseEvent @param event: QMouseEvent
""" """
self._adaptor.sendMouseEvent(event, True) self._adaptor.sendMouseEvent(event, True)
def mouseReleaseEvent(self, event): def mouseReleaseEvent(self, event):
""" """
Call when button mouse is released @summary: Call when button mouse is released
@param event: QMouseEvent @param event: QMouseEvent
""" """
self._adaptor.sendMouseEvent(event, False) self._adaptor.sendMouseEvent(event, False)
def keyPressEvent(self, event): def keyPressEvent(self, event):
""" """
Call when button key is pressed @summary: Call when button key is pressed
@param event: QKeyEvent @param event: QKeyEvent
""" """
self._adaptor.sendKeyEvent(event, True) self._adaptor.sendKeyEvent(event, True)
def keyReleaseEvent(self, event): def keyReleaseEvent(self, event):
""" """
Call when button key is released @summary: Call when button key is released
@param event: QKeyEvent @param event: QKeyEvent
""" """
self._adaptor.sendKeyEvent(event, False) self._adaptor.sendKeyEvent(event, False)
def wheelEvent(self, event):
"""
@summary: Call on wheel event
@param event: QWheelEvent
"""
self._adaptor.sendWheelEvent(event)
def closeEvent(self, event): def closeEvent(self, event):
""" """
Call when widget is closed @summary: Call when widget is closed
@param event: QCloseEvent @param event: QCloseEvent
""" """
self._adaptor.closeEvent(event) self._adaptor.closeEvent(event)

View File

@@ -4,8 +4,12 @@ import setuptools
from distutils.core import setup, Extension from distutils.core import setup, Extension
setup(name='rdpy', setup(name='rdpy',
version='1.0.1', version='1.1.1',
description='Remote Desktop Protocol in Python', description='Remote Desktop Protocol in Python',
long_description="""
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol.
RDPY is built over the event driven network engine Twisted.
""",
author='Sylvain Peyrefitte', author='Sylvain Peyrefitte',
author_email='citronneur@gmail.com', author_email='citronneur@gmail.com',
url='https://github.com/citronneur/rdpy', url='https://github.com/citronneur/rdpy',
@@ -21,15 +25,16 @@ setup(name='rdpy',
], ],
ext_modules=[Extension('rle', ['ext/rle.c'])], ext_modules=[Extension('rle', ['ext/rle.c'])],
scripts = [ scripts = [
'bin/rdpy-rdpclient', 'bin/rdpy-rdpclient.py',
'bin/rdpy-rdpproxy', 'bin/rdpy-rdpproxy.py',
'bin/rdpy-rdpscreenshot', 'bin/rdpy-rdpscreenshot.py',
'bin/rdpy-vncclient', 'bin/rdpy-vncclient.py',
'bin/rdpy-vncscreenshot' 'bin/rdpy-vncscreenshot.py'
], ],
install_requires=[ install_requires=[
'twisted', 'twisted',
'pyopenssl', 'pyopenssl',
'service_identity'
'qt4reactor', 'qt4reactor',
], ],
) )