diff --git a/.gitignore b/.gitignore
index b8f27d5..56750d3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,7 +2,8 @@
.project
.pydevproject
README.md~
-rdpy/core/tmp/*
*.so
*.os
-.sconsign.dblite
+dist/*
+build/*
+rdpy.egg-info/*
diff --git a/README.md b/README.md
index bfdf510..6717182 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,8 @@
-# RDPY [](https://travis-ci.org/citronneur/rdpy)
+# RDPY [](https://travis-ci.org/citronneur/rdpy)
Remote Desktop Protocol in twisted PYthon.
-RDPY is still under development.
-
-RDPY is a pure Python implementation ot the Microsoft RDP (Remote Desktop Protocol) protocol. RDPY is built over the event driven network engine Twisted.
+RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol. RDPY is built over the event driven network engine Twisted.
## Build
@@ -22,9 +20,10 @@ sudo apt-get install python-qt4
#### 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)
+
[PyWin32](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.7.exe/download)
-### Make
+### Build
```
$ git clone https://github.com/citronneur/rdpy.git rdpy
@@ -52,7 +51,7 @@ RDPY comes with some very useful binaries; These binaries are linux and windows
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
@@ -60,7 +59,7 @@ $ rdpy/bin/rdpy-rdpclient [-u username] [-p password] [-d domain] [...] XXX.XXX.
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
@@ -68,7 +67,7 @@ $ rdpy/bin/rdpy-vncclient [-p password] XXX.XXX.XXX.XXX[:5900]
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
@@ -76,7 +75,7 @@ $ rdpy/bin/rdpy-rdpscreenshot [-w width] [-l height] [-o output_file_path] XXX.X
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
@@ -84,7 +83,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/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:
@@ -120,9 +119,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.
-The RDP client code looks like this:
+### Simple RDP Client
-```
+```python
from rdpy.protocol.rdp import rdp
class MyRDPFactory(rdp.ClientFactory):
@@ -134,20 +133,36 @@ class MyRDPFactory(rdp.ClientFactory):
reactor.stop()
def buildObserver(self, controller, addr):
+
class MyObserver(rdp.RDPClientObserver)
- def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
- #here code handle bitmap
- pass
-
- def onReady(self):
+ def onReady(self):
+ """
+ @summary: Call when stack is ready
+ """
#send 'r' key
self._controller.sendKeyEventUnicode(ord(unicode("r".toUtf8(), encoding="UTF-8")), True)
#mouse move and click at pixel 200x200
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):
- pass
+ """
+ @summary: Call when stack is close
+ """
return MyObserver(controller)
@@ -156,11 +171,66 @@ reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRDPFactory())
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
-class MyRDPFactory(rfb.ClientFactory):
+class MyRFBFactory(rfb.ClientFactory):
def clientConnectionLost(self, connector, reason):
reactor.stop()
@@ -172,18 +242,41 @@ class MyRDPFactory(rfb.ClientFactory):
class MyObserver(rfb.RFBClientObserver)
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):
- #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):
- pass
+ """
+ @summary: Call when stack is close
+ """
return MyObserver(controller)
from twisted.internet import reactor
-reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRDPFactory())
+reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRFBFactory())
reactor.run()
```
diff --git a/bin/rdpy-rdpclient b/bin/rdpy-rdpclient.py
similarity index 64%
rename from bin/rdpy-rdpclient
rename to bin/rdpy-rdpclient.py
index 1424e81..cfbc907 100755
--- a/bin/rdpy-rdpclient
+++ b/bin/rdpy-rdpclient.py
@@ -17,14 +17,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-
"""
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.protocol.rdp import rdp
@@ -35,19 +34,25 @@ class RDPClientQtFactory(rdp.ClientFactory):
"""
@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 heigth: heigth of client
@param username: username present to the server
@param password: password present to the server
@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._height = height
self._username = username
self._passwod = password
self._domain = domain
+ self._fullscreen = fullscreen
+ self._keyboardLayout = keyboardLayout
+ self._optimized = optimized
self._w = None
def buildObserver(self, controller, addr):
@@ -63,12 +68,18 @@ class RDPClientQtFactory(rdp.ClientFactory):
#create qt widget
self._w = client.getWidget()
self._w.setWindowTitle('rdpy-rdpclient')
- self._w.show()
+ if self._fullscreen:
+ self._w.showFullScreen()
+ else:
+ self._w.show()
controller.setUsername(self._username)
controller.setPassword(self._passwod)
controller.setDomain(self._domain)
- controller.setPerformanceSession()
+ controller.setKeyboardLayout(self._keyboardLayout)
+ controller.setHostname(socket.gethostname())
+ if self._optimized:
+ controller.setPerformanceSession()
return client
@@ -95,24 +106,54 @@ class RDPClientQtFactory(rdp.ClientFactory):
reactor.stop()
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
+ import ctypes.windll.user32 as user32
+ w = user32.GetForegroundWindow()
+ tid = user32.GetWindowThreadProcessId(w, 0)
+ result = user32.GetKeyboardLayout(tid)
+ if result == 0x409409:
+ return "fr"
+ except:
+ log.info("failed to auto detect keyboard layout")
+ pass
+ return "en"
+
def help():
print "Usage: rdpy-rdpclient [options] ip[:port]"
print "\t-u: user name"
print "\t-p: password"
print "\t-d: domain"
- print "\t-w: width of screen default value is 1024"
- print "\t-l: height of screen default value is 800"
+ print "\t-w: width of screen [default : 1024]"
+ 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__':
+
#default script argument
username = ""
password = ""
domain = ""
width = 1024
height = 800
+ fullscreen = False
+ optimized = False
+ keyboardLayout = autoDetectKeyboardLayout()
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:
help()
for opt, arg in opts:
@@ -129,12 +170,18 @@ if __name__ == '__main__':
width = int(arg)
elif opt == "-l":
height = int(arg)
+ elif opt == "-f":
+ fullscreen = True
+ elif opt == "-o":
+ optimized = True
+ elif opt == "-k":
+ keyboardLayout = arg
if ':' in args[0]:
ip, port = args[0].split(':')
else:
ip, port = args[0], "3389"
-
+
#create application
app = QtGui.QApplication(sys.argv)
@@ -142,7 +189,16 @@ if __name__ == '__main__':
import qt4reactor
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
- 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()
app.exec_()
\ No newline at end of file
diff --git a/bin/rdpy-rdpproxy b/bin/rdpy-rdpproxy.py
similarity index 99%
rename from bin/rdpy-rdpproxy
rename to bin/rdpy-rdpproxy.py
index 97950e1..4bde05f 100755
--- a/bin/rdpy-rdpproxy
+++ b/bin/rdpy-rdpproxy.py
@@ -37,7 +37,7 @@ from rdpy.ui import view
from twisted.internet import reactor
from PyQt4 import QtCore, QtGui
-log._LOG_LEVEL = log.Level.INFO
+#log._LOG_LEVEL = log.Level.INFO
class ProxyServer(rdp.RDPServerObserver):
"""
diff --git a/bin/rdpy-rdpscreenshot b/bin/rdpy-rdpscreenshot.py
similarity index 100%
rename from bin/rdpy-rdpscreenshot
rename to bin/rdpy-rdpscreenshot.py
diff --git a/bin/rdpy-vncclient b/bin/rdpy-vncclient.py
similarity index 100%
rename from bin/rdpy-vncclient
rename to bin/rdpy-vncclient.py
diff --git a/bin/rdpy-vncscreenshot b/bin/rdpy-vncscreenshot.py
similarity index 100%
rename from bin/rdpy-vncscreenshot
rename to bin/rdpy-vncscreenshot.py
diff --git a/rdpy/protocol/rdp/gcc.py b/rdpy/protocol/rdp/gcc.py
index a934e71..c419cab 100644
--- a/rdpy/protocol/rdp/gcc.py
+++ b/rdpy/protocol/rdp/gcc.py
@@ -230,7 +230,7 @@ class ClientCoreData(CompositeType):
self.desktopHeight = UInt16Le(800)
self.colorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP)
self.sasSequence = UInt16Le(Sequence.RNS_UD_SAS_DEL)
- self.kbdLayout = UInt32Le(KeyboardLayout.FRENCH)
+ self.kbdLayout = UInt32Le(KeyboardLayout.US)
self.clientBuild = UInt32Le(3790)
self.clientName = String("rdpy" + "\x00"*11, readLen = UInt8(32), unicode = True)
self.keyboardType = UInt32Le(KeyboardType.IBM_101_102_KEYS)
diff --git a/rdpy/protocol/rdp/pdu/data.py b/rdpy/protocol/rdp/pdu/data.py
index 8435fc6..d88e28f 100644
--- a/rdpy/protocol/rdp/pdu/data.py
+++ b/rdpy/protocol/rdp/pdu/data.py
@@ -930,7 +930,7 @@ class OrderUpdateDataPDU(CompositeType):
self.pad2OctetsA = UInt16Le()
self.numberOrders = UInt16Le(lambda:len(self.orderData._array))
self.pad2OctetsB = UInt16Le()
- self.orderData = ArrayType(order.DrawingOrder, readLen = self.numberOrders)
+ self.orderData = ArrayType(order.PrimaryDrawingOrder, readLen = self.numberOrders)
class BitmapCompressedDataHeader(CompositeType):
"""
diff --git a/rdpy/protocol/rdp/pdu/layer.py b/rdpy/protocol/rdp/pdu/layer.py
index bcafdbc..7aa0db1 100644
--- a/rdpy/protocol/rdp/pdu/layer.py
+++ b/rdpy/protocol/rdp/pdu/layer.py
@@ -136,6 +136,7 @@ class Client(PDULayer, tpkt.IFastPathListener):
self._listener = listener
#enable or not fast path
self._fastPathSender = None
+ self._licenceManager = lic.LicenseManager(self)
def connect(self):
"""
@@ -177,18 +178,9 @@ class Client(PDULayer, tpkt.IFastPathListener):
if not (securityFlag.value & data.SecurityFlag.SEC_LICENSE_PKT):
raise InvalidExpectedDataException("Waiting license packet")
- validClientPdu = lic.LicPacket()
- 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:
+ if self._licenceManager.recv(s):
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):
"""
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))
+ 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):
"""
Send all client capabilities
@@ -592,8 +591,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
def sendDemandActivePDU(self):
"""
- Send server capabilities
- server automata PDU
+ @summary: Send server capabilities server automata PDU
"""
#init general capability
generalCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability
diff --git a/rdpy/protocol/rdp/pdu/lic.py b/rdpy/protocol/rdp/pdu/lic.py
index adf6094..557c1d9 100644
--- a/rdpy/protocol/rdp/pdu/lic.py
+++ b/rdpy/protocol/rdp/pdu/lic.py
@@ -18,17 +18,20 @@
#
"""
-RDP extended license
+@summary: RDP extended license
@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
import rdpy.base.log as log
+import rdpy.protocol.rdp.sec as sec
+import rdpy.protocol.rdp.rc4 as rc4
class MessageType(object):
"""
- License packet message type
+ @summary: License packet message type
"""
LICENSE_REQUEST = 0x01
PLATFORM_CHALLENGE = 0x02
@@ -42,7 +45,7 @@ class MessageType(object):
class ErrorCode(object):
"""
- License error message code
+ @summary: License error message code
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
"""
ERR_INVALID_SERVER_CERTIFICATE = 0x00000001
@@ -57,7 +60,7 @@ class ErrorCode(object):
class StateTransition(object):
"""
- Automata state transition
+ @summary: Automata state transition
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
"""
ST_TOTAL_ABORT = 0x00000001
@@ -67,9 +70,10 @@ class StateTransition(object):
class BinaryBlobType(object):
"""
- Binary blob data type
+ @summary: Binary blob data type
@see: http://msdn.microsoft.com/en-us/library/cc240481.aspx
"""
+ BB_ANY_BLOB = 0x0000
BB_DATA_BLOB = 0x0001
BB_RANDOM_BLOB = 0x0002
BB_CERTIFICATE_BLOB = 0x0003
@@ -82,25 +86,26 @@ class BinaryBlobType(object):
class Preambule(object):
"""
- Preambule version
+ @summary: Preambule version
"""
PREAMBLE_VERSION_2_0 = 0x2
PREAMBLE_VERSION_3_0 = 0x3
+ EXTENDED_ERROR_MSG_SUPPORTED = 0x80
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
"""
- def __init__(self, blobType = 0):
+ def __init__(self, blobType = BinaryBlobType.BB_ANY_BLOB):
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.blobData = String(readLen = self.wBlobLen)
class LicensingErrorMessage(CompositeType):
"""
- License error message
+ @summary: License error message
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
"""
_MESSAGE_TYPE_ = MessageType.ERROR_ALERT
@@ -113,7 +118,7 @@ class LicensingErrorMessage(CompositeType):
class ProductInformation(CompositeType):
"""
- License server product information
+ @summary: License server product information
@see: http://msdn.microsoft.com/en-us/library/cc241915.aspx
"""
def __init__(self):
@@ -121,15 +126,15 @@ class ProductInformation(CompositeType):
self.dwVersion = UInt32Le()
self.cbCompanyName = UInt32Le(lambda:sizeof(self.pbCompanyName))
#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))
#may contain "A02" from microsoft license server
- self.pbProductId = String(readLen = self.cbProductId)
+ self.pbProductId = String(readLen = self.cbProductId, unicode = True)
class Scope(CompositeType):
"""
- Use in license nego
+ @summary: Use in license nego
@see: http://msdn.microsoft.com/en-us/library/cc241917.aspx
"""
def __init__(self):
@@ -138,7 +143,7 @@ class Scope(CompositeType):
class ScopeList(CompositeType):
"""
- Use in license nego
+ @summary: Use in license nego
@see: http://msdn.microsoft.com/en-us/library/cc241916.aspx
"""
def __init__(self):
@@ -148,8 +153,8 @@ class ScopeList(CompositeType):
class ServerLicenseRequest(CompositeType):
"""
- Send by server to signal license request
- server -> client
+ @summary: Send by server to signal license request
+ server -> client
@see: http://msdn.microsoft.com/en-us/library/cc241914.aspx
"""
_MESSAGE_TYPE_ = MessageType.LICENSE_REQUEST
@@ -164,8 +169,8 @@ class ServerLicenseRequest(CompositeType):
class ClientNewLicenseRequest(CompositeType):
"""
- Send by client to ask new license for client.
- RDPY doesn'support license reuse, need it in futur version
+ @summary: Send by client to ask new license for client.
+ RDPY doesn'support license reuse, need it in futur version
@see: http://msdn.microsoft.com/en-us/library/cc241918.aspx
"""
_MESSAGE_TYPE_ = MessageType.NEW_LICENSE_REQUEST
@@ -181,10 +186,36 @@ class ClientNewLicenseRequest(CompositeType):
self.encryptedPreMasterSecret = LicenseBinaryBlob(BinaryBlobType.BB_RANDOM_BLOB)
self.ClientUserName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_USER_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):
"""
- A license packet
+ @summary: A license packet
"""
def __init__(self, message = None):
CompositeType.__init__(self)
@@ -195,10 +226,10 @@ class LicPacket(CompositeType):
def LicensingMessageFactory():
"""
- factory for message nego
+ @summary: factory for message nego
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_:
return c()
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")
self.licensingMessage = message
-
+
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
- server automata message
+ @summary: handle license automata
+ @see: http://msdn.microsoft.com/en-us/library/cc241890.aspx
"""
- message = LicensingErrorMessage()
- message.dwErrorCode.value = ErrorCode.STATUS_VALID_CLIENT
- message.dwStateTransition.value = StateTransition.ST_NO_TRANSITION
- return LicPacket(message = message)
-
-def createNewLicenseRequest(serverLicenseRequest):
- """
- 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
- @todo: need RDP license server
- """
- return LicPacket(message = ClientNewLicenseRequest())
\ No newline at end of file
+ def __init__(self, transport):
+ """
+ @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._username = ""
+ self._hostname = ""
+
+ 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))
\ No newline at end of file
diff --git a/rdpy/protocol/rdp/rc4.py b/rdpy/protocol/rdp/rc4.py
new file mode 100644
index 0000000..476bfa3
--- /dev/null
+++ b/rdpy/protocol/rdp/rc4.py
@@ -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])
\ No newline at end of file
diff --git a/rdpy/protocol/rdp/rdp.py b/rdpy/protocol/rdp/rdp.py
index 657e68e..f8c09d0 100644
--- a/rdpy/protocol/rdp/rdp.py
+++ b/rdpy/protocol/rdp/rdp.py
@@ -68,13 +68,13 @@ class RDPClientController(pdu.layer.PDUClientListener):
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
def setScreen(self, width, height):
"""
- Set screen dim of session
+ @summary: Set screen dim of session
@param width: width in pixel of screen
@param height: height in pixel of screen
"""
@@ -84,15 +84,16 @@ class RDPClientController(pdu.layer.PDUClientListener):
def setUsername(self, username):
"""
- Set the username for session
+ @summary: Set the username for session
@param username: username of session
"""
#username in PDU info packet
self._pduLayer._info.userName.value = username
+ self._pduLayer._licenceManager._username = username
def setPassword(self, password):
"""
- Set password for session
+ @summary: Set password for session
@param password: password of session
"""
self.setAutologon()
@@ -100,7 +101,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def setDomain(self, domain):
"""
- Set the windows domain of session
+ @summary: Set the windows domain of session
@param domain: domain of session
"""
self._pduLayer._info.domain.value = domain
@@ -111,16 +112,33 @@ class RDPClientController(pdu.layer.PDUClientListener):
"""
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):
"""
- Add observer to RDP protocol
+ @summary: Add observer to RDP protocol
@param observer: new observer to add
"""
self._clientObserver.append(observer)
def removeClientObserver(self, observer):
"""
- Remove observer to RDP protocol stack
+ @summary: Remove observer to RDP protocol stack
@param observer: observer to remove
"""
for i in range(0, len(self._clientObserver)):
@@ -130,7 +148,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
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
"""
for observer in self._clientObserver:
@@ -140,7 +158,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def onReady(self):
"""
- Call when PDU layer is connected
+ @summary: Call when PDU layer is connected
"""
self._isReady = True
#signal all listener
@@ -149,7 +167,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def onClose(self):
"""
- Event call when RDP stack is closed
+ @summary: Event call when RDP stack is closed
"""
self._isReady = False
for observer in self._clientObserver:
@@ -157,7 +175,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def sendPointerEvent(self, x, y, button, isPressed):
"""
- send pointer events
+ @summary: send pointer events
@param x: x position of pointer
@param y: y position of pointer
@param button: 1 or 2 or 3
@@ -189,10 +207,44 @@ class RDPClientController(pdu.layer.PDUClientListener):
except InvalidValue:
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):
"""
- Send a scan code to RDP stack
+ @summary: Send a scan code to RDP stack
@param code: scan code
@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):
"""
- Send a scan code to RDP stack
+ @summary: Send a scan code to RDP stack
@param code: unicode
@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):
"""
- Force server to resend a particular zone
+ @summary: Force server to resend a particular zone
@param left: left coordinate
@param top: top coordinate
@param right: right coordinate
@@ -253,13 +305,13 @@ class RDPClientController(pdu.layer.PDUClientListener):
def close(self):
"""
- Close protocol stack
+ @summary: Close protocol stack
"""
self._pduLayer.close()
class RDPServerController(pdu.layer.PDUServerListener):
"""
- Controller use in server side mode
+ @summary: Controller use in server side mode
"""
def __init__(self, privateKeyFileName, certificateFileName, colorDepth):
"""
@@ -283,7 +335,7 @@ class RDPServerController(pdu.layer.PDUServerListener):
def close(self):
"""
- Close protocol stack
+ @summary: Close protocol stack
"""
self._pduLayer.close()
@@ -296,28 +348,28 @@ class RDPServerController(pdu.layer.PDUServerListener):
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 self._pduLayer._info.userName.value
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 self._pduLayer._info.password.value
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 self._pduLayer._info.domain.value
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 (self.getDomain(), self.getUsername(), self.getPassword())
@@ -337,15 +389,15 @@ class RDPServerController(pdu.layer.PDUServerListener):
def addServerObserver(self, observer):
"""
- Add observer to RDP protocol
+ @summary: Add observer to RDP protocol
@param observer: new observer to add
"""
self._serverObserver.append(observer)
def setColorDepth(self, colorDepth):
"""
- Set color depth of session
- if PDU stack is already connected send a deactive-reactive sequence
+ @summary: Set color depth of session
+ if PDU stack is already connected send a deactive-reactive sequence
@param colorDepth: depth of session (15, 16, 24)
"""
self._colorDepth = colorDepth
@@ -357,13 +409,13 @@ class RDPServerController(pdu.layer.PDUServerListener):
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
def onReady(self):
"""
- RDP stack is now ready
+ @summary: RDP stack is now ready
"""
self._isReady = True
for observer in self._serverObserver:
@@ -371,7 +423,7 @@ class RDPServerController(pdu.layer.PDUServerListener):
def onClose(self):
"""
- Event call when RDP stack is closed
+ @summary: Event call when RDP stack is closed
"""
self._isReady = False
for observer in self._serverObserver:
@@ -379,7 +431,7 @@ class RDPServerController(pdu.layer.PDUServerListener):
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]
"""
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):
"""
- send bitmap update
+ @summary: send bitmap update
@param destLeft: xmin position
@param destTop: ymin position
@param destRight: xmax position because RDP can send bitmap with padding
@@ -454,7 +506,7 @@ class ClientFactory(layer.RawLayerClientFactory):
class ServerFactory(layer.RawLayerServerFactory):
"""
- Factory of Server RDP protocol
+ @summary: Factory of Server RDP protocol
"""
def __init__(self, privateKeyFileName, certificateFileName, colorDepth):
"""
@@ -476,7 +528,7 @@ class ServerFactory(layer.RawLayerServerFactory):
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
"""
controller = RDPServerController(self._privateKeyFileName, self._certificateFileName, self._colorDepth)
@@ -485,7 +537,7 @@ class ServerFactory(layer.RawLayerServerFactory):
def buildObserver(self, controller, addr):
"""
- Build observer use for connection
+ @summary: Build observer use for connection
@param controller: RDP stack controller
@param addr: destination address
"""
@@ -493,7 +545,7 @@ class ServerFactory(layer.RawLayerServerFactory):
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):
"""
@@ -504,19 +556,19 @@ class RDPClientObserver(object):
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"))
def onClose(self):
"""
- Stack is closes
+ @summary: Stack is closes
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver"))
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
- Notify bitmap update
+ @summary: Notify bitmap update
@param destLeft: xmin position
@param destTop: ymin position
@param destRight: xmax position because RDP can send bitmap with padding
@@ -531,7 +583,7 @@ class RDPClientObserver(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):
"""
@@ -542,20 +594,20 @@ class RDPServerObserver(object):
def onReady(self):
"""
- Stack is ready and connected
+ @summary: Stack is ready and connected
May be called after an setColorDepth too
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPServerObserver"))
def onClose(self):
"""
- Stack is closes
+ @summary: Stack is closes
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver"))
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 isPressed: True if key is down
"""
@@ -563,7 +615,7 @@ class RDPServerObserver(object):
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 isPressed: True if key is down
"""
@@ -571,7 +623,7 @@ class RDPServerObserver(object):
def onPointerEvent(self, x, y, button, isPressed):
"""
- Event call on mouse event
+ @summary: Event call on mouse event
@param x: x position
@param y: y position
@param button: 1, 2 or 3 button
diff --git a/rdpy/protocol/rdp/sec.py b/rdpy/protocol/rdp/sec.py
new file mode 100644
index 0000000..d5b94e5
--- /dev/null
+++ b/rdpy/protocol/rdp/sec.py
@@ -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 .
+#
+
+"""
+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()
\ No newline at end of file
diff --git a/rdpy/protocol/rdp/tpkt.py b/rdpy/protocol/rdp/tpkt.py
index fb64096..fda5791 100644
--- a/rdpy/protocol/rdp/tpkt.py
+++ b/rdpy/protocol/rdp/tpkt.py
@@ -76,8 +76,6 @@ class TPKT(RawLayer, IFastPathSender):
@param fastPathListener: IFastPathListener
"""
RawLayer.__init__(self, presentation)
- #last packet version read from header
- self._lastPacketVersion = UInt8()
#length may be coded on more than 1 bytes
self._lastShortLength = UInt8()
#fast path listener
@@ -104,9 +102,10 @@ class TPKT(RawLayer, IFastPathSender):
@param data: Stream received from twisted layer
"""
#first read packet version
- data.readType(self._lastPacketVersion)
+ version = UInt8()
+ data.readType(version)
#classic packet
- if self._lastPacketVersion.value == Action.FASTPATH_ACTION_X224:
+ if version.value == Action.FASTPATH_ACTION_X224:
#padding
data.readType(UInt8())
#read end header
diff --git a/rdpy/ui/qt4.py b/rdpy/ui/qt4.py
index 041e9e2..735fc34 100644
--- a/rdpy/ui/qt4.py
+++ b/rdpy/ui/qt4.py
@@ -34,12 +34,12 @@ import rle
class QAdaptor(object):
"""
- Adaptor model with link between protocol
- And Qt widget
+ @summary: Adaptor model with link between protocol
+ And Qt widget
"""
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 isPressed: event come from press or release action
"""
@@ -47,11 +47,18 @@ class QAdaptor(object):
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 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):
"""
@@ -61,7 +68,7 @@ class QAdaptor(object):
def closeEvent(self, e):
"""
- Call when you want to close connection
+ @summary: Call when you want to close connection
@param: QCloseEvent
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "closeEvent", "QAdaptor"))
@@ -77,7 +84,7 @@ def qtImageFormatFromRFBPixelFormat(pixelFormat):
class RFBClientQt(RFBClientObserver, QAdaptor):
"""
- QAdaptor for specific RFB protocol stack
+ @summary: QAdaptor for specific RFB protocol stack
is to an RFB observer
"""
def __init__(self, controller):
@@ -97,7 +104,7 @@ class RFBClientQt(RFBClientObserver, QAdaptor):
def onUpdate(self, width, height, x, y, pixelFormat, encoding, data):
"""
- Implement RFBClientObserver interface
+ @summary: Implement RFBClientObserver interface
@param width: width of new image
@param height: height 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
@param text: text received
"""
- pass
def onBell(self):
"""
@summary: event when server send biiip
"""
- pass
def onReady(self):
"""
@@ -136,7 +141,7 @@ class RFBClientQt(RFBClientObserver, QAdaptor):
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 isPressed: event come from press or release action
"""
@@ -152,29 +157,37 @@ class RFBClientQt(RFBClientObserver, QAdaptor):
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 isPressed: event come from press or release action
"""
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):
"""
- Call when you want to close connection
+ @summary: Call when you want to close connection
@param: QCloseEvent
"""
self._controller.close()
def onClose(self):
"""
- Call when stack is close
+ @summary: Call when stack is close
"""
#do something maybe a message
pass
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 height: height of bitmap
@param bitsPerPixel: number of bit per pixel
@@ -204,15 +217,15 @@ def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data):
if isCompress:
buf = bytearray(width * height * 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:
- 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:
if isCompress:
buf = bytearray(width * height * 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:
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:
@@ -222,7 +235,7 @@ def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data):
class RDPClientQt(RDPClientObserver, QAdaptor):
"""
- Adaptor for RDP client
+ @summary: Adaptor for RDP client
"""
def __init__(self, controller, width, height):
"""
@@ -243,7 +256,7 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
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 isPressed: event come from press(true) or release(false) action
"""
@@ -251,15 +264,15 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
buttonNumber = 0
if button == QtCore.Qt.LeftButton:
buttonNumber = 1
- elif button == QtCore.Qt.MidButton:
- buttonNumber = 2
elif button == QtCore.Qt.RightButton:
+ buttonNumber = 2
+ elif button == QtCore.Qt.MidButton:
buttonNumber = 3
self._controller.sendPointerEvent(e.pos().x(), e.pos().y(), buttonNumber, 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 isPressed: event come from press or release action
"""
@@ -267,17 +280,25 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
if sys.platform == "linux2":
code -= 8
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):
"""
- Convert Qt close widget event into close stack event
+ @summary: Convert Qt close widget event into close stack event
@param e: QCloseEvent
"""
self._controller.close()
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
- Notify bitmap update
+ @summary: Notify bitmap update
@param destLeft: xmin position
@param destTop: ymin position
@param destRight: xmax position because RDP can send bitmap with padding
@@ -295,14 +316,14 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
def onReady(self):
"""
- Call when stack is ready
+ @summary: Call when stack is ready
"""
#do something maybe a loader
pass
def onClose(self):
"""
- Call when stack is close
+ @summary: Call when stack is close
"""
#do something maybe a message
pass
@@ -310,7 +331,7 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
class QRemoteDesktop(QtGui.QWidget):
"""
- Qt display widget
+ @summary: Qt display widget
"""
def __init__(self, adaptor, width, height):
"""
@@ -334,7 +355,7 @@ class QRemoteDesktop(QtGui.QWidget):
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 y: y position of new image
@param qimage: new QImage
@@ -346,7 +367,7 @@ class QRemoteDesktop(QtGui.QWidget):
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
"""
#fill buffer image
@@ -362,42 +383,49 @@ class QRemoteDesktop(QtGui.QWidget):
def mouseMoveEvent(self, event):
"""
- Call when mouse move
+ @summary: Call when mouse move
@param event: QMouseEvent
"""
self._adaptor.sendMouseEvent(event, False)
def mousePressEvent(self, event):
"""
- Call when button mouse is pressed
+ @summary: Call when button mouse is pressed
@param event: QMouseEvent
"""
self._adaptor.sendMouseEvent(event, True)
def mouseReleaseEvent(self, event):
"""
- Call when button mouse is released
+ @summary: Call when button mouse is released
@param event: QMouseEvent
"""
self._adaptor.sendMouseEvent(event, False)
def keyPressEvent(self, event):
"""
- Call when button key is pressed
+ @summary: Call when button key is pressed
@param event: QKeyEvent
"""
self._adaptor.sendKeyEvent(event, True)
def keyReleaseEvent(self, event):
"""
- Call when button key is released
+ @summary: Call when button key is released
@param event: QKeyEvent
"""
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):
"""
- Call when widget is closed
+ @summary: Call when widget is closed
@param event: QCloseEvent
"""
self._adaptor.closeEvent(event)
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 127c4c9..a818be0 100644
--- a/setup.py
+++ b/setup.py
@@ -4,8 +4,12 @@ import setuptools
from distutils.core import setup, Extension
setup(name='rdpy',
- version='1.0.1',
+ version='1.1.0',
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_email='citronneur@gmail.com',
url='https://github.com/citronneur/rdpy',
@@ -21,11 +25,11 @@ setup(name='rdpy',
],
ext_modules=[Extension('rle', ['ext/rle.c'])],
scripts = [
- 'bin/rdpy-rdpclient',
- 'bin/rdpy-rdpproxy',
- 'bin/rdpy-rdpscreenshot',
- 'bin/rdpy-vncclient',
- 'bin/rdpy-vncscreenshot'
+ 'bin/rdpy-rdpclient.py',
+ 'bin/rdpy-rdpproxy.py',
+ 'bin/rdpy-rdpscreenshot.py',
+ 'bin/rdpy-vncclient.py',
+ 'bin/rdpy-vncscreenshot.py'
],
install_requires=[
'twisted',