20 Commits

Author SHA1 Message Date
speyrefitte
1e2cf2ef88 add missing links 2015-06-09 18:11:36 +02:00
speyrefitte
040ac6185d Merge branch 'dev' of https://github.com/citronneur/rdpy into dev 2015-06-01 18:16:39 +02:00
speyrefitte
0f30de7d20 synthax 2015-06-01 18:15:57 +02:00
speyrefitte
a23ae25a1f fix issue on unhandle upadte 2015-05-21 10:29:32 +02:00
speyrefitte
763ed2e3ee fix major bug on update handle 2015-05-20 17:51:43 +02:00
speyrefitte
11d66a4818 add onSessionReady event -> user session is ready 2015-05-19 17:53:15 +02:00
speyrefitte
9b99365f80 fix bug on lwin key activation 2015-05-19 16:42:22 +02:00
speyrefitte
bd7c708bf3 update version 2015-05-04 11:49:37 +02:00
speyrefitte
1deb2d69ea bug fixing 2015-05-04 11:47:25 +02:00
speyrefitte
0a5a1fd12c add keylogger on rss player and file format 2015-04-29 12:13:24 +02:00
speyrefitte
c97b451ce3 Merge branch 'hotfix' of https://github.com/citronneur/rdpy into dev 2015-04-29 09:32:50 +02:00
speyrefitte
80f989a804 Merge branch 'dev' of https://github.com/citronneur/rdpy into dev 2015-04-29 09:32:44 +02:00
speyrefitte
c6e100f9a6 Merge branch 'master' of https://github.com/citronneur/rdpy into dev 2015-04-29 09:31:05 +02:00
Sylvain Peyrefitte
15df00ec20 Merge pull request #24 from citronneur/master
Update readme with pypi version
2015-03-25 14:48:18 +01:00
Sylvain Peyrefitte
342349cf41 Merge pull request #23 from citronneur/master
Just add pypi package
2015-03-25 14:47:14 +01:00
Sylvain Peyrefitte
d6043106e3 Update README.md 2015-03-25 12:15:57 +01:00
speyrefitte
bd7e73a6e7 change log format 2015-03-20 17:54:48 +01:00
Sylvain Peyrefitte
fc1685e652 Merge pull request #21 from ojosdegris/patch-1
Added OS X install example
2015-03-18 10:47:01 +01:00
vittore
4320824aae Fixed example to follow existing style. 2015-03-13 12:11:41 -04:00
vittore
5a438174b9 Added OS X install example 2015-03-13 12:07:22 -04:00
18 changed files with 344 additions and 112 deletions

View File

@@ -1,4 +1,4 @@
# RDPY [![Build Status](https://travis-ci.org/citronneur/rdpy.svg?branch=dev)](https://travis-ci.org/citronneur/rdpy)
# RDPY [![Build Status](https://travis-ci.org/citronneur/rdpy.svg?branch=dev)](https://travis-ci.org/citronneur/rdpy) [![PyPI version](https://badge.fury.io/py/rdpy.png)](http://badge.fury.io/py/rdpy)
Remote Desktop Protocol in twisted python.
@@ -33,6 +33,12 @@ Example for Debian based systems :
sudo apt-get install python-qt4
```
#### OS X
Example for OS X to install PyQt with homebrew
```
$ brew install qt sip pyqt
```
#### Windows
x86 | x86_64
@@ -176,6 +182,11 @@ class MyRDPFactory(rdp.ClientFactory):
@param data: bitmap data
"""
def onSessionReady(self):
"""
@summary: Windows session is ready
"""
def onClose(self):
"""
@summary: Call when stack is close

View File

@@ -115,7 +115,11 @@ class RDPClientQtFactory(rdp.ClientFactory):
self._nego = security == "nego"
self._recodedPath = recodedPath
if self._nego:
#compute start nego nla need credentials
if username != "" and password != "":
self._security = rdp.SecurityLevel.RDP_LEVEL_NLA
else:
self._security = rdp.SecurityLevel.RDP_LEVEL_SSL
else:
self._security = security
self._w = None
@@ -168,7 +172,7 @@ class RDPClientQtFactory(rdp.ClientFactory):
connector.connect()
return
QtGui.QMessageBox.warning(self._w, "Warning", "Lost connection : %s"%reason)
log.info("Lost connection : %s"%reason)
reactor.stop()
app.exit()
@@ -178,7 +182,7 @@ class RDPClientQtFactory(rdp.ClientFactory):
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
@param reason: str use to advertise reason of lost connection
"""
QtGui.QMessageBox.warning(self._w, "Warning", "Connection failed : %s"%reason)
log.info("Connection failed : %s"%reason)
reactor.stop()
app.exit()
@@ -190,7 +194,7 @@ def autoDetectKeyboardLayout():
if os.name == 'posix':
from subprocess import check_output
result = check_output(["setxkbmap", "-print"])
if "azerty" in result:
if 'azerty' in result:
return "fr"
elif os.name == 'nt':
import win32api, win32con, win32process

View File

@@ -70,7 +70,7 @@ class HoneyPotServer(rdp.RDPServerObserver):
def onClose(self):
""" HoneyPot """
def onKeyEventScancode(self, code, isPressed):
def onKeyEventScancode(self, code, isPressed, isExtended):
""" HoneyPot """
def onKeyEventUnicode(self, code, isPressed):

View File

@@ -92,16 +92,18 @@ class ProxyServer(rdp.RDPServerObserver):
return
self._client._controller.close()
def onKeyEventScancode(self, code, isPressed):
def onKeyEventScancode(self, code, isPressed, isExtended):
"""
@summary: Event call when a keyboard event is catch in scan code format
@param code: {int} scan code of key
@param isPressed: {bool} True if key is down
@param code: {integer} scan code of key
@param isPressed: {boolean} True if key is down
@param isExtended: {boolean} True if a special key
@see: rdp.RDPServerObserver.onKeyEventScancode
"""
if self._client is None:
return
self._client._controller.sendKeyEventScancode(code, isPressed)
self._client._controller.sendKeyEventScancode(code, isPressed, isExtended)
self._rss.keyScancode(code, isPressed)
def onKeyEventUnicode(self, code, isPressed):
"""
@@ -113,6 +115,7 @@ class ProxyServer(rdp.RDPServerObserver):
if self._client is None:
return
self._client._controller.sendKeyEventUnicode(code, isPressed)
self._rss.keyUnicode(code, isPressed)
def onPointerEvent(self, x, y, button, isPressed):
"""
@@ -176,6 +179,13 @@ class ProxyClient(rdp.RDPClientObserver):
#maybe color depth change
self._server._controller.setColorDepth(self._controller.getColorDepth())
def onSessionReady(self):
"""
@summary: Windows session is ready
@see: rdp.RDPClientObserver.onSessionReady
"""
pass
def onClose(self):
"""
@summary: Event inform that stack is close

View File

@@ -136,6 +136,13 @@ class RDPScreenShotFactory(rdp.ClientFactory):
"""
log.info("connected %s"%addr)
def onSessionReady(self):
"""
@summary: Windows session is ready
@see: rdp.RDPClientObserver.onSessionReady
"""
pass
def onClose(self):
"""
@summary: callback use when RDP stack is closed

View File

@@ -27,6 +27,7 @@ from PyQt4 import QtGui, QtCore
from rdpy.core import log, rss
from rdpy.ui.qt4 import QRemoteDesktop, RDPBitmapToQtImage
from rdpy.core.scancode import scancodeToChar
log._LOG_LEVEL = log.Level.INFO
class RssPlayerWidget(QRemoteDesktop):
@@ -45,9 +46,28 @@ class RssPlayerWidget(QRemoteDesktop):
""" Not Handle """
QRemoteDesktop.__init__(self, width, height, RssAdaptor())
def drawInfos(self, domain, username, password, hostname):
QtGui.QMessageBox.about(self, "Credentials Event", "domain : %s\nusername : %s\npassword : %s\nhostname : %s" % (
domain, username, password, hostname))
class RssPlayerWindow(QtGui.QWidget):
"""
@summary: main window of rss player
"""
def __init__(self):
super(RssPlayerWindow, self).__init__()
self._viewer = RssPlayerWidget(800, 600)
self._text = QtGui.QTextEdit()
self._text.setReadOnly(True)
self._text.setFixedHeight(150)
scrollViewer = QtGui.QScrollArea()
scrollViewer.setWidget(self._viewer)
layout = QtGui.QVBoxLayout()
layout.addWidget(scrollViewer, 1)
layout.addWidget(self._text, 2)
self.setLayout(layout)
self.setGeometry(0, 0, 800, 600)
def help():
print "Usage: rdpy-rssplayer [-h] rss_filepath"
@@ -64,16 +84,20 @@ def loop(widget, rssFile, nextEvent):
if nextEvent.type.value == rss.EventType.UPDATE:
image = RDPBitmapToQtImage(nextEvent.event.width.value, nextEvent.event.height.value, nextEvent.event.bpp.value, nextEvent.event.format.value == rss.UpdateFormat.BMP, nextEvent.event.data.value);
widget.notifyImage(nextEvent.event.destLeft.value, nextEvent.event.destTop.value, image, nextEvent.event.destRight.value - nextEvent.event.destLeft.value + 1, nextEvent.event.destBottom.value - nextEvent.event.destTop.value + 1)
widget._viewer.notifyImage(nextEvent.event.destLeft.value, nextEvent.event.destTop.value, image, nextEvent.event.destRight.value - nextEvent.event.destLeft.value + 1, nextEvent.event.destBottom.value - nextEvent.event.destTop.value + 1)
elif nextEvent.type.value == rss.EventType.SCREEN:
widget.resize(nextEvent.event.width.value, nextEvent.event.height.value)
widget._viewer.resize(nextEvent.event.width.value, nextEvent.event.height.value)
elif nextEvent.type.value == rss.EventType.INFO:
widget.drawInfos(nextEvent.event.domain.value, nextEvent.event.username.value, nextEvent.event.password.value, nextEvent.event.hostname.value)
widget._text.append("Domain : %s\nUsername : %s\nPassword : %s\nHostname : %s\n" % (
nextEvent.event.domain.value, nextEvent.event.username.value, nextEvent.event.password.value, nextEvent.event.hostname.value))
elif nextEvent.type.value == rss.EventType.KEY_SCANCODE:
if nextEvent.event.isPressed.value == 0:
widget._text.moveCursor(QtGui.QTextCursor.End)
widget._text.insertPlainText(scancodeToChar(nextEvent.event.code.value))
elif nextEvent.type.value == rss.EventType.CLOSE:
widget.close()
return
e = rssFile.nextEvent()
@@ -92,8 +116,10 @@ if __name__ == '__main__':
filepath = args[0]
#create application
app = QtGui.QApplication(sys.argv)
widget = RssPlayerWidget(800, 600)
widget.show()
mainWindow = RssPlayerWindow()
mainWindow.show()
rssFile = rss.createReader(filepath)
start(widget, rssFile)
start(mainWindow, rssFile)
sys.exit(app.exec_())

View File

@@ -39,7 +39,7 @@ def log(message):
@summary: Main log function
@param message: string to print
"""
print message
print "[*] %s"%message
def error(message):
"""
@@ -48,7 +48,7 @@ def error(message):
"""
if _LOG_LEVEL > Level.ERROR:
return
log("ERROR : %s"%message)
log("ERROR:\t%s"%message)
def warning(message):
"""
@@ -57,7 +57,7 @@ def warning(message):
"""
if _LOG_LEVEL > Level.WARNING:
return
log("WARNING : %s"%message)
log("WARNING:\t%s"%message)
def info(message):
"""
@@ -66,7 +66,7 @@ def info(message):
"""
if _LOG_LEVEL > Level.INFO:
return
log("INFO : %s"%message)
log("INFO:\t%s"%message)
def debug(message):
"""
@@ -75,4 +75,4 @@ def debug(message):
"""
if _LOG_LEVEL > Level.DEBUG:
return
log("DEBUG : %s"%message)
log("DEBUG:\t%s"%message)

View File

@@ -34,6 +34,8 @@ class EventType(object):
SCREEN = 0x0002
INFO = 0x0003
CLOSE = 0x0004
KEY_UNICODE = 0x0005
KEY_SCANCODE = 0x0006
class UpdateFormat(object):
"""
@@ -56,7 +58,7 @@ class Event(CompositeType):
"""
@summary: Closure for event factory
"""
for c in [UpdateEvent, ScreenEvent, InfoEvent, CloseEvent]:
for c in [UpdateEvent, ScreenEvent, InfoEvent, CloseEvent, KeyEventScancode, KeyEventUnicode]:
if self.type.value == c._TYPE_:
return c(readLen = self.length)
log.debug("unknown event type : %s"%hex(self.type.value))
@@ -123,6 +125,26 @@ class CloseEvent(CompositeType):
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
class KeyEventUnicode(CompositeType):
"""
@summary: keyboard event (keylogger) as unicode event
"""
_TYPE_ = EventType.KEY_UNICODE
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.code = UInt32Le()
self.isPressed = UInt8()
class KeyEventScancode(CompositeType):
"""
@summary: keyboard event (keylogger)
"""
_TYPE_ = EventType.KEY_SCANCODE
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.code = UInt32Le()
self.isPressed = UInt8()
def timeMs():
"""
@return: {int} time stamp in milliseconds
@@ -212,6 +234,28 @@ class FileRecorder(object):
infoEvent.hostname.value = hostname
self.rec(infoEvent)
def keyUnicode(self, code, isPressed):
"""
@summary: record key event as unicode
@param code: unicode code
@param isPressed: True if a key press event
"""
keyEvent = KeyEventUnicode()
keyEvent.code.value = code
keyEvent.isPressed.value = 0 if isPressed else 1
self.rec(keyEvent)
def keyScancode(self, code, isPressed):
"""
@summary: record key event as scancode
@param code: scancode code
@param isPressed: True if a key press event
"""
keyEvent = KeyEventScancode()
keyEvent.code.value = code
keyEvent.isPressed.value = 0 if isPressed else 1
self.rec(keyEvent)
def close(self):
"""
@summary: end of scenario

60
rdpy/core/scancode.py Normal file
View File

@@ -0,0 +1,60 @@
#
# Copyright (c) 2014-2015 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/>.
#
"""
Basic virtual scancode mapping
"""
_SCANCODE_QWERTY_ = {
0x10 : "q",
0x11 : "w",
0x12 : "e",
0x13 : "r",
0x14 : "t",
0x15 : "y",
0x16 : "u",
0x17 : "i",
0x18 : "o",
0x19 : "p",
0x1e : "a",
0x1f : "s",
0x20 : "d",
0x21 : "f",
0x22 : "g",
0x23 : "h",
0x24 : "j",
0x25 : "k",
0x26 : "l",
0x2c : "z",
0x2d : "x",
0x2e : "c",
0x2f : "v",
0x30 : "b",
0x31 : "n",
0x32 : "m"
}
def scancodeToChar(code):
"""
@summary: try to convert native code to char code
@return: char
"""
if not _SCANCODE_QWERTY_.has_key(code):
return "<unknown scancode %x>"%code
return _SCANCODE_QWERTY_[code];

View File

@@ -477,7 +477,7 @@ class CompositeType(Type):
raise e
if not self._readLen is None and readLen < self._readLen.value:
log.debug("Still have correct data in packet %s, read it as padding"%self.__class__)
log.debug("Still have correct data in packet %s, read %s bytes as padding"%(self.__class__, self._readLen.value - readLen))
s.read(self._readLen.value - readLen)
def __write__(self, s):

View File

@@ -234,7 +234,7 @@ class LicPacket(CompositeType):
if self.bMsgtype.value == c._MESSAGE_TYPE_:
return c(readLen = self.wMsgSize - 4)
log.debug("unknown license message : %s"%self.bMsgtype.value)
return String()
return String(readLen = self.wMsgSize - 4)
if message is None:
message = FactoryType(LicensingMessageFactory)

View File

@@ -202,6 +202,16 @@ class Display(object):
SUPPRESS_DISPLAY_UPDATES = 0x00
ALLOW_DISPLAY_UPDATES = 0x01
class ToogleFlag(object):
"""
@summary: Use to known state of keyboard
@see: https://msdn.microsoft.com/en-us/library/cc240588.aspx
"""
TS_SYNC_SCROLL_LOCK = 0x00000001
TS_SYNC_NUM_LOCK = 0x00000002
TS_SYNC_CAPS_LOCK = 0x00000004
TS_SYNC_KANA_LOCK = 0x00000008
class ErrorInfo(object):
"""
@summary: Error code use in Error info PDU
@@ -413,8 +423,6 @@ class ErrorInfo(object):
ERRINFO_VCDATATOOLONG : "The size of a received Virtual Channel PDU (section 2.2.6.1) exceeds the chunking size specified in the Virtual Channel Capability Set (section 2.2.7.1.10).",
}
class ShareControlHeader(CompositeType):
"""
@summary: PDU share control header
@@ -461,10 +469,10 @@ class PDU(CompositeType):
"""
for c in [DemandActivePDU, ConfirmActivePDU, DataPDU, DeactiveAllPDU]:
if self.shareControlHeader.pduType.value == c._PDUTYPE_:
return c()
return c(readLen = CallableValue(self.shareControlHeader.totalLength.value - sizeof(self.shareControlHeader)))
log.debug("unknown PDU type : %s"%hex(self.shareControlHeader.pduType.value))
#read entire packet
return String()
return String(readLen = CallableValue(self.shareControlHeader.totalLength.value - sizeof(self.shareControlHeader)))
if pduMessage is None:
pduMessage = FactoryType(PDUMessageFactory)
@@ -481,8 +489,8 @@ class DemandActivePDU(CompositeType):
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_DEMANDACTIVEPDU
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.shareId = UInt32Le()
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
self.lengthCombinedCapabilities = UInt16Le(lambda:(sizeof(self.numberCapabilities) + sizeof(self.pad2Octets) + sizeof(self.capabilitySets)))
@@ -500,8 +508,8 @@ class ConfirmActivePDU(CompositeType):
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_CONFIRMACTIVEPDU
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.shareId = UInt32Le()
self.originatorId = UInt16Le(0x03EA, constant = True)
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
@@ -519,10 +527,10 @@ class DeactiveAllPDU(CompositeType):
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_DEACTIVATEALLPDU
def __init__(self):
def __init__(self, readLen = None):
#in old version this packet is empty i don't know
#and not specified
CompositeType.__init__(self, optional = True)
CompositeType.__init__(self, optional = True, readLen = readLen)
self.shareId = UInt32Le()
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
self.sourceDescriptor = String("rdpy", readLen = self.lengthSourceDescriptor)
@@ -534,19 +542,19 @@ class DataPDU(CompositeType):
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_DATAPDU
def __init__(self, pduData = None, shareId = 0):
CompositeType.__init__(self)
def __init__(self, pduData = None, shareId = 0, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.shareDataHeader = ShareDataHeader(lambda:sizeof(self), lambda:self.pduData.__class__._PDUTYPE2_, shareId)
def PDUDataFactory():
"""
@summary: Create object in accordance self.shareDataHeader.pduType2 value
"""
for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU, SupressOutputDataPDU]:
for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU, SupressOutputDataPDU, SaveSessionInfoPDU]:
if self.shareDataHeader.pduType2.value == c._PDUTYPE2_:
return c()
return c(readLen = CallableValue(readLen.value - sizeof(self.shareDataHeader)))
log.debug("unknown PDU data type : %s"%hex(self.shareDataHeader.pduType2.value))
return String()
return String(readLen = CallableValue(readLen.value - sizeof(self.shareDataHeader)))
if pduData is None:
pduData = FactoryType(PDUDataFactory)
@@ -655,8 +663,8 @@ class PersistentListPDU(CompositeType):
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_BITMAPCACHE_PERSISTENT_LIST
def __init__(self, userId = 0, shareId = 0):
CompositeType.__init__(self)
def __init__(self, userId = 0, shareId = 0, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.numEntriesCache0 = UInt16Le()
self.numEntriesCache1 = UInt16Le()
self.numEntriesCache2 = UInt16Le()
@@ -679,8 +687,8 @@ class ClientInputEventPDU(CompositeType):
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_INPUT
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.numEvents = UInt16Le(lambda:len(self.slowPathInputEvents._array))
self.pad2Octets = UInt16Le()
self.slowPathInputEvents = ArrayType(SlowPathInputEvent, readLen = self.numEvents)
@@ -691,8 +699,8 @@ class ShutdownRequestPDU(CompositeType):
client -> server
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_REQUEST
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
class ShutdownDeniedPDU(CompositeType):
"""
@@ -700,8 +708,8 @@ class ShutdownDeniedPDU(CompositeType):
server -> client
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_DENIED
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
class InclusiveRectangle(CompositeType):
"""
@@ -761,9 +769,9 @@ class UpdateDataPDU(CompositeType):
"""
for c in [BitmapUpdateDataPDU]:
if self.updateType.value == c._UPDATE_TYPE_:
return c()
return c(readLen = CallableValue(readLen.value - 2))
log.debug("unknown PDU update data type : %s"%hex(self.updateType.value))
return String()
return String(readLen = CallableValue(readLen.value - 2))
if updateData is None:
updateData = FactoryType(UpdateDataFactory, conditional = lambda:(self.updateType.value != UpdateType.UPDATETYPE_SYNCHRONIZE))
@@ -772,6 +780,18 @@ class UpdateDataPDU(CompositeType):
self.updateData = updateData
class SaveSessionInfoPDU(CompositeType):
"""
@see: https://msdn.microsoft.com/en-us/library/cc240636.aspx
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_SAVE_SESSION_INFO
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.infoType = UInt32Le()
#TODO parse info data
self.infoData = String()
class FastPathUpdatePDU(CompositeType):
"""
@summary: Fast path update PDU packet
@@ -789,9 +809,9 @@ class FastPathUpdatePDU(CompositeType):
"""
for c in [FastPathBitmapUpdateDataPDU]:
if (self.updateHeader.value & 0xf) == c._FASTPATH_UPDATE_TYPE_:
return c()
return c(readLen = self.size)
log.debug("unknown Fast Path PDU update data type : %s"%hex(self.updateHeader.value & 0xf))
return String()
return String(readLen = self.size)
if updateData is None:
updateData = FactoryType(UpdateDataFactory)
@@ -821,8 +841,8 @@ class OrderUpdateDataPDU(CompositeType):
@see: http://msdn.microsoft.com/en-us/library/cc241571.aspx
@todo: not implemented yet but need it
"""
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.pad2OctetsA = UInt16Le()
self.numberOrders = UInt16Le(lambda:len(self.orderData._array))
self.pad2OctetsB = UInt16Le()
@@ -882,8 +902,8 @@ class FastPathBitmapUpdateDataPDU(CompositeType):
"""
_FASTPATH_UPDATE_TYPE_ = FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.header = UInt16Le(FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP, constant = True)
self.numberRectangles = UInt16Le(lambda:len(self.rectangles._array))
self.rectangles = ArrayType(BitmapData, readLen = self.numberRectangles)
@@ -899,11 +919,10 @@ class SlowPathInputEvent(CompositeType):
self.messageType = UInt16Le(lambda:self.slowPathInputData.__class__._INPUT_MESSAGE_TYPE_)
def SlowPathInputDataFactory():
for c in [PointerEvent, ScancodeKeyEvent, UnicodeKeyEvent]:
for c in [PointerEvent, ScancodeKeyEvent, UnicodeKeyEvent, SynchronizeEvent]:
if self.messageType.value == c._INPUT_MESSAGE_TYPE_:
return c()
log.debug("unknown slow path input : %s"%hex(self.messageType.value))
return String()
raise InvalidExpectedDataException("unknown slow path input : %s"%hex(self.messageType.value))
if messageData is None:
messageData = FactoryType(SlowPathInputDataFactory)
@@ -912,6 +931,18 @@ class SlowPathInputEvent(CompositeType):
self.slowPathInputData = messageData
class SynchronizeEvent(CompositeType):
"""
@summary: Synchronize keyboard
@see: https://msdn.microsoft.com/en-us/library/cc240588.aspx
"""
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_SYNC
def __init__(self):
CompositeType.__init__(self)
self.pad2Octets = UInt16Le()
self.toggleFlags = UInt32Le()
class PointerEvent(CompositeType):
"""
@summary: Event use to communicate mouse position

View File

@@ -25,6 +25,7 @@ In this layer are managed all mains bitmap update orders end user inputs
from rdpy.core.layer import LayerAutomata
from rdpy.core.error import CallPureVirtualFuntion
from rdpy.core.type import ArrayType
import rdpy.core.log as log
import rdpy.protocol.rdp.tpkt as tpkt
import data, caps
@@ -39,6 +40,13 @@ class PDUClientListener(object):
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUClientListener"))
def onSessionReady(self):
"""
@summary: Event call when Windows session is ready
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSessionReady", "PDUClientListener"))
def onUpdate(self, rectangles):
"""
@summary: call when a bitmap data is received from update PDU
@@ -259,8 +267,9 @@ class Client(PDULayer):
@summary: Main receive function after connection sequence
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
pdus = ArrayType(data.PDU)
s.readType(pdus)
for pdu in pdus:
if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU:
self.readDataPDU(pdu.pduMessage)
elif pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DEACTIVATEALLPDU:
@@ -276,10 +285,11 @@ class Client(PDULayer):
@param fastPathS: {Stream} that contain fast path data
@param secFlag: {SecFlags}
"""
fastPathPDU = data.FastPathUpdatePDU()
fastPathS.readType(fastPathPDU)
if fastPathPDU.updateHeader.value == data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP:
self._listener.onUpdate(fastPathPDU.updateData.rectangles._array)
updates = ArrayType(data.FastPathUpdatePDU)
fastPathS.readType(updates)
for update in updates:
if update.updateHeader.value == data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP:
self._listener.onUpdate(update.updateData.rectangles._array)
def readDataPDU(self, dataPDU):
"""
@@ -287,14 +297,20 @@ class Client(PDULayer):
@param dataPDU: DataPDU object
"""
if dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU:
#ignore 0 error code because is not an error code
if dataPDU.pduData.errorInfo.value == 0:
return
errorMessage = "Unknown code %s"%hex(dataPDU.pduData.errorInfo.value)
if data.ErrorInfo._MESSAGES_.has_key(dataPDU.pduData.errorInfo):
errorMessage = data.ErrorInfo._MESSAGES_[dataPDU.pduData.errorInfo]
log.error("INFO PDU : %s"%errorMessage)
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SHUTDOWN_DENIED:
#may be an event to ask to user
self._transport.close()
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SAVE_SESSION_INFO:
#handle session event
self._listener.onSessionReady()
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_UPDATE:
self.readUpdateDataPDU(dataPDU.pduData)

View File

@@ -100,7 +100,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def setUsername(self, username):
"""
@summary: Set the username for session
@param username: username of session
@param username: {string} username of session
"""
#username in PDU info packet
self._secLayer._info.userName.value = username
@@ -109,7 +109,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def setPassword(self, password):
"""
@summary: Set password for session
@param password: password of session
@param password: {string} password of session
"""
self.setAutologon()
self._secLayer._info.password.value = password
@@ -117,7 +117,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def setDomain(self, domain):
"""
@summary: Set the windows domain of session
@param domain: domain of session
@param domain: {string} domain of session
"""
self._secLayer._info.domain.value = domain
@@ -127,6 +127,13 @@ class RDPClientController(pdu.layer.PDUClientListener):
"""
self._secLayer._info.flag |= sec.InfoFlag.INFO_AUTOLOGON
def setAlternateShell(self, appName):
"""
@summary: set application name of app which start at the begining of session
@param appName: {string} application name
"""
self._secLayer._info.alternateShell.value = appName
def setKeyboardLayout(self, layout):
"""
@summary: keyboard layout
@@ -192,6 +199,15 @@ class RDPClientController(pdu.layer.PDUClientListener):
for observer in self._clientObserver:
observer.onReady()
def onSessionReady(self):
"""
@summary: Call when Windows session is ready (connected)
"""
self._isReady = True
#signal all listener
for observer in self._clientObserver:
observer.onSessionReady()
def onClose(self):
"""
@summary: Event call when RDP stack is closed
@@ -269,11 +285,12 @@ class RDPClientController(pdu.layer.PDUClientListener):
except InvalidValue:
log.info("try send wheel event with incorrect position")
def sendKeyEventScancode(self, code, isPressed):
def sendKeyEventScancode(self, code, isPressed, extended = False):
"""
@summary: Send a scan code to RDP stack
@param code: scan code
@param isPressed: True if key is pressed and false if it's released
@param extended: {boolean} extended scancode like ctr or win button
"""
if not self._isReady:
return
@@ -281,11 +298,12 @@ class RDPClientController(pdu.layer.PDUClientListener):
try:
event = pdu.data.ScancodeKeyEvent()
event.keyCode.value = code
if isPressed:
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_DOWN
else:
if not isPressed:
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_RELEASE
if extended:
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_EXTENDED
#send event
self._pduLayer.sendInputEvents([event])
@@ -478,7 +496,7 @@ class RDPServerController(pdu.layer.PDUServerListener):
for event in slowPathInputEvents:
#scan code
if event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_SCANCODE:
observer.onKeyEventScancode(event.slowPathInputData.keyCode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE))
observer.onKeyEventScancode(event.slowPathInputData.keyCode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE), bool(event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_EXTENDED))
#unicode
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_UNICODE:
observer.onKeyEventUnicode(event.slowPathInputData.unicode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE))
@@ -607,6 +625,12 @@ class RDPClientObserver(object):
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPClientObserver"))
def onSessionReady(self):
"""
@summary: Windows session is ready
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSessionReady", "RDPClientObserver"))
def onClose(self):
"""
@summary: Stack is closes
@@ -652,11 +676,12 @@ class RDPServerObserver(object):
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver"))
def onKeyEventScancode(self, code, isPressed):
def onKeyEventScancode(self, code, isPressed, isExtended):
"""
@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
@param code: {integer} scan code of key
@param isPressed: {boolean} True if key is down
@param isExtended: {boolean} True if a special key
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onKeyEventScanCode", "RDPServerObserver"))

View File

@@ -24,7 +24,7 @@ RDP Standard security layer
import sha, md5
import lic, tpkt
from t125 import gcc, mcs
from rdpy.core.type import CompositeType, CallableValue, Stream, UInt32Le, UInt16Le, String, sizeof, UInt8
from rdpy.core.type import CompositeType, CallableValue, Stream, UInt32Le, UInt16Le, String, sizeof
from rdpy.core.layer import LayerAutomata, IStreamSender
from rdpy.core.error import InvalidExpectedDataException
from rdpy.core import log
@@ -56,6 +56,7 @@ class SecurityFlag(object):
class InfoFlag(object):
"""
Client capabilities informations
@see: https://msdn.microsoft.com/en-us/library/cc240475.aspx
"""
INFO_MOUSE = 0x00000001
INFO_DISABLECTRLALTDEL = 0x00000002
@@ -80,6 +81,7 @@ class InfoFlag(object):
class PerfFlag(object):
"""
Network performances flag
@see: https://msdn.microsoft.com/en-us/library/cc240476.aspx
"""
PERF_DISABLE_WALLPAPER = 0x00000001
PERF_DISABLE_FULLWINDOWDRAG = 0x00000002
@@ -323,7 +325,7 @@ class RDPInfo(CompositeType):
#code page
self.codePage = UInt32Le()
#support flag
self.flag = UInt32Le(InfoFlag.INFO_MOUSE | InfoFlag.INFO_UNICODE | InfoFlag.INFO_LOGONNOTIFY | InfoFlag.INFO_LOGONERRORS | InfoFlag.INFO_DISABLECTRLALTDEL)
self.flag = UInt32Le(InfoFlag.INFO_MOUSE | InfoFlag.INFO_UNICODE | InfoFlag.INFO_LOGONNOTIFY | InfoFlag.INFO_LOGONERRORS | InfoFlag.INFO_DISABLECTRLALTDEL | InfoFlag.INFO_ENABLEWINDOWSKEY)
self.cbDomain = UInt16Le(lambda:sizeof(self.domain) - 2)
self.cbUserName = UInt16Le(lambda:sizeof(self.userName) - 2)
self.cbPassword = UInt16Le(lambda:sizeof(self.password) - 2)

View File

@@ -27,7 +27,6 @@ It exist channel for file system order, audio channel, clipboard etc...
from rdpy.core.layer import LayerAutomata, IStreamSender, Layer
from rdpy.core.type import sizeof, Stream, UInt8, UInt16Le, String
from rdpy.core.error import InvalidExpectedDataException, InvalidValue, InvalidSize, CallPureVirtualFuntion
from ber import writeLength
import rdpy.core.log as log
import ber, gcc, per
@@ -255,7 +254,7 @@ class MCSLayer(LayerAutomata):
domainParam = (ber.writeInteger(maxChannels), ber.writeInteger(maxUsers), ber.writeInteger(maxTokens),
ber.writeInteger(1), ber.writeInteger(0), ber.writeInteger(1),
ber.writeInteger(maxPduSize), ber.writeInteger(2))
return (ber.writeUniversalTag(ber.Tag.BER_TAG_SEQUENCE, True), writeLength(sizeof(domainParam)), domainParam)
return (ber.writeUniversalTag(ber.Tag.BER_TAG_SEQUENCE, True), ber.writeLength(sizeof(domainParam)), domainParam)
def writeMCSPDUHeader(self, mcsPdu, options = 0):
"""

View File

@@ -303,7 +303,7 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
@param isCompress: {bool} use RLE compression
@param data: {str} bitmap data
"""
image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data);
image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data)
#if image need to be cut
#For bit alignement server may send more than image pixel
self._widget.notifyImage(destLeft, destTop, image, destRight - destLeft + 1, destBottom - destTop + 1)
@@ -311,12 +311,21 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
def onReady(self):
"""
@summary: Call when stack is ready
@see: rdp.RDPClientObserver.onReady
"""
#do something maybe a loader
def onSessionReady(self):
"""
@summary: Windows session is ready
@see: rdp.RDPClientObserver.onSessionReady
"""
pass
def onClose(self):
"""
@summary: Call when stack is close
@see: rdp.RDPClientObserver.onClose
"""
#do something maybe a message
@@ -336,12 +345,6 @@ class QRemoteDesktop(QtGui.QWidget):
self._adaptor = adaptor
#set correct size
self.resize(width, height)
#refresh stack of image
#because we can update image only in paint
#event function. When protocol receive image
#we will stock into refresh list
#and in paint event paint list of all refresh images
self._refresh = []
#bind mouse event
self.setMouseTracking(True)
#buffer image
@@ -354,8 +357,9 @@ class QRemoteDesktop(QtGui.QWidget):
@param y: y position of new image
@param qimage: new QImage
"""
#save in refresh list (order is important)
self._refresh.append((x, y, qimage, width, height))
#fill buffer image
with QtGui.QPainter(self._buffer) as qp:
qp.drawImage(x, y, qimage, 0, 0, width, height)
#force update
self.update()
@@ -373,17 +377,10 @@ class QRemoteDesktop(QtGui.QWidget):
@summary: Call when Qt renderer engine estimate that is needed
@param e: QEvent
"""
#fill buffer image
with QtGui.QPainter(self._buffer) as qp:
#draw image
for (x, y, image, width, height) in self._refresh:
qp.drawImage(x, y, image, 0, 0, width, height)
#draw in widget
with QtGui.QPainter(self) as qp:
qp.drawImage(0, 0, self._buffer)
self._refresh = []
def mouseMoveEvent(self, event):
"""
@summary: Call when mouse move

View File

@@ -4,7 +4,7 @@ import setuptools
from distutils.core import setup, Extension
setup(name='rdpy',
version='1.3.0',
version='1.3.2',
description='Remote Desktop Protocol in Python',
long_description="""
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (Client and Server side). RDPY is built over the event driven network engine Twisted.