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