paramiko.packet

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

# Source Code for Module paramiko.packet

      1  # Copyright (C) 2003-2007  Robey Pointer <robey@lag.net>
      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 = ''
    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 % 100) == 0:
    315                  # stirring the randpool takes 30ms on my ibook!!
    316                  randpool.stir()
    317              if ((self.__sent_packets >= self.REKEY_PACKETS) or (self.__sent_bytes >= self.REKEY_BYTES)) \
    318                     and not self.__need_rekey:
    319                  # only ask once for rekeying
    320                  self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes sent)' %
    321                            (self.__sent_packets, self.__sent_bytes))
    322                  self.__received_packets_overflow = 0
    323                  self._trigger_rekey()
    324          finally:
    325              self.__write_lock.release()
    326
    327 -    def read_message(self):
    328          """
    329          Only one thread should ever be in this function (no other locking is
    330          done).
    331
    332          @raise SSHException: if the packet is mangled
    333          @raise NeedRekeyException: if the transport should rekey
    334          """
    335          header = self.read_all(self.__block_size_in, check_rekey=True)
    336          if self.__block_engine_in != None:
    337              header = self.__block_engine_in.decrypt(header)
    338          if self.__dump_packets:
    339              self._log(DEBUG, util.format_binary(header, 'IN: '));
    340          packet_size = struct.unpack('>I', header[:4])[0]
    341          # leftover contains decrypted bytes from the first block (after the length field)
    342          leftover = header[4:]
    343          if (packet_size - len(leftover)) % self.__block_size_in != 0:
    344              raise SSHException('Invalid packet blocking')
    345          buf = self.read_all(packet_size + self.__mac_size_in - len(leftover))
    346          packet = buf[:packet_size - len(leftover)]
    347          post_packet = buf[packet_size - len(leftover):]
    348          if self.__block_engine_in != None:
    349              packet = self.__block_engine_in.decrypt(packet)
    350          if self.__dump_packets:
    351              self._log(DEBUG, util.format_binary(packet, 'IN: '));
    352          packet = leftover + packet
    353
    354          if self.__mac_size_in > 0:
    355              mac = post_packet[:self.__mac_size_in]
    356              mac_payload = struct.pack('>II', self.__sequence_number_in, packet_size) + packet
    357              my_mac = compute_hmac(self.__mac_key_in, mac_payload, self.__mac_engine_in)[:self.__mac_size_in]
    358              if my_mac != mac:
    359                  raise SSHException('Mismatched MAC')
    360          padding = ord(packet[0])
    361          payload = packet[1:packet_size - padding]
    362          randpool.add_event()
    363          if self.__dump_packets:
    364              self._log(DEBUG, 'Got payload (%d bytes, %d padding)' % (packet_size, padding))
    365
    366          if self.__compress_engine_in is not None:
    367              payload = self.__compress_engine_in(payload)
    368
    369          msg = Message(payload[1:])
    370          msg.seqno = self.__sequence_number_in
    371          self.__sequence_number_in = (self.__sequence_number_in + 1) & 0xffffffffL
    372
    373          # check for rekey
    374          self.__received_bytes += packet_size + self.__mac_size_in + 4
    375          self.__received_packets += 1
    376          if self.__need_rekey:
    377              # we've asked to rekey -- give them 20 packets to comply before
    378              # dropping the connection
    379              self.__received_packets_overflow += 1
    380              if self.__received_packets_overflow >= 20:
    381                  raise SSHException('Remote transport is ignoring rekey requests')
    382          elif (self.__received_packets >= self.REKEY_PACKETS) or \
    383               (self.__received_bytes >= self.REKEY_BYTES):
    384              # only ask once for rekeying
    385              self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes received)' %
    386                        (self.__received_packets, self.__received_bytes))
    387              self.__received_packets_overflow = 0
    388              self._trigger_rekey()
    389
    390          cmd = ord(payload[0])
    391          if cmd in MSG_NAMES:
    392              cmd_name = MSG_NAMES[cmd]
    393          else:
    394              cmd_name = '$%x' % cmd
    395          if self.__dump_packets:
    396              self._log(DEBUG, 'Read packet <%s>, length %d' % (cmd_name, len(payload)))
    397          return cmd, msg
    398
    399
    400      ##########  protected
    401
    402
    403 -    def _log(self, level, msg):
    404          if self.__logger is None:
    405              return
    406          if issubclass(type(msg), list):
    407              for m in msg:
    408                  self.__logger.log(level, m)
    409          else:
    410              self.__logger.log(level, msg)
    411
    412 -    def _check_keepalive(self):
    413          if (not self.__keepalive_interval) or (not self.__block_engine_out) or \
    414              self.__need_rekey:
    415              # wait till we're encrypting, and not in the middle of rekeying
    416              return
    417          now = time.time()
    418          if now > self.__keepalive_last + self.__keepalive_interval:
    419              self.__keepalive_callback()
    420              self.__keepalive_last = now
    421
    422 -    def _py22_read_all(self, n, out):
    423          while n > 0:
    424              r, w, e = select.select([self.__socket], [], [], 0.1)
    425              if self.__socket not in r:
    426                  if self.__closed:
    427                      raise EOFError()
    428                  self._check_keepalive()
    429              else:
    430                  x = self.__socket.recv(n)
    431                  if len(x) == 0:
    432                      raise EOFError()
    433                  out += x
    434                  n -= len(x)
    435          return out
    436
    437 -    def _py22_read_timeout(self, timeout):
    438          start = time.time()
    439          while True:
    440              r, w, e = select.select([self.__socket], [], [], 0.1)
    441              if self.__socket in r:
    442                  x = self.__socket.recv(1)
    443                  if len(x) == 0:
    444                      raise EOFError()
    445                  break
    446              if self.__closed:
    447                  raise EOFError()
    448              now = time.time()
    449              if now - start >= timeout:
    450                  raise socket.timeout()
    451          return x
    452
    453 -    def _read_timeout(self, timeout):
    454          if PY22:
    455              return self._py22_read_timeout(timeout)
    456          start = time.time()
    457          while True:
    458              try:
    459                  x = self.__socket.recv(128)
    460                  if len(x) == 0:
    461                      raise EOFError()
    462                  break
    463              except socket.timeout:
    464                  pass
    465              if self.__closed:
    466                  raise EOFError()
    467              now = time.time()
    468              if now - start >= timeout:
    469                  raise socket.timeout()
    470          return x
    471
    472 -    def _build_packet(self, payload):
    473          # pad up at least 4 bytes, to nearest block-size (usually 8)
    474          bsize = self.__block_size_out
    475          padding = 3 + bsize - ((len(payload) + 8) % bsize)
    476          packet = struct.pack('>IB', len(payload) + padding + 1, padding)
    477          packet += payload
    478          if self.__block_engine_out is not None:
    479              packet += randpool.get_bytes(padding)
    480          else:
    481              # cute trick i caught openssh doing: if we're not encrypting,
    482              # don't waste random bytes for the padding
    483              packet += (chr(0) * padding)
    484          return packet
    485
    486 -    def _trigger_rekey(self):
    487          # outside code should check for this flag
    488          self.__need_rekey = True
    489