Package paramiko :: Module packet |
[frames] | no frames]

# Source Code for Module paramiko.packet

      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 the
      6  # terms of the GNU Lesser General Public License as published by the Free
      7  # Software Foundation; either version 2.1 of the License, or (at your option)
      8  # any later version.
      9  #
     10  # Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
     11  # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     12  # A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
     13  # details.
     14  #
     15  # You should have received a copy of the GNU Lesser General Public License
     16  # along with Paramiko; if not, write to the Free Software Foundation, Inc.,
     17  # 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
     18
     19  """
     20  Packetizer.
     21  """
     22
     23  import errno
     24  import select
     25  import socket
     26  import struct
     27  import threading
     28  import time
     29
     30  from paramiko.common import *
     31  from paramiko import util
     32  from paramiko.ssh_exception import SSHException
     33  from paramiko.message import Message
     34
     35
     36  got_r_hmac = False
     37  try:
     38      import r_hmac
     39      got_r_hmac = True
     40  except ImportError:
     41      pass
     42 -def compute_hmac(key, message, digest_class):
     43      if got_r_hmac:
     44          return r_hmac.HMAC(key, message, digest_class).digest()
     45      from Crypto.Hash import HMAC
     46      return HMAC.HMAC(key, message, digest_class).digest()
     47
     48
     49 -class NeedRekeyException (Exception):
     50      pass
     51
     52
     53 -class Packetizer (object):
     54      """
     55      Implementation of the base SSH packet protocol.
     56      """
     57
     58      # READ the secsh RFC's before raising these values.  if anything,
     59      # they should probably be lower.
     60      REKEY_PACKETS = pow(2, 29)
     61      REKEY_BYTES = pow(2, 29)
     62
     63      REKEY_PACKETS_OVERFLOW_MAX = pow(2,29)      # Allow receiving this many packets after a re-key request before terminating
     64      REKEY_BYTES_OVERFLOW_MAX = pow(2,29)        # Allow receiving this many bytes after a re-key request before terminating
     65
     66 -    def __init__(self, socket):
     67          self.__socket = socket
     68          self.__logger = None
     69          self.__closed = False
     70          self.__dump_packets = False
     71          self.__need_rekey = False
     72          self.__init_count = 0
     73          self.__remainder = ''
     74
     75          # used for noticing when to re-key:
     76          self.__sent_bytes = 0
     77          self.__sent_packets = 0
     78          self.__received_bytes = 0
     79          self.__received_packets = 0
     80          self.__received_bytes_overflow = 0
     81          self.__received_packets_overflow = 0
     82
     83          # current inbound/outbound ciphering:
     84          self.__block_size_out = 8
     85          self.__block_size_in = 8
     86          self.__mac_size_out = 0
     87          self.__mac_size_in = 0
     88          self.__block_engine_out = None
     89          self.__block_engine_in = None
     90          self.__mac_engine_out = None
     91          self.__mac_engine_in = None
     92          self.__mac_key_out = ''
     93          self.__mac_key_in = ''
     94          self.__compress_engine_out = None
     95          self.__compress_engine_in = None
     96          self.__sequence_number_out = 0L
     97          self.__sequence_number_in = 0L
     98
     99          # lock around outbound writes (packet computation)
    100          self.__write_lock = threading.RLock()
    101
    102          # keepalives:
    103          self.__keepalive_interval = 0
    104          self.__keepalive_last = time.time()
    105          self.__keepalive_callback = None
    106
    107 -    def set_log(self, log):
    108          """
    109          Set the python log object to use for logging.
    110          """
    111          self.__logger = log
    112
    113 -    def set_outbound_cipher(self, block_engine, block_size, mac_engine, mac_size, mac_key):
    114          """
    115          Switch outbound data cipher.
    116          """
    117          self.__block_engine_out = block_engine
    118          self.__block_size_out = block_size
    119          self.__mac_engine_out = mac_engine
    120          self.__mac_size_out = mac_size
    121          self.__mac_key_out = mac_key
    122          self.__sent_bytes = 0
    123          self.__sent_packets = 0
    124          # wait until the reset happens in both directions before clearing rekey flag
    125          self.__init_count |= 1
    126          if self.__init_count == 3:
    127              self.__init_count = 0
    128              self.__need_rekey = False
    129
    130 -    def set_inbound_cipher(self, block_engine, block_size, mac_engine, mac_size, mac_key):
    131          """
    132          Switch inbound data cipher.
    133          """
    134          self.__block_engine_in = block_engine
    135          self.__block_size_in = block_size
    136          self.__mac_engine_in = mac_engine
    137          self.__mac_size_in = mac_size
    138          self.__mac_key_in = mac_key
    139          self.__received_bytes = 0
    140          self.__received_packets = 0
    141          self.__received_bytes_overflow = 0
    142          self.__received_packets_overflow = 0
    143          # wait until the reset happens in both directions before clearing rekey flag
    144          self.__init_count |= 2
    145          if self.__init_count == 3:
    146              self.__init_count = 0
    147              self.__need_rekey = False
    148
    149 -    def set_outbound_compressor(self, compressor):
    150          self.__compress_engine_out = compressor
    151
    152 -    def set_inbound_compressor(self, compressor):
    153          self.__compress_engine_in = compressor
    154
    155 -    def close(self):
    156          self.__closed = True
    157          self.__socket.close()
    158
    159 -    def set_hexdump(self, hexdump):
    160          self.__dump_packets = hexdump
    161
    162 -    def get_hexdump(self):
    163          return self.__dump_packets
    164
    165 -    def get_mac_size_in(self):
    166          return self.__mac_size_in
    167
    168 -    def get_mac_size_out(self):
    169          return self.__mac_size_out
    170
    171 -    def need_rekey(self):
    172          """
    173          Returns C{True} if a new set of keys needs to be negotiated.  This
    174          will be triggered during a packet read or write, so it should be
    175          checked after every read or write, or at least after every few.
    176
    177          @return: C{True} if a new set of keys needs to be negotiated
    178          """
    179          return self.__need_rekey
    180
    181 -    def set_keepalive(self, interval, callback):
    182          """
    183          Turn on/off the callback keepalive.  If C{interval} seconds pass with
    184          no data read from or written to the socket, the callback will be
    185          executed and the timer will be reset.
    186          """
    187          self.__keepalive_interval = interval
    188          self.__keepalive_callback = callback
    189          self.__keepalive_last = time.time()
    190
    191 -    def read_all(self, n, check_rekey=False):
    192          """
    193          Read as close to N bytes as possible, blocking as long as necessary.
    194
    195          @param n: number of bytes to read
    196          @type n: int
    197          @return: the data read
    198          @rtype: str
    199          @raise EOFError: if the socket was closed before all the bytes could
    200              be read
    201          """
    202          out = ''
    203          # handle over-reading from reading the banner line
    204          if len(self.__remainder) > 0:
    205              out = self.__remainder[:n]
    206              self.__remainder = self.__remainder[n:]
    207              n -= len(out)
    208          if PY22:
    209              return self._py22_read_all(n, out)
    210          while n > 0:
    211              got_timeout = False
    212              try:
    213                  x = self.__socket.recv(n)
    214                  if len(x) == 0:
    215                      raise EOFError()
    216                  out += x
    217                  n -= len(x)
    218              except socket.timeout:
    219                  got_timeout = True
    220              except socket.error, e:
    221                  # on Linux, sometimes instead of socket.timeout, we get
    222                  # EAGAIN.  this is a bug in recent (> 2.6.9) kernels but
    223                  # we need to work around it.
    224                  if (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EAGAIN):
    225                      got_timeout = True
    226                  elif (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EINTR):
    227                      # syscall interrupted; try again
    228                      pass
    229                  elif self.__closed:
    230                      raise EOFError()
    231                  else:
    232                      raise
    233              if got_timeout:
    234                  if self.__closed:
    235                      raise EOFError()
    236                  if check_rekey and (len(out) == 0) and self.__need_rekey:
    237                      raise NeedRekeyException()
    238                  self._check_keepalive()
    239          return out
    240
    241 -    def write_all(self, out):
    242          self.__keepalive_last = time.time()
    243          while len(out) > 0:
    244              got_timeout = False
    245              try:
    246                  n = self.__socket.send(out)
    247              except socket.timeout:
    248                  got_timeout = True
    249              except socket.error, e:
    250                  if (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EAGAIN):
    251                      got_timeout = True
    252                  elif (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EINTR):
    253                      # syscall interrupted; try again
    254                      pass
    255                  else:
    256                      n = -1
    257              except Exception:
    258                  # could be: (32, 'Broken pipe')
    259                  n = -1
    260              if got_timeout:
    261                  n = 0
    262                  if self.__closed:
    263                      n = -1
    264              if n < 0:
    265                  raise EOFError()
    266              if n == len(out):
    267                  break
    268              out = out[n:]
    269          return
    270
    271 -    def readline(self, timeout):
    272          """
    273          Read a line from the socket.  We assume no data is pending after the
    274          line, so it's okay to attempt large reads.
    275          """
    276          buf = self.__remainder
    277          while not '\n' in buf:
    278              buf += self._read_timeout(timeout)
    279          n = buf.index('\n')
    280          self.__remainder = buf[n+1:]
    281          buf = buf[:n]
    282          if (len(buf) > 0) and (buf[-1] == '\r'):
    283              buf = buf[:-1]
    284          return buf
    285
    286 -    def send_message(self, data):
    287          """
    288          Write a block of data using the current cipher, as an SSH block.
    289          """
    290          # encrypt this sucka
    291          data = str(data)
    292          cmd = ord(data[0])
    293          if cmd in MSG_NAMES:
    294              cmd_name = MSG_NAMES[cmd]
    295          else:
    296              cmd_name = '$%x' % cmd
    297          orig_len = len(data)
    298          self.__write_lock.acquire()
    299          try:
    300              if self.__compress_engine_out is not None:
    301                  data = self.__compress_engine_out(data)
    302              packet = self._build_packet(data)
    303              if self.__dump_packets:
    304                  self._log(DEBUG, 'Write packet <%s>, length %d' % (cmd_name, orig_len))
    305                  self._log(DEBUG, util.format_binary(packet, 'OUT: '))
    306              if self.__block_engine_out != None:
    307                  out = self.__block_engine_out.encrypt(packet)
    308              else:
    309                  out = packet
    310              # + mac
    311              if self.__block_engine_out != None:
    312                  payload = struct.pack('>I', self.__sequence_number_out) + packet
    313                  out += compute_hmac(self.__mac_key_out, payload, self.__mac_engine_out)[:self.__mac_size_out]
    314              self.__sequence_number_out = (self.__sequence_number_out + 1) & 0xffffffffL
    315              self.write_all(out)
    316
    317              self.__sent_bytes += len(out)
    318              self.__sent_packets += 1
    319              if ((self.__sent_packets >= self.REKEY_PACKETS) or (self.__sent_bytes >= self.REKEY_BYTES)) \
    320                     and not self.__need_rekey:
    321                  # only ask once for rekeying
    322                  self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes sent)' %
    323                            (self.__sent_packets, self.__sent_bytes))
    324                  self.__received_bytes_overflow = 0
    325                  self.__received_packets_overflow = 0
    326                  self._trigger_rekey()
    327          finally:
    328              self.__write_lock.release()
    329
    330 -    def read_message(self):
    331          """
    332          Only one thread should ever be in this function (no other locking is
    333          done).
    334
    335          @raise SSHException: if the packet is mangled
    336          @raise NeedRekeyException: if the transport should rekey
    337          """
    338          header = self.read_all(self.__block_size_in, check_rekey=True)
    339          if self.__block_engine_in != None:
    340              header = self.__block_engine_in.decrypt(header)
    341          if self.__dump_packets:
    342              self._log(DEBUG, util.format_binary(header, 'IN: '));
    343          packet_size = struct.unpack('>I', header[:4])[0]
    344          # leftover contains decrypted bytes from the first block (after the length field)
    345          leftover = header[4:]
    346          if (packet_size - len(leftover)) % self.__block_size_in != 0:
    347              raise SSHException('Invalid packet blocking')
    348          buf = self.read_all(packet_size + self.__mac_size_in - len(leftover))
    349          packet = buf[:packet_size - len(leftover)]
    350          post_packet = buf[packet_size - len(leftover):]
    351          if self.__block_engine_in != None:
    352              packet = self.__block_engine_in.decrypt(packet)
    353          if self.__dump_packets:
    354              self._log(DEBUG, util.format_binary(packet, 'IN: '));
    355          packet = leftover + packet
    356
    357          if self.__mac_size_in > 0:
    358              mac = post_packet[:self.__mac_size_in]
    359              mac_payload = struct.pack('>II', self.__sequence_number_in, packet_size) + packet
    360              my_mac = compute_hmac(self.__mac_key_in, mac_payload, self.__mac_engine_in)[:self.__mac_size_in]
    361              if my_mac != mac:
    362                  raise SSHException('Mismatched MAC')
    363          padding = ord(packet[0])
    364          payload = packet[1:packet_size - padding]
    365
    366          if self.__dump_packets:
    367              self._log(DEBUG, 'Got payload (%d bytes, %d padding)' % (packet_size, padding))
    368
    369          if self.__compress_engine_in is not None:
    370              payload = self.__compress_engine_in(payload)
    371
    372          msg = Message(payload[1:])
    373          msg.seqno = self.__sequence_number_in
    374          self.__sequence_number_in = (self.__sequence_number_in + 1) & 0xffffffffL
    375
    376          # check for rekey
    377          raw_packet_size = packet_size + self.__mac_size_in + 4
    378          self.__received_bytes += raw_packet_size
    379          self.__received_packets += 1
    380          if self.__need_rekey:
    381              # we've asked to rekey -- give them some packets to comply before
    382              # dropping the connection
    383              self.__received_bytes_overflow += raw_packet_size
    384              self.__received_packets_overflow += 1
    385              if (self.__received_packets_overflow >= self.REKEY_PACKETS_OVERFLOW_MAX) or \
    386                 (self.__received_bytes_overflow >= self.REKEY_BYTES_OVERFLOW_MAX):
    387                  raise SSHException('Remote transport is ignoring rekey requests')
    388          elif (self.__received_packets >= self.REKEY_PACKETS) or \
    389               (self.__received_bytes >= self.REKEY_BYTES):
    390              # only ask once for rekeying
    391              self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes received)' %
    392                        (self.__received_packets, self.__received_bytes))
    393              self.__received_bytes_overflow = 0
    394              self.__received_packets_overflow = 0
    395              self._trigger_rekey()
    396
    397          cmd = ord(payload[0])
    398          if cmd in MSG_NAMES:
    399              cmd_name = MSG_NAMES[cmd]
    400          else:
    401              cmd_name = '$%x' % cmd
    402          if self.__dump_packets:
    403              self._log(DEBUG, 'Read packet <%s>, length %d' % (cmd_name, len(payload)))
    404          return cmd, msg
    405
    406
    407      ##########  protected
    408
    409
    410 -    def _log(self, level, msg):
    411          if self.__logger is None:
    412              return
    413          if issubclass(type(msg), list):
    414              for m in msg:
    415                  self.__logger.log(level, m)
    416          else:
    417              self.__logger.log(level, msg)
    418
    419 -    def _check_keepalive(self):
    420          if (not self.__keepalive_interval) or (not self.__block_engine_out) or \
    421              self.__need_rekey:
    422              # wait till we're encrypting, and not in the middle of rekeying
    423              return
    424          now = time.time()
    425          if now > self.__keepalive_last + self.__keepalive_interval:
    426              self.__keepalive_callback()
    427              self.__keepalive_last = now
    428
    429 -    def _py22_read_all(self, n, out):
    430          while n > 0:
    431              r, w, e = select.select([self.__socket], [], [], 0.1)
    432              if self.__socket not in r:
    433                  if self.__closed:
    434                      raise EOFError()
    435                  self._check_keepalive()
    436              else:
    437                  x = self.__socket.recv(n)
    438                  if len(x) == 0:
    439                      raise EOFError()
    440                  out += x
    441                  n -= len(x)
    442          return out
    443
    444 -    def _py22_read_timeout(self, timeout):
    445          start = time.time()
    446          while True:
    447              r, w, e = select.select([self.__socket], [], [], 0.1)
    448              if self.__socket in r:
    449                  x = self.__socket.recv(1)
    450                  if len(x) == 0:
    451                      raise EOFError()
    452                  break
    453              if self.__closed:
    454                  raise EOFError()
    455              now = time.time()
    456              if now - start >= timeout:
    457                  raise socket.timeout()
    458          return x
    459
    460 -    def _read_timeout(self, timeout):
    461          if PY22:
    462              return self._py22_read_timeout(timeout)
    463          start = time.time()
    464          while True:
    465              try:
    466                  x = self.__socket.recv(128)
    467                  if len(x) == 0:
    468                      raise EOFError()
    469                  break
    470              except socket.timeout:
    471                  pass
    472              if self.__closed:
    473                  raise EOFError()
    474              now = time.time()
    475              if now - start >= timeout:
    476                  raise socket.timeout()
    477          return x
    478
    479 -    def _build_packet(self, payload):
    480          # pad up at least 4 bytes, to nearest block-size (usually 8)
    481          bsize = self.__block_size_out
    482          padding = 3 + bsize - ((len(payload) + 8) % bsize)
    483          packet = struct.pack('>IB', len(payload) + padding + 1, padding)
    484          packet += payload
    485          if self.__block_engine_out is not None:
    486              packet += rng.read(padding)
    487          else:
    488              # cute trick i caught openssh doing: if we're not encrypting,
    489              # don't waste random bytes for the padding
    490              packet += (chr(0) * padding)
    491          return packet
    492
    493 -    def _trigger_rekey(self):
    494          # outside code should check for this flag
    495          self.__need_rekey = True
    496