Esempio n. 1
0
 def sstpMsgCallConnectRequestReceived(self, protocolId):
     if self.state in (CALL_ABORT_TIMEOUT_PENDING, CALL_ABORT_PENDING,
             CALL_DISCONNECT_ACK_PENDING, CALL_DISCONNECT_TIMEOUT_PENDING):
         return
     if self.state != SERVER_CONNECT_REQUEST_PENDING:
         logging.warn('Not in the state.')
         self.transport.loseConnection()
         return
     if protocolId != SSTP_ENCAPSULATED_PROTOCOL_PPP:
         logging.warn('Unsupported encapsulated protocol.')
         nak = SSTPControlPacket(SSTP_MSG_CALL_CONNECT_NAK)
         nak.attributes = [(SSTP_ATTRIB_ENCAPSULATED_PROTOCOL_ID,
                 ATTRIB_STATUS_VALUE_NOT_SUPPORTED)]
         self.addRetryCounterOrAbrot()
         return
     self.nonce = os.urandom(32)
     ack = SSTPControlPacket(SSTP_MSG_CALL_CONNECT_ACK)
     # 3 bytes reserved + 1 byte hash bitmap (SHA-1 only) + nonce.
     ack.attributes = [(SSTP_ATTRIB_CRYPTO_BINDING_REQ,
             '\x00\x00\x00' + '\x03' + self.nonce)]
     ack.writeTo(self.transport.write)
     self.pppd = PPPDProtocol()
     self.pppd.sstp = self
     self.pppd.remote = self.factory.remotePool.apply()
     if self.pppd.remote is None:
         logging.warn('IP address pool is full. '
                 'Cannot accpet new connection.')
         self.abort()
     addressArgument = '%s:%s' % (self.factory.local, self.pppd.remote)
     reactor.spawnProcess(self.pppd, self.factory.pppd,
             args=['local', 'file', self.factory.pppdConfigFile,
                 '115200', addressArgument], usePTY=True)
     self.transport.registerProducer(self.pppd, True)
     self.pppd.resumeProducing()
     self.state = SERVER_CALL_CONNECTED_PENDING
Esempio n. 2
0
    def sstpMsgCallConnectRequestReceived(self, protocolId):
        if self.state in (CALL_ABORT_TIMEOUT_PENDING, CALL_ABORT_PENDING,
                CALL_DISCONNECT_ACK_PENDING, CALL_DISCONNECT_TIMEOUT_PENDING):
            return
        if self.state != SERVER_CONNECT_REQUEST_PENDING:
            logging.warn('Not in the state.')
            self.transport.loseConnection()
            return
        if protocolId != SSTP_ENCAPSULATED_PROTOCOL_PPP:
            logging.warn('Unsupported encapsulated protocol.')
            nak = SSTPControlPacket(SSTP_MSG_CALL_CONNECT_NAK)
            nak.attributes = [(SSTP_ATTRIB_ENCAPSULATED_PROTOCOL_ID,
                    ATTRIB_STATUS_VALUE_NOT_SUPPORTED)]
            self.addRetryCounterOrAbrot()
            return
        self.nonce = os.urandom(32)
        ack = SSTPControlPacket(SSTP_MSG_CALL_CONNECT_ACK)
        # 3 bytes reserved + 1 byte hash bitmap (SHA-1 only) + nonce.
        ack.attributes = [(SSTP_ATTRIB_CRYPTO_BINDING_REQ,
                '\x00\x00\x00' + '\x03' + self.nonce)]
        ack.writeTo(self.transport.write)
        self.pppd = PPPDProtocol()
        self.pppd.sstp = self
        if self.factory.remotePool:
            self.pppd.remote = self.factory.remotePool.apply()
            if self.pppd.remote is None:
                logging.warn('IP address pool is full. '
                             'Cannot accpet new connection.')
                self.abort()
        else:
            self.pppd.remote = ''

        addressArgument = '%s:%s' % (self.factory.local, self.pppd.remote)
        reactor.spawnProcess(self.pppd, self.factory.pppd,
                args=['local', 'file', self.factory.pppdConfigFile,
                    '115200', addressArgument,
                    'remotenumber', str(self.transport.getPeer().host)], usePTY=True)
        self.transport.registerProducer(self.pppd, True)
        self.pppd.resumeProducing()
        self.state = SERVER_CALL_CONNECTED_PENDING
Esempio n. 3
0
class SSTPProtocol(Protocol):
    state = SERVER_CALL_DISCONNECTED
    sstpPacketLength = 0
    receiveBuffer = ''
    nonce = None
    pppd = None
    retryCounter = 0

    def __init__(self):
        self.helloTimer = reactor.callLater(60, self.helloTimerExpired)


    def dataReceived(self, data):
        if self.state == SERVER_CALL_DISCONNECTED:
            self.httpDataReceived(data)
        else:
            self.sstpDataReceived(data)


    def connectionLost(self, reason):
        logging.info('Connection finished.')
        if self.pppd is not None and self.pppd.transport is not None:
            self.pppd.transport.loseConnection()
            self.factory.remotePool.unregister(self.pppd.remote)
        if self.helloTimer.active():
            self.helloTimer.cancel()


    def httpDataReceived(self, data):
        self.receiveBuffer += data
        if "\r\n\r\n" not in self.receiveBuffer:
            return
        requestLine = self.receiveBuffer.split('\r\n')[0]
        self.receiveBuffer = ''
        method, uri, version = requestLine.split()
        if method != "SSTP_DUPLEX_POST" and version != "HTTP/1.1":
            logging.warn('Unexpected HTTP method and/or version.')
            self.transport.loseConnection()
            return
        self.transport.write('HTTP/1.1 200 OK\r\n'
                'Content-Length: 18446744073709551615\r\n'
                'Server: sorztest/0.1\r\n\r\n')
        self.state = SERVER_CONNECT_REQUEST_PENDING


    def sstpDataReceived(self, data):
        self.receiveBuffer += data
        while len(self.receiveBuffer) >= 4:
            # Check version.
            if self.receiveBuffer[0] != '\x10':
                logging.warn('Unsupported SSTP version.')
                self.transport.loseConnection()
                return
            # Get length if necessary.
            if not self.sstpPacketLength:
                self.sstpPacketLength = parseLength(self.receiveBuffer[2:4])
            if len(self.receiveBuffer) < self.sstpPacketLength:
                return
            packet = self.receiveBuffer[:self.sstpPacketLength]
            self.receiveBuffer = self.receiveBuffer[self.sstpPacketLength:]
            self.sstpPacketLength = 0
            self.sstpPacketReceived(packet)


    def sstpPacketReceived(self, packet):
        self.helloTimer.reset(60)
        c = ord(packet[1]) & 0x01
        if c == 0:  # Data packet
            self.sstpDataPacketReceived(packet[4:])
        else:  # Control packet
            messageType = packet[4:6]
            numAttributes = struct.unpack('!H', packet[6:8])[0]
            attributes = []
            attrs = packet[8:]
            while len(attributes) < numAttributes:
                id = attrs[1]
                length = parseLength(attrs[2:4])
                value = attrs[4:length]
                attrs = attrs[length:]
                attributes.append((id, value))
            self.sstpControlPacketReceived(messageType, attributes)


    def sstpDataPacketReceived(self, data):
        logging.debug('Forwarding SSTP data to pppd (%s bytes).' % len(data))
        logging.log(VERBOSE, hexdump(data))
        if self.pppd is None:
            print('pppd is None.')
            return
        self.pppd.writeFrame(data)


    def sstpControlPacketReceived(self, messageType, attributes):
        logging.info('SSTP control packet (type %s) received.' % ord(messageType[1]))
        if messageType == SSTP_MSG_CALL_CONNECT_REQUEST:
            protocolId = attributes[0][1]
            self.sstpMsgCallConnectRequestReceived(protocolId)
        elif messageType == SSTP_MSG_CALL_CONNECTED:
            attr = attributes[0][1]
            hashType = attr[3:4]
            nonce = attr[4:36]
            certHash = attr[36:68]
            macHash = attr[68:72]
            self.sstpMsgCallConnectedReceived(hashType, nonce, certHash, macHash)
        elif messageType == SSTP_MSG_CALL_ABORT:
            if attributes:
                self.sstpMsgCallAbort(attributes[0][1])
            else:
                self.sstpMsgCallAbort()
        elif messageType == SSTP_MSG_CALL_DISCONNECT:
            if attributes:
                self.sstpMsgCallDisconnect(attributes[0][1])
            else:
                self.sstpMsgCallDisconnect()
        elif messageType == SSTP_MSG_CALL_DISCONNECT_ACK:
            self.sstpMsgCallDisconnectAck()
        elif messageType == SSTP_MSG_ECHO_REQUEST:
            self.sstpMsgEchoRequest()
        elif messageType == SSTP_MSG_ECHO_RESPONSE:
            self.sstpMsgEchoResponse()
        else:
            logging.warn('Unknown type of SSTP control packet.')
            self.abort(ATTRIB_STATUS_INVALID_FRAME_RECEIVED)


    def sstpMsgCallConnectRequestReceived(self, protocolId):
        if self.state in (CALL_ABORT_TIMEOUT_PENDING, CALL_ABORT_PENDING,
                CALL_DISCONNECT_ACK_PENDING, CALL_DISCONNECT_TIMEOUT_PENDING):
            return
        if self.state != SERVER_CONNECT_REQUEST_PENDING:
            logging.warn('Not in the state.')
            self.transport.loseConnection()
            return
        if protocolId != SSTP_ENCAPSULATED_PROTOCOL_PPP:
            logging.warn('Unsupported encapsulated protocol.')
            nak = SSTPControlPacket(SSTP_MSG_CALL_CONNECT_NAK)
            nak.attributes = [(SSTP_ATTRIB_ENCAPSULATED_PROTOCOL_ID,
                    ATTRIB_STATUS_VALUE_NOT_SUPPORTED)]
            self.addRetryCounterOrAbrot()
            return
        self.nonce = os.urandom(32)
        ack = SSTPControlPacket(SSTP_MSG_CALL_CONNECT_ACK)
        # 3 bytes reserved + 1 byte hash bitmap (SHA-1 only) + nonce.
        ack.attributes = [(SSTP_ATTRIB_CRYPTO_BINDING_REQ,
                '\x00\x00\x00' + '\x03' + self.nonce)]
        ack.writeTo(self.transport.write)
        self.pppd = PPPDProtocol()
        self.pppd.sstp = self
        self.pppd.remote = self.factory.remotePool.apply()
        if self.pppd.remote is None:
            logging.warn('IP address pool is full. '
                    'Cannot accpet new connection.')
            self.abort()
        addressArgument = '%s:%s' % (self.factory.local, self.pppd.remote)
        reactor.spawnProcess(self.pppd, self.factory.pppd,
                args=['local', 'file', self.factory.pppdConfigFile,
                    '115200', addressArgument], usePTY=True)
        self.transport.registerProducer(self.pppd, True)
        self.pppd.resumeProducing()
        self.state = SERVER_CALL_CONNECTED_PENDING


    def sstpMsgCallConnectedReceived(self, hashType, nonce, certHash, macHash):
        if self.state in (CALL_ABORT_TIMEOUT_PENDING, CALL_ABORT_PENDING,
                CALL_DISCONNECT_ACK_PENDING, CALL_DISCONNECT_TIMEOUT_PENDING):
            return
        if self.state != SERVER_CALL_CONNECTED_PENDING:
            self.abort(ATTRIB_STATUS_UNACCEPTED_FRAME_RECEIVED)
        # TODO: check certHash and macHash
        logging.debug("Received cert hash: %s", certHash.encode('hex'))
        logging.debug("Received MAC hash: %s", macHash.encode('hex'))
        logging.debug("Hash type: %s", hex(ord(hashType)))

        if nonce != self.nonce:
            logging.warn('Received wrong nonce.')
            self.abort(ATTRIB_STATUS_INVALID_FRAME_RECEIVED)
            return
        self.nonce = None

        if certHash is not None and certHash not in self.factory.certHash:
            logging.warning("Certificate hash mismatch between server's "
                            "and client's. Reject this connection.")
            self.abort(ATTRIB_STATUS_INVALID_FRAME_RECEIVED)
            return

        self.state = SERVER_CALL_CONNECTED
        logging.info('Connection established.')


    def sstpMsgCallAbort(self, status=None):
        if self.state in (CALL_ABORT_TIMEOUT_PENDING,
                CALL_DISCONNECT_TIMEOUT_PENDING):
            return
        logging.warn("Call abort.")
        if self.state == CALL_ABORT_PENDING:
            reactor.callLater(1, self.transport.loseConnection)
            return
        self.state = CALL_ABORT_IN_PROGRESS_2
        msg = SSTPControlPacket(SSTP_MSG_CALL_ABORT)
        msg.writeTo(self.transport.write)
        self.state = CALL_ABORT_PENDING
        reactor.callLater(1, self.transport.loseConnection)


    def sstpMsgCallDisconnect(self, status=None):
        if self.state in (CALL_ABORT_TIMEOUT_PENDING, CALL_ABORT_PENDING,
                CALL_DISCONNECT_TIMEOUT_PENDING):
            return
        logging.info('Received call disconnect request.')
        self.state = CALL_DISCONNECT_IN_PROGRESS_2
        ack = SSTPControlPacket(SSTP_MSG_CALL_DISCONNECT_ACK)
        ack.writeTo(self.transport.write)
        self.state = CALL_DISCONNECT_TIMEOUT_PENDING
        reactor.callLater(1, self.transport.loseConnection)


    def sstpMsgCallDisconnectAck(self):
        if self.state == CALL_DISCONNECT_ACK_PENDING:
            self.transport.loseConnection()
        elif self.state in (CALL_ABORT_PENDING, CALL_ABORT_TIMEOUT_PENDING,
                CALL_DISCONNECT_TIMEOUT_PENDING):
            return
        self.abort(ATTRIB_STATUS_UNACCEPTED_FRAME_RECEIVED)


    def sstpMsgEchoRequest(self):
        if self.state == SERVER_CALL_CONNECTED:
            response = SSTPControlPacket(SSTP_MSG_ECHO_RESPONSE)
            response.writeTo(self.transport.write)
        elif self.state in (CALL_ABORT_TIMEOUT_PENDING, CALL_ABORT_PENDING,
                CALL_DISCONNECT_ACK_PENDING, CALL_DISCONNECT_TIMEOUT_PENDING):
            return
        self.abort(ATTRIB_STATUS_UNACCEPTED_FRAME_RECEIVED)


    def sstpMsgEchoResponse(self):
        if self.state == SERVER_CALL_CONNECTED:
            self.helloTimer = reactor.callLater(60, self.helloTimerExpired)
        elif self.state in (CALL_ABORT_TIMEOUT_PENDING, CALL_ABORT_PENDING,
                CALL_DISCONNECT_ACK_PENDING, CALL_DISCONNECT_TIMEOUT_PENDING):
            return
        self.abort(ATTRIB_STATUS_UNACCEPTED_FRAME_RECEIVED)


    def helloTimerExpired(self, close=False):
        if self.state == SERVER_CALL_DISCONNECTED:
            self.transport.loseConnection()  # TODO: follow HTTP
        elif close:
            logging.warn('Ping time out.')
            self.abort(ATTRIB_STATUS_NEGOTIATION_TIMEOUT)
        else:
            logging.info('Send echo request.')
            echo = SSTPControlPacket(SSTP_MSG_ECHO_REQUEST)
            echo.writeTo(self.transport.write)
            self.helloTimer = reactor.callLater(60, self.helloTimerExpired, True)


    def addRetryCounterOrAbrot(self):
        self.retryCounter += 1
        if self.retryCounter > 3:
            self.abort(ATTRIB_STATUS_RETRY_COUNT_EXCEEDED)


    def abort(self, status=None):
        if status is None:
            logging.warn('Abort.')
        else:
            logging.warn('Abort (%s).' % ord(status[-1]))
        self.state = CALL_DISCONNECT_IN_PROGRESS_1
        msg = SSTPControlPacket(SSTP_MSG_CALL_ABORT)
        if status is not None:
            msg.attributes = [(SSTP_ATTRIB_STATUS_INFO, status)]
        msg.writeTo(self.transport.write)
        self.state = CALL_ABORT_PENDING
        reactor.callLater(3, self.transport.loseConnection)


    def writePPPControlFrame(self, frame):
        logging.debug('PPP control frame received (%s bytes).' % len(frame))
        logging.log(VERBOSE, hexdump(frame))
        if self.state == SERVER_CALL_CONNECTED_PENDING or \
                self.state == SERVER_CALL_CONNECTED:
            packet = SSTPDataPacket(frame)
            packet.writeTo(self.transport.write)


    def writePPPDataFrame(self, frame):
        logging.debug('PPP data frame received (%s bytes).' % len(frame))
        logging.log(VERBOSE, hexdump(frame))
        if self.state == SERVER_CALL_CONNECTED:
            packet = SSTPDataPacket(frame)
            packet.writeTo(self.transport.write)


    def pppStoped(self):
        if (self.state != SERVER_CONNECT_REQUEST_PENDING and
                self.state != SERVER_CALL_CONNECTED_PENDING and
                self.state != SERVER_CALL_CONNECTED):
            self.transport.loseConnection()
            return
        self.state = CALL_DISCONNECT_IN_PROGRESS_1
        msg = SSTPControlPacket(SSTP_MSG_CALL_DISCONNECT)
        msg.attributes = [(SSTP_ATTRIB_NO_ERROR, ATTRIB_STATUS_NO_ERROR)]
        msg.writeTo(self.transport.write)
        self.state = CALL_DISCONNECT_ACK_PENDING
        reactor.callLater(5, self.transport.loseConnection)
Esempio n. 4
0
class SSTPProtocol(Protocol):
    state = SERVER_CALL_DISCONNECTED
    sstpPacketLength = 0
    receiveBuffer = ''
    nonce = None
    pppd = None
    retryCounter = 0

    def __init__(self):
        self.helloTimer = reactor.callLater(60, self.helloTimerExpired)


    def dataReceived(self, data):
        if self.state == SERVER_CALL_DISCONNECTED:
            self.httpDataReceived(data)
        else:
            self.sstpDataReceived(data)


    def connectionLost(self, reason):
        logging.info('Connection finished.')
        if self.pppd is not None and self.pppd.transport is not None:
            self.pppd.transport.loseConnection()
            if self.factory.remotePool is not None:
                self.factory.remotePool.unregister(self.pppd.remote)
        if self.helloTimer.active():
            self.helloTimer.cancel()


    def httpDataReceived(self, data):
        self.receiveBuffer += data
        if "\r\n\r\n" not in self.receiveBuffer:
            return
        requestLine = self.receiveBuffer.split('\r\n')[0]
        self.receiveBuffer = ''
        method, uri, version = requestLine.split()
        if method != "SSTP_DUPLEX_POST" and version != "HTTP/1.1":
            logging.warn('Unexpected HTTP method and/or version.')
            self.transport.loseConnection()
            return
        self.transport.write('HTTP/1.1 200 OK\r\n'
                'Content-Length: 18446744073709551615\r\n'
                'Server: sorztest/0.1\r\n\r\n')
        self.state = SERVER_CONNECT_REQUEST_PENDING


    def sstpDataReceived(self, data):
        self.receiveBuffer += data
        while len(self.receiveBuffer) >= 4:
            # Check version.
            if self.receiveBuffer[0] != '\x10':
                logging.warn('Unsupported SSTP version.')
                self.transport.loseConnection()
                return
            # Get length if necessary.
            if not self.sstpPacketLength:
                self.sstpPacketLength = parseLength(self.receiveBuffer[2:4])
            if len(self.receiveBuffer) < self.sstpPacketLength:
                return
            packet = self.receiveBuffer[:self.sstpPacketLength]
            self.receiveBuffer = self.receiveBuffer[self.sstpPacketLength:]
            self.sstpPacketLength = 0
            self.sstpPacketReceived(packet)


    def sstpPacketReceived(self, packet):
        self.helloTimer.reset(60)
        c = ord(packet[1]) & 0x01
        if c == 0:  # Data packet
            self.sstpDataPacketReceived(packet[4:])
        else:  # Control packet
            messageType = packet[4:6]
            numAttributes = struct.unpack('!H', packet[6:8])[0]
            attributes = []
            attrs = packet[8:]
            while len(attributes) < numAttributes:
                id = attrs[1]
                length = parseLength(attrs[2:4])
                value = attrs[4:length]
                attrs = attrs[length:]
                attributes.append((id, value))
            self.sstpControlPacketReceived(messageType, attributes)


    def sstpDataPacketReceived(self, data):
        if __debug__:
            logging.debug('Forwarding SSTP data to pppd (%s bytes).' % len(data))
            logging.log(VERBOSE, hexdump(data))
        if self.pppd is None:
            print('pppd is None.')
            return
        self.pppd.writeFrame(data)


    def sstpControlPacketReceived(self, messageType, attributes):
        logging.info('SSTP control packet (type %s) received.' % ord(messageType[1]))
        if messageType == SSTP_MSG_CALL_CONNECT_REQUEST:
            protocolId = attributes[0][1]
            self.sstpMsgCallConnectRequestReceived(protocolId)
        elif messageType == SSTP_MSG_CALL_CONNECTED:
            attr = attributes[0][1]
            hashType = attr[3:4]
            nonce = attr[4:36]
            certHash = attr[36:68]
            macHash = attr[68:72]
            self.sstpMsgCallConnectedReceived(hashType, nonce, certHash, macHash)
        elif messageType == SSTP_MSG_CALL_ABORT:
            if attributes:
                self.sstpMsgCallAbort(attributes[0][1])
            else:
                self.sstpMsgCallAbort()
        elif messageType == SSTP_MSG_CALL_DISCONNECT:
            if attributes:
                self.sstpMsgCallDisconnect(attributes[0][1])
            else:
                self.sstpMsgCallDisconnect()
        elif messageType == SSTP_MSG_CALL_DISCONNECT_ACK:
            self.sstpMsgCallDisconnectAck()
        elif messageType == SSTP_MSG_ECHO_REQUEST:
            self.sstpMsgEchoRequest()
        elif messageType == SSTP_MSG_ECHO_RESPONSE:
            self.sstpMsgEchoResponse()
        else:
            logging.warn('Unknown type of SSTP control packet.')
            self.abort(ATTRIB_STATUS_INVALID_FRAME_RECEIVED)


    def sstpMsgCallConnectRequestReceived(self, protocolId):
        if self.state in (CALL_ABORT_TIMEOUT_PENDING, CALL_ABORT_PENDING,
                CALL_DISCONNECT_ACK_PENDING, CALL_DISCONNECT_TIMEOUT_PENDING):
            return
        if self.state != SERVER_CONNECT_REQUEST_PENDING:
            logging.warn('Not in the state.')
            self.transport.loseConnection()
            return
        if protocolId != SSTP_ENCAPSULATED_PROTOCOL_PPP:
            logging.warn('Unsupported encapsulated protocol.')
            nak = SSTPControlPacket(SSTP_MSG_CALL_CONNECT_NAK)
            nak.attributes = [(SSTP_ATTRIB_ENCAPSULATED_PROTOCOL_ID,
                    ATTRIB_STATUS_VALUE_NOT_SUPPORTED)]
            self.addRetryCounterOrAbrot()
            return
        self.nonce = os.urandom(32)
        ack = SSTPControlPacket(SSTP_MSG_CALL_CONNECT_ACK)
        # 3 bytes reserved + 1 byte hash bitmap (SHA-1 only) + nonce.
        ack.attributes = [(SSTP_ATTRIB_CRYPTO_BINDING_REQ,
                '\x00\x00\x00' + '\x03' + self.nonce)]
        ack.writeTo(self.transport.write)
        self.pppd = PPPDProtocol()
        self.pppd.sstp = self
        if self.factory.remotePool:
            self.pppd.remote = self.factory.remotePool.apply()
            if self.pppd.remote is None:
                logging.warn('IP address pool is full. '
                             'Cannot accpet new connection.')
                self.abort()
        else:
            self.pppd.remote = ''

        addressArgument = '%s:%s' % (self.factory.local, self.pppd.remote)
        reactor.spawnProcess(self.pppd, self.factory.pppd,
                args=['local', 'file', self.factory.pppdConfigFile,
                    '115200', addressArgument,
                    'remotenumber', str(self.transport.getPeer().host)], usePTY=True)
        self.transport.registerProducer(self.pppd, True)
        self.pppd.resumeProducing()
        self.state = SERVER_CALL_CONNECTED_PENDING


    def sstpMsgCallConnectedReceived(self, hashType, nonce, certHash, macHash):
        if self.state in (CALL_ABORT_TIMEOUT_PENDING, CALL_ABORT_PENDING,
                CALL_DISCONNECT_ACK_PENDING, CALL_DISCONNECT_TIMEOUT_PENDING):
            return
        if self.state != SERVER_CALL_CONNECTED_PENDING:
            self.abort(ATTRIB_STATUS_UNACCEPTED_FRAME_RECEIVED)
        # TODO: check certHash and macHash
        logging.debug("Received cert hash: %s", certHash.encode('hex'))
        logging.debug("Received MAC hash: %s", macHash.encode('hex'))
        logging.debug("Hash type: %s", hex(ord(hashType)))

        if nonce != self.nonce:
            logging.warn('Received wrong nonce.')
            self.abort(ATTRIB_STATUS_INVALID_FRAME_RECEIVED)
            return
        self.nonce = None

        if certHash is not None and certHash not in self.factory.certHash:
            logging.warning("Certificate hash mismatch between server's "
                            "and client's. Reject this connection.")
            self.abort(ATTRIB_STATUS_INVALID_FRAME_RECEIVED)
            return

        self.state = SERVER_CALL_CONNECTED
        logging.info('Connection established.')


    def sstpMsgCallAbort(self, status=None):
        if self.state in (CALL_ABORT_TIMEOUT_PENDING,
                CALL_DISCONNECT_TIMEOUT_PENDING):
            return
        logging.warn("Call abort.")
        if self.state == CALL_ABORT_PENDING:
            reactor.callLater(1, self.transport.loseConnection)
            return
        self.state = CALL_ABORT_IN_PROGRESS_2
        msg = SSTPControlPacket(SSTP_MSG_CALL_ABORT)
        msg.writeTo(self.transport.write)
        self.state = CALL_ABORT_PENDING
        reactor.callLater(1, self.transport.loseConnection)


    def sstpMsgCallDisconnect(self, status=None):
        if self.state in (CALL_ABORT_TIMEOUT_PENDING, CALL_ABORT_PENDING,
                CALL_DISCONNECT_TIMEOUT_PENDING):
            return
        logging.info('Received call disconnect request.')
        self.state = CALL_DISCONNECT_IN_PROGRESS_2
        ack = SSTPControlPacket(SSTP_MSG_CALL_DISCONNECT_ACK)
        ack.writeTo(self.transport.write)
        self.state = CALL_DISCONNECT_TIMEOUT_PENDING
        reactor.callLater(1, self.transport.loseConnection)


    def sstpMsgCallDisconnectAck(self):
        if self.state == CALL_DISCONNECT_ACK_PENDING:
            self.transport.loseConnection()
        elif self.state in (CALL_ABORT_PENDING, CALL_ABORT_TIMEOUT_PENDING,
                CALL_DISCONNECT_TIMEOUT_PENDING):
            return
        self.abort(ATTRIB_STATUS_UNACCEPTED_FRAME_RECEIVED)


    def sstpMsgEchoRequest(self):
        if self.state == SERVER_CALL_CONNECTED:
            response = SSTPControlPacket(SSTP_MSG_ECHO_RESPONSE)
            response.writeTo(self.transport.write)
        elif self.state in (CALL_ABORT_TIMEOUT_PENDING, CALL_ABORT_PENDING,
                CALL_DISCONNECT_ACK_PENDING, CALL_DISCONNECT_TIMEOUT_PENDING):
            return
        self.abort(ATTRIB_STATUS_UNACCEPTED_FRAME_RECEIVED)


    def sstpMsgEchoResponse(self):
        if self.state == SERVER_CALL_CONNECTED:
            self.helloTimer = reactor.callLater(60, self.helloTimerExpired)
        elif self.state in (CALL_ABORT_TIMEOUT_PENDING, CALL_ABORT_PENDING,
                CALL_DISCONNECT_ACK_PENDING, CALL_DISCONNECT_TIMEOUT_PENDING):
            return
        self.abort(ATTRIB_STATUS_UNACCEPTED_FRAME_RECEIVED)


    def helloTimerExpired(self, close=False):
        if self.state == SERVER_CALL_DISCONNECTED:
            self.transport.loseConnection()  # TODO: follow HTTP
        elif close:
            logging.warn('Ping time out.')
            self.abort(ATTRIB_STATUS_NEGOTIATION_TIMEOUT)
        else:
            logging.info('Send echo request.')
            echo = SSTPControlPacket(SSTP_MSG_ECHO_REQUEST)
            echo.writeTo(self.transport.write)
            self.helloTimer = reactor.callLater(60, self.helloTimerExpired, True)


    def addRetryCounterOrAbrot(self):
        self.retryCounter += 1
        if self.retryCounter > 3:
            self.abort(ATTRIB_STATUS_RETRY_COUNT_EXCEEDED)


    def abort(self, status=None):
        if status is None:
            logging.warn('Abort.')
        else:
            logging.warn('Abort (%s).' % ord(status[-1]))
        self.state = CALL_DISCONNECT_IN_PROGRESS_1
        msg = SSTPControlPacket(SSTP_MSG_CALL_ABORT)
        if status is not None:
            msg.attributes = [(SSTP_ATTRIB_STATUS_INFO, status)]
        msg.writeTo(self.transport.write)
        self.state = CALL_ABORT_PENDING
        reactor.callLater(3, self.transport.loseConnection)


    def writePPPControlFrame(self, frame):
        logging.debug('PPP control frame received (%s bytes).' % len(frame))
        logging.log(VERBOSE, hexdump(frame))
        if self.state == SERVER_CALL_CONNECTED_PENDING or \
                self.state == SERVER_CALL_CONNECTED:
            packet = SSTPDataPacket(frame)
            packet.writeTo(self.transport.write)


    def writePPPDataFrame(self, frame):
        if __debug__:
            logging.debug('PPP data frame received (%s bytes).' % len(frame))
            logging.log(VERBOSE, hexdump(frame))
        if self.state == SERVER_CALL_CONNECTED:
            packet = SSTPDataPacket(frame)
            packet.writeTo(self.transport.write)


    def pppStoped(self):
        if (self.state != SERVER_CONNECT_REQUEST_PENDING and
                self.state != SERVER_CALL_CONNECTED_PENDING and
                self.state != SERVER_CALL_CONNECTED):
            self.transport.loseConnection()
            return
        self.state = CALL_DISCONNECT_IN_PROGRESS_1
        msg = SSTPControlPacket(SSTP_MSG_CALL_DISCONNECT)
        msg.attributes = [(SSTP_ATTRIB_NO_ERROR, ATTRIB_STATUS_NO_ERROR)]
        msg.writeTo(self.transport.write)
        self.state = CALL_DISCONNECT_ACK_PENDING
        reactor.callLater(5, self.transport.loseConnection)