packet.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  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. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18. """
  19. Packet handling
  20. """
  21. import errno
  22. import os
  23. import socket
  24. import struct
  25. import threading
  26. import time
  27. from hmac import HMAC
  28. from paramiko import util
  29. from paramiko.common import (
  30. linefeed_byte,
  31. cr_byte_value,
  32. asbytes,
  33. MSG_NAMES,
  34. DEBUG,
  35. xffffffff,
  36. zero_byte,
  37. )
  38. from paramiko.py3compat import u, byte_ord
  39. from paramiko.ssh_exception import SSHException, ProxyCommandFailure
  40. from paramiko.message import Message
  41. def compute_hmac(key, message, digest_class):
  42. return HMAC(key, message, digest_class).digest()
  43. class NeedRekeyException(Exception):
  44. """
  45. Exception indicating a rekey is needed.
  46. """
  47. pass
  48. def first_arg(e):
  49. arg = None
  50. if type(e.args) is tuple and len(e.args) > 0:
  51. arg = e.args[0]
  52. return arg
  53. class Packetizer(object):
  54. """
  55. Implementation of the base SSH packet protocol.
  56. """
  57. # READ the secsh RFC's before raising these values. if anything,
  58. # they should probably be lower.
  59. REKEY_PACKETS = pow(2, 29)
  60. REKEY_BYTES = pow(2, 29)
  61. # Allow receiving this many packets after a re-key request before
  62. # terminating
  63. REKEY_PACKETS_OVERFLOW_MAX = pow(2, 29)
  64. # Allow receiving this many bytes after a re-key request before terminating
  65. REKEY_BYTES_OVERFLOW_MAX = pow(2, 29)
  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 = bytes()
  74. # used for noticing when to re-key:
  75. self.__sent_bytes = 0
  76. self.__sent_packets = 0
  77. self.__received_bytes = 0
  78. self.__received_packets = 0
  79. self.__received_bytes_overflow = 0
  80. self.__received_packets_overflow = 0
  81. # current inbound/outbound ciphering:
  82. self.__block_size_out = 8
  83. self.__block_size_in = 8
  84. self.__mac_size_out = 0
  85. self.__mac_size_in = 0
  86. self.__block_engine_out = None
  87. self.__block_engine_in = None
  88. self.__sdctr_out = False
  89. self.__mac_engine_out = None
  90. self.__mac_engine_in = None
  91. self.__mac_key_out = bytes()
  92. self.__mac_key_in = bytes()
  93. self.__compress_engine_out = None
  94. self.__compress_engine_in = None
  95. self.__sequence_number_out = 0
  96. self.__sequence_number_in = 0
  97. self.__etm_out = False
  98. self.__etm_in = False
  99. # lock around outbound writes (packet computation)
  100. self.__write_lock = threading.RLock()
  101. # keepalives:
  102. self.__keepalive_interval = 0
  103. self.__keepalive_last = time.time()
  104. self.__keepalive_callback = None
  105. self.__timer = None
  106. self.__handshake_complete = False
  107. self.__timer_expired = False
  108. @property
  109. def closed(self):
  110. return self.__closed
  111. def set_log(self, log):
  112. """
  113. Set the Python log object to use for logging.
  114. """
  115. self.__logger = log
  116. def set_outbound_cipher(
  117. self,
  118. block_engine,
  119. block_size,
  120. mac_engine,
  121. mac_size,
  122. mac_key,
  123. sdctr=False,
  124. etm=False,
  125. ):
  126. """
  127. Switch outbound data cipher.
  128. :param etm: Set encrypt-then-mac from OpenSSH
  129. """
  130. self.__block_engine_out = block_engine
  131. self.__sdctr_out = sdctr
  132. self.__block_size_out = block_size
  133. self.__mac_engine_out = mac_engine
  134. self.__mac_size_out = mac_size
  135. self.__mac_key_out = mac_key
  136. self.__sent_bytes = 0
  137. self.__sent_packets = 0
  138. self.__etm_out = etm
  139. # wait until the reset happens in both directions before clearing
  140. # rekey flag
  141. self.__init_count |= 1
  142. if self.__init_count == 3:
  143. self.__init_count = 0
  144. self.__need_rekey = False
  145. def set_inbound_cipher(
  146. self,
  147. block_engine,
  148. block_size,
  149. mac_engine,
  150. mac_size,
  151. mac_key,
  152. etm=False,
  153. ):
  154. """
  155. Switch inbound data cipher.
  156. :param etm: Set encrypt-then-mac from OpenSSH
  157. """
  158. self.__block_engine_in = block_engine
  159. self.__block_size_in = block_size
  160. self.__mac_engine_in = mac_engine
  161. self.__mac_size_in = mac_size
  162. self.__mac_key_in = mac_key
  163. self.__received_bytes = 0
  164. self.__received_packets = 0
  165. self.__received_bytes_overflow = 0
  166. self.__received_packets_overflow = 0
  167. self.__etm_in = etm
  168. # wait until the reset happens in both directions before clearing
  169. # rekey flag
  170. self.__init_count |= 2
  171. if self.__init_count == 3:
  172. self.__init_count = 0
  173. self.__need_rekey = False
  174. def set_outbound_compressor(self, compressor):
  175. self.__compress_engine_out = compressor
  176. def set_inbound_compressor(self, compressor):
  177. self.__compress_engine_in = compressor
  178. def close(self):
  179. self.__closed = True
  180. self.__socket.close()
  181. def set_hexdump(self, hexdump):
  182. self.__dump_packets = hexdump
  183. def get_hexdump(self):
  184. return self.__dump_packets
  185. def get_mac_size_in(self):
  186. return self.__mac_size_in
  187. def get_mac_size_out(self):
  188. return self.__mac_size_out
  189. def need_rekey(self):
  190. """
  191. Returns ``True`` if a new set of keys needs to be negotiated. This
  192. will be triggered during a packet read or write, so it should be
  193. checked after every read or write, or at least after every few.
  194. """
  195. return self.__need_rekey
  196. def set_keepalive(self, interval, callback):
  197. """
  198. Turn on/off the callback keepalive. If ``interval`` seconds pass with
  199. no data read from or written to the socket, the callback will be
  200. executed and the timer will be reset.
  201. """
  202. self.__keepalive_interval = interval
  203. self.__keepalive_callback = callback
  204. self.__keepalive_last = time.time()
  205. def read_timer(self):
  206. self.__timer_expired = True
  207. def start_handshake(self, timeout):
  208. """
  209. Tells `Packetizer` that the handshake process started.
  210. Starts a book keeping timer that can signal a timeout in the
  211. handshake process.
  212. :param float timeout: amount of seconds to wait before timing out
  213. """
  214. if not self.__timer:
  215. self.__timer = threading.Timer(float(timeout), self.read_timer)
  216. self.__timer.start()
  217. def handshake_timed_out(self):
  218. """
  219. Checks if the handshake has timed out.
  220. If `start_handshake` wasn't called before the call to this function,
  221. the return value will always be `False`. If the handshake completed
  222. before a timeout was reached, the return value will be `False`
  223. :return: handshake time out status, as a `bool`
  224. """
  225. if not self.__timer:
  226. return False
  227. if self.__handshake_complete:
  228. return False
  229. return self.__timer_expired
  230. def complete_handshake(self):
  231. """
  232. Tells `Packetizer` that the handshake has completed.
  233. """
  234. if self.__timer:
  235. self.__timer.cancel()
  236. self.__timer_expired = False
  237. self.__handshake_complete = True
  238. def read_all(self, n, check_rekey=False):
  239. """
  240. Read as close to N bytes as possible, blocking as long as necessary.
  241. :param int n: number of bytes to read
  242. :return: the data read, as a `str`
  243. :raises:
  244. ``EOFError`` -- if the socket was closed before all the bytes could
  245. be read
  246. """
  247. out = bytes()
  248. # handle over-reading from reading the banner line
  249. if len(self.__remainder) > 0:
  250. out = self.__remainder[:n]
  251. self.__remainder = self.__remainder[n:]
  252. n -= len(out)
  253. while n > 0:
  254. got_timeout = False
  255. if self.handshake_timed_out():
  256. raise EOFError()
  257. try:
  258. x = self.__socket.recv(n)
  259. if len(x) == 0:
  260. raise EOFError()
  261. out += x
  262. n -= len(x)
  263. except socket.timeout:
  264. got_timeout = True
  265. except socket.error as e:
  266. # on Linux, sometimes instead of socket.timeout, we get
  267. # EAGAIN. this is a bug in recent (> 2.6.9) kernels but
  268. # we need to work around it.
  269. arg = first_arg(e)
  270. if arg == errno.EAGAIN:
  271. got_timeout = True
  272. elif arg == errno.EINTR:
  273. # syscall interrupted; try again
  274. pass
  275. elif self.__closed:
  276. raise EOFError()
  277. else:
  278. raise
  279. if got_timeout:
  280. if self.__closed:
  281. raise EOFError()
  282. if check_rekey and (len(out) == 0) and self.__need_rekey:
  283. raise NeedRekeyException()
  284. self._check_keepalive()
  285. return out
  286. def write_all(self, out):
  287. self.__keepalive_last = time.time()
  288. iteration_with_zero_as_return_value = 0
  289. while len(out) > 0:
  290. retry_write = False
  291. try:
  292. n = self.__socket.send(out)
  293. except socket.timeout:
  294. retry_write = True
  295. except socket.error as e:
  296. arg = first_arg(e)
  297. if arg == errno.EAGAIN:
  298. retry_write = True
  299. elif arg == errno.EINTR:
  300. # syscall interrupted; try again
  301. retry_write = True
  302. else:
  303. n = -1
  304. except ProxyCommandFailure:
  305. raise # so it doesn't get swallowed by the below catchall
  306. except Exception:
  307. # could be: (32, 'Broken pipe')
  308. n = -1
  309. if retry_write:
  310. n = 0
  311. if self.__closed:
  312. n = -1
  313. else:
  314. if n == 0 and iteration_with_zero_as_return_value > 10:
  315. # We shouldn't retry the write, but we didn't
  316. # manage to send anything over the socket. This might be an
  317. # indication that we have lost contact with the remote
  318. # side, but are yet to receive an EOFError or other socket
  319. # errors. Let's give it some iteration to try and catch up.
  320. n = -1
  321. iteration_with_zero_as_return_value += 1
  322. if n < 0:
  323. raise EOFError()
  324. if n == len(out):
  325. break
  326. out = out[n:]
  327. return
  328. def readline(self, timeout):
  329. """
  330. Read a line from the socket. We assume no data is pending after the
  331. line, so it's okay to attempt large reads.
  332. """
  333. buf = self.__remainder
  334. while linefeed_byte not in buf:
  335. buf += self._read_timeout(timeout)
  336. n = buf.index(linefeed_byte)
  337. self.__remainder = buf[n + 1 :]
  338. buf = buf[:n]
  339. if (len(buf) > 0) and (buf[-1] == cr_byte_value):
  340. buf = buf[:-1]
  341. return u(buf)
  342. def send_message(self, data):
  343. """
  344. Write a block of data using the current cipher, as an SSH block.
  345. """
  346. # encrypt this sucka
  347. data = asbytes(data)
  348. cmd = byte_ord(data[0])
  349. if cmd in MSG_NAMES:
  350. cmd_name = MSG_NAMES[cmd]
  351. else:
  352. cmd_name = "${:x}".format(cmd)
  353. orig_len = len(data)
  354. self.__write_lock.acquire()
  355. try:
  356. if self.__compress_engine_out is not None:
  357. data = self.__compress_engine_out(data)
  358. packet = self._build_packet(data)
  359. if self.__dump_packets:
  360. self._log(
  361. DEBUG,
  362. "Write packet <{}>, length {}".format(cmd_name, orig_len),
  363. )
  364. self._log(DEBUG, util.format_binary(packet, "OUT: "))
  365. if self.__block_engine_out is not None:
  366. if self.__etm_out:
  367. # packet length is not encrypted in EtM
  368. out = packet[0:4] + self.__block_engine_out.update(
  369. packet[4:]
  370. )
  371. else:
  372. out = self.__block_engine_out.update(packet)
  373. else:
  374. out = packet
  375. # + mac
  376. if self.__block_engine_out is not None:
  377. packed = struct.pack(">I", self.__sequence_number_out)
  378. payload = packed + (out if self.__etm_out else packet)
  379. out += compute_hmac(
  380. self.__mac_key_out, payload, self.__mac_engine_out
  381. )[: self.__mac_size_out]
  382. self.__sequence_number_out = (
  383. self.__sequence_number_out + 1
  384. ) & xffffffff
  385. self.write_all(out)
  386. self.__sent_bytes += len(out)
  387. self.__sent_packets += 1
  388. sent_too_much = (
  389. self.__sent_packets >= self.REKEY_PACKETS
  390. or self.__sent_bytes >= self.REKEY_BYTES
  391. )
  392. if sent_too_much and not self.__need_rekey:
  393. # only ask once for rekeying
  394. msg = "Rekeying (hit {} packets, {} bytes sent)"
  395. self._log(
  396. DEBUG, msg.format(self.__sent_packets, self.__sent_bytes)
  397. )
  398. self.__received_bytes_overflow = 0
  399. self.__received_packets_overflow = 0
  400. self._trigger_rekey()
  401. finally:
  402. self.__write_lock.release()
  403. def read_message(self):
  404. """
  405. Only one thread should ever be in this function (no other locking is
  406. done).
  407. :raises: `.SSHException` -- if the packet is mangled
  408. :raises: `.NeedRekeyException` -- if the transport should rekey
  409. """
  410. header = self.read_all(self.__block_size_in, check_rekey=True)
  411. if self.__etm_in:
  412. packet_size = struct.unpack(">I", header[:4])[0]
  413. remaining = packet_size - self.__block_size_in + 4
  414. packet = header[4:] + self.read_all(remaining, check_rekey=False)
  415. mac = self.read_all(self.__mac_size_in, check_rekey=False)
  416. mac_payload = (
  417. struct.pack(">II", self.__sequence_number_in, packet_size)
  418. + packet
  419. )
  420. my_mac = compute_hmac(
  421. self.__mac_key_in, mac_payload, self.__mac_engine_in
  422. )[: self.__mac_size_in]
  423. if not util.constant_time_bytes_eq(my_mac, mac):
  424. raise SSHException("Mismatched MAC")
  425. header = packet
  426. if self.__block_engine_in is not None:
  427. header = self.__block_engine_in.update(header)
  428. if self.__dump_packets:
  429. self._log(DEBUG, util.format_binary(header, "IN: "))
  430. # When ETM is in play, we've already read the packet size & decrypted
  431. # everything, so just set the packet back to the header we obtained.
  432. if self.__etm_in:
  433. packet = header
  434. # Otherwise, use the older non-ETM logic
  435. else:
  436. packet_size = struct.unpack(">I", header[:4])[0]
  437. # leftover contains decrypted bytes from the first block (after the
  438. # length field)
  439. leftover = header[4:]
  440. if (packet_size - len(leftover)) % self.__block_size_in != 0:
  441. raise SSHException("Invalid packet blocking")
  442. buf = self.read_all(
  443. packet_size + self.__mac_size_in - len(leftover)
  444. )
  445. packet = buf[: packet_size - len(leftover)]
  446. post_packet = buf[packet_size - len(leftover) :]
  447. if self.__block_engine_in is not None:
  448. packet = self.__block_engine_in.update(packet)
  449. packet = leftover + packet
  450. if self.__dump_packets:
  451. self._log(DEBUG, util.format_binary(packet, "IN: "))
  452. if self.__mac_size_in > 0 and not self.__etm_in:
  453. mac = post_packet[: self.__mac_size_in]
  454. mac_payload = (
  455. struct.pack(">II", self.__sequence_number_in, packet_size)
  456. + packet
  457. )
  458. my_mac = compute_hmac(
  459. self.__mac_key_in, mac_payload, self.__mac_engine_in
  460. )[: self.__mac_size_in]
  461. if not util.constant_time_bytes_eq(my_mac, mac):
  462. raise SSHException("Mismatched MAC")
  463. padding = byte_ord(packet[0])
  464. payload = packet[1 : packet_size - padding]
  465. if self.__dump_packets:
  466. self._log(
  467. DEBUG,
  468. "Got payload ({} bytes, {} padding)".format(
  469. packet_size, padding
  470. ),
  471. )
  472. if self.__compress_engine_in is not None:
  473. payload = self.__compress_engine_in(payload)
  474. msg = Message(payload[1:])
  475. msg.seqno = self.__sequence_number_in
  476. self.__sequence_number_in = (self.__sequence_number_in + 1) & xffffffff
  477. # check for rekey
  478. raw_packet_size = packet_size + self.__mac_size_in + 4
  479. self.__received_bytes += raw_packet_size
  480. self.__received_packets += 1
  481. if self.__need_rekey:
  482. # we've asked to rekey -- give them some packets to comply before
  483. # dropping the connection
  484. self.__received_bytes_overflow += raw_packet_size
  485. self.__received_packets_overflow += 1
  486. if (
  487. self.__received_packets_overflow
  488. >= self.REKEY_PACKETS_OVERFLOW_MAX
  489. ) or (
  490. self.__received_bytes_overflow >= self.REKEY_BYTES_OVERFLOW_MAX
  491. ):
  492. raise SSHException(
  493. "Remote transport is ignoring rekey requests"
  494. )
  495. elif (self.__received_packets >= self.REKEY_PACKETS) or (
  496. self.__received_bytes >= self.REKEY_BYTES
  497. ):
  498. # only ask once for rekeying
  499. err = "Rekeying (hit {} packets, {} bytes received)"
  500. self._log(
  501. DEBUG,
  502. err.format(self.__received_packets, self.__received_bytes),
  503. )
  504. self.__received_bytes_overflow = 0
  505. self.__received_packets_overflow = 0
  506. self._trigger_rekey()
  507. cmd = byte_ord(payload[0])
  508. if cmd in MSG_NAMES:
  509. cmd_name = MSG_NAMES[cmd]
  510. else:
  511. cmd_name = "${:x}".format(cmd)
  512. if self.__dump_packets:
  513. self._log(
  514. DEBUG,
  515. "Read packet <{}>, length {}".format(cmd_name, len(payload)),
  516. )
  517. return cmd, msg
  518. # ...protected...
  519. def _log(self, level, msg):
  520. if self.__logger is None:
  521. return
  522. if issubclass(type(msg), list):
  523. for m in msg:
  524. self.__logger.log(level, m)
  525. else:
  526. self.__logger.log(level, msg)
  527. def _check_keepalive(self):
  528. if (
  529. not self.__keepalive_interval
  530. or not self.__block_engine_out
  531. or self.__need_rekey
  532. ):
  533. # wait till we're encrypting, and not in the middle of rekeying
  534. return
  535. now = time.time()
  536. if now > self.__keepalive_last + self.__keepalive_interval:
  537. self.__keepalive_callback()
  538. self.__keepalive_last = now
  539. def _read_timeout(self, timeout):
  540. start = time.time()
  541. while True:
  542. try:
  543. x = self.__socket.recv(128)
  544. if len(x) == 0:
  545. raise EOFError()
  546. break
  547. except socket.timeout:
  548. pass
  549. except EnvironmentError as e:
  550. if first_arg(e) == errno.EINTR:
  551. pass
  552. else:
  553. raise
  554. if self.__closed:
  555. raise EOFError()
  556. now = time.time()
  557. if now - start >= timeout:
  558. raise socket.timeout()
  559. return x
  560. def _build_packet(self, payload):
  561. # pad up at least 4 bytes, to nearest block-size (usually 8)
  562. bsize = self.__block_size_out
  563. # do not include payload length in computations for padding in EtM mode
  564. # (payload length won't be encrypted)
  565. addlen = 4 if self.__etm_out else 8
  566. padding = 3 + bsize - ((len(payload) + addlen) % bsize)
  567. packet = struct.pack(">IB", len(payload) + padding + 1, padding)
  568. packet += payload
  569. if self.__sdctr_out or self.__block_engine_out is None:
  570. # cute trick i caught openssh doing: if we're not encrypting or
  571. # SDCTR mode (RFC4344),
  572. # don't waste random bytes for the padding
  573. packet += zero_byte * padding
  574. else:
  575. packet += os.urandom(padding)
  576. return packet
  577. def _trigger_rekey(self):
  578. # outside code should check for this flag
  579. self.__need_rekey = True