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