paramiko.packet

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