Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
packet.py650 linesDownload Raw Back to paramiko
1# Copyright (C) 2003-2007  Robey Pointer <robeypointer@gmail.com>2#3# This file is part of paramiko.4#5# Paramiko is free software; you can redistribute it and/or modify it under the6# terms of the GNU Lesser General Public License as published by the Free7# Software Foundation; either version 2.1 of the License, or (at your option)8# any later version.9#10# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY11# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR12# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more13# details.14#15# You should have received a copy of the GNU Lesser General Public License16# along with Paramiko; if not, write to the Free Software Foundation, Inc.,17# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.18 19"""20Packet handling21"""22 23import errno24import os25import socket26import struct27import threading28import time29from hmac import HMAC30 31from paramiko import util32from paramiko.common import (33    linefeed_byte,34    cr_byte_value,35    MSG_NAMES,36    DEBUG,37    xffffffff,38    zero_byte,39    byte_ord,40)41from paramiko.util import u42from paramiko.ssh_exception import SSHException, ProxyCommandFailure43from paramiko.message import Message44 45 46def compute_hmac(key, message, digest_class):47    return HMAC(key, message, digest_class).digest()48 49 50class NeedRekeyException(Exception):51    """52    Exception indicating a rekey is needed.53    """54 55    pass56 57 58def first_arg(e):59    arg = None60    if type(e.args) is tuple and len(e.args) > 0:61        arg = e.args[0]62    return arg63 64 65class Packetizer:66    """67    Implementation of the base SSH packet protocol.68    """69 70    # READ the secsh RFC's before raising these values.  if anything,71    # they should probably be lower.72    REKEY_PACKETS = pow(2, 29)73    REKEY_BYTES = pow(2, 29)74 75    # Allow receiving this many packets after a re-key request before76    # terminating77    REKEY_PACKETS_OVERFLOW_MAX = pow(2, 29)78    # Allow receiving this many bytes after a re-key request before terminating79    REKEY_BYTES_OVERFLOW_MAX = pow(2, 29)80 81    def __init__(self, socket):82        self.__socket = socket83        self.__logger = None84        self.__closed = False85        self.__dump_packets = False86        self.__need_rekey = False87        self.__init_count = 088        self.__remainder = bytes()89        self._initial_kex_done = False90 91        # used for noticing when to re-key:92        self.__sent_bytes = 093        self.__sent_packets = 094        self.__received_bytes = 095        self.__received_packets = 096        self.__received_bytes_overflow = 097        self.__received_packets_overflow = 098 99        # current inbound/outbound ciphering:100        self.__block_size_out = 8101        self.__block_size_in = 8102        self.__mac_size_out = 0103        self.__mac_size_in = 0104        self.__block_engine_out = None105        self.__block_engine_in = None106        self.__sdctr_out = False107        self.__mac_engine_out = None108        self.__mac_engine_in = None109        self.__mac_key_out = bytes()110        self.__mac_key_in = bytes()111        self.__compress_engine_out = None112        self.__compress_engine_in = None113        self.__sequence_number_out = 0114        self.__sequence_number_in = 0115        self.__etm_out = False116        self.__etm_in = False117 118        # lock around outbound writes (packet computation)119        self.__write_lock = threading.RLock()120 121        # keepalives:122        self.__keepalive_interval = 0123        self.__keepalive_last = time.time()124        self.__keepalive_callback = None125 126        self.__timer = None127        self.__handshake_complete = False128        self.__timer_expired = False129 130    @property131    def closed(self):132        return self.__closed133 134    def reset_seqno_out(self):135        self.__sequence_number_out = 0136 137    def reset_seqno_in(self):138        self.__sequence_number_in = 0139 140    def set_log(self, log):141        """142        Set the Python log object to use for logging.143        """144        self.__logger = log145 146    def set_outbound_cipher(147        self,148        block_engine,149        block_size,150        mac_engine,151        mac_size,152        mac_key,153        sdctr=False,154        etm=False,155    ):156        """157        Switch outbound data cipher.158        :param etm: Set encrypt-then-mac from OpenSSH159        """160        self.__block_engine_out = block_engine161        self.__sdctr_out = sdctr162        self.__block_size_out = block_size163        self.__mac_engine_out = mac_engine164        self.__mac_size_out = mac_size165        self.__mac_key_out = mac_key166        self.__sent_bytes = 0167        self.__sent_packets = 0168        self.__etm_out = etm169        # wait until the reset happens in both directions before clearing170        # rekey flag171        self.__init_count |= 1172        if self.__init_count == 3:173            self.__init_count = 0174            self.__need_rekey = False175 176    def set_inbound_cipher(177        self,178        block_engine,179        block_size,180        mac_engine,181        mac_size,182        mac_key,183        etm=False,184    ):185        """186        Switch inbound data cipher.187        :param etm: Set encrypt-then-mac from OpenSSH188        """189        self.__block_engine_in = block_engine190        self.__block_size_in = block_size191        self.__mac_engine_in = mac_engine192        self.__mac_size_in = mac_size193        self.__mac_key_in = mac_key194        self.__received_bytes = 0195        self.__received_packets = 0196        self.__received_bytes_overflow = 0197        self.__received_packets_overflow = 0198        self.__etm_in = etm199        # wait until the reset happens in both directions before clearing200        # rekey flag201        self.__init_count |= 2202        if self.__init_count == 3:203            self.__init_count = 0204            self.__need_rekey = False205 206    def set_outbound_compressor(self, compressor):207        self.__compress_engine_out = compressor208 209    def set_inbound_compressor(self, compressor):210        self.__compress_engine_in = compressor211 212    def close(self):213        self.__closed = True214        self.__socket.close()215 216    def set_hexdump(self, hexdump):217        self.__dump_packets = hexdump218 219    def get_hexdump(self):220        return self.__dump_packets221 222    def get_mac_size_in(self):223        return self.__mac_size_in224 225    def get_mac_size_out(self):226        return self.__mac_size_out227 228    def need_rekey(self):229        """230        Returns ``True`` if a new set of keys needs to be negotiated.  This231        will be triggered during a packet read or write, so it should be232        checked after every read or write, or at least after every few.233        """234        return self.__need_rekey235 236    def set_keepalive(self, interval, callback):237        """238        Turn on/off the callback keepalive.  If ``interval`` seconds pass with239        no data read from or written to the socket, the callback will be240        executed and the timer will be reset.241        """242        self.__keepalive_interval = interval243        self.__keepalive_callback = callback244        self.__keepalive_last = time.time()245 246    def read_timer(self):247        self.__timer_expired = True248 249    def start_handshake(self, timeout):250        """251        Tells `Packetizer` that the handshake process started.252        Starts a book keeping timer that can signal a timeout in the253        handshake process.254 255        :param float timeout: amount of seconds to wait before timing out256        """257        if not self.__timer:258            self.__timer = threading.Timer(float(timeout), self.read_timer)259            self.__timer.start()260 261    def handshake_timed_out(self):262        """263        Checks if the handshake has timed out.264 265        If `start_handshake` wasn't called before the call to this function,266        the return value will always be `False`. If the handshake completed267        before a timeout was reached, the return value will be `False`268 269        :return: handshake time out status, as a `bool`270        """271        if not self.__timer:272            return False273        if self.__handshake_complete:274            return False275        return self.__timer_expired276 277    def complete_handshake(self):278        """279        Tells `Packetizer` that the handshake has completed.280        """281        if self.__timer:282            self.__timer.cancel()283            self.__timer_expired = False284            self.__handshake_complete = True285 286    def read_all(self, n, check_rekey=False):287        """288        Read as close to N bytes as possible, blocking as long as necessary.289 290        :param int n: number of bytes to read291        :return: the data read, as a `str`292 293        :raises:294            ``EOFError`` -- if the socket was closed before all the bytes could295            be read296        """297        out = bytes()298        # handle over-reading from reading the banner line299        if len(self.__remainder) > 0:300            out = self.__remainder[:n]301            self.__remainder = self.__remainder[n:]302            n -= len(out)303        while n > 0:304            got_timeout = False305            if self.handshake_timed_out():306                raise EOFError()307            try:308                x = self.__socket.recv(n)309                if len(x) == 0:310                    raise EOFError()311                out += x312                n -= len(x)313            except socket.timeout:314                got_timeout = True315            except socket.error as e:316                # on Linux, sometimes instead of socket.timeout, we get317                # EAGAIN.  this is a bug in recent (> 2.6.9) kernels but318                # we need to work around it.319                arg = first_arg(e)320                if arg == errno.EAGAIN:321                    got_timeout = True322                elif self.__closed:323                    raise EOFError()324                else:325                    raise326            if got_timeout:327                if self.__closed:328                    raise EOFError()329                if check_rekey and (len(out) == 0) and self.__need_rekey:330                    raise NeedRekeyException()331                self._check_keepalive()332        return out333 334    def write_all(self, out):335        self.__keepalive_last = time.time()336        iteration_with_zero_as_return_value = 0337        while len(out) > 0:338            retry_write = False339            try:340                n = self.__socket.send(out)341            except socket.timeout:342                retry_write = True343            except socket.error as e:344                arg = first_arg(e)345                if arg == errno.EAGAIN:346                    retry_write = True347                else:348                    n = -1349            except ProxyCommandFailure:350                raise  # so it doesn't get swallowed by the below catchall351            except Exception:352                # could be: (32, 'Broken pipe')353                n = -1354            if retry_write:355                n = 0356                if self.__closed:357                    n = -1358            else:359                if n == 0 and iteration_with_zero_as_return_value > 10:360                    # We shouldn't retry the write, but we didn't361                    # manage to send anything over the socket. This might be an362                    # indication that we have lost contact with the remote363                    # side, but are yet to receive an EOFError or other socket364                    # errors. Let's give it some iteration to try and catch up.365                    n = -1366                iteration_with_zero_as_return_value += 1367            if n < 0:368                raise EOFError()369            if n == len(out):370                break371            out = out[n:]372        return373 374    def readline(self, timeout):375        """376        Read a line from the socket.  We assume no data is pending after the377        line, so it's okay to attempt large reads.378        """379        buf = self.__remainder380        while linefeed_byte not in buf:381            buf += self._read_timeout(timeout)382        n = buf.index(linefeed_byte)383        self.__remainder = buf[n + 1 :]384        buf = buf[:n]385        if (len(buf) > 0) and (buf[-1] == cr_byte_value):386            buf = buf[:-1]387        return u(buf)388 389    def send_message(self, data):390        """391        Write a block of data using the current cipher, as an SSH block.392        """393        # encrypt this sucka394        data = data.asbytes()395        cmd = byte_ord(data[0])396        if cmd in MSG_NAMES:397            cmd_name = MSG_NAMES[cmd]398        else:399            cmd_name = "${:x}".format(cmd)400        orig_len = len(data)401        self.__write_lock.acquire()402        try:403            if self.__compress_engine_out is not None:404                data = self.__compress_engine_out(data)405            packet = self._build_packet(data)406            if self.__dump_packets:407                self._log(408                    DEBUG,409                    "Write packet <{}>, length {}".format(cmd_name, orig_len),410                )411                self._log(DEBUG, util.format_binary(packet, "OUT: "))412            if self.__block_engine_out is not None:413                if self.__etm_out:414                    # packet length is not encrypted in EtM415                    out = packet[0:4] + self.__block_engine_out.update(416                        packet[4:]417                    )418                else:419                    out = self.__block_engine_out.update(packet)420            else:421                out = packet422            # + mac423            if self.__block_engine_out is not None:424                packed = struct.pack(">I", self.__sequence_number_out)425                payload = packed + (out if self.__etm_out else packet)426                out += compute_hmac(427                    self.__mac_key_out, payload, self.__mac_engine_out428                )[: self.__mac_size_out]429            next_seq = (self.__sequence_number_out + 1) & xffffffff430            if next_seq == 0 and not self._initial_kex_done:431                raise SSHException(432                    "Sequence number rolled over during initial kex!"433                )434            self.__sequence_number_out = next_seq435            self.write_all(out)436 437            self.__sent_bytes += len(out)438            self.__sent_packets += 1439            sent_too_much = (440                self.__sent_packets >= self.REKEY_PACKETS441                or self.__sent_bytes >= self.REKEY_BYTES442            )443            if sent_too_much and not self.__need_rekey:444                # only ask once for rekeying445                msg = "Rekeying (hit {} packets, {} bytes sent)"446                self._log(447                    DEBUG, msg.format(self.__sent_packets, self.__sent_bytes)448                )449                self.__received_bytes_overflow = 0450                self.__received_packets_overflow = 0451                self._trigger_rekey()452        finally:453            self.__write_lock.release()454 455    def read_message(self):456        """457        Only one thread should ever be in this function (no other locking is458        done).459 460        :raises: `.SSHException` -- if the packet is mangled461        :raises: `.NeedRekeyException` -- if the transport should rekey462        """463        header = self.read_all(self.__block_size_in, check_rekey=True)464        if self.__etm_in:465            packet_size = struct.unpack(">I", header[:4])[0]466            remaining = packet_size - self.__block_size_in + 4467            packet = header[4:] + self.read_all(remaining, check_rekey=False)468            mac = self.read_all(self.__mac_size_in, check_rekey=False)469            mac_payload = (470                struct.pack(">II", self.__sequence_number_in, packet_size)471                + packet472            )473            my_mac = compute_hmac(474                self.__mac_key_in, mac_payload, self.__mac_engine_in475            )[: self.__mac_size_in]476            if not util.constant_time_bytes_eq(my_mac, mac):477                raise SSHException("Mismatched MAC")478            header = packet479 480        if self.__block_engine_in is not None:481            header = self.__block_engine_in.update(header)482        if self.__dump_packets:483            self._log(DEBUG, util.format_binary(header, "IN: "))484 485        # When ETM is in play, we've already read the packet size & decrypted486        # everything, so just set the packet back to the header we obtained.487        if self.__etm_in:488            packet = header489        # Otherwise, use the older non-ETM logic490        else:491            packet_size = struct.unpack(">I", header[:4])[0]492 493            # leftover contains decrypted bytes from the first block (after the494            # length field)495            leftover = header[4:]496            if (packet_size - len(leftover)) % self.__block_size_in != 0:497                raise SSHException("Invalid packet blocking")498            buf = self.read_all(499                packet_size + self.__mac_size_in - len(leftover)500            )501            packet = buf[: packet_size - len(leftover)]502            post_packet = buf[packet_size - len(leftover) :]503 504            if self.__block_engine_in is not None:505                packet = self.__block_engine_in.update(packet)506            packet = leftover + packet507 508        if self.__dump_packets:509            self._log(DEBUG, util.format_binary(packet, "IN: "))510 511        if self.__mac_size_in > 0 and not self.__etm_in:512            mac = post_packet[: self.__mac_size_in]513            mac_payload = (514                struct.pack(">II", self.__sequence_number_in, packet_size)515                + packet516            )517            my_mac = compute_hmac(518                self.__mac_key_in, mac_payload, self.__mac_engine_in519            )[: self.__mac_size_in]520            if not util.constant_time_bytes_eq(my_mac, mac):521                raise SSHException("Mismatched MAC")522        padding = byte_ord(packet[0])523        payload = packet[1 : packet_size - padding]524 525        if self.__dump_packets:526            self._log(527                DEBUG,528                "Got payload ({} bytes, {} padding)".format(529                    packet_size, padding530                ),531            )532 533        if self.__compress_engine_in is not None:534            payload = self.__compress_engine_in(payload)535 536        msg = Message(payload[1:])537        msg.seqno = self.__sequence_number_in538        next_seq = (self.__sequence_number_in + 1) & xffffffff539        if next_seq == 0 and not self._initial_kex_done:540            raise SSHException(541                "Sequence number rolled over during initial kex!"542            )543        self.__sequence_number_in = next_seq544 545        # check for rekey546        raw_packet_size = packet_size + self.__mac_size_in + 4547        self.__received_bytes += raw_packet_size548        self.__received_packets += 1549        if self.__need_rekey:550            # we've asked to rekey -- give them some packets to comply before551            # dropping the connection552            self.__received_bytes_overflow += raw_packet_size553            self.__received_packets_overflow += 1554            if (555                self.__received_packets_overflow556                >= self.REKEY_PACKETS_OVERFLOW_MAX557            ) or (558                self.__received_bytes_overflow >= self.REKEY_BYTES_OVERFLOW_MAX559            ):560                raise SSHException(561                    "Remote transport is ignoring rekey requests"562                )563        elif (self.__received_packets >= self.REKEY_PACKETS) or (564            self.__received_bytes >= self.REKEY_BYTES565        ):566            # only ask once for rekeying567            err = "Rekeying (hit {} packets, {} bytes received)"568            self._log(569                DEBUG,570                err.format(self.__received_packets, self.__received_bytes),571            )572            self.__received_bytes_overflow = 0573            self.__received_packets_overflow = 0574            self._trigger_rekey()575 576        cmd = byte_ord(payload[0])577        if cmd in MSG_NAMES:578            cmd_name = MSG_NAMES[cmd]579        else:580            cmd_name = "${:x}".format(cmd)581        if self.__dump_packets:582            self._log(583                DEBUG,584                "Read packet <{}>, length {}".format(cmd_name, len(payload)),585            )586        return cmd, msg587 588    # ...protected...589 590    def _log(self, level, msg):591        if self.__logger is None:592            return593        if issubclass(type(msg), list):594            for m in msg:595                self.__logger.log(level, m)596        else:597            self.__logger.log(level, msg)598 599    def _check_keepalive(self):600        if (601            not self.__keepalive_interval602            or not self.__block_engine_out603            or self.__need_rekey604        ):605            # wait till we're encrypting, and not in the middle of rekeying606            return607        now = time.time()608        if now > self.__keepalive_last + self.__keepalive_interval:609            self.__keepalive_callback()610            self.__keepalive_last = now611 612    def _read_timeout(self, timeout):613        start = time.time()614        while True:615            try:616                x = self.__socket.recv(128)617                if len(x) == 0:618                    raise EOFError()619                break620            except socket.timeout:621                pass622            if self.__closed:623                raise EOFError()624            now = time.time()625            if now - start >= timeout:626                raise socket.timeout()627        return x628 629    def _build_packet(self, payload):630        # pad up at least 4 bytes, to nearest block-size (usually 8)631        bsize = self.__block_size_out632        # do not include payload length in computations for padding in EtM mode633        # (payload length won't be encrypted)634        addlen = 4 if self.__etm_out else 8635        padding = 3 + bsize - ((len(payload) + addlen) % bsize)636        packet = struct.pack(">IB", len(payload) + padding + 1, padding)637        packet += payload638        if self.__sdctr_out or self.__block_engine_out is None:639            # cute trick i caught openssh doing: if we're not encrypting or640            # SDCTR mode (RFC4344),641            # don't waste random bytes for the padding642            packet += zero_byte * padding643        else:644            packet += os.urandom(padding)645        return packet646 647    def _trigger_rekey(self):648        # outside code should check for this flag649        self.__need_rekey = True650 
codekingpro/portable-devtools · Team Ai