Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
894dc44a52 | ||
|
|
47dba5bb6e | ||
|
|
c4d071a2e0 | ||
|
|
44d99eaca4 | ||
|
|
9419bea9b0 | ||
|
|
bd91093617 | ||
|
|
15fadfe3dd | ||
|
|
fa25a40721 | ||
|
|
99198321a4 | ||
|
|
3c3d7423a5 | ||
|
|
5af9f0708a | ||
|
|
4e7ea06906 | ||
|
|
7fc25e35f1 | ||
|
|
078aaa13f6 | ||
|
|
7e98bc373a | ||
|
|
0d9b766a03 | ||
|
|
c755148a3c |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -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
148
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.
|
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()
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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_()
|
||||||
@@ -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):
|
||||||
"""
|
"""
|
||||||
@@ -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_()
|
||||||
@@ -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_()
|
||||||
@@ -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))
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
56
rdpy/protocol/rdp/rc4.py
Normal 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])
|
||||||
@@ -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
96
rdpy/protocol/rdp/sec.py
Normal 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()
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
100
rdpy/ui/qt4.py
100
rdpy/ui/qt4.py
@@ -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)
|
||||||
17
setup.py
17
setup.py
@@ -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',
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user