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