diff --git a/rdpy/protocol/network/layer.py b/rdpy/protocol/network/layer.py index 15b1c9e..aa6a474 100644 --- a/rdpy/protocol/network/layer.py +++ b/rdpy/protocol/network/layer.py @@ -48,6 +48,14 @@ class Layer(object): ''' if not self._transport is None: self._transport.send(data) + + def close(self): + ''' + close layer and send close signal + to transport layer + ''' + if not self._transport is None: + self._transport.close() class LayerAutomata(Layer): ''' diff --git a/rdpy/protocol/network/type.py b/rdpy/protocol/network/type.py index 1a80ae9..00ae806 100644 --- a/rdpy/protocol/network/type.py +++ b/rdpy/protocol/network/type.py @@ -224,6 +224,13 @@ class SimpleType(Type): if not isinstance(other, SimpleType): other = self.__class__(other) return self.__class__(self.value.__rshift__(other.value)) + + def __hash__(self): + ''' + hash function to treat simple type in hash collection + @return: hash of inner value + ''' + return hash(self._value) class CompositeType(Type): @@ -416,6 +423,13 @@ class String(Type): ''' return self._value == other._value + def __hash__(self): + ''' + hash function to treat simple type in hash collection + @return: hash of inner value + ''' + return hash(self._value) + def __str__(self): ''' call when str function is call diff --git a/rdpy/protocol/rdp/gcc.py b/rdpy/protocol/rdp/gcc.py index 095e4c9..3863b05 100644 --- a/rdpy/protocol/rdp/gcc.py +++ b/rdpy/protocol/rdp/gcc.py @@ -321,6 +321,7 @@ def readServerDataBlocks(s): length -= blockLength.value s.seek(marker + blockLength.value) + return settings def writeClientCoreData(core): ''' diff --git a/rdpy/protocol/rdp/gdl.py b/rdpy/protocol/rdp/gdl.py new file mode 100644 index 0000000..678df5f --- /dev/null +++ b/rdpy/protocol/rdp/gdl.py @@ -0,0 +1,52 @@ +''' +@author: sylvain +''' + +from rdpy.protocol.network.layer import LayerAutomata +from rdpy.protocol.network.type import CompositeType, UInt8, UniString, UInt16Le, UInt32Le +from rdpy.utils.const import ConstAttributes, TypeAttributes + +@ConstAttributes +@TypeAttributes(UInt16Le) +class SecurityFlag(object): + SEC_INFO_PKT = 0x0040 + +class RDPInfo(CompositeType): + def __init__(self): + CompositeType.__init__(self) + self.audioCapture = UInt8() + self.audioPlayback = UInt8() + self.autoLogon = UInt8() + self.remoteApp = UInt8() + self.consoleAudio = UInt8() + self.compression = UInt8() + self.domain = UniString() + self.username = UniString() + self.password = UniString() + self.alternateShell = UniString() + +class RDPExtendedInfo(CompositeType): + def __init__(self): + CompositeType.__init__(self) + self.ipv6 = UInt8() + self.adress = UniString() + self.clientDir = UniString() + self.performanceFlags = UInt32Le() + +class GDL(LayerAutomata): + ''' + Global Display Layer + Global channel for mcs that handle session + identification and user and graphic controls + ''' + def __init__(self): + ''' + Constructor + ''' + LayerAutomata.__init__(self, None) + + def connect(self): + self.sendInfoPkt() + + def sendInfoPkt(self): + self._transport.send(self, (SecurityFlag.SEC_INFO_PKT, UInt16Le(), RDPInfo(), RDPExtendedInfo())) \ No newline at end of file diff --git a/rdpy/protocol/rdp/mcs.py b/rdpy/protocol/rdp/mcs.py index 8e3bcca..c1d114b 100644 --- a/rdpy/protocol/rdp/mcs.py +++ b/rdpy/protocol/rdp/mcs.py @@ -26,9 +26,16 @@ class DomainMCSPDU: domain mcs pdu header ''' ERECT_DOMAIN_REQUEST = 1 + DISCONNECT_PROVIDER_ULTIMATUM = 8 ATTACH_USER_REQUEST = 10 ATTACH_USER_CONFIRM = 11 - + CHANNEL_JOIN_REQUEST = 14 + CHANNEL_JOIN_CONFIRM = 15 + SEND_DATA_REQUEST = 25 + SEND_DATA_INDICATION = 26 + +@ConstAttributes +@TypeAttributes(UInt16Be) class Channel: MCS_GLOBAL_CHANNEL = 1003 MCS_USERCHANNEL_BASE = 1001 @@ -39,15 +46,20 @@ class MCS(LayerAutomata): the main layer of RDP protocol is why he can do everything and more! ''' - def __init__(self, presentation = None): + def __init__(self): ''' ctor call base class ctor @param presentation: presentation layer ''' - LayerAutomata.__init__(self, presentation) + LayerAutomata.__init__(self, None) self._clientSettings = gcc.ClientSettings() + self._serverSettings = gcc.ServerSettings() #default user Id self._userId = UInt16Be(1) + #list of channel use in this layer and connection state + self._channelIds = {Channel.MCS_GLOBAL_CHANNEL: None} + #use to record already requested channel + self._channelIdsRequest = {} def connect(self): ''' @@ -56,7 +68,26 @@ class MCS(LayerAutomata): ''' self._clientSettings.core.serverSelectedProtocol = self._transport._selectedProtocol self.sendConnectInitial() - + + def connectNextChannel(self): + ''' + send sendChannelJoinRequest message on next unconnect channel + ''' + for (channelId, layer) in self._channelIds.iteritems(): + #for each unconnect channel send a request + if not self._channelIdsRequest.has_key(channelId): + self.sendChannelJoinRequest(channelId) + self.setNextState(self.recvChannelJoinConfirm) + return + + #connection is done reinit class + self.setNextState(self.recvData) + #try connection on all requested channel + for (channelId, layer) in self._channelIds.iteritems(): + if self._channelIdsRequest[channelId] and not layer is None: + layer._transport = self + layer.connect() + def sendConnectInitial(self): ''' send connect initial packet @@ -86,6 +117,12 @@ class MCS(LayerAutomata): ''' self._transport.send(self.writeMCSPDUHeader(DomainMCSPDU.ATTACH_USER_REQUEST)) + def sendChannelJoinRequest(self, channelId): + ''' + send a formated Channel join request from client to server + ''' + self._transport.send((self.writeMCSPDUHeader(DomainMCSPDU.CHANNEL_JOIN_REQUEST), self._userId, channelId)) + def recvConnectResponse(self, data): ''' receive mcs connect response from server @@ -100,7 +137,8 @@ class MCS(LayerAutomata): gccRequestLength = ber.readLength(data) if data.dataLen() != gccRequestLength: raise InvalidSize("bad size of gcc request") - gcc.readConferenceCreateResponse(data) + self._serverSettings = gcc.readConferenceCreateResponse(data) + #send domain request self.sendErectDomainRequest() #send attach user request @@ -110,7 +148,7 @@ class MCS(LayerAutomata): def recvAttachUserConfirm(self, data): ''' - recaive a attach user confirm + receive an attach user confirm @param data: Stream ''' opcode = UInt8() @@ -122,6 +160,84 @@ class MCS(LayerAutomata): raise Exception("server reject user") if opcode & UInt8(2) == UInt8(2): data.readType(self._userId) + + #build channel list because we have user id + #add default channel + channels accepted by gcc connection sequence + self._channelIds[self._userId + Channel.MCS_USERCHANNEL_BASE] = None#TODO + [(x, False) for x in self._serverSettings.channelsId]) + + self.connectNextChannel() + + def recvChannelJoinConfirm(self, data): + ''' + receive a channel join confirm from server + @param data: Stream + ''' + opcode = UInt8() + confirm = UInt8() + data.readType((opcode, confirm)) + if not self.readMCSPDUHeader(opcode, DomainMCSPDU.CHANNEL_JOIN_CONFIRM): + raise InvalidExpectedDataException("invalid MCS PDU") + userId = UInt16Be() + channelId = UInt16Be() + data.readType((userId, channelId)) + #save state of channel + self._channelIdsRequest[channelId] = confirm == 0 + if confirm == 0: + print "server accept channel %d"%channelId.value + else: + print "server refused channel %d"%channelId.value + + self.connectNextChannel() + + def recvData(self, data): + ''' + main receive method + @param data: Stream + ''' + opcode = UInt8() + confirm = UInt8() + data.readType((opcode, confirm)) + + if self.readMCSPDUHeader(opcode, DomainMCSPDU.DISCONNECT_PROVIDER_ULTIMATUM): + print "receive DISCONNECT_PROVIDER_ULTIMATUM" + self.close() + + elif not self.readMCSPDUHeader(opcode, DomainMCSPDU.SEND_DATA_INDICATION): + raise InvalidExpectedDataException("invalid expected mcs opcode") + + userId = UInt16Be() + channelId = UInt16Be() + flags = UInt8() + length = UInt8() + + data.readType((userId, channelId, flags, length)) + + if length & UInt8(0x80) == UInt8(0x80): + lengthP2 = UInt8() + data.readType(lengthP2) + length = (UInt16Be(length.value) << 8) | lengthP2 + + #channel id doesn't match a requested layer + if not self._channelIdsRequest.has_key(channelId): + print "receive data for an unrequested layer" + return + + #channel id math an unconnected layer + if not self._channelIdsRequest[channelId]: + print "receive data for an unconnected layer" + return + + self._channelIds[channelId].recv(data) + + def send(self, fromLayer, data): + #retrieve channel id + channelId = None + for (channelIdTmp, layer) in self._channelIds.iteritems(): + if layer == fromLayer: + channelId = channelIdTmp + break + self._transport.send((self.writeMCSPDUHeader(DomainMCSPDU.SEND_DATA_REQUEST), self._userId, channelId, UInt8(0x70), UInt16Be(sizeof(data)) | UInt16Be(0x8000), data)) + def writeDomainParams(self, maxChannels, maxUsers, maxTokens, maxPduSize): ''' diff --git a/rdpy/protocol/rdp/rdp.py b/rdpy/protocol/rdp/rdp.py index d8c4b96..7d08808 100644 --- a/rdpy/protocol/rdp/rdp.py +++ b/rdpy/protocol/rdp/rdp.py @@ -2,13 +2,16 @@ @author: sylvain ''' from twisted.internet import protocol -import tpkt, tpdu, mcs +import tpkt, tpdu, mcs, gdl class Factory(protocol.Factory): ''' Factory of RDP protocol ''' def __init__(self): - self._protocol = tpkt.TPKT(tpdu.TPDU(mcs.MCS())) + mcsLayer = mcs.MCS() + #set global channel to graphic layer + mcsLayer._channelIds[mcs.Channel.MCS_GLOBAL_CHANNEL] = gdl.GDL() + self._protocol = tpkt.TPKT(tpdu.TPDU(mcsLayer)) def buildProtocol(self, addr): return self._protocol;