File manager - Edit - /home/newsbmcs.com/public_html/static/img/logo/paramiko.tar
Back
kex_group16.py 0000644 00000004360 15030210772 0007270 0 ustar 00 # Copyright (C) 2019 Edgar Sousa <https://github.com/edgsousa> # # This file is part of paramiko. # # Paramiko is free software; you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of 4096 bit key halves, using a known "p" prime and "g" generator. """ from paramiko.kex_group1 import KexGroup1 from hashlib import sha512 class KexGroup16SHA512(KexGroup1): name = "diffie-hellman-group16-sha512" # http://tools.ietf.org/html/rfc3526#section-5 P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF # noqa G = 2 name = "diffie-hellman-group16-sha512" hash_algo = sha512 sftp_file.py 0000644 00000052474 15030210772 0007102 0 ustar 00 # Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com> # # This file is part of paramiko. # # Paramiko is free software; you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ SFTP file object """ from binascii import hexlify from collections import deque import socket import threading import time from paramiko.common import DEBUG, io_sleep from paramiko.file import BufferedFile from paramiko.util import u from paramiko.sftp import ( CMD_CLOSE, CMD_READ, CMD_DATA, SFTPError, CMD_WRITE, CMD_STATUS, CMD_FSTAT, CMD_ATTRS, CMD_FSETSTAT, CMD_EXTENDED, int64, ) from paramiko.sftp_attr import SFTPAttributes class SFTPFile(BufferedFile): """ Proxy object for a file on the remote server, in client mode SFTP. Instances of this class may be used as context managers in the same way that built-in Python file objects are. """ # Some sftp servers will choke if you send read/write requests larger than # this size. MAX_REQUEST_SIZE = 32768 def __init__(self, sftp, handle, mode="r", bufsize=-1): BufferedFile.__init__(self) self.sftp = sftp self.handle = handle BufferedFile._set_mode(self, mode, bufsize) self.pipelined = False self._prefetching = False self._prefetch_done = False self._prefetch_data = {} self._prefetch_extents = {} self._prefetch_lock = threading.Lock() self._saved_exception = None self._reqs = deque() def __del__(self): self._close(async_=True) def close(self): """ Close the file. """ self._close(async_=False) def _close(self, async_=False): # We allow double-close without signaling an error, because real # Python file objects do. However, we must protect against actually # sending multiple CMD_CLOSE packets, because after we close our # handle, the same handle may be re-allocated by the server, and we # may end up mysteriously closing some random other file. (This is # especially important because we unconditionally call close() from # __del__.) if self._closed: return self.sftp._log(DEBUG, "close({})".format(u(hexlify(self.handle)))) if self.pipelined: self.sftp._finish_responses(self) BufferedFile.close(self) try: if async_: # GC'd file handle could be called from an arbitrary thread # -- don't wait for a response self.sftp._async_request(type(None), CMD_CLOSE, self.handle) else: self.sftp._request(CMD_CLOSE, self.handle) except EOFError: # may have outlived the Transport connection pass except (IOError, socket.error): # may have outlived the Transport connection pass def _data_in_prefetch_requests(self, offset, size): k = [ x for x in list(self._prefetch_extents.values()) if x[0] <= offset ] if len(k) == 0: return False k.sort(key=lambda x: x[0]) buf_offset, buf_size = k[-1] if buf_offset + buf_size <= offset: # prefetch request ends before this one begins return False if buf_offset + buf_size >= offset + size: # inclusive return True # well, we have part of the request. see if another chunk has # the rest. return self._data_in_prefetch_requests( buf_offset + buf_size, offset + size - buf_offset - buf_size ) def _data_in_prefetch_buffers(self, offset): """ if a block of data is present in the prefetch buffers, at the given offset, return the offset of the relevant prefetch buffer. otherwise, return None. this guarantees nothing about the number of bytes collected in the prefetch buffer so far. """ k = [i for i in self._prefetch_data.keys() if i <= offset] if len(k) == 0: return None index = max(k) buf_offset = offset - index if buf_offset >= len(self._prefetch_data[index]): # it's not here return None return index def _read_prefetch(self, size): """ read data out of the prefetch buffer, if possible. if the data isn't in the buffer, return None. otherwise, behaves like a normal read. """ # while not closed, and haven't fetched past the current position, # and haven't reached EOF... while True: offset = self._data_in_prefetch_buffers(self._realpos) if offset is not None: break if self._prefetch_done or self._closed: break self.sftp._read_response() self._check_exception() if offset is None: self._prefetching = False return None prefetch = self._prefetch_data[offset] del self._prefetch_data[offset] buf_offset = self._realpos - offset if buf_offset > 0: self._prefetch_data[offset] = prefetch[:buf_offset] prefetch = prefetch[buf_offset:] if size < len(prefetch): self._prefetch_data[self._realpos + size] = prefetch[size:] prefetch = prefetch[:size] return prefetch def _read(self, size): size = min(size, self.MAX_REQUEST_SIZE) if self._prefetching: data = self._read_prefetch(size) if data is not None: return data t, msg = self.sftp._request( CMD_READ, self.handle, int64(self._realpos), int(size) ) if t != CMD_DATA: raise SFTPError("Expected data") return msg.get_string() def _write(self, data): # may write less than requested if it would exceed max packet size chunk = min(len(data), self.MAX_REQUEST_SIZE) sftp_async_request = self.sftp._async_request( type(None), CMD_WRITE, self.handle, int64(self._realpos), data[:chunk], ) self._reqs.append(sftp_async_request) if not self.pipelined or ( len(self._reqs) > 100 and self.sftp.sock.recv_ready() ): while len(self._reqs): req = self._reqs.popleft() t, msg = self.sftp._read_response(req) if t != CMD_STATUS: raise SFTPError("Expected status") # convert_status already called return chunk def settimeout(self, timeout): """ Set a timeout on read/write operations on the underlying socket or ssh `.Channel`. :param float timeout: seconds to wait for a pending read/write operation before raising ``socket.timeout``, or ``None`` for no timeout .. seealso:: `.Channel.settimeout` """ self.sftp.sock.settimeout(timeout) def gettimeout(self): """ Returns the timeout in seconds (as a `float`) associated with the socket or ssh `.Channel` used for this file. .. seealso:: `.Channel.gettimeout` """ return self.sftp.sock.gettimeout() def setblocking(self, blocking): """ Set blocking or non-blocking mode on the underiying socket or ssh `.Channel`. :param int blocking: 0 to set non-blocking mode; non-0 to set blocking mode. .. seealso:: `.Channel.setblocking` """ self.sftp.sock.setblocking(blocking) def seekable(self): """ Check if the file supports random access. :return: `True` if the file supports random access. If `False`, :meth:`seek` will raise an exception """ return True def seek(self, offset, whence=0): """ Set the file's current position. See `file.seek` for details. """ self.flush() if whence == self.SEEK_SET: self._realpos = self._pos = offset elif whence == self.SEEK_CUR: self._pos += offset self._realpos = self._pos else: self._realpos = self._pos = self._get_size() + offset self._rbuffer = bytes() def stat(self): """ Retrieve information about this file from the remote system. This is exactly like `.SFTPClient.stat`, except that it operates on an already-open file. :returns: an `.SFTPAttributes` object containing attributes about this file. """ t, msg = self.sftp._request(CMD_FSTAT, self.handle) if t != CMD_ATTRS: raise SFTPError("Expected attributes") return SFTPAttributes._from_msg(msg) def chmod(self, mode): """ Change the mode (permissions) of this file. The permissions are unix-style and identical to those used by Python's `os.chmod` function. :param int mode: new permissions """ self.sftp._log( DEBUG, "chmod({}, {!r})".format(hexlify(self.handle), mode) ) attr = SFTPAttributes() attr.st_mode = mode self.sftp._request(CMD_FSETSTAT, self.handle, attr) def chown(self, uid, gid): """ Change the owner (``uid``) and group (``gid``) of this file. As with Python's `os.chown` function, you must pass both arguments, so if you only want to change one, use `stat` first to retrieve the current owner and group. :param int uid: new owner's uid :param int gid: new group id """ self.sftp._log( DEBUG, "chown({}, {!r}, {!r})".format(hexlify(self.handle), uid, gid), ) attr = SFTPAttributes() attr.st_uid, attr.st_gid = uid, gid self.sftp._request(CMD_FSETSTAT, self.handle, attr) def utime(self, times): """ Set the access and modified times of this file. If ``times`` is ``None``, then the file's access and modified times are set to the current time. Otherwise, ``times`` must be a 2-tuple of numbers, of the form ``(atime, mtime)``, which is used to set the access and modified times, respectively. This bizarre API is mimicked from Python for the sake of consistency -- I apologize. :param tuple times: ``None`` or a tuple of (access time, modified time) in standard internet epoch time (seconds since 01 January 1970 GMT) """ if times is None: times = (time.time(), time.time()) self.sftp._log( DEBUG, "utime({}, {!r})".format(hexlify(self.handle), times) ) attr = SFTPAttributes() attr.st_atime, attr.st_mtime = times self.sftp._request(CMD_FSETSTAT, self.handle, attr) def truncate(self, size): """ Change the size of this file. This usually extends or shrinks the size of the file, just like the ``truncate()`` method on Python file objects. :param size: the new size of the file """ self.sftp._log( DEBUG, "truncate({}, {!r})".format(hexlify(self.handle), size) ) attr = SFTPAttributes() attr.st_size = size self.sftp._request(CMD_FSETSTAT, self.handle, attr) def check(self, hash_algorithm, offset=0, length=0, block_size=0): """ Ask the server for a hash of a section of this file. This can be used to verify a successful upload or download, or for various rsync-like operations. The file is hashed from ``offset``, for ``length`` bytes. If ``length`` is 0, the remainder of the file is hashed. Thus, if both ``offset`` and ``length`` are zero, the entire file is hashed. Normally, ``block_size`` will be 0 (the default), and this method will return a byte string representing the requested hash (for example, a string of length 16 for MD5, or 20 for SHA-1). If a non-zero ``block_size`` is given, each chunk of the file (from ``offset`` to ``offset + length``) of ``block_size`` bytes is computed as a separate hash. The hash results are all concatenated and returned as a single string. For example, ``check('sha1', 0, 1024, 512)`` will return a string of length 40. The first 20 bytes will be the SHA-1 of the first 512 bytes of the file, and the last 20 bytes will be the SHA-1 of the next 512 bytes. :param str hash_algorithm: the name of the hash algorithm to use (normally ``"sha1"`` or ``"md5"``) :param offset: offset into the file to begin hashing (0 means to start from the beginning) :param length: number of bytes to hash (0 means continue to the end of the file) :param int block_size: number of bytes to hash per result (must not be less than 256; 0 means to compute only one hash of the entire segment) :return: `str` of bytes representing the hash of each block, concatenated together :raises: ``IOError`` -- if the server doesn't support the "check-file" extension, or possibly doesn't support the hash algorithm requested .. note:: Many (most?) servers don't support this extension yet. .. versionadded:: 1.4 """ t, msg = self.sftp._request( CMD_EXTENDED, "check-file", self.handle, hash_algorithm, int64(offset), int64(length), block_size, ) msg.get_text() # ext msg.get_text() # alg data = msg.get_remainder() return data def set_pipelined(self, pipelined=True): """ Turn on/off the pipelining of write operations to this file. When pipelining is on, paramiko won't wait for the server response after each write operation. Instead, they're collected as they come in. At the first non-write operation (including `.close`), all remaining server responses are collected. This means that if there was an error with one of your later writes, an exception might be thrown from within `.close` instead of `.write`. By default, files are not pipelined. :param bool pipelined: ``True`` if pipelining should be turned on for this file; ``False`` otherwise .. versionadded:: 1.5 """ self.pipelined = pipelined def prefetch(self, file_size=None, max_concurrent_requests=None): """ Pre-fetch the remaining contents of this file in anticipation of future `.read` calls. If reading the entire file, pre-fetching can dramatically improve the download speed by avoiding roundtrip latency. The file's contents are incrementally buffered in a background thread. The prefetched data is stored in a buffer until read via the `.read` method. Once data has been read, it's removed from the buffer. The data may be read in a random order (using `.seek`); chunks of the buffer that haven't been read will continue to be buffered. :param int file_size: When this is ``None`` (the default), this method calls `stat` to determine the remote file size. In some situations, doing so can cause exceptions or hangs (see `#562 <https://github.com/paramiko/paramiko/pull/562>`_); as a workaround, one may call `stat` explicitly and pass its value in via this parameter. :param int max_concurrent_requests: The maximum number of concurrent read requests to prefetch. See `.SFTPClient.get` (its ``max_concurrent_prefetch_requests`` param) for details. .. versionadded:: 1.5.1 .. versionchanged:: 1.16.0 The ``file_size`` parameter was added (with no default value). .. versionchanged:: 1.16.1 The ``file_size`` parameter was made optional for backwards compatibility. .. versionchanged:: 3.3 Added ``max_concurrent_requests``. """ if file_size is None: file_size = self.stat().st_size # queue up async reads for the rest of the file chunks = [] n = self._realpos while n < file_size: chunk = min(self.MAX_REQUEST_SIZE, file_size - n) chunks.append((n, chunk)) n += chunk if len(chunks) > 0: self._start_prefetch(chunks, max_concurrent_requests) def readv(self, chunks, max_concurrent_prefetch_requests=None): """ Read a set of blocks from the file by (offset, length). This is more efficient than doing a series of `.seek` and `.read` calls, since the prefetch machinery is used to retrieve all the requested blocks at once. :param chunks: a list of ``(offset, length)`` tuples indicating which sections of the file to read :param int max_concurrent_prefetch_requests: The maximum number of concurrent read requests to prefetch. See `.SFTPClient.get` (its ``max_concurrent_prefetch_requests`` param) for details. :return: a list of blocks read, in the same order as in ``chunks`` .. versionadded:: 1.5.4 .. versionchanged:: 3.3 Added ``max_concurrent_prefetch_requests``. """ self.sftp._log( DEBUG, "readv({}, {!r})".format(hexlify(self.handle), chunks) ) read_chunks = [] for offset, size in chunks: # don't fetch data that's already in the prefetch buffer if self._data_in_prefetch_buffers( offset ) or self._data_in_prefetch_requests(offset, size): continue # break up anything larger than the max read size while size > 0: chunk_size = min(size, self.MAX_REQUEST_SIZE) read_chunks.append((offset, chunk_size)) offset += chunk_size size -= chunk_size self._start_prefetch(read_chunks, max_concurrent_prefetch_requests) # now we can just devolve to a bunch of read()s :) for x in chunks: self.seek(x[0]) yield self.read(x[1]) # ...internals... def _get_size(self): try: return self.stat().st_size except: return 0 def _start_prefetch(self, chunks, max_concurrent_requests=None): self._prefetching = True self._prefetch_done = False t = threading.Thread( target=self._prefetch_thread, args=(chunks, max_concurrent_requests), ) t.daemon = True t.start() def _prefetch_thread(self, chunks, max_concurrent_requests): # do these read requests in a temporary thread because there may be # a lot of them, so it may block. for offset, length in chunks: # Limit the number of concurrent requests in a busy-loop if max_concurrent_requests is not None: while True: with self._prefetch_lock: pf_len = len(self._prefetch_extents) if pf_len < max_concurrent_requests: break time.sleep(io_sleep) num = self.sftp._async_request( self, CMD_READ, self.handle, int64(offset), int(length) ) with self._prefetch_lock: self._prefetch_extents[num] = (offset, length) def _async_response(self, t, msg, num): if t == CMD_STATUS: # save exception and re-raise it on next file operation try: self.sftp._convert_status(msg) except Exception as e: self._saved_exception = e return if t != CMD_DATA: raise SFTPError("Expected data") data = msg.get_string() while True: with self._prefetch_lock: # spin if in race with _prefetch_thread if num in self._prefetch_extents: offset, length = self._prefetch_extents[num] self._prefetch_data[offset] = data del self._prefetch_extents[num] if len(self._prefetch_extents) == 0: self._prefetch_done = True break def _check_exception(self): """if there's a saved exception, raise & clear it""" if self._saved_exception is not None: x = self._saved_exception self._saved_exception = None raise x agent.py 0000644 00000037005 15030210772 0006216 0 ustar 00 # Copyright (C) 2003-2007 John Rochester <john@jrochester.org> # # This file is part of paramiko. # # Paramiko is free software; you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ SSH Agent interface """ import os import socket import struct import sys import threading import time import tempfile import stat from logging import DEBUG from select import select from paramiko.common import io_sleep, byte_chr from paramiko.ssh_exception import SSHException, AuthenticationException from paramiko.message import Message from paramiko.pkey import PKey, UnknownKeyType from paramiko.util import asbytes, get_logger cSSH2_AGENTC_REQUEST_IDENTITIES = byte_chr(11) SSH2_AGENT_IDENTITIES_ANSWER = 12 cSSH2_AGENTC_SIGN_REQUEST = byte_chr(13) SSH2_AGENT_SIGN_RESPONSE = 14 SSH_AGENT_RSA_SHA2_256 = 2 SSH_AGENT_RSA_SHA2_512 = 4 # NOTE: RFC mildly confusing; while these flags are OR'd together, OpenSSH at # least really treats them like "AND"s, in the sense that if it finds the # SHA256 flag set it won't continue looking at the SHA512 one; it # short-circuits right away. # Thus, we never want to eg submit 6 to say "either's good". ALGORITHM_FLAG_MAP = { "rsa-sha2-256": SSH_AGENT_RSA_SHA2_256, "rsa-sha2-512": SSH_AGENT_RSA_SHA2_512, } for key, value in list(ALGORITHM_FLAG_MAP.items()): ALGORITHM_FLAG_MAP[f"{key}-cert-v01@openssh.com"] = value # TODO 4.0: rename all these - including making some of their methods public? class AgentSSH: def __init__(self): self._conn = None self._keys = () def get_keys(self): """ Return the list of keys available through the SSH agent, if any. If no SSH agent was running (or it couldn't be contacted), an empty list will be returned. This method performs no IO, just returns the list of keys retrieved when the connection was made. :return: a tuple of `.AgentKey` objects representing keys available on the SSH agent """ return self._keys def _connect(self, conn): self._conn = conn ptype, result = self._send_message(cSSH2_AGENTC_REQUEST_IDENTITIES) if ptype != SSH2_AGENT_IDENTITIES_ANSWER: raise SSHException("could not get keys from ssh-agent") keys = [] for i in range(result.get_int()): keys.append( AgentKey( agent=self, blob=result.get_binary(), comment=result.get_text(), ) ) self._keys = tuple(keys) def _close(self): if self._conn is not None: self._conn.close() self._conn = None self._keys = () def _send_message(self, msg): msg = asbytes(msg) self._conn.send(struct.pack(">I", len(msg)) + msg) data = self._read_all(4) msg = Message(self._read_all(struct.unpack(">I", data)[0])) return ord(msg.get_byte()), msg def _read_all(self, wanted): result = self._conn.recv(wanted) while len(result) < wanted: if len(result) == 0: raise SSHException("lost ssh-agent") extra = self._conn.recv(wanted - len(result)) if len(extra) == 0: raise SSHException("lost ssh-agent") result += extra return result class AgentProxyThread(threading.Thread): """ Class in charge of communication between two channels. """ def __init__(self, agent): threading.Thread.__init__(self, target=self.run) self._agent = agent self._exit = False def run(self): try: (r, addr) = self.get_connection() # Found that r should be either # a socket from the socket library or None self.__inr = r # The address should be an IP address as a string? or None self.__addr = addr self._agent.connect() if not isinstance(self._agent, int) and ( self._agent._conn is None or not hasattr(self._agent._conn, "fileno") ): raise AuthenticationException("Unable to connect to SSH agent") self._communicate() except: # XXX Not sure what to do here ... raise or pass ? raise def _communicate(self): import fcntl oldflags = fcntl.fcntl(self.__inr, fcntl.F_GETFL) fcntl.fcntl(self.__inr, fcntl.F_SETFL, oldflags | os.O_NONBLOCK) while not self._exit: events = select([self._agent._conn, self.__inr], [], [], 0.5) for fd in events[0]: if self._agent._conn == fd: data = self._agent._conn.recv(512) if len(data) != 0: self.__inr.send(data) else: self._close() break elif self.__inr == fd: data = self.__inr.recv(512) if len(data) != 0: self._agent._conn.send(data) else: self._close() break time.sleep(io_sleep) def _close(self): self._exit = True self.__inr.close() self._agent._conn.close() class AgentLocalProxy(AgentProxyThread): """ Class to be used when wanting to ask a local SSH Agent being asked from a remote fake agent (so use a unix socket for ex.) """ def __init__(self, agent): AgentProxyThread.__init__(self, agent) def get_connection(self): """ Return a pair of socket object and string address. May block! """ conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: conn.bind(self._agent._get_filename()) conn.listen(1) (r, addr) = conn.accept() return r, addr except: raise class AgentRemoteProxy(AgentProxyThread): """ Class to be used when wanting to ask a remote SSH Agent """ def __init__(self, agent, chan): AgentProxyThread.__init__(self, agent) self.__chan = chan def get_connection(self): return self.__chan, None def get_agent_connection(): """ Returns some SSH agent object, or None if none were found/supported. .. versionadded:: 2.10 """ if ("SSH_AUTH_SOCK" in os.environ) and (sys.platform != "win32"): conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: conn.connect(os.environ["SSH_AUTH_SOCK"]) return conn except: # probably a dangling env var: the ssh agent is gone return elif sys.platform == "win32": from . import win_pageant, win_openssh conn = None if win_pageant.can_talk_to_agent(): conn = win_pageant.PageantConnection() elif win_openssh.can_talk_to_agent(): conn = win_openssh.OpenSSHAgentConnection() return conn else: # no agent support return class AgentClientProxy: """ Class proxying request as a client: #. client ask for a request_forward_agent() #. server creates a proxy and a fake SSH Agent #. server ask for establishing a connection when needed, calling the forward_agent_handler at client side. #. the forward_agent_handler launch a thread for connecting the remote fake agent and the local agent #. Communication occurs ... """ def __init__(self, chanRemote): self._conn = None self.__chanR = chanRemote self.thread = AgentRemoteProxy(self, chanRemote) self.thread.start() def __del__(self): self.close() def connect(self): """ Method automatically called by ``AgentProxyThread.run``. """ conn = get_agent_connection() if not conn: return self._conn = conn def close(self): """ Close the current connection and terminate the agent Should be called manually """ if hasattr(self, "thread"): self.thread._exit = True self.thread.join(1000) if self._conn is not None: self._conn.close() class AgentServerProxy(AgentSSH): """ Allows an SSH server to access a forwarded agent. This also creates a unix domain socket on the system to allow external programs to also access the agent. For this reason, you probably only want to create one of these. :meth:`connect` must be called before it is usable. This will also load the list of keys the agent contains. You must also call :meth:`close` in order to clean up the unix socket and the thread that maintains it. (:class:`contextlib.closing` might be helpful to you.) :param .Transport t: Transport used for SSH Agent communication forwarding :raises: `.SSHException` -- mostly if we lost the agent """ def __init__(self, t): AgentSSH.__init__(self) self.__t = t self._dir = tempfile.mkdtemp("sshproxy") os.chmod(self._dir, stat.S_IRWXU) self._file = self._dir + "/sshproxy.ssh" self.thread = AgentLocalProxy(self) self.thread.start() def __del__(self): self.close() def connect(self): conn_sock = self.__t.open_forward_agent_channel() if conn_sock is None: raise SSHException("lost ssh-agent") conn_sock.set_name("auth-agent") self._connect(conn_sock) def close(self): """ Terminate the agent, clean the files, close connections Should be called manually """ os.remove(self._file) os.rmdir(self._dir) self.thread._exit = True self.thread.join(1000) self._close() def get_env(self): """ Helper for the environment under unix :return: a dict containing the ``SSH_AUTH_SOCK`` environment variables """ return {"SSH_AUTH_SOCK": self._get_filename()} def _get_filename(self): return self._file class AgentRequestHandler: """ Primary/default implementation of SSH agent forwarding functionality. Simply instantiate this class, handing it a live command-executing session object, and it will handle forwarding any local SSH agent processes it finds. For example:: # Connect client = SSHClient() client.connect(host, port, username) # Obtain session session = client.get_transport().open_session() # Forward local agent AgentRequestHandler(session) # Commands executed after this point will see the forwarded agent on # the remote end. session.exec_command("git clone https://my.git.repository/") """ def __init__(self, chanClient): self._conn = None self.__chanC = chanClient chanClient.request_forward_agent(self._forward_agent_handler) self.__clientProxys = [] def _forward_agent_handler(self, chanRemote): self.__clientProxys.append(AgentClientProxy(chanRemote)) def __del__(self): self.close() def close(self): for p in self.__clientProxys: p.close() class Agent(AgentSSH): """ Client interface for using private keys from an SSH agent running on the local machine. If an SSH agent is running, this class can be used to connect to it and retrieve `.PKey` objects which can be used when attempting to authenticate to remote SSH servers. Upon initialization, a session with the local machine's SSH agent is opened, if one is running. If no agent is running, initialization will succeed, but `get_keys` will return an empty tuple. :raises: `.SSHException` -- if an SSH agent is found, but speaks an incompatible protocol .. versionchanged:: 2.10 Added support for native openssh agent on windows (extending previous putty pageant support) """ def __init__(self): AgentSSH.__init__(self) conn = get_agent_connection() if not conn: return self._connect(conn) def close(self): """ Close the SSH agent connection. """ self._close() class AgentKey(PKey): """ Private key held in a local SSH agent. This type of key can be used for authenticating to a remote server (signing). Most other key operations work as expected. .. versionchanged:: 3.2 Added the ``comment`` kwarg and attribute. .. versionchanged:: 3.2 Added the ``.inner_key`` attribute holding a reference to the 'real' key instance this key is a proxy for, if one was obtainable, else None. """ def __init__(self, agent, blob, comment=""): self.agent = agent self.blob = blob self.comment = comment msg = Message(blob) self.name = msg.get_text() self._logger = get_logger(__file__) self.inner_key = None try: self.inner_key = PKey.from_type_string( key_type=self.name, key_bytes=blob ) except UnknownKeyType: # Log, but don't explode, since inner_key is a best-effort thing. err = "Unable to derive inner_key for agent key of type {!r}" self.log(DEBUG, err.format(self.name)) def log(self, *args, **kwargs): return self._logger.log(*args, **kwargs) def asbytes(self): # Prefer inner_key.asbytes, since that will differ for eg RSA-CERT return self.inner_key.asbytes() if self.inner_key else self.blob def get_name(self): return self.name def get_bits(self): # Have to work around PKey's default get_bits being crap if self.inner_key is not None: return self.inner_key.get_bits() return super().get_bits() def __getattr__(self, name): """ Proxy any un-implemented methods/properties to the inner_key. """ if self.inner_key is None: # nothing to proxy to raise AttributeError(name) return getattr(self.inner_key, name) @property def _fields(self): fallback = [self.get_name(), self.blob] return self.inner_key._fields if self.inner_key else fallback def sign_ssh_data(self, data, algorithm=None): msg = Message() msg.add_byte(cSSH2_AGENTC_SIGN_REQUEST) # NOTE: this used to be just self.blob, which is not entirely right for # RSA-CERT 'keys' - those end up always degrading to ssh-rsa type # signatures, for reasons probably internal to OpenSSH's agent code, # even if everything else wants SHA2 (including our flag map). msg.add_string(self.asbytes()) msg.add_string(data) msg.add_int(ALGORITHM_FLAG_MAP.get(algorithm, 0)) ptype, result = self.agent._send_message(msg) if ptype != SSH2_AGENT_SIGN_RESPONSE: raise SSHException("key cannot be used for signing") return result.get_binary() sftp.py 0000644 00000014507 15030210772 0006076 0 ustar 00 # Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com> # # This file is part of paramiko. # # Paramiko is free software; you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import select import socket import struct from paramiko import util from paramiko.common import DEBUG, byte_chr, byte_ord from paramiko.message import Message ( CMD_INIT, CMD_VERSION, CMD_OPEN, CMD_CLOSE, CMD_READ, CMD_WRITE, CMD_LSTAT, CMD_FSTAT, CMD_SETSTAT, CMD_FSETSTAT, CMD_OPENDIR, CMD_READDIR, CMD_REMOVE, CMD_MKDIR, CMD_RMDIR, CMD_REALPATH, CMD_STAT, CMD_RENAME, CMD_READLINK, CMD_SYMLINK, ) = range(1, 21) (CMD_STATUS, CMD_HANDLE, CMD_DATA, CMD_NAME, CMD_ATTRS) = range(101, 106) (CMD_EXTENDED, CMD_EXTENDED_REPLY) = range(200, 202) SFTP_OK = 0 ( SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED, SFTP_FAILURE, SFTP_BAD_MESSAGE, SFTP_NO_CONNECTION, SFTP_CONNECTION_LOST, SFTP_OP_UNSUPPORTED, ) = range(1, 9) SFTP_DESC = [ "Success", "End of file", "No such file", "Permission denied", "Failure", "Bad message", "No connection", "Connection lost", "Operation unsupported", ] SFTP_FLAG_READ = 0x1 SFTP_FLAG_WRITE = 0x2 SFTP_FLAG_APPEND = 0x4 SFTP_FLAG_CREATE = 0x8 SFTP_FLAG_TRUNC = 0x10 SFTP_FLAG_EXCL = 0x20 _VERSION = 3 # for debugging CMD_NAMES = { CMD_INIT: "init", CMD_VERSION: "version", CMD_OPEN: "open", CMD_CLOSE: "close", CMD_READ: "read", CMD_WRITE: "write", CMD_LSTAT: "lstat", CMD_FSTAT: "fstat", CMD_SETSTAT: "setstat", CMD_FSETSTAT: "fsetstat", CMD_OPENDIR: "opendir", CMD_READDIR: "readdir", CMD_REMOVE: "remove", CMD_MKDIR: "mkdir", CMD_RMDIR: "rmdir", CMD_REALPATH: "realpath", CMD_STAT: "stat", CMD_RENAME: "rename", CMD_READLINK: "readlink", CMD_SYMLINK: "symlink", CMD_STATUS: "status", CMD_HANDLE: "handle", CMD_DATA: "data", CMD_NAME: "name", CMD_ATTRS: "attrs", CMD_EXTENDED: "extended", CMD_EXTENDED_REPLY: "extended_reply", } # TODO: rewrite SFTP file/server modules' overly-flexible "make a request with # xyz components" so we don't need this very silly method of signaling whether # a given Python integer should be 32- or 64-bit. # NOTE: this only became an issue when dropping Python 2 support; prior to # doing so, we had to support actual-longs, which served as that signal. This # is simply recreating that structure in a more tightly scoped fashion. class int64(int): pass class SFTPError(Exception): pass class BaseSFTP: def __init__(self): self.logger = util.get_logger("paramiko.sftp") self.sock = None self.ultra_debug = False # ...internals... def _send_version(self): m = Message() m.add_int(_VERSION) self._send_packet(CMD_INIT, m) t, data = self._read_packet() if t != CMD_VERSION: raise SFTPError("Incompatible sftp protocol") version = struct.unpack(">I", data[:4])[0] # if version != _VERSION: # raise SFTPError('Incompatible sftp protocol') return version def _send_server_version(self): # winscp will freak out if the server sends version info before the # client finishes sending INIT. t, data = self._read_packet() if t != CMD_INIT: raise SFTPError("Incompatible sftp protocol") version = struct.unpack(">I", data[:4])[0] # advertise that we support "check-file" extension_pairs = ["check-file", "md5,sha1"] msg = Message() msg.add_int(_VERSION) msg.add(*extension_pairs) self._send_packet(CMD_VERSION, msg) return version def _log(self, level, msg, *args): self.logger.log(level, msg, *args) def _write_all(self, out): while len(out) > 0: n = self.sock.send(out) if n <= 0: raise EOFError() if n == len(out): return out = out[n:] return def _read_all(self, n): out = bytes() while n > 0: if isinstance(self.sock, socket.socket): # sometimes sftp is used directly over a socket instead of # through a paramiko channel. in this case, check periodically # if the socket is closed. (for some reason, recv() won't ever # return or raise an exception, but calling select on a closed # socket will.) while True: read, write, err = select.select([self.sock], [], [], 0.1) if len(read) > 0: x = self.sock.recv(n) break else: x = self.sock.recv(n) if len(x) == 0: raise EOFError() out += x n -= len(x) return out def _send_packet(self, t, packet): packet = packet.asbytes() out = struct.pack(">I", len(packet) + 1) + byte_chr(t) + packet if self.ultra_debug: self._log(DEBUG, util.format_binary(out, "OUT: ")) self._write_all(out) def _read_packet(self): x = self._read_all(4) # most sftp servers won't accept packets larger than about 32k, so # anything with the high byte set (> 16MB) is just garbage. if byte_ord(x[0]): raise SFTPError("Garbage packet received") size = struct.unpack(">I", x)[0] data = self._read_all(size) if self.ultra_debug: self._log(DEBUG, util.format_binary(data, "IN: ")) if size > 0: t = byte_ord(data[0]) return t, data[1:] return 0, bytes() channel.py 0000644 00000140047 15030210772 0006531 0 ustar 00 # Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com> # # This file is part of paramiko. # # Paramiko is free software; you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ Abstraction for an SSH2 channel. """ import binascii import os import socket import time import threading from functools import wraps from paramiko import util from paramiko.common import ( cMSG_CHANNEL_REQUEST, cMSG_CHANNEL_WINDOW_ADJUST, cMSG_CHANNEL_DATA, cMSG_CHANNEL_EXTENDED_DATA, DEBUG, ERROR, cMSG_CHANNEL_SUCCESS, cMSG_CHANNEL_FAILURE, cMSG_CHANNEL_EOF, cMSG_CHANNEL_CLOSE, ) from paramiko.message import Message from paramiko.ssh_exception import SSHException from paramiko.file import BufferedFile from paramiko.buffered_pipe import BufferedPipe, PipeTimeout from paramiko import pipe from paramiko.util import ClosingContextManager def open_only(func): """ Decorator for `.Channel` methods which performs an openness check. :raises: `.SSHException` -- If the wrapped method is called on an unopened `.Channel`. """ @wraps(func) def _check(self, *args, **kwds): if ( self.closed or self.eof_received or self.eof_sent or not self.active ): raise SSHException("Channel is not open") return func(self, *args, **kwds) return _check class Channel(ClosingContextManager): """ A secure tunnel across an SSH `.Transport`. A Channel is meant to behave like a socket, and has an API that should be indistinguishable from the Python socket API. Because SSH2 has a windowing kind of flow control, if you stop reading data from a Channel and its buffer fills up, the server will be unable to send you any more data until you read some of it. (This won't affect other channels on the same transport -- all channels on a single transport are flow-controlled independently.) Similarly, if the server isn't reading data you send, calls to `send` may block, unless you set a timeout. This is exactly like a normal network socket, so it shouldn't be too surprising. Instances of this class may be used as context managers. """ def __init__(self, chanid): """ Create a new channel. The channel is not associated with any particular session or `.Transport` until the Transport attaches it. Normally you would only call this method from the constructor of a subclass of `.Channel`. :param int chanid: the ID of this channel, as passed by an existing `.Transport`. """ #: Channel ID self.chanid = chanid #: Remote channel ID self.remote_chanid = 0 #: `.Transport` managing this channel self.transport = None #: Whether the connection is presently active self.active = False self.eof_received = 0 self.eof_sent = 0 self.in_buffer = BufferedPipe() self.in_stderr_buffer = BufferedPipe() self.timeout = None #: Whether the connection has been closed self.closed = False self.ultra_debug = False self.lock = threading.Lock() self.out_buffer_cv = threading.Condition(self.lock) self.in_window_size = 0 self.out_window_size = 0 self.in_max_packet_size = 0 self.out_max_packet_size = 0 self.in_window_threshold = 0 self.in_window_sofar = 0 self.status_event = threading.Event() self._name = str(chanid) self.logger = util.get_logger("paramiko.transport") self._pipe = None self.event = threading.Event() self.event_ready = False self.combine_stderr = False self.exit_status = -1 self.origin_addr = None def __del__(self): try: self.close() except: pass def __repr__(self): """ Return a string representation of this object, for debugging. """ out = "<paramiko.Channel {}".format(self.chanid) if self.closed: out += " (closed)" elif self.active: if self.eof_received: out += " (EOF received)" if self.eof_sent: out += " (EOF sent)" out += " (open) window={}".format(self.out_window_size) if len(self.in_buffer) > 0: out += " in-buffer={}".format(len(self.in_buffer)) out += " -> " + repr(self.transport) out += ">" return out @open_only def get_pty( self, term="vt100", width=80, height=24, width_pixels=0, height_pixels=0, ): """ Request a pseudo-terminal from the server. This is usually used right after creating a client channel, to ask the server to provide some basic terminal semantics for a shell invoked with `invoke_shell`. It isn't necessary (or desirable) to call this method if you're going to execute a single command with `exec_command`. :param str term: the terminal type to emulate (for example, ``'vt100'``) :param int width: width (in characters) of the terminal screen :param int height: height (in characters) of the terminal screen :param int width_pixels: width (in pixels) of the terminal screen :param int height_pixels: height (in pixels) of the terminal screen :raises: `.SSHException` -- if the request was rejected or the channel was closed """ m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) m.add_int(self.remote_chanid) m.add_string("pty-req") m.add_boolean(True) m.add_string(term) m.add_int(width) m.add_int(height) m.add_int(width_pixels) m.add_int(height_pixels) m.add_string(bytes()) self._event_pending() self.transport._send_user_message(m) self._wait_for_event() @open_only def invoke_shell(self): """ Request an interactive shell session on this channel. If the server allows it, the channel will then be directly connected to the stdin, stdout, and stderr of the shell. Normally you would call `get_pty` before this, in which case the shell will operate through the pty, and the channel will be connected to the stdin and stdout of the pty. When the shell exits, the channel will be closed and can't be reused. You must open a new channel if you wish to open another shell. :raises: `.SSHException` -- if the request was rejected or the channel was closed """ m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) m.add_int(self.remote_chanid) m.add_string("shell") m.add_boolean(True) self._event_pending() self.transport._send_user_message(m) self._wait_for_event() @open_only def exec_command(self, command): """ Execute a command on the server. If the server allows it, the channel will then be directly connected to the stdin, stdout, and stderr of the command being executed. When the command finishes executing, the channel will be closed and can't be reused. You must open a new channel if you wish to execute another command. :param str command: a shell command to execute. :raises: `.SSHException` -- if the request was rejected or the channel was closed """ m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) m.add_int(self.remote_chanid) m.add_string("exec") m.add_boolean(True) m.add_string(command) self._event_pending() self.transport._send_user_message(m) self._wait_for_event() @open_only def invoke_subsystem(self, subsystem): """ Request a subsystem on the server (for example, ``sftp``). If the server allows it, the channel will then be directly connected to the requested subsystem. When the subsystem finishes, the channel will be closed and can't be reused. :param str subsystem: name of the subsystem being requested. :raises: `.SSHException` -- if the request was rejected or the channel was closed """ m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) m.add_int(self.remote_chanid) m.add_string("subsystem") m.add_boolean(True) m.add_string(subsystem) self._event_pending() self.transport._send_user_message(m) self._wait_for_event() @open_only def resize_pty(self, width=80, height=24, width_pixels=0, height_pixels=0): """ Resize the pseudo-terminal. This can be used to change the width and height of the terminal emulation created in a previous `get_pty` call. :param int width: new width (in characters) of the terminal screen :param int height: new height (in characters) of the terminal screen :param int width_pixels: new width (in pixels) of the terminal screen :param int height_pixels: new height (in pixels) of the terminal screen :raises: `.SSHException` -- if the request was rejected or the channel was closed """ m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) m.add_int(self.remote_chanid) m.add_string("window-change") m.add_boolean(False) m.add_int(width) m.add_int(height) m.add_int(width_pixels) m.add_int(height_pixels) self.transport._send_user_message(m) @open_only def update_environment(self, environment): """ Updates this channel's remote shell environment. .. note:: This operation is additive - i.e. the current environment is not reset before the given environment variables are set. .. warning:: Servers may silently reject some environment variables; see the warning in `set_environment_variable` for details. :param dict environment: a dictionary containing the name and respective values to set :raises: `.SSHException` -- if any of the environment variables was rejected by the server or the channel was closed """ for name, value in environment.items(): try: self.set_environment_variable(name, value) except SSHException as e: err = 'Failed to set environment variable "{}".' raise SSHException(err.format(name), e) @open_only def set_environment_variable(self, name, value): """ Set the value of an environment variable. .. warning:: The server may reject this request depending on its ``AcceptEnv`` setting; such rejections will fail silently (which is common client practice for this particular request type). Make sure you understand your server's configuration before using! :param str name: name of the environment variable :param str value: value of the environment variable :raises: `.SSHException` -- if the request was rejected or the channel was closed """ m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) m.add_int(self.remote_chanid) m.add_string("env") m.add_boolean(False) m.add_string(name) m.add_string(value) self.transport._send_user_message(m) def exit_status_ready(self): """ Return true if the remote process has exited and returned an exit status. You may use this to poll the process status if you don't want to block in `recv_exit_status`. Note that the server may not return an exit status in some cases (like bad servers). :return: ``True`` if `recv_exit_status` will return immediately, else ``False``. .. versionadded:: 1.7.3 """ return self.closed or self.status_event.is_set() def recv_exit_status(self): """ Return the exit status from the process on the server. This is mostly useful for retrieving the results of an `exec_command`. If the command hasn't finished yet, this method will wait until it does, or until the channel is closed. If no exit status is provided by the server, -1 is returned. .. warning:: In some situations, receiving remote output larger than the current `.Transport` or session's ``window_size`` (e.g. that set by the ``default_window_size`` kwarg for `.Transport.__init__`) will cause `.recv_exit_status` to hang indefinitely if it is called prior to a sufficiently large `.Channel.recv` (or if there are no threads calling `.Channel.recv` in the background). In these cases, ensuring that `.recv_exit_status` is called *after* `.Channel.recv` (or, again, using threads) can avoid the hang. :return: the exit code (as an `int`) of the process on the server. .. versionadded:: 1.2 """ self.status_event.wait() assert self.status_event.is_set() return self.exit_status def send_exit_status(self, status): """ Send the exit status of an executed command to the client. (This really only makes sense in server mode.) Many clients expect to get some sort of status code back from an executed command after it completes. :param int status: the exit code of the process .. versionadded:: 1.2 """ # in many cases, the channel will not still be open here. # that's fine. m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) m.add_int(self.remote_chanid) m.add_string("exit-status") m.add_boolean(False) m.add_int(status) self.transport._send_user_message(m) @open_only def request_x11( self, screen_number=0, auth_protocol=None, auth_cookie=None, single_connection=False, handler=None, ): """ Request an x11 session on this channel. If the server allows it, further x11 requests can be made from the server to the client, when an x11 application is run in a shell session. From :rfc:`4254`:: It is RECOMMENDED that the 'x11 authentication cookie' that is sent be a fake, random cookie, and that the cookie be checked and replaced by the real cookie when a connection request is received. If you omit the auth_cookie, a new secure random 128-bit value will be generated, used, and returned. You will need to use this value to verify incoming x11 requests and replace them with the actual local x11 cookie (which requires some knowledge of the x11 protocol). If a handler is passed in, the handler is called from another thread whenever a new x11 connection arrives. The default handler queues up incoming x11 connections, which may be retrieved using `.Transport.accept`. The handler's calling signature is:: handler(channel: Channel, (address: str, port: int)) :param int screen_number: the x11 screen number (0, 10, etc.) :param str auth_protocol: the name of the X11 authentication method used; if none is given, ``"MIT-MAGIC-COOKIE-1"`` is used :param str auth_cookie: hexadecimal string containing the x11 auth cookie; if none is given, a secure random 128-bit value is generated :param bool single_connection: if True, only a single x11 connection will be forwarded (by default, any number of x11 connections can arrive over this session) :param handler: an optional callable handler to use for incoming X11 connections :return: the auth_cookie used """ if auth_protocol is None: auth_protocol = "MIT-MAGIC-COOKIE-1" if auth_cookie is None: auth_cookie = binascii.hexlify(os.urandom(16)) m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) m.add_int(self.remote_chanid) m.add_string("x11-req") m.add_boolean(True) m.add_boolean(single_connection) m.add_string(auth_protocol) m.add_string(auth_cookie) m.add_int(screen_number) self._event_pending() self.transport._send_user_message(m) self._wait_for_event() self.transport._set_x11_handler(handler) return auth_cookie @open_only def request_forward_agent(self, handler): """ Request for a forward SSH Agent on this channel. This is only valid for an ssh-agent from OpenSSH !!! :param handler: a required callable handler to use for incoming SSH Agent connections :return: True if we are ok, else False (at that time we always return ok) :raises: SSHException in case of channel problem. """ m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) m.add_int(self.remote_chanid) m.add_string("auth-agent-req@openssh.com") m.add_boolean(False) self.transport._send_user_message(m) self.transport._set_forward_agent_handler(handler) return True def get_transport(self): """ Return the `.Transport` associated with this channel. """ return self.transport def set_name(self, name): """ Set a name for this channel. Currently it's only used to set the name of the channel in logfile entries. The name can be fetched with the `get_name` method. :param str name: new channel name """ self._name = name def get_name(self): """ Get the name of this channel that was previously set by `set_name`. """ return self._name def get_id(self): """ Return the `int` ID # for this channel. The channel ID is unique across a `.Transport` and usually a small number. It's also the number passed to `.ServerInterface.check_channel_request` when determining whether to accept a channel request in server mode. """ return self.chanid def set_combine_stderr(self, combine): """ Set whether stderr should be combined into stdout on this channel. The default is ``False``, but in some cases it may be convenient to have both streams combined. If this is ``False``, and `exec_command` is called (or ``invoke_shell`` with no pty), output to stderr will not show up through the `recv` and `recv_ready` calls. You will have to use `recv_stderr` and `recv_stderr_ready` to get stderr output. If this is ``True``, data will never show up via `recv_stderr` or `recv_stderr_ready`. :param bool combine: ``True`` if stderr output should be combined into stdout on this channel. :return: the previous setting (a `bool`). .. versionadded:: 1.1 """ data = bytes() self.lock.acquire() try: old = self.combine_stderr self.combine_stderr = combine if combine and not old: # copy old stderr buffer into primary buffer data = self.in_stderr_buffer.empty() finally: self.lock.release() if len(data) > 0: self._feed(data) return old # ...socket API... def settimeout(self, timeout): """ Set a timeout on blocking read/write operations. The ``timeout`` argument can be a nonnegative float expressing seconds, or ``None``. If a float is given, subsequent channel read/write operations will raise a timeout exception if the timeout period value has elapsed before the operation has completed. Setting a timeout of ``None`` disables timeouts on socket operations. ``chan.settimeout(0.0)`` is equivalent to ``chan.setblocking(0)``; ``chan.settimeout(None)`` is equivalent to ``chan.setblocking(1)``. :param float timeout: seconds to wait for a pending read/write operation before raising ``socket.timeout``, or ``None`` for no timeout. """ self.timeout = timeout def gettimeout(self): """ Returns the timeout in seconds (as a float) associated with socket operations, or ``None`` if no timeout is set. This reflects the last call to `setblocking` or `settimeout`. """ return self.timeout def setblocking(self, blocking): """ Set blocking or non-blocking mode of the channel: if ``blocking`` is 0, the channel is set to non-blocking mode; otherwise it's set to blocking mode. Initially all channels are in blocking mode. In non-blocking mode, if a `recv` call doesn't find any data, or if a `send` call can't immediately dispose of the data, an error exception is raised. In blocking mode, the calls block until they can proceed. An EOF condition is considered "immediate data" for `recv`, so if the channel is closed in the read direction, it will never block. ``chan.setblocking(0)`` is equivalent to ``chan.settimeout(0)``; ``chan.setblocking(1)`` is equivalent to ``chan.settimeout(None)``. :param int blocking: 0 to set non-blocking mode; non-0 to set blocking mode. """ if blocking: self.settimeout(None) else: self.settimeout(0.0) def getpeername(self): """ Return the address of the remote side of this Channel, if possible. This simply wraps `.Transport.getpeername`, used to provide enough of a socket-like interface to allow asyncore to work. (asyncore likes to call ``'getpeername'``.) """ return self.transport.getpeername() def close(self): """ Close the channel. All future read/write operations on the channel will fail. The remote end will receive no more data (after queued data is flushed). Channels are automatically closed when their `.Transport` is closed or when they are garbage collected. """ self.lock.acquire() try: # only close the pipe when the user explicitly closes the channel. # otherwise they will get unpleasant surprises. (and do it before # checking self.closed, since the remote host may have already # closed the connection.) if self._pipe is not None: self._pipe.close() self._pipe = None if not self.active or self.closed: return msgs = self._close_internal() finally: self.lock.release() for m in msgs: if m is not None: self.transport._send_user_message(m) def recv_ready(self): """ Returns true if data is buffered and ready to be read from this channel. A ``False`` result does not mean that the channel has closed; it means you may need to wait before more data arrives. :return: ``True`` if a `recv` call on this channel would immediately return at least one byte; ``False`` otherwise. """ return self.in_buffer.read_ready() def recv(self, nbytes): """ Receive data from the channel. The return value is a string representing the data received. The maximum amount of data to be received at once is specified by ``nbytes``. If a string of length zero is returned, the channel stream has closed. :param int nbytes: maximum number of bytes to read. :return: received data, as a `bytes`. :raises socket.timeout: if no data is ready before the timeout set by `settimeout`. """ try: out = self.in_buffer.read(nbytes, self.timeout) except PipeTimeout: raise socket.timeout() ack = self._check_add_window(len(out)) # no need to hold the channel lock when sending this if ack > 0: m = Message() m.add_byte(cMSG_CHANNEL_WINDOW_ADJUST) m.add_int(self.remote_chanid) m.add_int(ack) self.transport._send_user_message(m) return out def recv_stderr_ready(self): """ Returns true if data is buffered and ready to be read from this channel's stderr stream. Only channels using `exec_command` or `invoke_shell` without a pty will ever have data on the stderr stream. :return: ``True`` if a `recv_stderr` call on this channel would immediately return at least one byte; ``False`` otherwise. .. versionadded:: 1.1 """ return self.in_stderr_buffer.read_ready() def recv_stderr(self, nbytes): """ Receive data from the channel's stderr stream. Only channels using `exec_command` or `invoke_shell` without a pty will ever have data on the stderr stream. The return value is a string representing the data received. The maximum amount of data to be received at once is specified by ``nbytes``. If a string of length zero is returned, the channel stream has closed. :param int nbytes: maximum number of bytes to read. :return: received data as a `bytes` :raises socket.timeout: if no data is ready before the timeout set by `settimeout`. .. versionadded:: 1.1 """ try: out = self.in_stderr_buffer.read(nbytes, self.timeout) except PipeTimeout: raise socket.timeout() ack = self._check_add_window(len(out)) # no need to hold the channel lock when sending this if ack > 0: m = Message() m.add_byte(cMSG_CHANNEL_WINDOW_ADJUST) m.add_int(self.remote_chanid) m.add_int(ack) self.transport._send_user_message(m) return out def send_ready(self): """ Returns true if data can be written to this channel without blocking. This means the channel is either closed (so any write attempt would return immediately) or there is at least one byte of space in the outbound buffer. If there is at least one byte of space in the outbound buffer, a `send` call will succeed immediately and return the number of bytes actually written. :return: ``True`` if a `send` call on this channel would immediately succeed or fail """ self.lock.acquire() try: if self.closed or self.eof_sent: return True return self.out_window_size > 0 finally: self.lock.release() def send(self, s): """ Send data to the channel. Returns the number of bytes sent, or 0 if the channel stream is closed. Applications are responsible for checking that all data has been sent: if only some of the data was transmitted, the application needs to attempt delivery of the remaining data. :param bytes s: data to send :return: number of bytes actually sent, as an `int` :raises socket.timeout: if no data could be sent before the timeout set by `settimeout`. """ m = Message() m.add_byte(cMSG_CHANNEL_DATA) m.add_int(self.remote_chanid) return self._send(s, m) def send_stderr(self, s): """ Send data to the channel on the "stderr" stream. This is normally only used by servers to send output from shell commands -- clients won't use this. Returns the number of bytes sent, or 0 if the channel stream is closed. Applications are responsible for checking that all data has been sent: if only some of the data was transmitted, the application needs to attempt delivery of the remaining data. :param bytes s: data to send. :return: number of bytes actually sent, as an `int`. :raises socket.timeout: if no data could be sent before the timeout set by `settimeout`. .. versionadded:: 1.1 """ m = Message() m.add_byte(cMSG_CHANNEL_EXTENDED_DATA) m.add_int(self.remote_chanid) m.add_int(1) return self._send(s, m) def sendall(self, s): """ Send data to the channel, without allowing partial results. Unlike `send`, this method continues to send data from the given string until either all data has been sent or an error occurs. Nothing is returned. :param bytes s: data to send. :raises socket.timeout: if sending stalled for longer than the timeout set by `settimeout`. :raises socket.error: if an error occurred before the entire string was sent. .. note:: If the channel is closed while only part of the data has been sent, there is no way to determine how much data (if any) was sent. This is irritating, but identically follows Python's API. """ while s: sent = self.send(s) s = s[sent:] return None def sendall_stderr(self, s): """ Send data to the channel's "stderr" stream, without allowing partial results. Unlike `send_stderr`, this method continues to send data from the given bytestring until all data has been sent or an error occurs. Nothing is returned. :param bytes s: data to send to the client as "stderr" output. :raises socket.timeout: if sending stalled for longer than the timeout set by `settimeout`. :raises socket.error: if an error occurred before the entire string was sent. .. versionadded:: 1.1 """ while s: sent = self.send_stderr(s) s = s[sent:] return None def makefile(self, *params): """ Return a file-like object associated with this channel. The optional ``mode`` and ``bufsize`` arguments are interpreted the same way as by the built-in ``file()`` function in Python. :return: `.ChannelFile` object which can be used for Python file I/O. """ return ChannelFile(*([self] + list(params))) def makefile_stderr(self, *params): """ Return a file-like object associated with this channel's stderr stream. Only channels using `exec_command` or `invoke_shell` without a pty will ever have data on the stderr stream. The optional ``mode`` and ``bufsize`` arguments are interpreted the same way as by the built-in ``file()`` function in Python. For a client, it only makes sense to open this file for reading. For a server, it only makes sense to open this file for writing. :returns: `.ChannelStderrFile` object which can be used for Python file I/O. .. versionadded:: 1.1 """ return ChannelStderrFile(*([self] + list(params))) def makefile_stdin(self, *params): """ Return a file-like object associated with this channel's stdin stream. The optional ``mode`` and ``bufsize`` arguments are interpreted the same way as by the built-in ``file()`` function in Python. For a client, it only makes sense to open this file for writing. For a server, it only makes sense to open this file for reading. :returns: `.ChannelStdinFile` object which can be used for Python file I/O. .. versionadded:: 2.6 """ return ChannelStdinFile(*([self] + list(params))) def fileno(self): """ Returns an OS-level file descriptor which can be used for polling, but but not for reading or writing. This is primarily to allow Python's ``select`` module to work. The first time ``fileno`` is called on a channel, a pipe is created to simulate real OS-level file descriptor (FD) behavior. Because of this, two OS-level FDs are created, which will use up FDs faster than normal. (You won't notice this effect unless you have hundreds of channels open at the same time.) :return: an OS-level file descriptor (`int`) .. warning:: This method causes channel reads to be slightly less efficient. """ self.lock.acquire() try: if self._pipe is not None: return self._pipe.fileno() # create the pipe and feed in any existing data self._pipe = pipe.make_pipe() p1, p2 = pipe.make_or_pipe(self._pipe) self.in_buffer.set_event(p1) self.in_stderr_buffer.set_event(p2) return self._pipe.fileno() finally: self.lock.release() def shutdown(self, how): """ Shut down one or both halves of the connection. If ``how`` is 0, further receives are disallowed. If ``how`` is 1, further sends are disallowed. If ``how`` is 2, further sends and receives are disallowed. This closes the stream in one or both directions. :param int how: 0 (stop receiving), 1 (stop sending), or 2 (stop receiving and sending). """ if (how == 0) or (how == 2): # feign "read" shutdown self.eof_received = 1 if (how == 1) or (how == 2): self.lock.acquire() try: m = self._send_eof() finally: self.lock.release() if m is not None: self.transport._send_user_message(m) def shutdown_read(self): """ Shutdown the receiving side of this socket, closing the stream in the incoming direction. After this call, future reads on this channel will fail instantly. This is a convenience method, equivalent to ``shutdown(0)``, for people who don't make it a habit to memorize unix constants from the 1970s. .. versionadded:: 1.2 """ self.shutdown(0) def shutdown_write(self): """ Shutdown the sending side of this socket, closing the stream in the outgoing direction. After this call, future writes on this channel will fail instantly. This is a convenience method, equivalent to ``shutdown(1)``, for people who don't make it a habit to memorize unix constants from the 1970s. .. versionadded:: 1.2 """ self.shutdown(1) @property def _closed(self): # Concession to Python 3's socket API, which has a private ._closed # attribute instead of a semipublic .closed attribute. return self.closed # ...calls from Transport def _set_transport(self, transport): self.transport = transport self.logger = util.get_logger(self.transport.get_log_channel()) def _set_window(self, window_size, max_packet_size): self.in_window_size = window_size self.in_max_packet_size = max_packet_size # threshold of bytes we receive before we bother to send # a window update self.in_window_threshold = window_size // 10 self.in_window_sofar = 0 self._log(DEBUG, "Max packet in: {} bytes".format(max_packet_size)) def _set_remote_channel(self, chanid, window_size, max_packet_size): self.remote_chanid = chanid self.out_window_size = window_size self.out_max_packet_size = self.transport._sanitize_packet_size( max_packet_size ) self.active = 1 self._log( DEBUG, "Max packet out: {} bytes".format(self.out_max_packet_size) ) def _request_success(self, m): self._log(DEBUG, "Sesch channel {} request ok".format(self.chanid)) self.event_ready = True self.event.set() return def _request_failed(self, m): self.lock.acquire() try: msgs = self._close_internal() finally: self.lock.release() for m in msgs: if m is not None: self.transport._send_user_message(m) def _feed(self, m): if isinstance(m, bytes): # passed from _feed_extended s = m else: s = m.get_binary() self.in_buffer.feed(s) def _feed_extended(self, m): code = m.get_int() s = m.get_binary() if code != 1: self._log( ERROR, "unknown extended_data type {}; discarding".format(code) ) return if self.combine_stderr: self._feed(s) else: self.in_stderr_buffer.feed(s) def _window_adjust(self, m): nbytes = m.get_int() self.lock.acquire() try: if self.ultra_debug: self._log(DEBUG, "window up {}".format(nbytes)) self.out_window_size += nbytes self.out_buffer_cv.notify_all() finally: self.lock.release() def _handle_request(self, m): key = m.get_text() want_reply = m.get_boolean() server = self.transport.server_object ok = False if key == "exit-status": self.exit_status = m.get_int() self.status_event.set() ok = True elif key == "xon-xoff": # ignore ok = True elif key == "pty-req": term = m.get_string() width = m.get_int() height = m.get_int() pixelwidth = m.get_int() pixelheight = m.get_int() modes = m.get_string() if server is None: ok = False else: ok = server.check_channel_pty_request( self, term, width, height, pixelwidth, pixelheight, modes ) elif key == "shell": if server is None: ok = False else: ok = server.check_channel_shell_request(self) elif key == "env": name = m.get_string() value = m.get_string() if server is None: ok = False else: ok = server.check_channel_env_request(self, name, value) elif key == "exec": cmd = m.get_string() if server is None: ok = False else: ok = server.check_channel_exec_request(self, cmd) elif key == "subsystem": name = m.get_text() if server is None: ok = False else: ok = server.check_channel_subsystem_request(self, name) elif key == "window-change": width = m.get_int() height = m.get_int() pixelwidth = m.get_int() pixelheight = m.get_int() if server is None: ok = False else: ok = server.check_channel_window_change_request( self, width, height, pixelwidth, pixelheight ) elif key == "x11-req": single_connection = m.get_boolean() auth_proto = m.get_text() auth_cookie = m.get_binary() screen_number = m.get_int() if server is None: ok = False else: ok = server.check_channel_x11_request( self, single_connection, auth_proto, auth_cookie, screen_number, ) elif key == "auth-agent-req@openssh.com": if server is None: ok = False else: ok = server.check_channel_forward_agent_request(self) else: self._log(DEBUG, 'Unhandled channel request "{}"'.format(key)) ok = False if want_reply: m = Message() if ok: m.add_byte(cMSG_CHANNEL_SUCCESS) else: m.add_byte(cMSG_CHANNEL_FAILURE) m.add_int(self.remote_chanid) self.transport._send_user_message(m) def _handle_eof(self, m): self.lock.acquire() try: if not self.eof_received: self.eof_received = True self.in_buffer.close() self.in_stderr_buffer.close() if self._pipe is not None: self._pipe.set_forever() finally: self.lock.release() self._log(DEBUG, "EOF received ({})".format(self._name)) def _handle_close(self, m): self.lock.acquire() try: msgs = self._close_internal() self.transport._unlink_channel(self.chanid) finally: self.lock.release() for m in msgs: if m is not None: self.transport._send_user_message(m) # ...internals... def _send(self, s, m): size = len(s) self.lock.acquire() try: if self.closed: # this doesn't seem useful, but it is the documented behavior # of Socket raise socket.error("Socket is closed") size = self._wait_for_send_window(size) if size == 0: # eof or similar return 0 m.add_string(s[:size]) finally: self.lock.release() # Note: We release self.lock before calling _send_user_message. # Otherwise, we can deadlock during re-keying. self.transport._send_user_message(m) return size def _log(self, level, msg, *args): self.logger.log(level, "[chan " + self._name + "] " + msg, *args) def _event_pending(self): self.event.clear() self.event_ready = False def _wait_for_event(self): self.event.wait() assert self.event.is_set() if self.event_ready: return e = self.transport.get_exception() if e is None: e = SSHException("Channel closed.") raise e def _set_closed(self): # you are holding the lock. self.closed = True self.in_buffer.close() self.in_stderr_buffer.close() self.out_buffer_cv.notify_all() # Notify any waiters that we are closed self.event.set() self.status_event.set() if self._pipe is not None: self._pipe.set_forever() def _send_eof(self): # you are holding the lock. if self.eof_sent: return None m = Message() m.add_byte(cMSG_CHANNEL_EOF) m.add_int(self.remote_chanid) self.eof_sent = True self._log(DEBUG, "EOF sent ({})".format(self._name)) return m def _close_internal(self): # you are holding the lock. if not self.active or self.closed: return None, None m1 = self._send_eof() m2 = Message() m2.add_byte(cMSG_CHANNEL_CLOSE) m2.add_int(self.remote_chanid) self._set_closed() # can't unlink from the Transport yet -- the remote side may still # try to send meta-data (exit-status, etc) return m1, m2 def _unlink(self): # server connection could die before we become active: # still signal the close! if self.closed: return self.lock.acquire() try: self._set_closed() self.transport._unlink_channel(self.chanid) finally: self.lock.release() def _check_add_window(self, n): self.lock.acquire() try: if self.closed or self.eof_received or not self.active: return 0 if self.ultra_debug: self._log(DEBUG, "addwindow {}".format(n)) self.in_window_sofar += n if self.in_window_sofar <= self.in_window_threshold: return 0 if self.ultra_debug: self._log( DEBUG, "addwindow send {}".format(self.in_window_sofar) ) out = self.in_window_sofar self.in_window_sofar = 0 return out finally: self.lock.release() def _wait_for_send_window(self, size): """ (You are already holding the lock.) Wait for the send window to open up, and allocate up to ``size`` bytes for transmission. If no space opens up before the timeout, a timeout exception is raised. Returns the number of bytes available to send (may be less than requested). """ # you are already holding the lock if self.closed or self.eof_sent: return 0 if self.out_window_size == 0: # should we block? if self.timeout == 0.0: raise socket.timeout() # loop here in case we get woken up but a different thread has # filled the buffer timeout = self.timeout while self.out_window_size == 0: if self.closed or self.eof_sent: return 0 then = time.time() self.out_buffer_cv.wait(timeout) if timeout is not None: timeout -= time.time() - then if timeout <= 0.0: raise socket.timeout() # we have some window to squeeze into if self.closed or self.eof_sent: return 0 if self.out_window_size < size: size = self.out_window_size if self.out_max_packet_size - 64 < size: size = self.out_max_packet_size - 64 self.out_window_size -= size if self.ultra_debug: self._log(DEBUG, "window down to {}".format(self.out_window_size)) return size class ChannelFile(BufferedFile): """ A file-like wrapper around `.Channel`. A ChannelFile is created by calling `Channel.makefile`. .. warning:: To correctly emulate the file object created from a socket's `makefile <python:socket.socket.makefile>` method, a `.Channel` and its `.ChannelFile` should be able to be closed or garbage-collected independently. Currently, closing the `ChannelFile` does nothing but flush the buffer. """ def __init__(self, channel, mode="r", bufsize=-1): self.channel = channel BufferedFile.__init__(self) self._set_mode(mode, bufsize) def __repr__(self): """ Returns a string representation of this object, for debugging. """ return "<paramiko.ChannelFile from " + repr(self.channel) + ">" def _read(self, size): return self.channel.recv(size) def _write(self, data): self.channel.sendall(data) return len(data) class ChannelStderrFile(ChannelFile): """ A file-like wrapper around `.Channel` stderr. See `Channel.makefile_stderr` for details. """ def _read(self, size): return self.channel.recv_stderr(size) def _write(self, data): self.channel.sendall_stderr(data) return len(data) class ChannelStdinFile(ChannelFile): """ A file-like wrapper around `.Channel` stdin. See `Channel.makefile_stdin` for details. """ def close(self): super().close() self.channel.shutdown_write() _winapi.py 0000644 00000025704 15030210772 0006551 0 ustar 00 """ Windows API functions implemented as ctypes functions and classes as found in jaraco.windows (3.4.1). If you encounter issues with this module, please consider reporting the issues in jaraco.windows and asking the author to port the fixes back here. """ import builtins import ctypes.wintypes from paramiko.util import u ###################### # jaraco.windows.error def format_system_message(errno): """ Call FormatMessage with a system error number to retrieve the descriptive error message. """ # first some flags used by FormatMessageW ALLOCATE_BUFFER = 0x100 FROM_SYSTEM = 0x1000 # Let FormatMessageW allocate the buffer (we'll free it below) # Also, let it know we want a system error message. flags = ALLOCATE_BUFFER | FROM_SYSTEM source = None message_id = errno language_id = 0 result_buffer = ctypes.wintypes.LPWSTR() buffer_size = 0 arguments = None bytes = ctypes.windll.kernel32.FormatMessageW( flags, source, message_id, language_id, ctypes.byref(result_buffer), buffer_size, arguments, ) # note the following will cause an infinite loop if GetLastError # repeatedly returns an error that cannot be formatted, although # this should not happen. handle_nonzero_success(bytes) message = result_buffer.value ctypes.windll.kernel32.LocalFree(result_buffer) return message class WindowsError(builtins.WindowsError): """more info about errors at http://msdn.microsoft.com/en-us/library/ms681381(VS.85).aspx""" def __init__(self, value=None): if value is None: value = ctypes.windll.kernel32.GetLastError() strerror = format_system_message(value) args = 0, strerror, None, value super().__init__(*args) @property def message(self): return self.strerror @property def code(self): return self.winerror def __str__(self): return self.message def __repr__(self): return "{self.__class__.__name__}({self.winerror})".format(**vars()) def handle_nonzero_success(result): if result == 0: raise WindowsError() ########################### # jaraco.windows.api.memory GMEM_MOVEABLE = 0x2 GlobalAlloc = ctypes.windll.kernel32.GlobalAlloc GlobalAlloc.argtypes = ctypes.wintypes.UINT, ctypes.c_size_t GlobalAlloc.restype = ctypes.wintypes.HANDLE GlobalLock = ctypes.windll.kernel32.GlobalLock GlobalLock.argtypes = (ctypes.wintypes.HGLOBAL,) GlobalLock.restype = ctypes.wintypes.LPVOID GlobalUnlock = ctypes.windll.kernel32.GlobalUnlock GlobalUnlock.argtypes = (ctypes.wintypes.HGLOBAL,) GlobalUnlock.restype = ctypes.wintypes.BOOL GlobalSize = ctypes.windll.kernel32.GlobalSize GlobalSize.argtypes = (ctypes.wintypes.HGLOBAL,) GlobalSize.restype = ctypes.c_size_t CreateFileMapping = ctypes.windll.kernel32.CreateFileMappingW CreateFileMapping.argtypes = [ ctypes.wintypes.HANDLE, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.LPWSTR, ] CreateFileMapping.restype = ctypes.wintypes.HANDLE MapViewOfFile = ctypes.windll.kernel32.MapViewOfFile MapViewOfFile.restype = ctypes.wintypes.HANDLE UnmapViewOfFile = ctypes.windll.kernel32.UnmapViewOfFile UnmapViewOfFile.argtypes = (ctypes.wintypes.HANDLE,) RtlMoveMemory = ctypes.windll.kernel32.RtlMoveMemory RtlMoveMemory.argtypes = (ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t) ctypes.windll.kernel32.LocalFree.argtypes = (ctypes.wintypes.HLOCAL,) ##################### # jaraco.windows.mmap class MemoryMap: """ A memory map object which can have security attributes overridden. """ def __init__(self, name, length, security_attributes=None): self.name = name self.length = length self.security_attributes = security_attributes self.pos = 0 def __enter__(self): p_SA = ( ctypes.byref(self.security_attributes) if self.security_attributes else None ) INVALID_HANDLE_VALUE = -1 PAGE_READWRITE = 0x4 FILE_MAP_WRITE = 0x2 filemap = ctypes.windll.kernel32.CreateFileMappingW( INVALID_HANDLE_VALUE, p_SA, PAGE_READWRITE, 0, self.length, u(self.name), ) handle_nonzero_success(filemap) if filemap == INVALID_HANDLE_VALUE: raise Exception("Failed to create file mapping") self.filemap = filemap self.view = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0) return self def seek(self, pos): self.pos = pos def write(self, msg): assert isinstance(msg, bytes) n = len(msg) if self.pos + n >= self.length: # A little safety. raise ValueError(f"Refusing to write {n} bytes") dest = self.view + self.pos length = ctypes.c_size_t(n) ctypes.windll.kernel32.RtlMoveMemory(dest, msg, length) self.pos += n def read(self, n): """ Read n bytes from mapped view. """ out = ctypes.create_string_buffer(n) source = self.view + self.pos length = ctypes.c_size_t(n) ctypes.windll.kernel32.RtlMoveMemory(out, source, length) self.pos += n return out.raw def __exit__(self, exc_type, exc_val, tb): ctypes.windll.kernel32.UnmapViewOfFile(self.view) ctypes.windll.kernel32.CloseHandle(self.filemap) ############################# # jaraco.windows.api.security # from WinNT.h READ_CONTROL = 0x00020000 STANDARD_RIGHTS_REQUIRED = 0x000F0000 STANDARD_RIGHTS_READ = READ_CONTROL STANDARD_RIGHTS_WRITE = READ_CONTROL STANDARD_RIGHTS_EXECUTE = READ_CONTROL STANDARD_RIGHTS_ALL = 0x001F0000 # from NTSecAPI.h POLICY_VIEW_LOCAL_INFORMATION = 0x00000001 POLICY_VIEW_AUDIT_INFORMATION = 0x00000002 POLICY_GET_PRIVATE_INFORMATION = 0x00000004 POLICY_TRUST_ADMIN = 0x00000008 POLICY_CREATE_ACCOUNT = 0x00000010 POLICY_CREATE_SECRET = 0x00000020 POLICY_CREATE_PRIVILEGE = 0x00000040 POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080 POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100 POLICY_AUDIT_LOG_ADMIN = 0x00000200 POLICY_SERVER_ADMIN = 0x00000400 POLICY_LOOKUP_NAMES = 0x00000800 POLICY_NOTIFICATION = 0x00001000 POLICY_ALL_ACCESS = ( STANDARD_RIGHTS_REQUIRED | POLICY_VIEW_LOCAL_INFORMATION | POLICY_VIEW_AUDIT_INFORMATION | POLICY_GET_PRIVATE_INFORMATION | POLICY_TRUST_ADMIN | POLICY_CREATE_ACCOUNT | POLICY_CREATE_SECRET | POLICY_CREATE_PRIVILEGE | POLICY_SET_DEFAULT_QUOTA_LIMITS | POLICY_SET_AUDIT_REQUIREMENTS | POLICY_AUDIT_LOG_ADMIN | POLICY_SERVER_ADMIN | POLICY_LOOKUP_NAMES ) POLICY_READ = ( STANDARD_RIGHTS_READ | POLICY_VIEW_AUDIT_INFORMATION | POLICY_GET_PRIVATE_INFORMATION ) POLICY_WRITE = ( STANDARD_RIGHTS_WRITE | POLICY_TRUST_ADMIN | POLICY_CREATE_ACCOUNT | POLICY_CREATE_SECRET | POLICY_CREATE_PRIVILEGE | POLICY_SET_DEFAULT_QUOTA_LIMITS | POLICY_SET_AUDIT_REQUIREMENTS | POLICY_AUDIT_LOG_ADMIN | POLICY_SERVER_ADMIN ) POLICY_EXECUTE = ( STANDARD_RIGHTS_EXECUTE | POLICY_VIEW_LOCAL_INFORMATION | POLICY_LOOKUP_NAMES ) class TokenAccess: TOKEN_QUERY = 0x8 class TokenInformationClass: TokenUser = 1 class TOKEN_USER(ctypes.Structure): num = 1 _fields_ = [ ("SID", ctypes.c_void_p), ("ATTRIBUTES", ctypes.wintypes.DWORD), ] class SECURITY_DESCRIPTOR(ctypes.Structure): """ typedef struct _SECURITY_DESCRIPTOR { UCHAR Revision; UCHAR Sbz1; SECURITY_DESCRIPTOR_CONTROL Control; PSID Owner; PSID Group; PACL Sacl; PACL Dacl; } SECURITY_DESCRIPTOR; """ SECURITY_DESCRIPTOR_CONTROL = ctypes.wintypes.USHORT REVISION = 1 _fields_ = [ ("Revision", ctypes.c_ubyte), ("Sbz1", ctypes.c_ubyte), ("Control", SECURITY_DESCRIPTOR_CONTROL), ("Owner", ctypes.c_void_p), ("Group", ctypes.c_void_p), ("Sacl", ctypes.c_void_p), ("Dacl", ctypes.c_void_p), ] class SECURITY_ATTRIBUTES(ctypes.Structure): """ typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; } SECURITY_ATTRIBUTES; """ _fields_ = [ ("nLength", ctypes.wintypes.DWORD), ("lpSecurityDescriptor", ctypes.c_void_p), ("bInheritHandle", ctypes.wintypes.BOOL), ] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.nLength = ctypes.sizeof(SECURITY_ATTRIBUTES) @property def descriptor(self): return self._descriptor @descriptor.setter def descriptor(self, value): self._descriptor = value self.lpSecurityDescriptor = ctypes.addressof(value) ctypes.windll.advapi32.SetSecurityDescriptorOwner.argtypes = ( ctypes.POINTER(SECURITY_DESCRIPTOR), ctypes.c_void_p, ctypes.wintypes.BOOL, ) ######################### # jaraco.windows.security def GetTokenInformation(token, information_class): """ Given a token, get the token information for it. """ data_size = ctypes.wintypes.DWORD() ctypes.windll.advapi32.GetTokenInformation( token, information_class.num, 0, 0, ctypes.byref(data_size) ) data = ctypes.create_string_buffer(data_size.value) handle_nonzero_success( ctypes.windll.advapi32.GetTokenInformation( token, information_class.num, ctypes.byref(data), ctypes.sizeof(data), ctypes.byref(data_size), ) ) return ctypes.cast(data, ctypes.POINTER(TOKEN_USER)).contents def OpenProcessToken(proc_handle, access): result = ctypes.wintypes.HANDLE() proc_handle = ctypes.wintypes.HANDLE(proc_handle) handle_nonzero_success( ctypes.windll.advapi32.OpenProcessToken( proc_handle, access, ctypes.byref(result) ) ) return result def get_current_user(): """ Return a TOKEN_USER for the owner of this process. """ process = OpenProcessToken( ctypes.windll.kernel32.GetCurrentProcess(), TokenAccess.TOKEN_QUERY ) return GetTokenInformation(process, TOKEN_USER) def get_security_attributes_for_user(user=None): """ Return a SECURITY_ATTRIBUTES structure with the SID set to the specified user (uses current user if none is specified). """ if user is None: user = get_current_user() assert isinstance(user, TOKEN_USER), "user must be TOKEN_USER instance" SD = SECURITY_DESCRIPTOR() SA = SECURITY_ATTRIBUTES() # by attaching the actual security descriptor, it will be garbage- # collected with the security attributes SA.descriptor = SD SA.bInheritHandle = 1 ctypes.windll.advapi32.InitializeSecurityDescriptor( ctypes.byref(SD), SECURITY_DESCRIPTOR.REVISION ) ctypes.windll.advapi32.SetSecurityDescriptorOwner( ctypes.byref(SD), user.SID, 0 ) return SA proxy.py 0000644 00000011050 15030210772 0006271 0 ustar 00 # Copyright (C) 2012 Yipit, Inc <coders@yipit.com> # # This file is part of paramiko. # # Paramiko is free software; you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import os import shlex import signal from select import select import socket import time # Try-and-ignore import so platforms w/o subprocess (eg Google App Engine) can # still import paramiko. subprocess, subprocess_import_error = None, None try: import subprocess except ImportError as e: subprocess_import_error = e from paramiko.ssh_exception import ProxyCommandFailure from paramiko.util import ClosingContextManager class ProxyCommand(ClosingContextManager): """ Wraps a subprocess running ProxyCommand-driven programs. This class implements a the socket-like interface needed by the `.Transport` and `.Packetizer` classes. Using this class instead of a regular socket makes it possible to talk with a Popen'd command that will proxy traffic between the client and a server hosted in another machine. Instances of this class may be used as context managers. """ def __init__(self, command_line): """ Create a new CommandProxy instance. The instance created by this class can be passed as an argument to the `.Transport` class. :param str command_line: the command that should be executed and used as the proxy. """ if subprocess is None: raise subprocess_import_error self.cmd = shlex.split(command_line) self.process = subprocess.Popen( self.cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0, ) self.timeout = None def send(self, content): """ Write the content received from the SSH client to the standard input of the forked command. :param str content: string to be sent to the forked command """ try: self.process.stdin.write(content) except IOError as e: # There was a problem with the child process. It probably # died and we can't proceed. The best option here is to # raise an exception informing the user that the informed # ProxyCommand is not working. raise ProxyCommandFailure(" ".join(self.cmd), e.strerror) return len(content) def recv(self, size): """ Read from the standard output of the forked program. :param int size: how many chars should be read :return: the string of bytes read, which may be shorter than requested """ try: buffer = b"" start = time.time() while len(buffer) < size: select_timeout = None if self.timeout is not None: elapsed = time.time() - start if elapsed >= self.timeout: raise socket.timeout() select_timeout = self.timeout - elapsed r, w, x = select([self.process.stdout], [], [], select_timeout) if r and r[0] == self.process.stdout: buffer += os.read( self.process.stdout.fileno(), size - len(buffer) ) return buffer except socket.timeout: if buffer: # Don't raise socket.timeout, return partial result instead return buffer raise # socket.timeout is a subclass of IOError except IOError as e: raise ProxyCommandFailure(" ".join(self.cmd), e.strerror) def close(self): os.kill(self.process.pid, signal.SIGTERM) @property def closed(self): return self.process.returncode is not None @property def _closed(self): # Concession to Python 3 socket-like API return self.closed def settimeout(self, timeout): self.timeout = timeout auth_strategy.py 0000644 00000026255 15030210772 0010010 0 ustar 00 """ Modern, adaptable authentication machinery. Replaces certain parts of `.SSHClient`. For a concrete implementation, see the ``OpenSSHAuthStrategy`` class in `Fabric <https://fabfile.org>`_. """ from collections import namedtuple from .agent import AgentKey from .util import get_logger from .ssh_exception import AuthenticationException class AuthSource: """ Some SSH authentication source, such as a password, private key, or agent. See subclasses in this module for concrete implementations. All implementations must accept at least a ``username`` (``str``) kwarg. """ def __init__(self, username): self.username = username def _repr(self, **kwargs): # TODO: are there any good libs for this? maybe some helper from # structlog? pairs = [f"{k}={v!r}" for k, v in kwargs.items()] joined = ", ".join(pairs) return f"{self.__class__.__name__}({joined})" def __repr__(self): return self._repr() def authenticate(self, transport): """ Perform authentication. """ raise NotImplementedError class NoneAuth(AuthSource): """ Auth type "none", ie https://www.rfc-editor.org/rfc/rfc4252#section-5.2 . """ def authenticate(self, transport): return transport.auth_none(self.username) class Password(AuthSource): """ Password authentication. :param callable password_getter: A lazy callable that should return a `str` password value at authentication time, such as a `functools.partial` wrapping `getpass.getpass`, an API call to a secrets store, or similar. If you already know the password at instantiation time, you should simply use something like ``lambda: "my literal"`` (for a literal, but also, shame on you!) or ``lambda: variable_name`` (for something stored in a variable). """ def __init__(self, username, password_getter): super().__init__(username=username) self.password_getter = password_getter def __repr__(self): # Password auth is marginally more 'username-caring' than pkeys, so may # as well log that info here. return super()._repr(user=self.username) def authenticate(self, transport): # Lazily get the password, in case it's prompting a user # TODO: be nice to log source _of_ the password? password = self.password_getter() return transport.auth_password(self.username, password) # TODO 4.0: twiddle this, or PKey, or both, so they're more obviously distinct. # TODO 4.0: the obvious is to make this more wordy (PrivateKeyAuth), the # minimalist approach might be to rename PKey to just Key (esp given all the # subclasses are WhateverKey and not WhateverPKey) class PrivateKey(AuthSource): """ Essentially a mixin for private keys. Knows how to auth, but leaves key material discovery/loading/decryption to subclasses. Subclasses **must** ensure that they've set ``self.pkey`` to a decrypted `.PKey` instance before calling ``super().authenticate``; typically either in their ``__init__``, or in an overridden ``authenticate`` prior to its `super` call. """ def authenticate(self, transport): return transport.auth_publickey(self.username, self.pkey) class InMemoryPrivateKey(PrivateKey): """ An in-memory, decrypted `.PKey` object. """ def __init__(self, username, pkey): super().__init__(username=username) # No decryption (presumably) necessary! self.pkey = pkey def __repr__(self): # NOTE: most of interesting repr-bits for private keys is in PKey. # TODO: tacking on agent-ness like this is a bit awkward, but, eh? rep = super()._repr(pkey=self.pkey) if isinstance(self.pkey, AgentKey): rep += " [agent]" return rep class OnDiskPrivateKey(PrivateKey): """ Some on-disk private key that needs opening and possibly decrypting. :param str source: String tracking where this key's path was specified; should be one of ``"ssh-config"``, ``"python-config"``, or ``"implicit-home"``. :param Path path: The filesystem path this key was loaded from. :param PKey pkey: The `PKey` object this auth source uses/represents. """ def __init__(self, username, source, path, pkey): super().__init__(username=username) self.source = source allowed = ("ssh-config", "python-config", "implicit-home") if source not in allowed: raise ValueError(f"source argument must be one of: {allowed!r}") self.path = path # Superclass wants .pkey, other two are mostly for display/debugging. self.pkey = pkey def __repr__(self): return self._repr( key=self.pkey, source=self.source, path=str(self.path) ) # TODO re sources: is there anything in an OpenSSH config file that doesn't fit # into what Paramiko already had kwargs for? SourceResult = namedtuple("SourceResult", ["source", "result"]) # TODO: tempting to make this an OrderedDict, except the keys essentially want # to be rich objects (AuthSources) which do not make for useful user indexing? # TODO: members being vanilla tuples is pretty old-school/expedient; they # "really" want to be something that's type friendlier (unless the tuple's 2nd # member being a Union of two types is "fine"?), which I assume means yet more # classes, eg an abstract SourceResult with concrete AuthSuccess and # AuthFailure children? # TODO: arguably we want __init__ typechecking of the members (or to leverage # mypy by classifying this literally as list-of-AuthSource?) class AuthResult(list): """ Represents a partial or complete SSH authentication attempt. This class conceptually extends `AuthStrategy` by pairing the former's authentication **sources** with the **results** of trying to authenticate with them. `AuthResult` is a (subclass of) `list` of `namedtuple`, which are of the form ``namedtuple('SourceResult', 'source', 'result')`` (where the ``source`` member is an `AuthSource` and the ``result`` member is either a return value from the relevant `.Transport` method, or an exception object). .. note:: Transport auth method results are always themselves a ``list`` of "next allowable authentication methods". In the simple case of "you just authenticated successfully", it's an empty list; if your auth was rejected but you're allowed to try again, it will be a list of string method names like ``pubkey`` or ``password``. The ``__str__`` of this class represents the empty-list scenario as the word ``success``, which should make reading the result of an authentication session more obvious to humans. Instances also have a `strategy` attribute referencing the `AuthStrategy` which was attempted. """ def __init__(self, strategy, *args, **kwargs): self.strategy = strategy super().__init__(*args, **kwargs) def __str__(self): # NOTE: meaningfully distinct from __repr__, which still wants to use # superclass' implementation. # TODO: go hog wild, use rich.Table? how is that on degraded term's? # TODO: test this lol return "\n".join( f"{x.source} -> {x.result or 'success'}" for x in self ) # TODO 4.0: descend from SSHException or even just Exception class AuthFailure(AuthenticationException): """ Basic exception wrapping an `AuthResult` indicating overall auth failure. Note that `AuthFailure` descends from `AuthenticationException` but is generally "higher level"; the latter is now only raised by individual `AuthSource` attempts and should typically only be seen by users when encapsulated in this class. It subclasses `AuthenticationException` primarily for backwards compatibility reasons. """ def __init__(self, result): self.result = result def __str__(self): return "\n" + str(self.result) class AuthStrategy: """ This class represents one or more attempts to auth with an SSH server. By default, subclasses must at least accept an ``ssh_config`` (`.SSHConfig`) keyword argument, but may opt to accept more as needed for their particular strategy. """ def __init__( self, ssh_config, ): self.ssh_config = ssh_config self.log = get_logger(__name__) def get_sources(self): """ Generator yielding `AuthSource` instances, in the order to try. This is the primary override point for subclasses: you figure out what sources you need, and ``yield`` them. Subclasses _of_ subclasses may find themselves wanting to do things like filtering or discarding around a call to `super`. """ raise NotImplementedError def authenticate(self, transport): """ Handles attempting `AuthSource` instances yielded from `get_sources`. You *normally* won't need to override this, but it's an option for advanced users. """ succeeded = False overall_result = AuthResult(strategy=self) # TODO: arguably we could fit in a "send none auth, record allowed auth # types sent back" thing here as OpenSSH-client does, but that likely # wants to live in fabric.OpenSSHAuthStrategy as not all target servers # will implement it! # TODO: needs better "server told us too many attempts" checking! for source in self.get_sources(): self.log.debug(f"Trying {source}") try: # NOTE: this really wants to _only_ wrap the authenticate()! result = source.authenticate(transport) succeeded = True # TODO: 'except PartialAuthentication' is needed for 2FA and # similar, as per old SSHClient.connect - it is the only way # AuthHandler supplies access to the 'name-list' field from # MSG_USERAUTH_FAILURE, at present. except Exception as e: result = e # TODO: look at what this could possibly raise, we don't really # want Exception here, right? just SSHException subclasses? or # do we truly want to capture anything at all with assumption # it's easy enough for users to look afterwards? # NOTE: showing type, not message, for tersity & also most of # the time it's basically just "Authentication failed." source_class = e.__class__.__name__ self.log.info( f"Authentication via {source} failed with {source_class}" ) overall_result.append(SourceResult(source, result)) if succeeded: break # Gotta die here if nothing worked, otherwise Transport's main loop # just kinda hangs out until something times out! if not succeeded: raise AuthFailure(result=overall_result) # Success: give back what was done, in case they care. return overall_result # TODO: is there anything OpenSSH client does which _can't_ cleanly map to # iterating a generator? compress.py 0000644 00000002402 15030210772 0006744 0 ustar 00 # Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com> # # This file is part of paramiko. # # Paramiko is free software; you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ Compression implementations for a Transport. """ import zlib class ZlibCompressor: def __init__(self): # Use the default level of zlib compression self.z = zlib.compressobj() def __call__(self, data): return self.z.compress(data) + self.z.flush(zlib.Z_FULL_FLUSH) class ZlibDecompressor: def __init__(self): self.z = zlib.decompressobj() def __call__(self, data): return self.z.decompress(data) rsakey.py 0000644 00000016572 15030210772 0006424 0 ustar 00 # Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com> # # This file is part of paramiko. # # Paramiko is free software; you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ RSA keys. """ from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import rsa, padding from paramiko.message import Message from paramiko.pkey import PKey from paramiko.ssh_exception import SSHException class RSAKey(PKey): """ Representation of an RSA key which can be used to sign and verify SSH2 data. """ name = "ssh-rsa" HASHES = { "ssh-rsa": hashes.SHA1, "ssh-rsa-cert-v01@openssh.com": hashes.SHA1, "rsa-sha2-256": hashes.SHA256, "rsa-sha2-256-cert-v01@openssh.com": hashes.SHA256, "rsa-sha2-512": hashes.SHA512, "rsa-sha2-512-cert-v01@openssh.com": hashes.SHA512, } def __init__( self, msg=None, data=None, filename=None, password=None, key=None, file_obj=None, ): self.key = None self.public_blob = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if key is not None: self.key = key else: self._check_type_and_load_cert( msg=msg, # NOTE: this does NOT change when using rsa2 signatures; it's # purely about key loading, not exchange or verification key_type=self.name, cert_type="ssh-rsa-cert-v01@openssh.com", ) self.key = rsa.RSAPublicNumbers( e=msg.get_mpint(), n=msg.get_mpint() ).public_key(default_backend()) @classmethod def identifiers(cls): return list(cls.HASHES.keys()) @property def size(self): return self.key.key_size @property def public_numbers(self): if isinstance(self.key, rsa.RSAPrivateKey): return self.key.private_numbers().public_numbers else: return self.key.public_numbers() def asbytes(self): m = Message() m.add_string(self.name) m.add_mpint(self.public_numbers.e) m.add_mpint(self.public_numbers.n) return m.asbytes() def __str__(self): # NOTE: see #853 to explain some legacy behavior. # TODO 4.0: replace with a nice clean fingerprint display or something return self.asbytes().decode("utf8", errors="ignore") @property def _fields(self): return (self.get_name(), self.public_numbers.e, self.public_numbers.n) def get_name(self): return self.name def get_bits(self): return self.size def can_sign(self): return isinstance(self.key, rsa.RSAPrivateKey) def sign_ssh_data(self, data, algorithm=None): if algorithm is None: algorithm = self.name sig = self.key.sign( data, padding=padding.PKCS1v15(), # HASHES being just a map from long identifier to either SHA1 or # SHA256 - cert'ness is not truly relevant. algorithm=self.HASHES[algorithm](), ) m = Message() # And here again, cert'ness is irrelevant, so it is stripped out. m.add_string(algorithm.replace("-cert-v01@openssh.com", "")) m.add_string(sig) return m def verify_ssh_sig(self, data, msg): sig_algorithm = msg.get_text() if sig_algorithm not in self.HASHES: return False key = self.key if isinstance(key, rsa.RSAPrivateKey): key = key.public_key() # NOTE: pad received signature with leading zeros, key.verify() # expects a signature of key size (e.g. PuTTY doesn't pad) sign = msg.get_binary() diff = key.key_size - len(sign) * 8 if diff > 0: sign = b"\x00" * ((diff + 7) // 8) + sign try: key.verify( sign, data, padding.PKCS1v15(), self.HASHES[sig_algorithm]() ) except InvalidSignature: return False else: return True def write_private_key_file(self, filename, password=None): self._write_private_key_file( filename, self.key, serialization.PrivateFormat.TraditionalOpenSSL, password=password, ) def write_private_key(self, file_obj, password=None): self._write_private_key( file_obj, self.key, serialization.PrivateFormat.TraditionalOpenSSL, password=password, ) @staticmethod def generate(bits, progress_func=None): """ Generate a new private RSA key. This factory function can be used to generate a new host key or authentication key. :param int bits: number of bits the generated key should be. :param progress_func: Unused :return: new `.RSAKey` private key """ key = rsa.generate_private_key( public_exponent=65537, key_size=bits, backend=default_backend() ) return RSAKey(key=key) # ...internals... def _from_private_key_file(self, filename, password): data = self._read_private_key_file("RSA", filename, password) self._decode_key(data) def _from_private_key(self, file_obj, password): data = self._read_private_key("RSA", file_obj, password) self._decode_key(data) def _decode_key(self, data): pkformat, data = data if pkformat == self._PRIVATE_KEY_FORMAT_ORIGINAL: try: key = serialization.load_der_private_key( data, password=None, backend=default_backend() ) except (ValueError, TypeError, UnsupportedAlgorithm) as e: raise SSHException(str(e)) elif pkformat == self._PRIVATE_KEY_FORMAT_OPENSSH: n, e, d, iqmp, p, q = self._uint32_cstruct_unpack(data, "iiiiii") public_numbers = rsa.RSAPublicNumbers(e=e, n=n) key = rsa.RSAPrivateNumbers( p=p, q=q, d=d, dmp1=d % (p - 1), dmq1=d % (q - 1), iqmp=iqmp, public_numbers=public_numbers, ).private_key(default_backend()) else: self._got_bad_key_format_id(pkformat) assert isinstance(key, rsa.RSAPrivateKey) self.key = key sftp_si.py 0000644 00000030400 15030210772 0006557 0 ustar 00 # Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com> # # This file is part of paramiko. # # Paramiko is free software; you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ An interface to override for SFTP server support. """ import os import sys from paramiko.sftp import SFTP_OP_UNSUPPORTED class SFTPServerInterface: """ This class defines an interface for controlling the behavior of paramiko when using the `.SFTPServer` subsystem to provide an SFTP server. Methods on this class are called from the SFTP session's thread, so you can block as long as necessary without affecting other sessions (even other SFTP sessions). However, raising an exception will usually cause the SFTP session to abruptly end, so you will usually want to catch exceptions and return an appropriate error code. All paths are in string form instead of unicode because not all SFTP clients & servers obey the requirement that paths be encoded in UTF-8. """ def __init__(self, server, *args, **kwargs): """ Create a new SFTPServerInterface object. This method does nothing by default and is meant to be overridden by subclasses. :param .ServerInterface server: the server object associated with this channel and SFTP subsystem """ super().__init__(*args, **kwargs) def session_started(self): """ The SFTP server session has just started. This method is meant to be overridden to perform any necessary setup before handling callbacks from SFTP operations. """ pass def session_ended(self): """ The SFTP server session has just ended, either cleanly or via an exception. This method is meant to be overridden to perform any necessary cleanup before this `.SFTPServerInterface` object is destroyed. """ pass def open(self, path, flags, attr): """ Open a file on the server and create a handle for future operations on that file. On success, a new object subclassed from `.SFTPHandle` should be returned. This handle will be used for future operations on the file (read, write, etc). On failure, an error code such as ``SFTP_PERMISSION_DENIED`` should be returned. ``flags`` contains the requested mode for opening (read-only, write-append, etc) as a bitset of flags from the ``os`` module: - ``os.O_RDONLY`` - ``os.O_WRONLY`` - ``os.O_RDWR`` - ``os.O_APPEND`` - ``os.O_CREAT`` - ``os.O_TRUNC`` - ``os.O_EXCL`` (One of ``os.O_RDONLY``, ``os.O_WRONLY``, or ``os.O_RDWR`` will always be set.) The ``attr`` object contains requested attributes of the file if it has to be created. Some or all attribute fields may be missing if the client didn't specify them. .. note:: The SFTP protocol defines all files to be in "binary" mode. There is no equivalent to Python's "text" mode. :param str path: the requested path (relative or absolute) of the file to be opened. :param int flags: flags or'd together from the ``os`` module indicating the requested mode for opening the file. :param .SFTPAttributes attr: requested attributes of the file if it is newly created. :return: a new `.SFTPHandle` or error code. """ return SFTP_OP_UNSUPPORTED def list_folder(self, path): """ Return a list of files within a given folder. The ``path`` will use posix notation (``"/"`` separates folder names) and may be an absolute or relative path. The list of files is expected to be a list of `.SFTPAttributes` objects, which are similar in structure to the objects returned by ``os.stat``. In addition, each object should have its ``filename`` field filled in, since this is important to a directory listing and not normally present in ``os.stat`` results. The method `.SFTPAttributes.from_stat` will usually do what you want. In case of an error, you should return one of the ``SFTP_*`` error codes, such as ``SFTP_PERMISSION_DENIED``. :param str path: the requested path (relative or absolute) to be listed. :return: a list of the files in the given folder, using `.SFTPAttributes` objects. .. note:: You should normalize the given ``path`` first (see the `os.path` module) and check appropriate permissions before returning the list of files. Be careful of malicious clients attempting to use relative paths to escape restricted folders, if you're doing a direct translation from the SFTP server path to your local filesystem. """ return SFTP_OP_UNSUPPORTED def stat(self, path): """ Return an `.SFTPAttributes` object for a path on the server, or an error code. If your server supports symbolic links (also known as "aliases"), you should follow them. (`lstat` is the corresponding call that doesn't follow symlinks/aliases.) :param str path: the requested path (relative or absolute) to fetch file statistics for. :return: an `.SFTPAttributes` object for the given file, or an SFTP error code (like ``SFTP_PERMISSION_DENIED``). """ return SFTP_OP_UNSUPPORTED def lstat(self, path): """ Return an `.SFTPAttributes` object for a path on the server, or an error code. If your server supports symbolic links (also known as "aliases"), you should not follow them -- instead, you should return data on the symlink or alias itself. (`stat` is the corresponding call that follows symlinks/aliases.) :param str path: the requested path (relative or absolute) to fetch file statistics for. :type path: str :return: an `.SFTPAttributes` object for the given file, or an SFTP error code (like ``SFTP_PERMISSION_DENIED``). """ return SFTP_OP_UNSUPPORTED def remove(self, path): """ Delete a file, if possible. :param str path: the requested path (relative or absolute) of the file to delete. :return: an SFTP error code `int` like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED def rename(self, oldpath, newpath): """ Rename (or move) a file. The SFTP specification implies that this method can be used to move an existing file into a different folder, and since there's no other (easy) way to move files via SFTP, it's probably a good idea to implement "move" in this method too, even for files that cross disk partition boundaries, if at all possible. .. note:: You should return an error if a file with the same name as ``newpath`` already exists. (The rename operation should be non-desctructive.) .. note:: This method implements 'standard' SFTP ``RENAME`` behavior; those seeking the OpenSSH "POSIX rename" extension behavior should use `posix_rename`. :param str oldpath: the requested path (relative or absolute) of the existing file. :param str newpath: the requested new path of the file. :return: an SFTP error code `int` like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED def posix_rename(self, oldpath, newpath): """ Rename (or move) a file, following posix conventions. If newpath already exists, it will be overwritten. :param str oldpath: the requested path (relative or absolute) of the existing file. :param str newpath: the requested new path of the file. :return: an SFTP error code `int` like ``SFTP_OK``. :versionadded: 2.2 """ return SFTP_OP_UNSUPPORTED def mkdir(self, path, attr): """ Create a new directory with the given attributes. The ``attr`` object may be considered a "hint" and ignored. The ``attr`` object will contain only those fields provided by the client in its request, so you should use ``hasattr`` to check for the presence of fields before using them. In some cases, the ``attr`` object may be completely empty. :param str path: requested path (relative or absolute) of the new folder. :param .SFTPAttributes attr: requested attributes of the new folder. :return: an SFTP error code `int` like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED def rmdir(self, path): """ Remove a directory if it exists. The ``path`` should refer to an existing, empty folder -- otherwise this method should return an error. :param str path: requested path (relative or absolute) of the folder to remove. :return: an SFTP error code `int` like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED def chattr(self, path, attr): """ Change the attributes of a file. The ``attr`` object will contain only those fields provided by the client in its request, so you should check for the presence of fields before using them. :param str path: requested path (relative or absolute) of the file to change. :param attr: requested attributes to change on the file (an `.SFTPAttributes` object) :return: an error code `int` like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED def canonicalize(self, path): """ Return the canonical form of a path on the server. For example, if the server's home folder is ``/home/foo``, the path ``"../betty"`` would be canonicalized to ``"/home/betty"``. Note the obvious security issues: if you're serving files only from a specific folder, you probably don't want this method to reveal path names outside that folder. You may find the Python methods in ``os.path`` useful, especially ``os.path.normpath`` and ``os.path.realpath``. The default implementation returns ``os.path.normpath('/' + path)``. """ if os.path.isabs(path): out = os.path.normpath(path) else: out = os.path.normpath("/" + path) if sys.platform == "win32": # on windows, normalize backslashes to sftp/posix format out = out.replace("\\", "/") return out def readlink(self, path): """ Return the target of a symbolic link (or shortcut) on the server. If the specified path doesn't refer to a symbolic link, an error should be returned. :param str path: path (relative or absolute) of the symbolic link. :return: the target `str` path of the symbolic link, or an error code like ``SFTP_NO_SUCH_FILE``. """ return SFTP_OP_UNSUPPORTED def symlink(self, target_path, path): """ Create a symbolic link on the server, as new pathname ``path``, with ``target_path`` as the target of the link. :param str target_path: path (relative or absolute) of the target for this new symbolic link. :param str path: path (relative or absolute) of the symbolic link to create. :return: an error code `int` like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED ecdsakey.py 0000644 00000026605 15030210772 0006714 0 ustar 00 # Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com> # # This file is part of paramiko. # # Paramiko is free software; you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ ECDSA keys """ from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.utils import ( decode_dss_signature, encode_dss_signature, ) from paramiko.common import four_byte from paramiko.message import Message from paramiko.pkey import PKey from paramiko.ssh_exception import SSHException from paramiko.util import deflate_long class _ECDSACurve: """ Represents a specific ECDSA Curve (nistp256, nistp384, etc). Handles the generation of the key format identifier and the selection of the proper hash function. Also grabs the proper curve from the 'ecdsa' package. """ def __init__(self, curve_class, nist_name): self.nist_name = nist_name self.key_length = curve_class.key_size # Defined in RFC 5656 6.2 self.key_format_identifier = "ecdsa-sha2-" + self.nist_name # Defined in RFC 5656 6.2.1 if self.key_length <= 256: self.hash_object = hashes.SHA256 elif self.key_length <= 384: self.hash_object = hashes.SHA384 else: self.hash_object = hashes.SHA512 self.curve_class = curve_class class _ECDSACurveSet: """ A collection to hold the ECDSA curves. Allows querying by oid and by key format identifier. The two ways in which ECDSAKey needs to be able to look up curves. """ def __init__(self, ecdsa_curves): self.ecdsa_curves = ecdsa_curves def get_key_format_identifier_list(self): return [curve.key_format_identifier for curve in self.ecdsa_curves] def get_by_curve_class(self, curve_class): for curve in self.ecdsa_curves: if curve.curve_class == curve_class: return curve def get_by_key_format_identifier(self, key_format_identifier): for curve in self.ecdsa_curves: if curve.key_format_identifier == key_format_identifier: return curve def get_by_key_length(self, key_length): for curve in self.ecdsa_curves: if curve.key_length == key_length: return curve class ECDSAKey(PKey): """ Representation of an ECDSA key which can be used to sign and verify SSH2 data. """ _ECDSA_CURVES = _ECDSACurveSet( [ _ECDSACurve(ec.SECP256R1, "nistp256"), _ECDSACurve(ec.SECP384R1, "nistp384"), _ECDSACurve(ec.SECP521R1, "nistp521"), ] ) def __init__( self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None, # TODO 4.0: remove; it does nothing since porting to cryptography.io validate_point=True, ): self.verifying_key = None self.signing_key = None self.public_blob = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.signing_key, self.verifying_key = vals c_class = self.signing_key.curve.__class__ self.ecdsa_curve = self._ECDSA_CURVES.get_by_curve_class(c_class) else: # Must set ecdsa_curve first; subroutines called herein may need to # spit out our get_name(), which relies on this. key_type = msg.get_text() # But this also means we need to hand it a real key/curve # identifier, so strip out any cert business. (NOTE: could push # that into _ECDSACurveSet.get_by_key_format_identifier(), but it # feels more correct to do it here?) suffix = "-cert-v01@openssh.com" if key_type.endswith(suffix): key_type = key_type[: -len(suffix)] self.ecdsa_curve = self._ECDSA_CURVES.get_by_key_format_identifier( key_type ) key_types = self._ECDSA_CURVES.get_key_format_identifier_list() cert_types = [ "{}-cert-v01@openssh.com".format(x) for x in key_types ] self._check_type_and_load_cert( msg=msg, key_type=key_types, cert_type=cert_types ) curvename = msg.get_text() if curvename != self.ecdsa_curve.nist_name: raise SSHException( "Can't handle curve of type {}".format(curvename) ) pointinfo = msg.get_binary() try: key = ec.EllipticCurvePublicKey.from_encoded_point( self.ecdsa_curve.curve_class(), pointinfo ) self.verifying_key = key except ValueError: raise SSHException("Invalid public key") @classmethod def identifiers(cls): return cls._ECDSA_CURVES.get_key_format_identifier_list() # TODO 4.0: deprecate/remove @classmethod def supported_key_format_identifiers(cls): return cls.identifiers() def asbytes(self): key = self.verifying_key m = Message() m.add_string(self.ecdsa_curve.key_format_identifier) m.add_string(self.ecdsa_curve.nist_name) numbers = key.public_numbers() key_size_bytes = (key.curve.key_size + 7) // 8 x_bytes = deflate_long(numbers.x, add_sign_padding=False) x_bytes = b"\x00" * (key_size_bytes - len(x_bytes)) + x_bytes y_bytes = deflate_long(numbers.y, add_sign_padding=False) y_bytes = b"\x00" * (key_size_bytes - len(y_bytes)) + y_bytes point_str = four_byte + x_bytes + y_bytes m.add_string(point_str) return m.asbytes() def __str__(self): return self.asbytes() @property def _fields(self): return ( self.get_name(), self.verifying_key.public_numbers().x, self.verifying_key.public_numbers().y, ) def get_name(self): return self.ecdsa_curve.key_format_identifier def get_bits(self): return self.ecdsa_curve.key_length def can_sign(self): return self.signing_key is not None def sign_ssh_data(self, data, algorithm=None): ecdsa = ec.ECDSA(self.ecdsa_curve.hash_object()) sig = self.signing_key.sign(data, ecdsa) r, s = decode_dss_signature(sig) m = Message() m.add_string(self.ecdsa_curve.key_format_identifier) m.add_string(self._sigencode(r, s)) return m def verify_ssh_sig(self, data, msg): if msg.get_text() != self.ecdsa_curve.key_format_identifier: return False sig = msg.get_binary() sigR, sigS = self._sigdecode(sig) signature = encode_dss_signature(sigR, sigS) try: self.verifying_key.verify( signature, data, ec.ECDSA(self.ecdsa_curve.hash_object()) ) except InvalidSignature: return False else: return True def write_private_key_file(self, filename, password=None): self._write_private_key_file( filename, self.signing_key, serialization.PrivateFormat.TraditionalOpenSSL, password=password, ) def write_private_key(self, file_obj, password=None): self._write_private_key( file_obj, self.signing_key, serialization.PrivateFormat.TraditionalOpenSSL, password=password, ) @classmethod def generate(cls, curve=ec.SECP256R1(), progress_func=None, bits=None): """ Generate a new private ECDSA key. This factory function can be used to generate a new host key or authentication key. :param progress_func: Not used for this type of key. :returns: A new private key (`.ECDSAKey`) object """ if bits is not None: curve = cls._ECDSA_CURVES.get_by_key_length(bits) if curve is None: raise ValueError("Unsupported key length: {:d}".format(bits)) curve = curve.curve_class() private_key = ec.generate_private_key(curve, backend=default_backend()) return ECDSAKey(vals=(private_key, private_key.public_key())) # ...internals... def _from_private_key_file(self, filename, password): data = self._read_private_key_file("EC", filename, password) self._decode_key(data) def _from_private_key(self, file_obj, password): data = self._read_private_key("EC", file_obj, password) self._decode_key(data) def _decode_key(self, data): pkformat, data = data if pkformat == self._PRIVATE_KEY_FORMAT_ORIGINAL: try: key = serialization.load_der_private_key( data, password=None, backend=default_backend() ) except ( ValueError, AssertionError, TypeError, UnsupportedAlgorithm, ) as e: raise SSHException(str(e)) elif pkformat == self._PRIVATE_KEY_FORMAT_OPENSSH: try: msg = Message(data) curve_name = msg.get_text() verkey = msg.get_binary() # noqa: F841 sigkey = msg.get_mpint() name = "ecdsa-sha2-" + curve_name curve = self._ECDSA_CURVES.get_by_key_format_identifier(name) if not curve: raise SSHException("Invalid key curve identifier") key = ec.derive_private_key( sigkey, curve.curve_class(), default_backend() ) except Exception as e: # PKey._read_private_key_openssh() should check or return # keytype - parsing could fail for any reason due to wrong type raise SSHException(str(e)) else: self._got_bad_key_format_id(pkformat) self.signing_key = key self.verifying_key = key.public_key() curve_class = key.curve.__class__ self.ecdsa_curve = self._ECDSA_CURVES.get_by_curve_class(curve_class) def _sigencode(self, r, s): msg = Message() msg.add_mpint(r) msg.add_mpint(s) return msg.asbytes() def _sigdecode(self, sig): msg = Message(sig) r = msg.get_mpint() s = msg.get_mpint() return r, s __pycache__/kex_group16.cpython-310.pyc 0000644 00000002313 15030210772 0013623 0 ustar 00 o �h� � @ s0 d Z ddlmZ ddlmZ G dd� de�ZdS )z� Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of 4096 bit key halves, using a known "p" prime and "g" generator. � )� KexGroup1)�sha512c @ s e Zd ZdZdZdZdZeZdS )�KexGroup16SHA512zdiffie-hellman-group16-sha512l ������ MrzM�MBr�}o�{c#Lu�S5�V> i �\;�p_^w��#�y�(�!wF���dR6f�5NG�x�Uv�A�_�K|G���nFgD-E9�`mli�Q�`#N^ �I�L&[t{Bh�Cxm�p�9B���d"?8�-�CbV�S}$A\Q6]`� �Xu�;u|H�+>$�)Cv3'�6E��_�/s�aDz�s�0%J�)2z��0�Qk}�c�aM>\�m�`�kUuE o�1�gN&�c�j�*�3� 4��Z�X�"-Gr �c�0"]�\Z%u>Is0\`�4�<�;��7�]�k> =`.:�d� C,�<�oq6�b���JX�E'@�+�) C�6���T�\+UD�<1�-G9�t*�u�$?ji�+qXAR�Y�wX(\>