diff options
Diffstat (limited to 'paramiko/packet.py')
-rw-r--r-- | paramiko/packet.py | 151 |
1 files changed, 55 insertions, 96 deletions
diff --git a/paramiko/packet.py b/paramiko/packet.py index 6ab7363..e97d92f 100644 --- a/paramiko/packet.py +++ b/paramiko/packet.py @@ -17,27 +17,25 @@ # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. """ -Packetizer. +Packet handling """ import errno -import select +import os import socket import struct import threading import time +from hmac import HMAC -from paramiko.common import * from paramiko import util +from paramiko.common import linefeed_byte, cr_byte_value, asbytes, MSG_NAMES, \ + DEBUG, xffffffff, zero_byte +from paramiko.py3compat import u, byte_ord from paramiko.ssh_exception import SSHException, ProxyCommandFailure from paramiko.message import Message -try: - from r_hmac import HMAC -except ImportError: - from Crypto.Hash.HMAC import HMAC - def compute_hmac(key, message, digest_class): return HMAC(key, message, digest_class).digest() @@ -56,8 +54,8 @@ class Packetizer (object): REKEY_PACKETS = pow(2, 29) REKEY_BYTES = pow(2, 29) - REKEY_PACKETS_OVERFLOW_MAX = pow(2,29) # Allow receiving this many packets after a re-key request before terminating - REKEY_BYTES_OVERFLOW_MAX = pow(2,29) # Allow receiving this many bytes after a re-key request before terminating + REKEY_PACKETS_OVERFLOW_MAX = pow(2, 29) # Allow receiving this many packets after a re-key request before terminating + REKEY_BYTES_OVERFLOW_MAX = pow(2, 29) # Allow receiving this many bytes after a re-key request before terminating def __init__(self, socket): self.__socket = socket @@ -66,7 +64,7 @@ class Packetizer (object): self.__dump_packets = False self.__need_rekey = False self.__init_count = 0 - self.__remainder = '' + self.__remainder = bytes() # used for noticing when to re-key: self.__sent_bytes = 0 @@ -86,12 +84,12 @@ class Packetizer (object): self.__sdctr_out = False self.__mac_engine_out = None self.__mac_engine_in = None - self.__mac_key_out = '' - self.__mac_key_in = '' + self.__mac_key_out = bytes() + self.__mac_key_in = bytes() self.__compress_engine_out = None self.__compress_engine_in = None - self.__sequence_number_out = 0L - self.__sequence_number_in = 0L + self.__sequence_number_out = 0 + self.__sequence_number_in = 0 # lock around outbound writes (packet computation) self.__write_lock = threading.RLock() @@ -103,7 +101,7 @@ class Packetizer (object): def set_log(self, log): """ - Set the python log object to use for logging. + Set the Python log object to use for logging. """ self.__logger = log @@ -152,6 +150,7 @@ class Packetizer (object): def close(self): self.__closed = True + self.__socket.close() def set_hexdump(self, hexdump): self.__dump_packets = hexdump @@ -167,17 +166,15 @@ class Packetizer (object): def need_rekey(self): """ - Returns C{True} if a new set of keys needs to be negotiated. This + Returns ``True`` if a new set of keys needs to be negotiated. This will be triggered during a packet read or write, so it should be checked after every read or write, or at least after every few. - - @return: C{True} if a new set of keys needs to be negotiated """ return self.__need_rekey def set_keepalive(self, interval, callback): """ - Turn on/off the callback keepalive. If C{interval} seconds pass with + Turn on/off the callback keepalive. If ``interval`` seconds pass with no data read from or written to the socket, the callback will be executed and the timer will be reset. """ @@ -189,21 +186,18 @@ class Packetizer (object): """ Read as close to N bytes as possible, blocking as long as necessary. - @param n: number of bytes to read - @type n: int - @return: the data read - @rtype: str - @raise EOFError: if the socket was closed before all the bytes could - be read + :param int n: number of bytes to read + :return: the data read, as a `str` + + :raises EOFError: + if the socket was closed before all the bytes could be read """ - out = '' + out = bytes() # handle over-reading from reading the banner line if len(self.__remainder) > 0: out = self.__remainder[:n] self.__remainder = self.__remainder[n:] n -= len(out) - if PY22: - return self._py22_read_all(n, out) while n > 0: got_timeout = False try: @@ -214,7 +208,7 @@ class Packetizer (object): n -= len(x) except socket.timeout: got_timeout = True - except socket.error, e: + except socket.error as e: # on Linux, sometimes instead of socket.timeout, we get # EAGAIN. this is a bug in recent (> 2.6.9) kernels but # we need to work around it. @@ -243,7 +237,7 @@ class Packetizer (object): n = self.__socket.send(out) except socket.timeout: retry_write = True - except socket.error, e: + except socket.error as e: if (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EAGAIN): retry_write = True elif (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EINTR): @@ -252,7 +246,7 @@ class Packetizer (object): else: n = -1 except ProxyCommandFailure: - raise # so it doesn't get swallowed by the below catchall + raise # so it doesn't get swallowed by the below catchall except Exception: # could be: (32, 'Broken pipe') n = -1 @@ -273,22 +267,22 @@ class Packetizer (object): line, so it's okay to attempt large reads. """ buf = self.__remainder - while not '\n' in buf: + while not linefeed_byte in buf: buf += self._read_timeout(timeout) - n = buf.index('\n') - self.__remainder = buf[n+1:] + n = buf.index(linefeed_byte) + self.__remainder = buf[n + 1:] buf = buf[:n] - if (len(buf) > 0) and (buf[-1] == '\r'): + if (len(buf) > 0) and (buf[-1] == cr_byte_value): buf = buf[:-1] - return buf + return u(buf) def send_message(self, data): """ Write a block of data using the current cipher, as an SSH block. """ # encrypt this sucka - data = str(data) - cmd = ord(data[0]) + data = asbytes(data) + cmd = byte_ord(data[0]) if cmd in MSG_NAMES: cmd_name = MSG_NAMES[cmd] else: @@ -302,21 +296,21 @@ class Packetizer (object): if self.__dump_packets: self._log(DEBUG, 'Write packet <%s>, length %d' % (cmd_name, orig_len)) self._log(DEBUG, util.format_binary(packet, 'OUT: ')) - if self.__block_engine_out != None: + if self.__block_engine_out is not None: out = self.__block_engine_out.encrypt(packet) else: out = packet # + mac - if self.__block_engine_out != None: + if self.__block_engine_out is not None: payload = struct.pack('>I', self.__sequence_number_out) + packet out += compute_hmac(self.__mac_key_out, payload, self.__mac_engine_out)[:self.__mac_size_out] - self.__sequence_number_out = (self.__sequence_number_out + 1) & 0xffffffffL + self.__sequence_number_out = (self.__sequence_number_out + 1) & xffffffff self.write_all(out) self.__sent_bytes += len(out) self.__sent_packets += 1 - if ((self.__sent_packets >= self.REKEY_PACKETS) or (self.__sent_bytes >= self.REKEY_BYTES)) \ - and not self.__need_rekey: + if (self.__sent_packets >= self.REKEY_PACKETS or self.__sent_bytes >= self.REKEY_BYTES)\ + and not self.__need_rekey: # only ask once for rekeying self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes sent)' % (self.__sent_packets, self.__sent_bytes)) @@ -331,14 +325,14 @@ class Packetizer (object): Only one thread should ever be in this function (no other locking is done). - @raise SSHException: if the packet is mangled - @raise NeedRekeyException: if the transport should rekey + :raises SSHException: if the packet is mangled + :raises NeedRekeyException: if the transport should rekey """ header = self.read_all(self.__block_size_in, check_rekey=True) - if self.__block_engine_in != None: + if self.__block_engine_in is not None: header = self.__block_engine_in.decrypt(header) if self.__dump_packets: - self._log(DEBUG, util.format_binary(header, 'IN: ')); + self._log(DEBUG, util.format_binary(header, 'IN: ')) packet_size = struct.unpack('>I', header[:4])[0] # leftover contains decrypted bytes from the first block (after the length field) leftover = header[4:] @@ -347,21 +341,21 @@ class Packetizer (object): buf = self.read_all(packet_size + self.__mac_size_in - len(leftover)) packet = buf[:packet_size - len(leftover)] post_packet = buf[packet_size - len(leftover):] - if self.__block_engine_in != None: + if self.__block_engine_in is not None: packet = self.__block_engine_in.decrypt(packet) if self.__dump_packets: - self._log(DEBUG, util.format_binary(packet, 'IN: ')); + self._log(DEBUG, util.format_binary(packet, 'IN: ')) packet = leftover + packet if self.__mac_size_in > 0: mac = post_packet[:self.__mac_size_in] mac_payload = struct.pack('>II', self.__sequence_number_in, packet_size) + packet my_mac = compute_hmac(self.__mac_key_in, mac_payload, self.__mac_engine_in)[:self.__mac_size_in] - if my_mac != mac: + if not util.constant_time_bytes_eq(my_mac, mac): raise SSHException('Mismatched MAC') - padding = ord(packet[0]) + padding = byte_ord(packet[0]) payload = packet[1:packet_size - padding] - + if self.__dump_packets: self._log(DEBUG, 'Got payload (%d bytes, %d padding)' % (packet_size, padding)) @@ -370,7 +364,7 @@ class Packetizer (object): msg = Message(payload[1:]) msg.seqno = self.__sequence_number_in - self.__sequence_number_in = (self.__sequence_number_in + 1) & 0xffffffffL + self.__sequence_number_in = (self.__sequence_number_in + 1) & xffffffff # check for rekey raw_packet_size = packet_size + self.__mac_size_in + 4 @@ -393,7 +387,7 @@ class Packetizer (object): self.__received_packets_overflow = 0 self._trigger_rekey() - cmd = ord(payload[0]) + cmd = byte_ord(payload[0]) if cmd in MSG_NAMES: cmd_name = MSG_NAMES[cmd] else: @@ -402,10 +396,8 @@ class Packetizer (object): self._log(DEBUG, 'Read packet <%s>, length %d' % (cmd_name, len(payload))) return cmd, msg - ########## protected - def _log(self, level, msg): if self.__logger is None: return @@ -417,7 +409,7 @@ class Packetizer (object): def _check_keepalive(self): if (not self.__keepalive_interval) or (not self.__block_engine_out) or \ - self.__need_rekey: + self.__need_rekey: # wait till we're encrypting, and not in the middle of rekeying return now = time.time() @@ -425,40 +417,7 @@ class Packetizer (object): self.__keepalive_callback() self.__keepalive_last = now - def _py22_read_all(self, n, out): - while n > 0: - r, w, e = select.select([self.__socket], [], [], 0.1) - if self.__socket not in r: - if self.__closed: - raise EOFError() - self._check_keepalive() - else: - x = self.__socket.recv(n) - if len(x) == 0: - raise EOFError() - out += x - n -= len(x) - return out - - def _py22_read_timeout(self, timeout): - start = time.time() - while True: - r, w, e = select.select([self.__socket], [], [], 0.1) - if self.__socket in r: - x = self.__socket.recv(1) - if len(x) == 0: - raise EOFError() - break - if self.__closed: - raise EOFError() - now = time.time() - if now - start >= timeout: - raise socket.timeout() - return x - def _read_timeout(self, timeout): - if PY22: - return self._py22_read_timeout(timeout) start = time.time() while True: try: @@ -468,9 +427,9 @@ class Packetizer (object): break except socket.timeout: pass - except EnvironmentError, e: - if ((type(e.args) is tuple) and (len(e.args) > 0) and - (e.args[0] == errno.EINTR)): + except EnvironmentError as e: + if (type(e.args) is tuple and len(e.args) > 0 and + e.args[0] == errno.EINTR): pass else: raise @@ -490,9 +449,9 @@ class Packetizer (object): if self.__sdctr_out or self.__block_engine_out is None: # cute trick i caught openssh doing: if we're not encrypting or SDCTR mode (RFC4344), # don't waste random bytes for the padding - packet += (chr(0) * padding) + packet += (zero_byte * padding) else: - packet += rng.read(padding) + packet += os.urandom(padding) return packet def _trigger_rekey(self): |