Beispiel #1
0
 def append_extended_data_received(self, data_type_code, data):
     """append_extended_data_received(self, data_type_code, data) -> None
     Indicates that the given extended data was received.
     """
     if data:
         if self.treat_extended_data_as_regular:
             self.append_data_received(data)
         else:
             if data_type_code in self.extended_recv_buffer:
                 self.extended_recv_buffer[data_type_code].write(data)
             else:
                 b = Buffer()
                 b.write(data)
                 self.extended_recv_buffer[data_type_code] = b
Beispiel #2
0
 def __init__(self, connection_service):
     self.connection_service = connection_service
     # Local reference for convenience.
     self.transport = connection_service.transport
     self.recv_buffer = Buffer()
     self.extended_recv_buffer = {}
     self.remote_channel = Remote_Channel()
     self.window_data_added_cv = coro.condition_variable()
     self.channel_request_cv = coro.condition_variable()
     self.channel_open_cv = coro.condition_variable()
Beispiel #3
0
class Channel:
    name = ""
    channel_id = 0
    window_size = 131072  # 128k
    max_packet_size = 131072  # 128k
    # Additional ssh.util.packet data types used in the CHANNEL_OPEN message.
    additional_packet_data_types = ()

    # This is a flag you can change if you want to handle extended data
    # differently.
    treat_extended_data_as_regular = 1

    # This is how many bytes the remote side can send.
    # Once it hits zero, I start ignoring data.
    # The current algorithm is to increase the window size back to the
    # initial size whenever this number drops below one half.
    window_data_left = window_size

    # Condition variable triggered whenever the window is updated.
    window_data_added_cv = None

    closed = 1
    eof = 1

    # This is the instance that is created for data flowing in from the other
    # side.
    remote_channel = None

    # This is a buffer of data received.  It is a Buffer instance.
    # A value of '' in the buffer indicates EOF.
    recv_buffer = None
    # This is a buffer of extended data received.
    # The key is the data_type_code, and the value is a Buffer instance.
    extended_recv_buffer = None

    # This is a condition variable triggered when channel request responses
    # are received.  The thread is awoken with a boolean value that indicates
    # whether or not the request succeeded.
    # XXX: There is a problem that this doesn't handle concurrent requests.
    channel_request_cv = None

    # This is a condition variable triggered when open success or failure
    # is received.
    channel_open_cv = None

    def __init__(self, connection_service):
        self.connection_service = connection_service
        # Local reference for convenience.
        self.transport = connection_service.transport
        self.recv_buffer = Buffer()
        self.extended_recv_buffer = {}
        self.remote_channel = Remote_Channel()
        self.window_data_added_cv = coro.condition_variable()
        self.channel_request_cv = coro.condition_variable()
        self.channel_open_cv = coro.condition_variable()

    def __str__(self):
        return "<Channel %s ID:%i>" % (self.name, self.channel_id)

    def get_additional_open_data(self):
        """get_additional_open_data(self) -> data
        Returns the additional information used for opening a channel.
        <data> is a tuple of the actual data that is appended to the packet.
        """
        # No additional data by default.
        return ()

    def set_additional_open_data(self, data):
        """set_additional_open_data(self, data) -> None
        Sets the additional data information.
        <data> is a tuple of the data elements specific to this channel type.
        """
        # By default ignore any open data.
        return None

    def send_channel_request(self, request_type, payload, data, want_reply=1, default_reply_handler=True):
        """send_channel_request(self, request_type, payload, data, want_reply=1, default_reply_handler=True) -> None
        This is a generic mechanism for sending a channel request packet.

        <request_type>: Request type to send.
        <payload>: The ssh_packet format definition.
        <data>: The data to go with the payload.
        <want_reply>: The want_reply flag.
        <default_reply_handler>: If true and want_reply is True, then the
                default reply handler will be used.  The default reply
                handler is pretty simple.  Will just return if
                CHANNEL_REQUEST_SUCCESS is received.  Will raise
                Channel_Request_Failure if FAILURE is received.
        """
        # XXX: There is a problem with the spec.  It does not indicate how to
        #      match requests with responses.  IIRC, they talked about it on
        #      the mailing list and updated the spec.  Need to investigate if
        #      they have clarified the spec on how to handle this.
        # XXX: Or maybe concurrent channel requests on the same channel are
        #      not allowed?
        if self.remote_channel.closed:
            raise Channel_Closed_Error
        pkt = packet.pack_payload(
            SSH_MSG_CHANNEL_REQUEST_PAYLOAD,
            (SSH_MSG_CHANNEL_REQUEST, self.remote_channel.channel_id, request_type, int(want_reply)),
        )
        pkt_data = packet.pack_payload(payload, data)
        self.transport.send_packet(pkt + pkt_data)
        if want_reply and default_reply_handler:
            # Wait for response.
            assert len(self.channel_request_cv) == 0, "Concurrent channel requests not supported!"
            if not self.channel_request_cv.wait():
                raise Channel_Request_Failure

    def append_data_received(self, data):
        """append_data_received(self, data) -> None
        Indicates that the given data was received.
        """
        if data:
            self.recv_buffer.write(data)

    def append_extended_data_received(self, data_type_code, data):
        """append_extended_data_received(self, data_type_code, data) -> None
        Indicates that the given extended data was received.
        """
        if data:
            if self.treat_extended_data_as_regular:
                self.append_data_received(data)
            else:
                if data_type_code in self.extended_recv_buffer:
                    self.extended_recv_buffer[data_type_code].write(data)
                else:
                    b = Buffer()
                    b.write(data)
                    self.extended_recv_buffer[data_type_code] = b

    def set_eof(self):
        """set_eof(self) -> None
        Indicate that there is no more data on this channel.
        """
        self.eof = 1
        self.recv_buffer.write("")
        for b in self.extended_recv_buffer.values():
            b.write("")

    def handle_request(self, request_type, want_reply, type_specific_packet_data):
        # Default is always to fail.  Specific channel types override this method.
        if want_reply:
            pkt = packet.pack_payload(
                SSH_MSG_CHANNEL_FAILURE_PAYLOAD, (SSH_MSG_CHANNEL_FAILURE, self.remote_channel.channel_id)
            )
            self.transport.send_packet(pkt)

    def open(self):
        """open(self) -> None
        Opens the channel to the remote side.
        """
        assert self.closed
        self.connection_service.register_channel(self)
        self.transport.debug.write(debug.DEBUG_2, "sending channel open request channel ID %i", (self.channel_id,))

        # Send the open request.
        additional_data = self.get_additional_open_data()
        packet_payload = SSH_MSG_CHANNEL_OPEN_PAYLOAD + self.additional_packet_data_types
        packet_data = (
            SSH_MSG_CHANNEL_OPEN,
            self.name,
            self.channel_id,
            self.window_size,
            self.max_packet_size,
        ) + additional_data
        pkt = packet.pack_payload(packet_payload, packet_data)
        self.transport.send_packet(pkt)
        success, data = self.channel_open_cv.wait()
        if success:
            self.set_additional_open_data(data)
        else:
            reason_code, reason_text, language = data
            raise Channel_Open_Error(self.channel_id, reason_code, reason_text, language)

    def close(self):
        """close(self) -> None
        Tell remote side to close its channel.
        Our side is not considered "closed" until after we receive
        SSH_MSG_CHANNEL_CLOSE from the remote side.
        """
        if not self.remote_channel.closed:
            self.remote_channel.closed = 1
            pkt = packet.pack_payload(
                SSH_MSG_CHANNEL_CLOSE_PAYLOAD, (SSH_MSG_CHANNEL_CLOSE, self.remote_channel.channel_id)
            )
            self.transport.send_packet(pkt)

            # We need to cause any threads that were trying to write on
            # this channel to stop trying to write. If they were asleep
            # waiting for one of the three condition variables, we need to
            # wake them up. They will notice that self.remote_channel.closed
            # is now true, and will do the right thing.
            self.window_data_added_cv.wake_all()
            self.channel_request_cv.wake_all(False)
            self.channel_open_cv.wake_all((False, (SSH_OPEN_CONNECT_FAILED, "Channel has been closed", None)))

    def send_window_adjustment(self, bytes_to_add):
        self.transport.debug.write(debug.DEBUG_2, "sending window adjustment to add %i bytes", (bytes_to_add,))
        pkt = packet.pack_payload(
            SSH_MSG_CHANNEL_WINDOW_ADJUST_PAYLOAD,
            (SSH_MSG_CHANNEL_WINDOW_ADJUST, self.remote_channel.channel_id, bytes_to_add),
        )
        self.transport.send_packet(pkt)
        self.window_data_left += bytes_to_add

    def has_data_to_read(self, extended=None):
        """has_data_to_read(self, extended=None) -> boolean
        Returns whether or not there is data available to read.

        <extended>: data_type_code of extended data type to read.
                    Set to None for normal data.
        """
        if extended is None:
            b = self.recv_buffer
        else:
            if extended in self.extended_recv_buffer:
                b = self.extended_recv_buffer[extended]
            else:
                return False
        if b and b.fifo.peek() != "":
            return True
        else:
            return False

    def _check_window_adjust(self):
        if self.window_data_left < self.window_size / 2:
            # Increase the window so that the other side may send more data.
            self.send_window_adjustment(self.window_size - self.window_data_left)

    def read(self, bytes, extended=None):
        """read(self, bytes, extended=None) -> data
        Read data off the channel.
        Reads at most <bytes> bytes.  It may return less than <bytes> even
        if there is more data in the buffer.

        <bytes>: Number of bytes to read.
        <extended>: data_type_code of extended data type to read.
                    Set to None to read normal data.
        """
        if extended is not None:
            if extended not in self.extended_recv_buffer:
                self.extended_recv_buffer[extended] = Buffer()
            b = self.extended_recv_buffer[extended]
        else:
            b = self.recv_buffer
        result = b.read_at_most(bytes)
        # Only adjust the window when the buffer is clear.
        if not b:
            self._check_window_adjust()
        return result

    def read_exact(self, bytes, extended=None):
        """read_exact(self, bytes, extended=None) -> data
        Read exactly <bytes> number of bytes off the channel.
        May return less than <bytes> bytes if EOF is reached.

        <bytes>: Number of bytes to read.  Blocks until enough data is
                 available.
        <extended>: data_type_code of extended data type to read.
                    Set to None to read normal data.
        """
        if extended is not None:
            if extended not in self.extended_recv_buffer:
                self.extended_recv_buffer[extended] = Buffer()
            b = self.extended_recv_buffer[extended]
        else:
            b = self.recv_buffer

        result = []
        bytes_left = bytes
        while bytes_left > 0:
            data = b.read_at_most(bytes_left)
            if not data:
                if result:
                    return "".join(result)
                else:
                    raise EOFError
            result.append(data)
            bytes_left -= len(data)
            # Only adjust the window when the buffer is clear.
            if not b:
                self._check_window_adjust()
        return "".join(result)

    # Make an alias for convenience.
    recv = read

    def send(self, data):
        """send(self, data) -> None
        Send the given data string.
        """
        data_start = 0
        while data_start < len(data):
            if self.remote_channel.closed:
                raise Channel_Closed_Error
            while self.remote_channel.window_data_left == 0:
                # Currently waiting for window update.
                self.window_data_added_cv.wait()
                # check again inside loop since if we're closed, the window
                # might never update
                if self.remote_channel.closed:
                    raise Channel_Closed_Error
            # Send what we can.
            max_size = min(self.remote_channel.window_data_left, self.remote_channel.max_packet_size)
            data_to_send = data[data_start : data_start + max_size]
            data_start += max_size

            pkt = packet.pack_payload(
                SSH_MSG_CHANNEL_DATA_PAYLOAD, (SSH_MSG_CHANNEL_DATA, self.remote_channel.channel_id, data_to_send)
            )
            self.transport.debug.write(
                debug.DEBUG_3,
                "channel %i window lowered by %i to %i",
                (self.remote_channel.channel_id, len(data_to_send), self.remote_channel.window_data_left),
            )
            self.remote_channel.window_data_left -= len(data_to_send)
            self.transport.send_packet(pkt)

    def send_extended(self, data, data_type_code):
        """send_extended(self, data, data_type_code) -> None
        Send the given data string as extended data with the given data_type_code.
        """
        data_start = 0

        while data_start < len(data):
            if self.remote_channel.closed:
                raise Channel_Closed_Error
            while self.remote_channel.window_data_left == 0:
                # Currently waiting for window update.
                self.window_data_added_cv.wait()
                # check again inside loop since if we're closed, the window
                # might never update
                if self.remote_channel.closed:
                    raise Channel_Closed_Error

            # Send what we can.
            max_size = min(self.remote_channel.window_data_left, self.remote_channel.max_packet_size)
            data_to_send = data[data_start : data_start + max_size]
            data_start += max_size

            pkt = packet.pack_payload(
                SSH_MSG_CHANNEL_EXTENDED_DATA_PAYLOAD,
                (SSH_MSG_CHANNEL_EXTENDED_DATA, self.remote_channel.channel_id, data_type_code, data_to_send),
            )
            self.remote_channel.window_data_left -= len(data_to_send)
            self.transport.send_packet(pkt)

    def channel_request_success(self):
        """channel_request_success(self) -> None
        This is called whenever a CHANNEL_SUCCESS message is received.
        """
        self.channel_request_cv.wake_one(args=True)

    def send_channel_request_success(self):
        self.transport.send(SSH_MSG_CHANNEL_SUCCESS_PAYLOAD, (SSH_MSG_CHANNEL_SUCCESS, self.remote_channel.channel_id))

    def channel_request_failure(self):
        """channel_request_success(self) -> None
        This is called whenever a CHANNEL_FAILURE message is received.
        """
        self.channel_request_cv.wake_one(args=False)

    def send_channel_request_failure(self):
        self.transport.send(SSH_MSG_CHANNEL_FAILURE_PAYLOAD, (SSH_MSG_CHANNEL_FAILURE, self.remote_channel.channel_id))

    def channel_open_success(self, data):
        """channel_open_success(self, data) -> None
        Indicates the channel is opened.

        <data> is a tuple of the data elements specific to this channel type.
        """
        # Default is to ignore any extra data.
        assert len(self.channel_open_cv) == 1
        self.channel_open_cv.wake_one((True, data))

    def channel_open_failure(self, reason_code, reason_text, language):
        """channel_open_failure(self, reason_code, reason_text, language) -> None
        This is called when opening a channel fails.
        """
        assert len(self.channel_open_cv) == 1
        self.channel_open_cv.wake_one((False, (reason_code, reason_text, language)))