Files
rdpy/bin/rdpy-rdpproxy
2014-07-28 22:37:18 +02:00

372 lines
12 KiB
Python
Executable File

#!/usr/bin/python
#
# 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/>.
#
"""
RDP proxy recorder and spy function
Proxy RDP protocol
"""
import sys, os, getopt, json
# Change path so we find rdpy
sys.path.insert(1, os.path.join(sys.path[0], '..'))
from rdpy.base import log, error
from rdpy.protocol.rdp import rdp
from rdpy.ui import view
from twisted.internet import reactor
from PyQt4 import QtCore, QtGui
class IProxyClient(object):
def getColorDepth(self):
pass
def sendKeyEventScancode(self, code, isPressed):
pass
def sendKeyEventUnicode(self, code, isPressed):
pass
def sendPointerEvent(self, x, y, button, isPressed):
pass
def sendRefreshOrder(self, left, top, right, bottom):
pass
class ProxyServer(rdp.RDPServerObserver):
"""
Server side of proxy
"""
def __init__(self, controller, credentialProvider):
"""
@param controller: RDPServerController
@param credentialProvider: CredentialProvider
"""
rdp.RDPServerObserver.__init__(self, controller)
self._credentialProvider = credentialProvider
self._client = None
def clientConnected(self, client):
"""
Event throw by client when it's ready
@param client: ProxyClient
"""
self._client = client
self._controller.setColorDepth(self._client.getColorDepth())
def onReady(self):
"""
Event use to inform state of server stack
Use to connect client
"""
if self._client is None:
#try a connection
domain, username, password = self._controller.getCredentials()
if self._credentialProvider.isAdmin(domain, username, password):
self.clientConnected(ProxyAdmin(self))
return
try:
ip, port = self._credentialProvider.getProxyPass(domain, username)
except error.InvalidExpectedDataException as e:
log.info(e.message)
#self._controller.close()
return
width, height = self._controller.getScreen()
reactor.connectTCP(ip, port, ProxyClientFactory(self, width, height, domain, username, password, "%s\\%s on %s:%s"%(domain, username, ip, port)))
else:
#refresh client
width, height = self._controller.getScreen()
self._client.sendRefreshOrder(0, 0, width, height)
def onKeyEventScancode(self, code, isPressed):
"""
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
"""
#no client connected
if self._client is None:
return
self._client.sendKeyEventScancode(code, isPressed)
def onKeyEventUnicode(self, code, isPressed):
"""
Event call when a keyboard event is catch in unicode format
@param code: unicode of key
@param isPressed: True if key is down
"""
#no client connectedomaind
if self._client is None:
return
self._client.sendKeyEventUnicode(code, isPressed)
def onPointerEvent(self, x, y, button, isPressed):
"""
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
"""
#no client connected
if self._client is None:
return
self._client.sendPointerEvent(x, y, button, isPressed)
class ProxyClient(rdp.RDPClientObserver, IProxyClient):
"""
Client side of proxy
"""
def __init__(self, controller, server):
"""
@param controller: RDPClientObserver
@param server: ProxyServer
@param name: name of session
"""
rdp.RDPClientObserver.__init__(self, controller)
self._server = server
def onReady(self):
"""
Event use to signal that RDP stack is ready
Inform proxy server that i'm connected
implement RDPClientObserver
"""
self._server.clientConnected(self)
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
Event use to inform bitmap update
implement RDPClientObserver
@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
"""
self._server._controller.sendUpdate(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data)
def getColorDepth(self):
return self._controller.getColorDepth()
def sendKeyEventScancode(self, code, isPressed):
self._controller.sendKeyEventScancode(code, isPressed)
def sendKeyEventUnicode(self, code, isPressed):
self._controller.sendKeyEventUnicode(code, isPressed)
def sendPointerEvent(self, x, y, button, isPressed):
self._controller.sendPointerEvent(x, y, button, isPressed)
def sendRefreshOrder(self, left, top, right, bottom):
self._controller.sendRefreshOrder(left, top, right, bottom)
class ProxyServerFactory(rdp.ServerFactory):
"""
Factory on listening events
"""
def __init__(self, credentialProvider):
"""
@param config: rdp-proxy configuration
@param credentialProvider: CredentialProvider
"""
rdp.ServerFactory.__init__(self, "/home/sylvain/dev/certificate/rdpy.key", "/home/sylvain/dev/certificate/rdpy.crt", 16)
self._credentialProvider = credentialProvider
def buildObserver(self, controller):
"""
Implement rdp.ServerFactory
@param controller: rdp.RDPServerController
"""
return ProxyServer(controller, self._credentialProvider)
def startedConnecting(self, connector):
pass
def clientConnectionLost(self, connector, reason):
pass
def clientConnectionFailed(self, connector, reason):
pass
class ProxyClientFactory(rdp.ClientFactory):
"""
Factory for proxy client
"""
_CLIENT_PROXY_ = {}
def __init__(self, server, width, height, domain, username, password, name):
"""
@param server: ProxyServer
@param width: screen width
@param height: screen height
@param domain: domain session
@param username: username session
@param password: password session
"""
self._server = server
self._width = width
self._height = height
self._domain = domain
self._username = username
self._password = password
self._name = name
def buildObserver(self, controller):
"""
Implement rdp.ClientFactory
Build observer (ProxyClient)
@param controller: rdp.RDPClientController
"""
#set screen resolution
controller.setScreen(self._width, self._height)
#set credential
controller.setDomain(self._domain)
controller.setUsername(self._username)
controller.setPassword(self._password)
proxy = ProxyClient(controller, self._server)
ProxyClientFactory._CLIENT_PROXY_[self._name] = proxy
return proxy
def startedConnecting(self, connector):
pass
def clientConnectionLost(self, connector, reason):
if ProxyClientFactory._CLIENT_PROXY_.has_key(self._name):
del ProxyClientFactory._CLIENT_PROXY_[self._name]
def clientConnectionFailed(self, connector, reason):
if ProxyClientFactory._CLIENT_PROXY_.has_key(self._name):
del ProxyClientFactory._CLIENT_PROXY_[self._name]
class CredentialProvider(object):
"""
Credential provider for proxy
"""
def __init__(self, config):
"""
@param config: rdp proxy config
"""
self._config = config
def getAccount(self, domain, username):
if not self._config.has_key(domain) or not self._config[domain].has_key(username):
raise error.InvalidExpectedDataException("Invalid credentials %s\\%s"%(domain, username))
return self._config[domain][username]
def getProxyPass(self, domain, username):
"""
@param domain: domain to check
@param username: username in domain
@return: (ip, port) or None if error
"""
account = self.getAccount(domain, username)
if not account.has_key("ip") or not account.has_key("port"):
raise error.InvalidExpectedDataException("Invalid credentials declaration %s\\%s"%(domain, username))
return str(account['ip']), account['port']
def isAdmin(self, domain, username, password):
"""
@return: True if credential match admin credential
"""
account = self.getAccount(domain, username)
return account.has_key("admin") and account["admin"] and account.has_key("password") and str(account["password"]) == password
class ProxyAdmin(IProxyClient):
"""
Use to manage client side of admin session
Add GUI to select wich session to see
"""
def __init__(self, server):
self._server = server
self._list = view.ListView(ProxyClientFactory._CLIENT_PROXY_.keys(), 300, 300, self.onSelect)
self._render = view.RDPRenderer(self._server._controller)
self._list.update(self._render)
def getColorDepth(self):
return 16
def sendKeyEventScancode(self, code, isPressed):
if isPressed:
self._list.keyEvent(code)
self._list.update(self._render)
def sendKeyEventUnicode(self, code, isPressed):
pass
def sendPointerEvent(self, x, y, button, isPressed):
pass
def sendRefreshOrder(self, left, top, right, bottom):
self._list.update(self._render)
def onSelect(self, name):
if ProxyClientFactory._CLIENT_PROXY_.has_key(name):
self._server.clientConnected(ProxyClient(ProxyClientFactory._CLIENT_PROXY_[name]._controller, self._server))
def help():
"""
Print help in console
"""
print "Usage: rdpy-rdpproxy -f config_file_path listen_port"
def loadConfig(configFilePath):
"""
Load and check config file
@param configFilePath: config file path
"""
if not os.path.isfile(configFilePath):
log.error("File %s doesn't exist"%configFilePath)
return None
f = open(configFilePath, 'r')
config = json.load(f)
f.close()
return config
if __name__ == '__main__':
configFilePath = None
try:
opts, args = getopt.getopt(sys.argv[1:], "hf:")
except getopt.GetoptError:
help()
for opt, arg in opts:
if opt == "-h":
help()
sys.exit()
elif opt == "-f":
configFilePath = arg
if configFilePath is None:
print "Config file is mandatory"
help()
sys.exit()
#load config file
config = loadConfig(configFilePath)
if config is None:
log.error("Bad configuration file")
sys.exit()
#use to init font
app = QtGui.QApplication(sys.argv)
reactor.listenTCP(int(args[0]), ProxyServerFactory(CredentialProvider(config)))
reactor.run()