First ansible commit
This commit is contained in:
130
.ve/lib/python2.7/site-packages/paramiko/__init__.py
Normal file
130
.ve/lib/python2.7/site-packages/paramiko/__init__.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# Copyright (C) 2003-2011 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
# flake8: noqa
|
||||
import sys
|
||||
from paramiko._version import __version__, __version_info__
|
||||
from paramiko.transport import SecurityOptions, Transport
|
||||
from paramiko.client import (
|
||||
SSHClient,
|
||||
MissingHostKeyPolicy,
|
||||
AutoAddPolicy,
|
||||
RejectPolicy,
|
||||
WarningPolicy,
|
||||
)
|
||||
from paramiko.auth_handler import AuthHandler
|
||||
from paramiko.ssh_gss import GSSAuth, GSS_AUTH_AVAILABLE, GSS_EXCEPTIONS
|
||||
from paramiko.channel import Channel, ChannelFile
|
||||
from paramiko.ssh_exception import (
|
||||
SSHException,
|
||||
PasswordRequiredException,
|
||||
BadAuthenticationType,
|
||||
ChannelException,
|
||||
BadHostKeyException,
|
||||
AuthenticationException,
|
||||
ProxyCommandFailure,
|
||||
)
|
||||
from paramiko.server import ServerInterface, SubsystemHandler, InteractiveQuery
|
||||
from paramiko.rsakey import RSAKey
|
||||
from paramiko.dsskey import DSSKey
|
||||
from paramiko.ecdsakey import ECDSAKey
|
||||
from paramiko.ed25519key import Ed25519Key
|
||||
from paramiko.sftp import SFTPError, BaseSFTP
|
||||
from paramiko.sftp_client import SFTP, SFTPClient
|
||||
from paramiko.sftp_server import SFTPServer
|
||||
from paramiko.sftp_attr import SFTPAttributes
|
||||
from paramiko.sftp_handle import SFTPHandle
|
||||
from paramiko.sftp_si import SFTPServerInterface
|
||||
from paramiko.sftp_file import SFTPFile
|
||||
from paramiko.message import Message
|
||||
from paramiko.packet import Packetizer
|
||||
from paramiko.file import BufferedFile
|
||||
from paramiko.agent import Agent, AgentKey
|
||||
from paramiko.pkey import PKey, PublicBlob
|
||||
from paramiko.hostkeys import HostKeys
|
||||
from paramiko.config import SSHConfig
|
||||
from paramiko.proxy import ProxyCommand
|
||||
|
||||
from paramiko.common import (
|
||||
AUTH_SUCCESSFUL,
|
||||
AUTH_PARTIALLY_SUCCESSFUL,
|
||||
AUTH_FAILED,
|
||||
OPEN_SUCCEEDED,
|
||||
OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED,
|
||||
OPEN_FAILED_CONNECT_FAILED,
|
||||
OPEN_FAILED_UNKNOWN_CHANNEL_TYPE,
|
||||
OPEN_FAILED_RESOURCE_SHORTAGE,
|
||||
)
|
||||
|
||||
from paramiko.sftp import (
|
||||
SFTP_OK,
|
||||
SFTP_EOF,
|
||||
SFTP_NO_SUCH_FILE,
|
||||
SFTP_PERMISSION_DENIED,
|
||||
SFTP_FAILURE,
|
||||
SFTP_BAD_MESSAGE,
|
||||
SFTP_NO_CONNECTION,
|
||||
SFTP_CONNECTION_LOST,
|
||||
SFTP_OP_UNSUPPORTED,
|
||||
)
|
||||
|
||||
from paramiko.common import io_sleep
|
||||
|
||||
|
||||
__author__ = "Jeff Forcier <jeff@bitprophet.org>"
|
||||
__license__ = "GNU Lesser General Public License (LGPL)"
|
||||
|
||||
__all__ = [
|
||||
"Agent",
|
||||
"AgentKey",
|
||||
"AuthenticationException",
|
||||
"AutoAddPolicy",
|
||||
"BadAuthenticationType",
|
||||
"BadHostKeyException",
|
||||
"BufferedFile",
|
||||
"Channel",
|
||||
"ChannelException",
|
||||
"DSSKey",
|
||||
"HostKeys",
|
||||
"Message",
|
||||
"MissingHostKeyPolicy",
|
||||
"PKey",
|
||||
"PasswordRequiredException",
|
||||
"ProxyCommand",
|
||||
"ProxyCommandFailure",
|
||||
"RSAKey",
|
||||
"RejectPolicy",
|
||||
"SFTP",
|
||||
"SFTPAttributes",
|
||||
"SFTPClient",
|
||||
"SFTPError",
|
||||
"SFTPFile",
|
||||
"SFTPHandle",
|
||||
"SFTPServer",
|
||||
"SFTPServerInterface",
|
||||
"SSHClient",
|
||||
"SSHConfig",
|
||||
"SSHException",
|
||||
"SecurityOptions",
|
||||
"ServerInterface",
|
||||
"SubsystemHandler",
|
||||
"Transport",
|
||||
"WarningPolicy",
|
||||
"io_sleep",
|
||||
"util",
|
||||
]
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/__init__.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/__init__.pyc
Normal file
Binary file not shown.
2
.ve/lib/python2.7/site-packages/paramiko/_version.py
Normal file
2
.ve/lib/python2.7/site-packages/paramiko/_version.py
Normal file
@@ -0,0 +1,2 @@
|
||||
__version_info__ = (2, 4, 2)
|
||||
__version__ = ".".join(map(str, __version_info__))
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/_version.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/_version.pyc
Normal file
Binary file not shown.
416
.ve/lib/python2.7/site-packages/paramiko/_winapi.py
Normal file
416
.ve/lib/python2.7/site-packages/paramiko/_winapi.py
Normal file
@@ -0,0 +1,416 @@
|
||||
"""
|
||||
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 sys
|
||||
import ctypes.wintypes
|
||||
|
||||
from paramiko.py3compat import u, builtins
|
||||
|
||||
|
||||
######################
|
||||
# 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)
|
||||
if sys.version_info > (3, 3):
|
||||
args = 0, strerror, None, value
|
||||
else:
|
||||
args = value, strerror
|
||||
super(WindowsError, self).__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(object):
|
||||
"""
|
||||
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("Refusing to write %d bytes" % n)
|
||||
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(SECURITY_ATTRIBUTES, self).__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
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/_winapi.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/_winapi.pyc
Normal file
Binary file not shown.
419
.ve/lib/python2.7/site-packages/paramiko/agent.py
Normal file
419
.ve/lib/python2.7/site-packages/paramiko/agent.py
Normal file
@@ -0,0 +1,419 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
SSH Agent interface
|
||||
"""
|
||||
|
||||
import os
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import tempfile
|
||||
import stat
|
||||
from select import select
|
||||
from paramiko.common import asbytes, io_sleep
|
||||
from paramiko.py3compat import byte_chr
|
||||
|
||||
from paramiko.ssh_exception import SSHException, AuthenticationException
|
||||
from paramiko.message import Message
|
||||
from paramiko.pkey import PKey
|
||||
from paramiko.util import retry_on_signal
|
||||
|
||||
cSSH2_AGENTC_REQUEST_IDENTITIES = byte_chr(11)
|
||||
SSH2_AGENT_IDENTITIES_ANSWER = 12
|
||||
cSSH2_AGENTC_SIGN_REQUEST = byte_chr(13)
|
||||
SSH2_AGENT_SIGN_RESPONSE = 14
|
||||
|
||||
|
||||
class AgentSSH(object):
|
||||
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.
|
||||
|
||||
: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(self, result.get_binary()))
|
||||
result.get_string()
|
||||
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)
|
||||
l = self._read_all(4)
|
||||
msg = Message(self._read_all(struct.unpack(">I", l)[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
|
||||
|
||||
|
||||
class AgentClientProxy(object):
|
||||
"""
|
||||
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``.
|
||||
"""
|
||||
if ("SSH_AUTH_SOCK" in os.environ) and (sys.platform != "win32"):
|
||||
conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
try:
|
||||
retry_on_signal(
|
||||
lambda: conn.connect(os.environ["SSH_AUTH_SOCK"])
|
||||
)
|
||||
except:
|
||||
# probably a dangling env var: the ssh agent is gone
|
||||
return
|
||||
elif sys.platform == "win32":
|
||||
import paramiko.win_pageant as win_pageant
|
||||
|
||||
if win_pageant.can_talk_to_agent():
|
||||
conn = win_pageant.PageantConnection()
|
||||
else:
|
||||
return
|
||||
else:
|
||||
# no agent support
|
||||
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):
|
||||
"""
|
||||
: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 environnement under unix
|
||||
|
||||
:return:
|
||||
a dict containing the ``SSH_AUTH_SOCK`` environnement variables
|
||||
"""
|
||||
return {"SSH_AUTH_SOCK": self._get_filename()}
|
||||
|
||||
def _get_filename(self):
|
||||
return self._file
|
||||
|
||||
|
||||
class AgentRequestHandler(object):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
AgentSSH.__init__(self)
|
||||
|
||||
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"])
|
||||
except:
|
||||
# probably a dangling env var: the ssh agent is gone
|
||||
return
|
||||
elif sys.platform == "win32":
|
||||
from . import win_pageant
|
||||
|
||||
if win_pageant.can_talk_to_agent():
|
||||
conn = win_pageant.PageantConnection()
|
||||
else:
|
||||
return
|
||||
else:
|
||||
# no agent support
|
||||
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.
|
||||
"""
|
||||
|
||||
def __init__(self, agent, blob):
|
||||
self.agent = agent
|
||||
self.blob = blob
|
||||
self.public_blob = None
|
||||
self.name = Message(blob).get_text()
|
||||
|
||||
def asbytes(self):
|
||||
return self.blob
|
||||
|
||||
def __str__(self):
|
||||
return self.asbytes()
|
||||
|
||||
def get_name(self):
|
||||
return self.name
|
||||
|
||||
def sign_ssh_data(self, data):
|
||||
msg = Message()
|
||||
msg.add_byte(cSSH2_AGENTC_SIGN_REQUEST)
|
||||
msg.add_string(self.blob)
|
||||
msg.add_string(data)
|
||||
msg.add_int(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()
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/agent.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/agent.pyc
Normal file
Binary file not shown.
847
.ve/lib/python2.7/site-packages/paramiko/auth_handler.py
Normal file
847
.ve/lib/python2.7/site-packages/paramiko/auth_handler.py
Normal file
@@ -0,0 +1,847 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
`.AuthHandler`
|
||||
"""
|
||||
|
||||
import weakref
|
||||
import time
|
||||
|
||||
from paramiko.common import (
|
||||
cMSG_SERVICE_REQUEST,
|
||||
cMSG_DISCONNECT,
|
||||
DISCONNECT_SERVICE_NOT_AVAILABLE,
|
||||
DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
|
||||
cMSG_USERAUTH_REQUEST,
|
||||
cMSG_SERVICE_ACCEPT,
|
||||
DEBUG,
|
||||
AUTH_SUCCESSFUL,
|
||||
INFO,
|
||||
cMSG_USERAUTH_SUCCESS,
|
||||
cMSG_USERAUTH_FAILURE,
|
||||
AUTH_PARTIALLY_SUCCESSFUL,
|
||||
cMSG_USERAUTH_INFO_REQUEST,
|
||||
WARNING,
|
||||
AUTH_FAILED,
|
||||
cMSG_USERAUTH_PK_OK,
|
||||
cMSG_USERAUTH_INFO_RESPONSE,
|
||||
MSG_SERVICE_REQUEST,
|
||||
MSG_SERVICE_ACCEPT,
|
||||
MSG_USERAUTH_REQUEST,
|
||||
MSG_USERAUTH_SUCCESS,
|
||||
MSG_USERAUTH_FAILURE,
|
||||
MSG_USERAUTH_BANNER,
|
||||
MSG_USERAUTH_INFO_REQUEST,
|
||||
MSG_USERAUTH_INFO_RESPONSE,
|
||||
cMSG_USERAUTH_GSSAPI_RESPONSE,
|
||||
cMSG_USERAUTH_GSSAPI_TOKEN,
|
||||
cMSG_USERAUTH_GSSAPI_MIC,
|
||||
MSG_USERAUTH_GSSAPI_RESPONSE,
|
||||
MSG_USERAUTH_GSSAPI_TOKEN,
|
||||
MSG_USERAUTH_GSSAPI_ERROR,
|
||||
MSG_USERAUTH_GSSAPI_ERRTOK,
|
||||
MSG_USERAUTH_GSSAPI_MIC,
|
||||
MSG_NAMES,
|
||||
cMSG_USERAUTH_BANNER,
|
||||
)
|
||||
from paramiko.message import Message
|
||||
from paramiko.py3compat import bytestring
|
||||
from paramiko.ssh_exception import (
|
||||
SSHException,
|
||||
AuthenticationException,
|
||||
BadAuthenticationType,
|
||||
PartialAuthentication,
|
||||
)
|
||||
from paramiko.server import InteractiveQuery
|
||||
from paramiko.ssh_gss import GSSAuth, GSS_EXCEPTIONS
|
||||
|
||||
|
||||
class AuthHandler(object):
|
||||
"""
|
||||
Internal class to handle the mechanics of authentication.
|
||||
"""
|
||||
|
||||
def __init__(self, transport):
|
||||
self.transport = weakref.proxy(transport)
|
||||
self.username = None
|
||||
self.authenticated = False
|
||||
self.auth_event = None
|
||||
self.auth_method = ""
|
||||
self.banner = None
|
||||
self.password = None
|
||||
self.private_key = None
|
||||
self.interactive_handler = None
|
||||
self.submethods = None
|
||||
# for server mode:
|
||||
self.auth_username = None
|
||||
self.auth_fail_count = 0
|
||||
# for GSSAPI
|
||||
self.gss_host = None
|
||||
self.gss_deleg_creds = True
|
||||
|
||||
def _log(self, *args):
|
||||
return self.transport._log(*args)
|
||||
|
||||
def is_authenticated(self):
|
||||
return self.authenticated
|
||||
|
||||
def get_username(self):
|
||||
if self.transport.server_mode:
|
||||
return self.auth_username
|
||||
else:
|
||||
return self.username
|
||||
|
||||
def auth_none(self, username, event):
|
||||
self.transport.lock.acquire()
|
||||
try:
|
||||
self.auth_event = event
|
||||
self.auth_method = "none"
|
||||
self.username = username
|
||||
self._request_auth()
|
||||
finally:
|
||||
self.transport.lock.release()
|
||||
|
||||
def auth_publickey(self, username, key, event):
|
||||
self.transport.lock.acquire()
|
||||
try:
|
||||
self.auth_event = event
|
||||
self.auth_method = "publickey"
|
||||
self.username = username
|
||||
self.private_key = key
|
||||
self._request_auth()
|
||||
finally:
|
||||
self.transport.lock.release()
|
||||
|
||||
def auth_password(self, username, password, event):
|
||||
self.transport.lock.acquire()
|
||||
try:
|
||||
self.auth_event = event
|
||||
self.auth_method = "password"
|
||||
self.username = username
|
||||
self.password = password
|
||||
self._request_auth()
|
||||
finally:
|
||||
self.transport.lock.release()
|
||||
|
||||
def auth_interactive(self, username, handler, event, submethods=""):
|
||||
"""
|
||||
response_list = handler(title, instructions, prompt_list)
|
||||
"""
|
||||
self.transport.lock.acquire()
|
||||
try:
|
||||
self.auth_event = event
|
||||
self.auth_method = "keyboard-interactive"
|
||||
self.username = username
|
||||
self.interactive_handler = handler
|
||||
self.submethods = submethods
|
||||
self._request_auth()
|
||||
finally:
|
||||
self.transport.lock.release()
|
||||
|
||||
def auth_gssapi_with_mic(self, username, gss_host, gss_deleg_creds, event):
|
||||
self.transport.lock.acquire()
|
||||
try:
|
||||
self.auth_event = event
|
||||
self.auth_method = "gssapi-with-mic"
|
||||
self.username = username
|
||||
self.gss_host = gss_host
|
||||
self.gss_deleg_creds = gss_deleg_creds
|
||||
self._request_auth()
|
||||
finally:
|
||||
self.transport.lock.release()
|
||||
|
||||
def auth_gssapi_keyex(self, username, event):
|
||||
self.transport.lock.acquire()
|
||||
try:
|
||||
self.auth_event = event
|
||||
self.auth_method = "gssapi-keyex"
|
||||
self.username = username
|
||||
self._request_auth()
|
||||
finally:
|
||||
self.transport.lock.release()
|
||||
|
||||
def abort(self):
|
||||
if self.auth_event is not None:
|
||||
self.auth_event.set()
|
||||
|
||||
# ...internals...
|
||||
|
||||
def _request_auth(self):
|
||||
m = Message()
|
||||
m.add_byte(cMSG_SERVICE_REQUEST)
|
||||
m.add_string("ssh-userauth")
|
||||
self.transport._send_message(m)
|
||||
|
||||
def _disconnect_service_not_available(self):
|
||||
m = Message()
|
||||
m.add_byte(cMSG_DISCONNECT)
|
||||
m.add_int(DISCONNECT_SERVICE_NOT_AVAILABLE)
|
||||
m.add_string("Service not available")
|
||||
m.add_string("en")
|
||||
self.transport._send_message(m)
|
||||
self.transport.close()
|
||||
|
||||
def _disconnect_no_more_auth(self):
|
||||
m = Message()
|
||||
m.add_byte(cMSG_DISCONNECT)
|
||||
m.add_int(DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE)
|
||||
m.add_string("No more auth methods available")
|
||||
m.add_string("en")
|
||||
self.transport._send_message(m)
|
||||
self.transport.close()
|
||||
|
||||
def _get_session_blob(self, key, service, username):
|
||||
m = Message()
|
||||
m.add_string(self.transport.session_id)
|
||||
m.add_byte(cMSG_USERAUTH_REQUEST)
|
||||
m.add_string(username)
|
||||
m.add_string(service)
|
||||
m.add_string("publickey")
|
||||
m.add_boolean(True)
|
||||
# Use certificate contents, if available, plain pubkey otherwise
|
||||
if key.public_blob:
|
||||
m.add_string(key.public_blob.key_type)
|
||||
m.add_string(key.public_blob.key_blob)
|
||||
else:
|
||||
m.add_string(key.get_name())
|
||||
m.add_string(key)
|
||||
return m.asbytes()
|
||||
|
||||
def wait_for_response(self, event):
|
||||
max_ts = None
|
||||
if self.transport.auth_timeout is not None:
|
||||
max_ts = time.time() + self.transport.auth_timeout
|
||||
while True:
|
||||
event.wait(0.1)
|
||||
if not self.transport.is_active():
|
||||
e = self.transport.get_exception()
|
||||
if (e is None) or issubclass(e.__class__, EOFError):
|
||||
e = AuthenticationException("Authentication failed.")
|
||||
raise e
|
||||
if event.is_set():
|
||||
break
|
||||
if max_ts is not None and max_ts <= time.time():
|
||||
raise AuthenticationException("Authentication timeout.")
|
||||
|
||||
if not self.is_authenticated():
|
||||
e = self.transport.get_exception()
|
||||
if e is None:
|
||||
e = AuthenticationException("Authentication failed.")
|
||||
# this is horrible. Python Exception isn't yet descended from
|
||||
# object, so type(e) won't work. :(
|
||||
if issubclass(e.__class__, PartialAuthentication):
|
||||
return e.allowed_types
|
||||
raise e
|
||||
return []
|
||||
|
||||
def _parse_service_request(self, m):
|
||||
service = m.get_text()
|
||||
if self.transport.server_mode and (service == "ssh-userauth"):
|
||||
# accepted
|
||||
m = Message()
|
||||
m.add_byte(cMSG_SERVICE_ACCEPT)
|
||||
m.add_string(service)
|
||||
self.transport._send_message(m)
|
||||
banner, language = self.transport.server_object.get_banner()
|
||||
if banner:
|
||||
m = Message()
|
||||
m.add_byte(cMSG_USERAUTH_BANNER)
|
||||
m.add_string(banner)
|
||||
m.add_string(language)
|
||||
self.transport._send_message(m)
|
||||
return
|
||||
# dunno this one
|
||||
self._disconnect_service_not_available()
|
||||
|
||||
def _parse_service_accept(self, m):
|
||||
service = m.get_text()
|
||||
if service == "ssh-userauth":
|
||||
self._log(DEBUG, "userauth is OK")
|
||||
m = Message()
|
||||
m.add_byte(cMSG_USERAUTH_REQUEST)
|
||||
m.add_string(self.username)
|
||||
m.add_string("ssh-connection")
|
||||
m.add_string(self.auth_method)
|
||||
if self.auth_method == "password":
|
||||
m.add_boolean(False)
|
||||
password = bytestring(self.password)
|
||||
m.add_string(password)
|
||||
elif self.auth_method == "publickey":
|
||||
m.add_boolean(True)
|
||||
# Use certificate contents, if available, plain pubkey
|
||||
# otherwise
|
||||
if self.private_key.public_blob:
|
||||
m.add_string(self.private_key.public_blob.key_type)
|
||||
m.add_string(self.private_key.public_blob.key_blob)
|
||||
else:
|
||||
m.add_string(self.private_key.get_name())
|
||||
m.add_string(self.private_key)
|
||||
blob = self._get_session_blob(
|
||||
self.private_key, "ssh-connection", self.username
|
||||
)
|
||||
sig = self.private_key.sign_ssh_data(blob)
|
||||
m.add_string(sig)
|
||||
elif self.auth_method == "keyboard-interactive":
|
||||
m.add_string("")
|
||||
m.add_string(self.submethods)
|
||||
elif self.auth_method == "gssapi-with-mic":
|
||||
sshgss = GSSAuth(self.auth_method, self.gss_deleg_creds)
|
||||
m.add_bytes(sshgss.ssh_gss_oids())
|
||||
# send the supported GSSAPI OIDs to the server
|
||||
self.transport._send_message(m)
|
||||
ptype, m = self.transport.packetizer.read_message()
|
||||
if ptype == MSG_USERAUTH_BANNER:
|
||||
self._parse_userauth_banner(m)
|
||||
ptype, m = self.transport.packetizer.read_message()
|
||||
if ptype == MSG_USERAUTH_GSSAPI_RESPONSE:
|
||||
# Read the mechanism selected by the server. We send just
|
||||
# the Kerberos V5 OID, so the server can only respond with
|
||||
# this OID.
|
||||
mech = m.get_string()
|
||||
m = Message()
|
||||
m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN)
|
||||
try:
|
||||
m.add_string(
|
||||
sshgss.ssh_init_sec_context(
|
||||
self.gss_host, mech, self.username
|
||||
)
|
||||
)
|
||||
except GSS_EXCEPTIONS as e:
|
||||
return self._handle_local_gss_failure(e)
|
||||
self.transport._send_message(m)
|
||||
while True:
|
||||
ptype, m = self.transport.packetizer.read_message()
|
||||
if ptype == MSG_USERAUTH_GSSAPI_TOKEN:
|
||||
srv_token = m.get_string()
|
||||
try:
|
||||
next_token = sshgss.ssh_init_sec_context(
|
||||
self.gss_host,
|
||||
mech,
|
||||
self.username,
|
||||
srv_token,
|
||||
)
|
||||
except GSS_EXCEPTIONS as e:
|
||||
return self._handle_local_gss_failure(e)
|
||||
# After this step the GSSAPI should not return any
|
||||
# token. If it does, we keep sending the token to
|
||||
# the server until no more token is returned.
|
||||
if next_token is None:
|
||||
break
|
||||
else:
|
||||
m = Message()
|
||||
m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN)
|
||||
m.add_string(next_token)
|
||||
self.transport.send_message(m)
|
||||
else:
|
||||
raise SSHException(
|
||||
"Received Package: {}".format(MSG_NAMES[ptype])
|
||||
)
|
||||
m = Message()
|
||||
m.add_byte(cMSG_USERAUTH_GSSAPI_MIC)
|
||||
# send the MIC to the server
|
||||
m.add_string(sshgss.ssh_get_mic(self.transport.session_id))
|
||||
elif ptype == MSG_USERAUTH_GSSAPI_ERRTOK:
|
||||
# RFC 4462 says we are not required to implement GSS-API
|
||||
# error messages.
|
||||
# See RFC 4462 Section 3.8 in
|
||||
# http://www.ietf.org/rfc/rfc4462.txt
|
||||
raise SSHException("Server returned an error token")
|
||||
elif ptype == MSG_USERAUTH_GSSAPI_ERROR:
|
||||
maj_status = m.get_int()
|
||||
min_status = m.get_int()
|
||||
err_msg = m.get_string()
|
||||
m.get_string() # Lang tag - discarded
|
||||
raise SSHException(
|
||||
"""GSS-API Error:
|
||||
Major Status: {}
|
||||
Minor Status: {}
|
||||
Error Message: {}
|
||||
""".format(
|
||||
maj_status, min_status, err_msg
|
||||
)
|
||||
)
|
||||
elif ptype == MSG_USERAUTH_FAILURE:
|
||||
self._parse_userauth_failure(m)
|
||||
return
|
||||
else:
|
||||
raise SSHException(
|
||||
"Received Package: {}".format(MSG_NAMES[ptype])
|
||||
)
|
||||
elif (
|
||||
self.auth_method == "gssapi-keyex"
|
||||
and self.transport.gss_kex_used
|
||||
):
|
||||
kexgss = self.transport.kexgss_ctxt
|
||||
kexgss.set_username(self.username)
|
||||
mic_token = kexgss.ssh_get_mic(self.transport.session_id)
|
||||
m.add_string(mic_token)
|
||||
elif self.auth_method == "none":
|
||||
pass
|
||||
else:
|
||||
raise SSHException(
|
||||
'Unknown auth method "{}"'.format(self.auth_method)
|
||||
)
|
||||
self.transport._send_message(m)
|
||||
else:
|
||||
self._log(
|
||||
DEBUG, 'Service request "{}" accepted (?)'.format(service)
|
||||
)
|
||||
|
||||
def _send_auth_result(self, username, method, result):
|
||||
# okay, send result
|
||||
m = Message()
|
||||
if result == AUTH_SUCCESSFUL:
|
||||
self._log(INFO, "Auth granted ({}).".format(method))
|
||||
m.add_byte(cMSG_USERAUTH_SUCCESS)
|
||||
self.authenticated = True
|
||||
else:
|
||||
self._log(INFO, "Auth rejected ({}).".format(method))
|
||||
m.add_byte(cMSG_USERAUTH_FAILURE)
|
||||
m.add_string(
|
||||
self.transport.server_object.get_allowed_auths(username)
|
||||
)
|
||||
if result == AUTH_PARTIALLY_SUCCESSFUL:
|
||||
m.add_boolean(True)
|
||||
else:
|
||||
m.add_boolean(False)
|
||||
self.auth_fail_count += 1
|
||||
self.transport._send_message(m)
|
||||
if self.auth_fail_count >= 10:
|
||||
self._disconnect_no_more_auth()
|
||||
if result == AUTH_SUCCESSFUL:
|
||||
self.transport._auth_trigger()
|
||||
|
||||
def _interactive_query(self, q):
|
||||
# make interactive query instead of response
|
||||
m = Message()
|
||||
m.add_byte(cMSG_USERAUTH_INFO_REQUEST)
|
||||
m.add_string(q.name)
|
||||
m.add_string(q.instructions)
|
||||
m.add_string(bytes())
|
||||
m.add_int(len(q.prompts))
|
||||
for p in q.prompts:
|
||||
m.add_string(p[0])
|
||||
m.add_boolean(p[1])
|
||||
self.transport._send_message(m)
|
||||
|
||||
def _parse_userauth_request(self, m):
|
||||
if not self.transport.server_mode:
|
||||
# er, uh... what?
|
||||
m = Message()
|
||||
m.add_byte(cMSG_USERAUTH_FAILURE)
|
||||
m.add_string("none")
|
||||
m.add_boolean(False)
|
||||
self.transport._send_message(m)
|
||||
return
|
||||
if self.authenticated:
|
||||
# ignore
|
||||
return
|
||||
username = m.get_text()
|
||||
service = m.get_text()
|
||||
method = m.get_text()
|
||||
self._log(
|
||||
DEBUG,
|
||||
"Auth request (type={}) service={}, username={}".format(
|
||||
method, service, username
|
||||
),
|
||||
)
|
||||
if service != "ssh-connection":
|
||||
self._disconnect_service_not_available()
|
||||
return
|
||||
if (self.auth_username is not None) and (
|
||||
self.auth_username != username
|
||||
):
|
||||
self._log(
|
||||
WARNING,
|
||||
"Auth rejected because the client attempted to change username in mid-flight", # noqa
|
||||
)
|
||||
self._disconnect_no_more_auth()
|
||||
return
|
||||
self.auth_username = username
|
||||
# check if GSS-API authentication is enabled
|
||||
gss_auth = self.transport.server_object.enable_auth_gssapi()
|
||||
|
||||
if method == "none":
|
||||
result = self.transport.server_object.check_auth_none(username)
|
||||
elif method == "password":
|
||||
changereq = m.get_boolean()
|
||||
password = m.get_binary()
|
||||
try:
|
||||
password = password.decode("UTF-8")
|
||||
except UnicodeError:
|
||||
# some clients/servers expect non-utf-8 passwords!
|
||||
# in this case, just return the raw byte string.
|
||||
pass
|
||||
if changereq:
|
||||
# always treated as failure, since we don't support changing
|
||||
# passwords, but collect the list of valid auth types from
|
||||
# the callback anyway
|
||||
self._log(DEBUG, "Auth request to change passwords (rejected)")
|
||||
newpassword = m.get_binary()
|
||||
try:
|
||||
newpassword = newpassword.decode("UTF-8", "replace")
|
||||
except UnicodeError:
|
||||
pass
|
||||
result = AUTH_FAILED
|
||||
else:
|
||||
result = self.transport.server_object.check_auth_password(
|
||||
username, password
|
||||
)
|
||||
elif method == "publickey":
|
||||
sig_attached = m.get_boolean()
|
||||
keytype = m.get_text()
|
||||
keyblob = m.get_binary()
|
||||
try:
|
||||
key = self.transport._key_info[keytype](Message(keyblob))
|
||||
except SSHException as e:
|
||||
self._log(INFO, "Auth rejected: public key: {}".format(str(e)))
|
||||
key = None
|
||||
except Exception as e:
|
||||
msg = (
|
||||
"Auth rejected: unsupported or mangled public key ({}: {})"
|
||||
) # noqa
|
||||
self._log(INFO, msg.format(e.__class__.__name__, e))
|
||||
key = None
|
||||
if key is None:
|
||||
self._disconnect_no_more_auth()
|
||||
return
|
||||
# first check if this key is okay... if not, we can skip the verify
|
||||
result = self.transport.server_object.check_auth_publickey(
|
||||
username, key
|
||||
)
|
||||
if result != AUTH_FAILED:
|
||||
# key is okay, verify it
|
||||
if not sig_attached:
|
||||
# client wants to know if this key is acceptable, before it
|
||||
# signs anything... send special "ok" message
|
||||
m = Message()
|
||||
m.add_byte(cMSG_USERAUTH_PK_OK)
|
||||
m.add_string(keytype)
|
||||
m.add_string(keyblob)
|
||||
self.transport._send_message(m)
|
||||
return
|
||||
sig = Message(m.get_binary())
|
||||
blob = self._get_session_blob(key, service, username)
|
||||
if not key.verify_ssh_sig(blob, sig):
|
||||
self._log(INFO, "Auth rejected: invalid signature")
|
||||
result = AUTH_FAILED
|
||||
elif method == "keyboard-interactive":
|
||||
submethods = m.get_string()
|
||||
result = self.transport.server_object.check_auth_interactive(
|
||||
username, submethods
|
||||
)
|
||||
if isinstance(result, InteractiveQuery):
|
||||
# make interactive query instead of response
|
||||
self._interactive_query(result)
|
||||
return
|
||||
elif method == "gssapi-with-mic" and gss_auth:
|
||||
sshgss = GSSAuth(method)
|
||||
# Read the number of OID mechanisms supported by the client.
|
||||
# OpenSSH sends just one OID. It's the Kerveros V5 OID and that's
|
||||
# the only OID we support.
|
||||
mechs = m.get_int()
|
||||
# We can't accept more than one OID, so if the SSH client sends
|
||||
# more than one, disconnect.
|
||||
if mechs > 1:
|
||||
self._log(
|
||||
INFO,
|
||||
"Disconnect: Received more than one GSS-API OID mechanism",
|
||||
)
|
||||
self._disconnect_no_more_auth()
|
||||
desired_mech = m.get_string()
|
||||
mech_ok = sshgss.ssh_check_mech(desired_mech)
|
||||
# if we don't support the mechanism, disconnect.
|
||||
if not mech_ok:
|
||||
self._log(
|
||||
INFO,
|
||||
"Disconnect: Received an invalid GSS-API OID mechanism",
|
||||
)
|
||||
self._disconnect_no_more_auth()
|
||||
# send the Kerberos V5 GSSAPI OID to the client
|
||||
supported_mech = sshgss.ssh_gss_oids("server")
|
||||
# RFC 4462 says we are not required to implement GSS-API error
|
||||
# messages. See section 3.8 in http://www.ietf.org/rfc/rfc4462.txt
|
||||
m = Message()
|
||||
m.add_byte(cMSG_USERAUTH_GSSAPI_RESPONSE)
|
||||
m.add_bytes(supported_mech)
|
||||
self.transport.auth_handler = GssapiWithMicAuthHandler(
|
||||
self, sshgss
|
||||
)
|
||||
self.transport._expected_packet = (
|
||||
MSG_USERAUTH_GSSAPI_TOKEN,
|
||||
MSG_USERAUTH_REQUEST,
|
||||
MSG_SERVICE_REQUEST,
|
||||
)
|
||||
self.transport._send_message(m)
|
||||
return
|
||||
elif method == "gssapi-keyex" and gss_auth:
|
||||
mic_token = m.get_string()
|
||||
sshgss = self.transport.kexgss_ctxt
|
||||
if sshgss is None:
|
||||
# If there is no valid context, we reject the authentication
|
||||
result = AUTH_FAILED
|
||||
self._send_auth_result(username, method, result)
|
||||
try:
|
||||
sshgss.ssh_check_mic(
|
||||
mic_token, self.transport.session_id, self.auth_username
|
||||
)
|
||||
except Exception:
|
||||
result = AUTH_FAILED
|
||||
self._send_auth_result(username, method, result)
|
||||
raise
|
||||
result = AUTH_SUCCESSFUL
|
||||
self.transport.server_object.check_auth_gssapi_keyex(
|
||||
username, result
|
||||
)
|
||||
else:
|
||||
result = self.transport.server_object.check_auth_none(username)
|
||||
# okay, send result
|
||||
self._send_auth_result(username, method, result)
|
||||
|
||||
def _parse_userauth_success(self, m):
|
||||
self._log(
|
||||
INFO, "Authentication ({}) successful!".format(self.auth_method)
|
||||
)
|
||||
self.authenticated = True
|
||||
self.transport._auth_trigger()
|
||||
if self.auth_event is not None:
|
||||
self.auth_event.set()
|
||||
|
||||
def _parse_userauth_failure(self, m):
|
||||
authlist = m.get_list()
|
||||
partial = m.get_boolean()
|
||||
if partial:
|
||||
self._log(INFO, "Authentication continues...")
|
||||
self._log(DEBUG, "Methods: " + str(authlist))
|
||||
self.transport.saved_exception = PartialAuthentication(authlist)
|
||||
elif self.auth_method not in authlist:
|
||||
for msg in (
|
||||
"Authentication type ({}) not permitted.".format(
|
||||
self.auth_method
|
||||
),
|
||||
"Allowed methods: {}".format(authlist),
|
||||
):
|
||||
self._log(DEBUG, msg)
|
||||
self.transport.saved_exception = BadAuthenticationType(
|
||||
"Bad authentication type", authlist
|
||||
)
|
||||
else:
|
||||
self._log(
|
||||
INFO, "Authentication ({}) failed.".format(self.auth_method)
|
||||
)
|
||||
self.authenticated = False
|
||||
self.username = None
|
||||
if self.auth_event is not None:
|
||||
self.auth_event.set()
|
||||
|
||||
def _parse_userauth_banner(self, m):
|
||||
banner = m.get_string()
|
||||
self.banner = banner
|
||||
self._log(INFO, "Auth banner: {}".format(banner))
|
||||
# who cares.
|
||||
|
||||
def _parse_userauth_info_request(self, m):
|
||||
if self.auth_method != "keyboard-interactive":
|
||||
raise SSHException("Illegal info request from server")
|
||||
title = m.get_text()
|
||||
instructions = m.get_text()
|
||||
m.get_binary() # lang
|
||||
prompts = m.get_int()
|
||||
prompt_list = []
|
||||
for i in range(prompts):
|
||||
prompt_list.append((m.get_text(), m.get_boolean()))
|
||||
response_list = self.interactive_handler(
|
||||
title, instructions, prompt_list
|
||||
)
|
||||
|
||||
m = Message()
|
||||
m.add_byte(cMSG_USERAUTH_INFO_RESPONSE)
|
||||
m.add_int(len(response_list))
|
||||
for r in response_list:
|
||||
m.add_string(r)
|
||||
self.transport._send_message(m)
|
||||
|
||||
def _parse_userauth_info_response(self, m):
|
||||
if not self.transport.server_mode:
|
||||
raise SSHException("Illegal info response from server")
|
||||
n = m.get_int()
|
||||
responses = []
|
||||
for i in range(n):
|
||||
responses.append(m.get_text())
|
||||
result = self.transport.server_object.check_auth_interactive_response(
|
||||
responses
|
||||
)
|
||||
if isinstance(result, InteractiveQuery):
|
||||
# make interactive query instead of response
|
||||
self._interactive_query(result)
|
||||
return
|
||||
self._send_auth_result(
|
||||
self.auth_username, "keyboard-interactive", result
|
||||
)
|
||||
|
||||
def _handle_local_gss_failure(self, e):
|
||||
self.transport.saved_exception = e
|
||||
self._log(DEBUG, "GSSAPI failure: {}".format(e))
|
||||
self._log(INFO, "Authentication ({}) failed.".format(self.auth_method))
|
||||
self.authenticated = False
|
||||
self.username = None
|
||||
if self.auth_event is not None:
|
||||
self.auth_event.set()
|
||||
return
|
||||
|
||||
# TODO: do the same to the other tables, in Transport.
|
||||
# TODO 3.0: MAY make sense to make these tables into actual
|
||||
# classes/instances that can be fed a mode bool or whatever. Or,
|
||||
# alternately (both?) make the message types small classes or enums that
|
||||
# embed this info within themselves (which could also then tidy up the
|
||||
# current 'integer -> human readable short string' stuff in common.py).
|
||||
# TODO: if we do that, also expose 'em publicly.
|
||||
|
||||
# Messages which should be handled _by_ servers (sent by clients)
|
||||
_server_handler_table = {
|
||||
MSG_SERVICE_REQUEST: _parse_service_request,
|
||||
MSG_USERAUTH_REQUEST: _parse_userauth_request,
|
||||
MSG_USERAUTH_INFO_RESPONSE: _parse_userauth_info_response,
|
||||
}
|
||||
|
||||
# Messages which should be handled _by_ clients (sent by servers)
|
||||
_client_handler_table = {
|
||||
MSG_SERVICE_ACCEPT: _parse_service_accept,
|
||||
MSG_USERAUTH_SUCCESS: _parse_userauth_success,
|
||||
MSG_USERAUTH_FAILURE: _parse_userauth_failure,
|
||||
MSG_USERAUTH_BANNER: _parse_userauth_banner,
|
||||
MSG_USERAUTH_INFO_REQUEST: _parse_userauth_info_request,
|
||||
}
|
||||
|
||||
# NOTE: prior to the fix for #1283, this was a static dict instead of a
|
||||
# property. Should be backwards compatible in most/all cases.
|
||||
@property
|
||||
def _handler_table(self):
|
||||
if self.transport.server_mode:
|
||||
return self._server_handler_table
|
||||
else:
|
||||
return self._client_handler_table
|
||||
|
||||
|
||||
class GssapiWithMicAuthHandler(object):
|
||||
"""A specialized Auth handler for gssapi-with-mic
|
||||
|
||||
During the GSSAPI token exchange we need a modified dispatch table,
|
||||
because the packet type numbers are not unique.
|
||||
"""
|
||||
|
||||
method = "gssapi-with-mic"
|
||||
|
||||
def __init__(self, delegate, sshgss):
|
||||
self._delegate = delegate
|
||||
self.sshgss = sshgss
|
||||
|
||||
def abort(self):
|
||||
self._restore_delegate_auth_handler()
|
||||
return self._delegate.abort()
|
||||
|
||||
@property
|
||||
def transport(self):
|
||||
return self._delegate.transport
|
||||
|
||||
@property
|
||||
def _send_auth_result(self):
|
||||
return self._delegate._send_auth_result
|
||||
|
||||
@property
|
||||
def auth_username(self):
|
||||
return self._delegate.auth_username
|
||||
|
||||
@property
|
||||
def gss_host(self):
|
||||
return self._delegate.gss_host
|
||||
|
||||
def _restore_delegate_auth_handler(self):
|
||||
self.transport.auth_handler = self._delegate
|
||||
|
||||
def _parse_userauth_gssapi_token(self, m):
|
||||
client_token = m.get_string()
|
||||
# use the client token as input to establish a secure
|
||||
# context.
|
||||
sshgss = self.sshgss
|
||||
try:
|
||||
token = sshgss.ssh_accept_sec_context(
|
||||
self.gss_host, client_token, self.auth_username
|
||||
)
|
||||
except Exception as e:
|
||||
self.transport.saved_exception = e
|
||||
result = AUTH_FAILED
|
||||
self._restore_delegate_auth_handler()
|
||||
self._send_auth_result(self.auth_username, self.method, result)
|
||||
raise
|
||||
if token is not None:
|
||||
m = Message()
|
||||
m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN)
|
||||
m.add_string(token)
|
||||
self.transport._expected_packet = (
|
||||
MSG_USERAUTH_GSSAPI_TOKEN,
|
||||
MSG_USERAUTH_GSSAPI_MIC,
|
||||
MSG_USERAUTH_REQUEST,
|
||||
)
|
||||
self.transport._send_message(m)
|
||||
|
||||
def _parse_userauth_gssapi_mic(self, m):
|
||||
mic_token = m.get_string()
|
||||
sshgss = self.sshgss
|
||||
username = self.auth_username
|
||||
self._restore_delegate_auth_handler()
|
||||
try:
|
||||
sshgss.ssh_check_mic(
|
||||
mic_token, self.transport.session_id, username
|
||||
)
|
||||
except Exception as e:
|
||||
self.transport.saved_exception = e
|
||||
result = AUTH_FAILED
|
||||
self._send_auth_result(username, self.method, result)
|
||||
raise
|
||||
# TODO: Implement client credential saving.
|
||||
# The OpenSSH server is able to create a TGT with the delegated
|
||||
# client credentials, but this is not supported by GSS-API.
|
||||
result = AUTH_SUCCESSFUL
|
||||
self.transport.server_object.check_auth_gssapi_with_mic(
|
||||
username, result
|
||||
)
|
||||
# okay, send result
|
||||
self._send_auth_result(username, self.method, result)
|
||||
|
||||
def _parse_service_request(self, m):
|
||||
self._restore_delegate_auth_handler()
|
||||
return self._delegate._parse_service_request(m)
|
||||
|
||||
def _parse_userauth_request(self, m):
|
||||
self._restore_delegate_auth_handler()
|
||||
return self._delegate._parse_userauth_request(m)
|
||||
|
||||
__handler_table = {
|
||||
MSG_SERVICE_REQUEST: _parse_service_request,
|
||||
MSG_USERAUTH_REQUEST: _parse_userauth_request,
|
||||
MSG_USERAUTH_GSSAPI_TOKEN: _parse_userauth_gssapi_token,
|
||||
MSG_USERAUTH_GSSAPI_MIC: _parse_userauth_gssapi_mic,
|
||||
}
|
||||
|
||||
@property
|
||||
def _handler_table(self):
|
||||
# TODO: determine if we can cut this up like we did for the primary
|
||||
# AuthHandler class.
|
||||
return self.__handler_table
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/auth_handler.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/auth_handler.pyc
Normal file
Binary file not shown.
138
.ve/lib/python2.7/site-packages/paramiko/ber.py
Normal file
138
.ve/lib/python2.7/site-packages/paramiko/ber.py
Normal file
@@ -0,0 +1,138 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
from paramiko.common import max_byte, zero_byte
|
||||
from paramiko.py3compat import b, byte_ord, byte_chr, long
|
||||
|
||||
import paramiko.util as util
|
||||
|
||||
|
||||
class BERException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BER(object):
|
||||
"""
|
||||
Robey's tiny little attempt at a BER decoder.
|
||||
"""
|
||||
|
||||
def __init__(self, content=bytes()):
|
||||
self.content = b(content)
|
||||
self.idx = 0
|
||||
|
||||
def asbytes(self):
|
||||
return self.content
|
||||
|
||||
def __str__(self):
|
||||
return self.asbytes()
|
||||
|
||||
def __repr__(self):
|
||||
return "BER('" + repr(self.content) + "')"
|
||||
|
||||
def decode(self):
|
||||
return self.decode_next()
|
||||
|
||||
def decode_next(self):
|
||||
if self.idx >= len(self.content):
|
||||
return None
|
||||
ident = byte_ord(self.content[self.idx])
|
||||
self.idx += 1
|
||||
if (ident & 31) == 31:
|
||||
# identifier > 30
|
||||
ident = 0
|
||||
while self.idx < len(self.content):
|
||||
t = byte_ord(self.content[self.idx])
|
||||
self.idx += 1
|
||||
ident = (ident << 7) | (t & 0x7f)
|
||||
if not (t & 0x80):
|
||||
break
|
||||
if self.idx >= len(self.content):
|
||||
return None
|
||||
# now fetch length
|
||||
size = byte_ord(self.content[self.idx])
|
||||
self.idx += 1
|
||||
if size & 0x80:
|
||||
# more complimicated...
|
||||
# FIXME: theoretically should handle indefinite-length (0x80)
|
||||
t = size & 0x7f
|
||||
if self.idx + t > len(self.content):
|
||||
return None
|
||||
size = util.inflate_long(
|
||||
self.content[self.idx : self.idx + t], True
|
||||
)
|
||||
self.idx += t
|
||||
if self.idx + size > len(self.content):
|
||||
# can't fit
|
||||
return None
|
||||
data = self.content[self.idx : self.idx + size]
|
||||
self.idx += size
|
||||
# now switch on id
|
||||
if ident == 0x30:
|
||||
# sequence
|
||||
return self.decode_sequence(data)
|
||||
elif ident == 2:
|
||||
# int
|
||||
return util.inflate_long(data)
|
||||
else:
|
||||
# 1: boolean (00 false, otherwise true)
|
||||
msg = "Unknown ber encoding type {:d} (robey is lazy)"
|
||||
raise BERException(msg.format(ident))
|
||||
|
||||
@staticmethod
|
||||
def decode_sequence(data):
|
||||
out = []
|
||||
ber = BER(data)
|
||||
while True:
|
||||
x = ber.decode_next()
|
||||
if x is None:
|
||||
break
|
||||
out.append(x)
|
||||
return out
|
||||
|
||||
def encode_tlv(self, ident, val):
|
||||
# no need to support ident > 31 here
|
||||
self.content += byte_chr(ident)
|
||||
if len(val) > 0x7f:
|
||||
lenstr = util.deflate_long(len(val))
|
||||
self.content += byte_chr(0x80 + len(lenstr)) + lenstr
|
||||
else:
|
||||
self.content += byte_chr(len(val))
|
||||
self.content += val
|
||||
|
||||
def encode(self, x):
|
||||
if type(x) is bool:
|
||||
if x:
|
||||
self.encode_tlv(1, max_byte)
|
||||
else:
|
||||
self.encode_tlv(1, zero_byte)
|
||||
elif (type(x) is int) or (type(x) is long):
|
||||
self.encode_tlv(2, util.deflate_long(x))
|
||||
elif type(x) is str:
|
||||
self.encode_tlv(4, x)
|
||||
elif (type(x) is list) or (type(x) is tuple):
|
||||
self.encode_tlv(0x30, self.encode_sequence(x))
|
||||
else:
|
||||
raise BERException(
|
||||
"Unknown type for encoding: {!r}".format(type(x))
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def encode_sequence(data):
|
||||
ber = BER()
|
||||
for item in data:
|
||||
ber.encode(item)
|
||||
return ber.asbytes()
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/ber.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/ber.pyc
Normal file
Binary file not shown.
222
.ve/lib/python2.7/site-packages/paramiko/buffered_pipe.py
Normal file
222
.ve/lib/python2.7/site-packages/paramiko/buffered_pipe.py
Normal file
@@ -0,0 +1,222 @@
|
||||
# Copyright (C) 2006-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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Attempt to generalize the "feeder" part of a `.Channel`: an object which can be
|
||||
read from and closed, but is reading from a buffer fed by another thread. The
|
||||
read operations are blocking and can have a timeout set.
|
||||
"""
|
||||
|
||||
import array
|
||||
import threading
|
||||
import time
|
||||
from paramiko.py3compat import PY2, b
|
||||
|
||||
|
||||
class PipeTimeout(IOError):
|
||||
"""
|
||||
Indicates that a timeout was reached on a read from a `.BufferedPipe`.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BufferedPipe(object):
|
||||
"""
|
||||
A buffer that obeys normal read (with timeout) & close semantics for a
|
||||
file or socket, but is fed data from another thread. This is used by
|
||||
`.Channel`.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._lock = threading.Lock()
|
||||
self._cv = threading.Condition(self._lock)
|
||||
self._event = None
|
||||
self._buffer = array.array("B")
|
||||
self._closed = False
|
||||
|
||||
if PY2:
|
||||
|
||||
def _buffer_frombytes(self, data):
|
||||
self._buffer.fromstring(data)
|
||||
|
||||
def _buffer_tobytes(self, limit=None):
|
||||
return self._buffer[:limit].tostring()
|
||||
|
||||
else:
|
||||
|
||||
def _buffer_frombytes(self, data):
|
||||
self._buffer.frombytes(data)
|
||||
|
||||
def _buffer_tobytes(self, limit=None):
|
||||
return self._buffer[:limit].tobytes()
|
||||
|
||||
def set_event(self, event):
|
||||
"""
|
||||
Set an event on this buffer. When data is ready to be read (or the
|
||||
buffer has been closed), the event will be set. When no data is
|
||||
ready, the event will be cleared.
|
||||
|
||||
:param threading.Event event: the event to set/clear
|
||||
"""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
self._event = event
|
||||
# Make sure the event starts in `set` state if we appear to already
|
||||
# be closed; otherwise, if we start in `clear` state & are closed,
|
||||
# nothing will ever call `.feed` and the event (& OS pipe, if we're
|
||||
# wrapping one - see `Channel.fileno`) will permanently stay in
|
||||
# `clear`, causing deadlock if e.g. `select`ed upon.
|
||||
if self._closed or len(self._buffer) > 0:
|
||||
event.set()
|
||||
else:
|
||||
event.clear()
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def feed(self, data):
|
||||
"""
|
||||
Feed new data into this pipe. This method is assumed to be called
|
||||
from a separate thread, so synchronization is done.
|
||||
|
||||
:param data: the data to add, as a ``str`` or ``bytes``
|
||||
"""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
if self._event is not None:
|
||||
self._event.set()
|
||||
self._buffer_frombytes(b(data))
|
||||
self._cv.notifyAll()
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def read_ready(self):
|
||||
"""
|
||||
Returns true if data is buffered and ready to be read from this
|
||||
feeder. A ``False`` result does not mean that the feeder has closed;
|
||||
it means you may need to wait before more data arrives.
|
||||
|
||||
:return:
|
||||
``True`` if a `read` call would immediately return at least one
|
||||
byte; ``False`` otherwise.
|
||||
"""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
if len(self._buffer) == 0:
|
||||
return False
|
||||
return True
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def read(self, nbytes, timeout=None):
|
||||
"""
|
||||
Read data from the pipe. 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 pipe has been closed.
|
||||
|
||||
The optional ``timeout`` argument can be a nonnegative float expressing
|
||||
seconds, or ``None`` for no timeout. If a float is given, a
|
||||
`.PipeTimeout` will be raised if the timeout period value has elapsed
|
||||
before any data arrives.
|
||||
|
||||
:param int nbytes: maximum number of bytes to read
|
||||
:param float timeout:
|
||||
maximum seconds to wait (or ``None``, the default, to wait forever)
|
||||
:return: the read data, as a ``str`` or ``bytes``
|
||||
|
||||
:raises:
|
||||
`.PipeTimeout` -- if a timeout was specified and no data was ready
|
||||
before that timeout
|
||||
"""
|
||||
out = bytes()
|
||||
self._lock.acquire()
|
||||
try:
|
||||
if len(self._buffer) == 0:
|
||||
if self._closed:
|
||||
return out
|
||||
# should we block?
|
||||
if timeout == 0.0:
|
||||
raise PipeTimeout()
|
||||
# loop here in case we get woken up but a different thread has
|
||||
# grabbed everything in the buffer.
|
||||
while (len(self._buffer) == 0) and not self._closed:
|
||||
then = time.time()
|
||||
self._cv.wait(timeout)
|
||||
if timeout is not None:
|
||||
timeout -= time.time() - then
|
||||
if timeout <= 0.0:
|
||||
raise PipeTimeout()
|
||||
|
||||
# something's in the buffer and we have the lock!
|
||||
if len(self._buffer) <= nbytes:
|
||||
out = self._buffer_tobytes()
|
||||
del self._buffer[:]
|
||||
if (self._event is not None) and not self._closed:
|
||||
self._event.clear()
|
||||
else:
|
||||
out = self._buffer_tobytes(nbytes)
|
||||
del self._buffer[:nbytes]
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
return out
|
||||
|
||||
def empty(self):
|
||||
"""
|
||||
Clear out the buffer and return all data that was in it.
|
||||
|
||||
:return:
|
||||
any data that was in the buffer prior to clearing it out, as a
|
||||
`str`
|
||||
"""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
out = self._buffer_tobytes()
|
||||
del self._buffer[:]
|
||||
if (self._event is not None) and not self._closed:
|
||||
self._event.clear()
|
||||
return out
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close this pipe object. Future calls to `read` after the buffer
|
||||
has been emptied will return immediately with an empty string.
|
||||
"""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
self._closed = True
|
||||
self._cv.notifyAll()
|
||||
if self._event is not None:
|
||||
self._event.set()
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Return the number of bytes buffered.
|
||||
|
||||
:return: number (`int`) of bytes buffered
|
||||
"""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
return len(self._buffer)
|
||||
finally:
|
||||
self._lock.release()
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/buffered_pipe.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/buffered_pipe.pyc
Normal file
Binary file not shown.
1362
.ve/lib/python2.7/site-packages/paramiko/channel.py
Normal file
1362
.ve/lib/python2.7/site-packages/paramiko/channel.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
.ve/lib/python2.7/site-packages/paramiko/channel.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/channel.pyc
Normal file
Binary file not shown.
824
.ve/lib/python2.7/site-packages/paramiko/client.py
Normal file
824
.ve/lib/python2.7/site-packages/paramiko/client.py
Normal file
@@ -0,0 +1,824 @@
|
||||
# Copyright (C) 2006-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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
SSH client & key policies
|
||||
"""
|
||||
|
||||
from binascii import hexlify
|
||||
import getpass
|
||||
import inspect
|
||||
import os
|
||||
import socket
|
||||
import warnings
|
||||
from errno import ECONNREFUSED, EHOSTUNREACH
|
||||
|
||||
from paramiko.agent import Agent
|
||||
from paramiko.common import DEBUG
|
||||
from paramiko.config import SSH_PORT
|
||||
from paramiko.dsskey import DSSKey
|
||||
from paramiko.ecdsakey import ECDSAKey
|
||||
from paramiko.ed25519key import Ed25519Key
|
||||
from paramiko.hostkeys import HostKeys
|
||||
from paramiko.py3compat import string_types
|
||||
from paramiko.rsakey import RSAKey
|
||||
from paramiko.ssh_exception import (
|
||||
SSHException,
|
||||
BadHostKeyException,
|
||||
NoValidConnectionsError,
|
||||
)
|
||||
from paramiko.transport import Transport
|
||||
from paramiko.util import retry_on_signal, ClosingContextManager
|
||||
|
||||
|
||||
class SSHClient(ClosingContextManager):
|
||||
"""
|
||||
A high-level representation of a session with an SSH server. This class
|
||||
wraps `.Transport`, `.Channel`, and `.SFTPClient` to take care of most
|
||||
aspects of authenticating and opening channels. A typical use case is::
|
||||
|
||||
client = SSHClient()
|
||||
client.load_system_host_keys()
|
||||
client.connect('ssh.example.com')
|
||||
stdin, stdout, stderr = client.exec_command('ls -l')
|
||||
|
||||
You may pass in explicit overrides for authentication and server host key
|
||||
checking. The default mechanism is to try to use local key files or an
|
||||
SSH agent (if one is running).
|
||||
|
||||
Instances of this class may be used as context managers.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Create a new SSHClient.
|
||||
"""
|
||||
self._system_host_keys = HostKeys()
|
||||
self._host_keys = HostKeys()
|
||||
self._host_keys_filename = None
|
||||
self._log_channel = None
|
||||
self._policy = RejectPolicy()
|
||||
self._transport = None
|
||||
self._agent = None
|
||||
|
||||
def load_system_host_keys(self, filename=None):
|
||||
"""
|
||||
Load host keys from a system (read-only) file. Host keys read with
|
||||
this method will not be saved back by `save_host_keys`.
|
||||
|
||||
This method can be called multiple times. Each new set of host keys
|
||||
will be merged with the existing set (new replacing old if there are
|
||||
conflicts).
|
||||
|
||||
If ``filename`` is left as ``None``, an attempt will be made to read
|
||||
keys from the user's local "known hosts" file, as used by OpenSSH,
|
||||
and no exception will be raised if the file can't be read. This is
|
||||
probably only useful on posix.
|
||||
|
||||
:param str filename: the filename to read, or ``None``
|
||||
|
||||
:raises: ``IOError`` --
|
||||
if a filename was provided and the file could not be read
|
||||
"""
|
||||
if filename is None:
|
||||
# try the user's .ssh key file, and mask exceptions
|
||||
filename = os.path.expanduser("~/.ssh/known_hosts")
|
||||
try:
|
||||
self._system_host_keys.load(filename)
|
||||
except IOError:
|
||||
pass
|
||||
return
|
||||
self._system_host_keys.load(filename)
|
||||
|
||||
def load_host_keys(self, filename):
|
||||
"""
|
||||
Load host keys from a local host-key file. Host keys read with this
|
||||
method will be checked after keys loaded via `load_system_host_keys`,
|
||||
but will be saved back by `save_host_keys` (so they can be modified).
|
||||
The missing host key policy `.AutoAddPolicy` adds keys to this set and
|
||||
saves them, when connecting to a previously-unknown server.
|
||||
|
||||
This method can be called multiple times. Each new set of host keys
|
||||
will be merged with the existing set (new replacing old if there are
|
||||
conflicts). When automatically saving, the last hostname is used.
|
||||
|
||||
:param str filename: the filename to read
|
||||
|
||||
:raises: ``IOError`` -- if the filename could not be read
|
||||
"""
|
||||
self._host_keys_filename = filename
|
||||
self._host_keys.load(filename)
|
||||
|
||||
def save_host_keys(self, filename):
|
||||
"""
|
||||
Save the host keys back to a file. Only the host keys loaded with
|
||||
`load_host_keys` (plus any added directly) will be saved -- not any
|
||||
host keys loaded with `load_system_host_keys`.
|
||||
|
||||
:param str filename: the filename to save to
|
||||
|
||||
:raises: ``IOError`` -- if the file could not be written
|
||||
"""
|
||||
|
||||
# update local host keys from file (in case other SSH clients
|
||||
# have written to the known_hosts file meanwhile.
|
||||
if self._host_keys_filename is not None:
|
||||
self.load_host_keys(self._host_keys_filename)
|
||||
|
||||
with open(filename, "w") as f:
|
||||
for hostname, keys in self._host_keys.items():
|
||||
for keytype, key in keys.items():
|
||||
f.write(
|
||||
"{} {} {}\n".format(
|
||||
hostname, keytype, key.get_base64()
|
||||
)
|
||||
)
|
||||
|
||||
def get_host_keys(self):
|
||||
"""
|
||||
Get the local `.HostKeys` object. This can be used to examine the
|
||||
local host keys or change them.
|
||||
|
||||
:return: the local host keys as a `.HostKeys` object.
|
||||
"""
|
||||
return self._host_keys
|
||||
|
||||
def set_log_channel(self, name):
|
||||
"""
|
||||
Set the channel for logging. The default is ``"paramiko.transport"``
|
||||
but it can be set to anything you want.
|
||||
|
||||
:param str name: new channel name for logging
|
||||
"""
|
||||
self._log_channel = name
|
||||
|
||||
def set_missing_host_key_policy(self, policy):
|
||||
"""
|
||||
Set policy to use when connecting to servers without a known host key.
|
||||
|
||||
Specifically:
|
||||
|
||||
* A **policy** is a "policy class" (or instance thereof), namely some
|
||||
subclass of `.MissingHostKeyPolicy` such as `.RejectPolicy` (the
|
||||
default), `.AutoAddPolicy`, `.WarningPolicy`, or a user-created
|
||||
subclass.
|
||||
* A host key is **known** when it appears in the client object's cached
|
||||
host keys structures (those manipulated by `load_system_host_keys`
|
||||
and/or `load_host_keys`).
|
||||
|
||||
:param .MissingHostKeyPolicy policy:
|
||||
the policy to use when receiving a host key from a
|
||||
previously-unknown server
|
||||
"""
|
||||
if inspect.isclass(policy):
|
||||
policy = policy()
|
||||
self._policy = policy
|
||||
|
||||
def _families_and_addresses(self, hostname, port):
|
||||
"""
|
||||
Yield pairs of address families and addresses to try for connecting.
|
||||
|
||||
:param str hostname: the server to connect to
|
||||
:param int port: the server port to connect to
|
||||
:returns: Yields an iterable of ``(family, address)`` tuples
|
||||
"""
|
||||
guess = True
|
||||
addrinfos = socket.getaddrinfo(
|
||||
hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM
|
||||
)
|
||||
for (family, socktype, proto, canonname, sockaddr) in addrinfos:
|
||||
if socktype == socket.SOCK_STREAM:
|
||||
yield family, sockaddr
|
||||
guess = False
|
||||
|
||||
# some OS like AIX don't indicate SOCK_STREAM support, so just
|
||||
# guess. :( We only do this if we did not get a single result marked
|
||||
# as socktype == SOCK_STREAM.
|
||||
if guess:
|
||||
for family, _, _, _, sockaddr in addrinfos:
|
||||
yield family, sockaddr
|
||||
|
||||
def connect(
|
||||
self,
|
||||
hostname,
|
||||
port=SSH_PORT,
|
||||
username=None,
|
||||
password=None,
|
||||
pkey=None,
|
||||
key_filename=None,
|
||||
timeout=None,
|
||||
allow_agent=True,
|
||||
look_for_keys=True,
|
||||
compress=False,
|
||||
sock=None,
|
||||
gss_auth=False,
|
||||
gss_kex=False,
|
||||
gss_deleg_creds=True,
|
||||
gss_host=None,
|
||||
banner_timeout=None,
|
||||
auth_timeout=None,
|
||||
gss_trust_dns=True,
|
||||
passphrase=None,
|
||||
):
|
||||
"""
|
||||
Connect to an SSH server and authenticate to it. The server's host key
|
||||
is checked against the system host keys (see `load_system_host_keys`)
|
||||
and any local host keys (`load_host_keys`). If the server's hostname
|
||||
is not found in either set of host keys, the missing host key policy
|
||||
is used (see `set_missing_host_key_policy`). The default policy is
|
||||
to reject the key and raise an `.SSHException`.
|
||||
|
||||
Authentication is attempted in the following order of priority:
|
||||
|
||||
- The ``pkey`` or ``key_filename`` passed in (if any)
|
||||
|
||||
- ``key_filename`` may contain OpenSSH public certificate paths
|
||||
as well as regular private-key paths; when files ending in
|
||||
``-cert.pub`` are found, they are assumed to match a private
|
||||
key, and both components will be loaded. (The private key
|
||||
itself does *not* need to be listed in ``key_filename`` for
|
||||
this to occur - *just* the certificate.)
|
||||
|
||||
- Any key we can find through an SSH agent
|
||||
- Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in
|
||||
``~/.ssh/``
|
||||
|
||||
- When OpenSSH-style public certificates exist that match an
|
||||
existing such private key (so e.g. one has ``id_rsa`` and
|
||||
``id_rsa-cert.pub``) the certificate will be loaded alongside
|
||||
the private key and used for authentication.
|
||||
|
||||
- Plain username/password auth, if a password was given
|
||||
|
||||
If a private key requires a password to unlock it, and a password is
|
||||
passed in, that password will be used to attempt to unlock the key.
|
||||
|
||||
:param str hostname: the server to connect to
|
||||
:param int port: the server port to connect to
|
||||
:param str username:
|
||||
the username to authenticate as (defaults to the current local
|
||||
username)
|
||||
:param str password:
|
||||
Used for password authentication; is also used for private key
|
||||
decryption if ``passphrase`` is not given.
|
||||
:param str passphrase:
|
||||
Used for decrypting private keys.
|
||||
:param .PKey pkey: an optional private key to use for authentication
|
||||
:param str key_filename:
|
||||
the filename, or list of filenames, of optional private key(s)
|
||||
and/or certs to try for authentication
|
||||
:param float timeout:
|
||||
an optional timeout (in seconds) for the TCP connect
|
||||
:param bool allow_agent:
|
||||
set to False to disable connecting to the SSH agent
|
||||
:param bool look_for_keys:
|
||||
set to False to disable searching for discoverable private key
|
||||
files in ``~/.ssh/``
|
||||
:param bool compress: set to True to turn on compression
|
||||
:param socket sock:
|
||||
an open socket or socket-like object (such as a `.Channel`) to use
|
||||
for communication to the target host
|
||||
:param bool gss_auth:
|
||||
``True`` if you want to use GSS-API authentication
|
||||
:param bool gss_kex:
|
||||
Perform GSS-API Key Exchange and user authentication
|
||||
:param bool gss_deleg_creds: Delegate GSS-API client credentials or not
|
||||
:param str gss_host:
|
||||
The targets name in the kerberos database. default: hostname
|
||||
:param bool gss_trust_dns:
|
||||
Indicates whether or not the DNS is trusted to securely
|
||||
canonicalize the name of the host being connected to (default
|
||||
``True``).
|
||||
:param float banner_timeout: an optional timeout (in seconds) to wait
|
||||
for the SSH banner to be presented.
|
||||
:param float auth_timeout: an optional timeout (in seconds) to wait for
|
||||
an authentication response.
|
||||
|
||||
:raises:
|
||||
`.BadHostKeyException` -- if the server's host key could not be
|
||||
verified
|
||||
:raises: `.AuthenticationException` -- if authentication failed
|
||||
:raises:
|
||||
`.SSHException` -- if there was any other error connecting or
|
||||
establishing an SSH session
|
||||
:raises socket.error: if a socket error occurred while connecting
|
||||
|
||||
.. versionchanged:: 1.15
|
||||
Added the ``banner_timeout``, ``gss_auth``, ``gss_kex``,
|
||||
``gss_deleg_creds`` and ``gss_host`` arguments.
|
||||
.. versionchanged:: 2.3
|
||||
Added the ``gss_trust_dns`` argument.
|
||||
.. versionchanged:: 2.4
|
||||
Added the ``passphrase`` argument.
|
||||
"""
|
||||
if not sock:
|
||||
errors = {}
|
||||
# Try multiple possible address families (e.g. IPv4 vs IPv6)
|
||||
to_try = list(self._families_and_addresses(hostname, port))
|
||||
for af, addr in to_try:
|
||||
try:
|
||||
sock = socket.socket(af, socket.SOCK_STREAM)
|
||||
if timeout is not None:
|
||||
try:
|
||||
sock.settimeout(timeout)
|
||||
except:
|
||||
pass
|
||||
retry_on_signal(lambda: sock.connect(addr))
|
||||
# Break out of the loop on success
|
||||
break
|
||||
except socket.error as e:
|
||||
# Raise anything that isn't a straight up connection error
|
||||
# (such as a resolution error)
|
||||
if e.errno not in (ECONNREFUSED, EHOSTUNREACH):
|
||||
raise
|
||||
# Capture anything else so we know how the run looks once
|
||||
# iteration is complete. Retain info about which attempt
|
||||
# this was.
|
||||
errors[addr] = e
|
||||
|
||||
# Make sure we explode usefully if no address family attempts
|
||||
# succeeded. We've no way of knowing which error is the "right"
|
||||
# one, so we construct a hybrid exception containing all the real
|
||||
# ones, of a subclass that client code should still be watching for
|
||||
# (socket.error)
|
||||
if len(errors) == len(to_try):
|
||||
raise NoValidConnectionsError(errors)
|
||||
|
||||
t = self._transport = Transport(
|
||||
sock, gss_kex=gss_kex, gss_deleg_creds=gss_deleg_creds
|
||||
)
|
||||
t.use_compression(compress=compress)
|
||||
t.set_gss_host(
|
||||
# t.hostname may be None, but GSS-API requires a target name.
|
||||
# Therefore use hostname as fallback.
|
||||
gss_host=gss_host or hostname,
|
||||
trust_dns=gss_trust_dns,
|
||||
gssapi_requested=gss_auth or gss_kex,
|
||||
)
|
||||
if self._log_channel is not None:
|
||||
t.set_log_channel(self._log_channel)
|
||||
if banner_timeout is not None:
|
||||
t.banner_timeout = banner_timeout
|
||||
if auth_timeout is not None:
|
||||
t.auth_timeout = auth_timeout
|
||||
|
||||
if port == SSH_PORT:
|
||||
server_hostkey_name = hostname
|
||||
else:
|
||||
server_hostkey_name = "[{}]:{}".format(hostname, port)
|
||||
our_server_keys = None
|
||||
|
||||
our_server_keys = self._system_host_keys.get(server_hostkey_name)
|
||||
if our_server_keys is None:
|
||||
our_server_keys = self._host_keys.get(server_hostkey_name)
|
||||
if our_server_keys is not None:
|
||||
keytype = our_server_keys.keys()[0]
|
||||
sec_opts = t.get_security_options()
|
||||
other_types = [x for x in sec_opts.key_types if x != keytype]
|
||||
sec_opts.key_types = [keytype] + other_types
|
||||
|
||||
t.start_client(timeout=timeout)
|
||||
|
||||
# If GSS-API Key Exchange is performed we are not required to check the
|
||||
# host key, because the host is authenticated via GSS-API / SSPI as
|
||||
# well as our client.
|
||||
if not self._transport.gss_kex_used:
|
||||
server_key = t.get_remote_server_key()
|
||||
if our_server_keys is None:
|
||||
# will raise exception if the key is rejected
|
||||
self._policy.missing_host_key(
|
||||
self, server_hostkey_name, server_key
|
||||
)
|
||||
else:
|
||||
our_key = our_server_keys.get(server_key.get_name())
|
||||
if our_key != server_key:
|
||||
if our_key is None:
|
||||
our_key = list(our_server_keys.values())[0]
|
||||
raise BadHostKeyException(hostname, server_key, our_key)
|
||||
|
||||
if username is None:
|
||||
username = getpass.getuser()
|
||||
|
||||
if key_filename is None:
|
||||
key_filenames = []
|
||||
elif isinstance(key_filename, string_types):
|
||||
key_filenames = [key_filename]
|
||||
else:
|
||||
key_filenames = key_filename
|
||||
|
||||
self._auth(
|
||||
username,
|
||||
password,
|
||||
pkey,
|
||||
key_filenames,
|
||||
allow_agent,
|
||||
look_for_keys,
|
||||
gss_auth,
|
||||
gss_kex,
|
||||
gss_deleg_creds,
|
||||
t.gss_host,
|
||||
passphrase,
|
||||
)
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close this SSHClient and its underlying `.Transport`.
|
||||
|
||||
.. warning::
|
||||
Failure to do this may, in some situations, cause your Python
|
||||
interpreter to hang at shutdown (often due to race conditions).
|
||||
It's good practice to `close` your client objects anytime you're
|
||||
done using them, instead of relying on garbage collection.
|
||||
"""
|
||||
if self._transport is None:
|
||||
return
|
||||
self._transport.close()
|
||||
self._transport = None
|
||||
|
||||
if self._agent is not None:
|
||||
self._agent.close()
|
||||
self._agent = None
|
||||
|
||||
def exec_command(
|
||||
self,
|
||||
command,
|
||||
bufsize=-1,
|
||||
timeout=None,
|
||||
get_pty=False,
|
||||
environment=None,
|
||||
):
|
||||
"""
|
||||
Execute a command on the SSH server. A new `.Channel` is opened and
|
||||
the requested command is executed. The command's input and output
|
||||
streams are returned as Python ``file``-like objects representing
|
||||
stdin, stdout, and stderr.
|
||||
|
||||
:param str command: the command to execute
|
||||
:param int bufsize:
|
||||
interpreted the same way as by the built-in ``file()`` function in
|
||||
Python
|
||||
:param int timeout:
|
||||
set command's channel timeout. See `.Channel.settimeout`
|
||||
:param dict environment:
|
||||
a dict of shell environment variables, to be merged into the
|
||||
default environment that the remote command executes within.
|
||||
|
||||
.. warning::
|
||||
Servers may silently reject some environment variables; see the
|
||||
warning in `.Channel.set_environment_variable` for details.
|
||||
|
||||
:return:
|
||||
the stdin, stdout, and stderr of the executing command, as a
|
||||
3-tuple
|
||||
|
||||
:raises: `.SSHException` -- if the server fails to execute the command
|
||||
"""
|
||||
chan = self._transport.open_session(timeout=timeout)
|
||||
if get_pty:
|
||||
chan.get_pty()
|
||||
chan.settimeout(timeout)
|
||||
if environment:
|
||||
chan.update_environment(environment)
|
||||
chan.exec_command(command)
|
||||
stdin = chan.makefile("wb", bufsize)
|
||||
stdout = chan.makefile("r", bufsize)
|
||||
stderr = chan.makefile_stderr("r", bufsize)
|
||||
return stdin, stdout, stderr
|
||||
|
||||
def invoke_shell(
|
||||
self,
|
||||
term="vt100",
|
||||
width=80,
|
||||
height=24,
|
||||
width_pixels=0,
|
||||
height_pixels=0,
|
||||
environment=None,
|
||||
):
|
||||
"""
|
||||
Start an interactive shell session on the SSH server. A new `.Channel`
|
||||
is opened and connected to a pseudo-terminal using the requested
|
||||
terminal type and size.
|
||||
|
||||
:param str term:
|
||||
the terminal type to emulate (for example, ``"vt100"``)
|
||||
:param int width: the width (in characters) of the terminal window
|
||||
:param int height: the height (in characters) of the terminal window
|
||||
:param int width_pixels: the width (in pixels) of the terminal window
|
||||
:param int height_pixels: the height (in pixels) of the terminal window
|
||||
:param dict environment: the command's environment
|
||||
:return: a new `.Channel` connected to the remote shell
|
||||
|
||||
:raises: `.SSHException` -- if the server fails to invoke a shell
|
||||
"""
|
||||
chan = self._transport.open_session()
|
||||
chan.get_pty(term, width, height, width_pixels, height_pixels)
|
||||
chan.invoke_shell()
|
||||
return chan
|
||||
|
||||
def open_sftp(self):
|
||||
"""
|
||||
Open an SFTP session on the SSH server.
|
||||
|
||||
:return: a new `.SFTPClient` session object
|
||||
"""
|
||||
return self._transport.open_sftp_client()
|
||||
|
||||
def get_transport(self):
|
||||
"""
|
||||
Return the underlying `.Transport` object for this SSH connection.
|
||||
This can be used to perform lower-level tasks, like opening specific
|
||||
kinds of channels.
|
||||
|
||||
:return: the `.Transport` for this connection
|
||||
"""
|
||||
return self._transport
|
||||
|
||||
def _key_from_filepath(self, filename, klass, password):
|
||||
"""
|
||||
Attempt to derive a `.PKey` from given string path ``filename``:
|
||||
|
||||
- If ``filename`` appears to be a cert, the matching private key is
|
||||
loaded.
|
||||
- Otherwise, the filename is assumed to be a private key, and the
|
||||
matching public cert will be loaded if it exists.
|
||||
"""
|
||||
cert_suffix = "-cert.pub"
|
||||
# Assume privkey, not cert, by default
|
||||
if filename.endswith(cert_suffix):
|
||||
key_path = filename[: -len(cert_suffix)]
|
||||
cert_path = filename
|
||||
else:
|
||||
key_path = filename
|
||||
cert_path = filename + cert_suffix
|
||||
# Blindly try the key path; if no private key, nothing will work.
|
||||
key = klass.from_private_key_file(key_path, password)
|
||||
# TODO: change this to 'Loading' instead of 'Trying' sometime; probably
|
||||
# when #387 is released, since this is a critical log message users are
|
||||
# likely testing/filtering for (bah.)
|
||||
msg = "Trying discovered key {} in {}".format(
|
||||
hexlify(key.get_fingerprint()), key_path
|
||||
)
|
||||
self._log(DEBUG, msg)
|
||||
# Attempt to load cert if it exists.
|
||||
if os.path.isfile(cert_path):
|
||||
key.load_certificate(cert_path)
|
||||
self._log(DEBUG, "Adding public certificate {}".format(cert_path))
|
||||
return key
|
||||
|
||||
def _auth(
|
||||
self,
|
||||
username,
|
||||
password,
|
||||
pkey,
|
||||
key_filenames,
|
||||
allow_agent,
|
||||
look_for_keys,
|
||||
gss_auth,
|
||||
gss_kex,
|
||||
gss_deleg_creds,
|
||||
gss_host,
|
||||
passphrase,
|
||||
):
|
||||
"""
|
||||
Try, in order:
|
||||
|
||||
- The key(s) passed in, if one was passed in.
|
||||
- Any key we can find through an SSH agent (if allowed).
|
||||
- Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in ~/.ssh/
|
||||
(if allowed).
|
||||
- Plain username/password auth, if a password was given.
|
||||
|
||||
(The password might be needed to unlock a private key [if 'passphrase'
|
||||
isn't also given], or for two-factor authentication [for which it is
|
||||
required].)
|
||||
"""
|
||||
saved_exception = None
|
||||
two_factor = False
|
||||
allowed_types = set()
|
||||
two_factor_types = {"keyboard-interactive", "password"}
|
||||
if passphrase is None and password is not None:
|
||||
passphrase = password
|
||||
|
||||
# If GSS-API support and GSS-PI Key Exchange was performed, we attempt
|
||||
# authentication with gssapi-keyex.
|
||||
if gss_kex and self._transport.gss_kex_used:
|
||||
try:
|
||||
self._transport.auth_gssapi_keyex(username)
|
||||
return
|
||||
except Exception as e:
|
||||
saved_exception = e
|
||||
|
||||
# Try GSS-API authentication (gssapi-with-mic) only if GSS-API Key
|
||||
# Exchange is not performed, because if we use GSS-API for the key
|
||||
# exchange, there is already a fully established GSS-API context, so
|
||||
# why should we do that again?
|
||||
if gss_auth:
|
||||
try:
|
||||
return self._transport.auth_gssapi_with_mic(
|
||||
username, gss_host, gss_deleg_creds
|
||||
)
|
||||
except Exception as e:
|
||||
saved_exception = e
|
||||
|
||||
if pkey is not None:
|
||||
try:
|
||||
self._log(
|
||||
DEBUG,
|
||||
"Trying SSH key {}".format(
|
||||
hexlify(pkey.get_fingerprint())
|
||||
),
|
||||
)
|
||||
allowed_types = set(
|
||||
self._transport.auth_publickey(username, pkey)
|
||||
)
|
||||
two_factor = allowed_types & two_factor_types
|
||||
if not two_factor:
|
||||
return
|
||||
except SSHException as e:
|
||||
saved_exception = e
|
||||
|
||||
if not two_factor:
|
||||
for key_filename in key_filenames:
|
||||
for pkey_class in (RSAKey, DSSKey, ECDSAKey, Ed25519Key):
|
||||
try:
|
||||
key = self._key_from_filepath(
|
||||
key_filename, pkey_class, passphrase
|
||||
)
|
||||
allowed_types = set(
|
||||
self._transport.auth_publickey(username, key)
|
||||
)
|
||||
two_factor = allowed_types & two_factor_types
|
||||
if not two_factor:
|
||||
return
|
||||
break
|
||||
except SSHException as e:
|
||||
saved_exception = e
|
||||
|
||||
if not two_factor and allow_agent:
|
||||
if self._agent is None:
|
||||
self._agent = Agent()
|
||||
|
||||
for key in self._agent.get_keys():
|
||||
try:
|
||||
id_ = hexlify(key.get_fingerprint())
|
||||
self._log(DEBUG, "Trying SSH agent key {}".format(id_))
|
||||
# for 2-factor auth a successfully auth'd key password
|
||||
# will return an allowed 2fac auth method
|
||||
allowed_types = set(
|
||||
self._transport.auth_publickey(username, key)
|
||||
)
|
||||
two_factor = allowed_types & two_factor_types
|
||||
if not two_factor:
|
||||
return
|
||||
break
|
||||
except SSHException as e:
|
||||
saved_exception = e
|
||||
|
||||
if not two_factor:
|
||||
keyfiles = []
|
||||
|
||||
for keytype, name in [
|
||||
(RSAKey, "rsa"),
|
||||
(DSSKey, "dsa"),
|
||||
(ECDSAKey, "ecdsa"),
|
||||
(Ed25519Key, "ed25519"),
|
||||
]:
|
||||
# ~/ssh/ is for windows
|
||||
for directory in [".ssh", "ssh"]:
|
||||
full_path = os.path.expanduser(
|
||||
"~/{}/id_{}".format(directory, name)
|
||||
)
|
||||
if os.path.isfile(full_path):
|
||||
# TODO: only do this append if below did not run
|
||||
keyfiles.append((keytype, full_path))
|
||||
if os.path.isfile(full_path + "-cert.pub"):
|
||||
keyfiles.append((keytype, full_path + "-cert.pub"))
|
||||
|
||||
if not look_for_keys:
|
||||
keyfiles = []
|
||||
|
||||
for pkey_class, filename in keyfiles:
|
||||
try:
|
||||
key = self._key_from_filepath(
|
||||
filename, pkey_class, passphrase
|
||||
)
|
||||
# for 2-factor auth a successfully auth'd key will result
|
||||
# in ['password']
|
||||
allowed_types = set(
|
||||
self._transport.auth_publickey(username, key)
|
||||
)
|
||||
two_factor = allowed_types & two_factor_types
|
||||
if not two_factor:
|
||||
return
|
||||
break
|
||||
except (SSHException, IOError) as e:
|
||||
saved_exception = e
|
||||
|
||||
if password is not None:
|
||||
try:
|
||||
self._transport.auth_password(username, password)
|
||||
return
|
||||
except SSHException as e:
|
||||
saved_exception = e
|
||||
elif two_factor:
|
||||
try:
|
||||
self._transport.auth_interactive_dumb(username)
|
||||
return
|
||||
except SSHException as e:
|
||||
saved_exception = e
|
||||
|
||||
# if we got an auth-failed exception earlier, re-raise it
|
||||
if saved_exception is not None:
|
||||
raise saved_exception
|
||||
raise SSHException("No authentication methods available")
|
||||
|
||||
def _log(self, level, msg):
|
||||
self._transport._log(level, msg)
|
||||
|
||||
|
||||
class MissingHostKeyPolicy(object):
|
||||
"""
|
||||
Interface for defining the policy that `.SSHClient` should use when the
|
||||
SSH server's hostname is not in either the system host keys or the
|
||||
application's keys. Pre-made classes implement policies for automatically
|
||||
adding the key to the application's `.HostKeys` object (`.AutoAddPolicy`),
|
||||
and for automatically rejecting the key (`.RejectPolicy`).
|
||||
|
||||
This function may be used to ask the user to verify the key, for example.
|
||||
"""
|
||||
|
||||
def missing_host_key(self, client, hostname, key):
|
||||
"""
|
||||
Called when an `.SSHClient` receives a server key for a server that
|
||||
isn't in either the system or local `.HostKeys` object. To accept
|
||||
the key, simply return. To reject, raised an exception (which will
|
||||
be passed to the calling application).
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class AutoAddPolicy(MissingHostKeyPolicy):
|
||||
"""
|
||||
Policy for automatically adding the hostname and new host key to the
|
||||
local `.HostKeys` object, and saving it. This is used by `.SSHClient`.
|
||||
"""
|
||||
|
||||
def missing_host_key(self, client, hostname, key):
|
||||
client._host_keys.add(hostname, key.get_name(), key)
|
||||
if client._host_keys_filename is not None:
|
||||
client.save_host_keys(client._host_keys_filename)
|
||||
client._log(
|
||||
DEBUG,
|
||||
"Adding {} host key for {}: {}".format(
|
||||
key.get_name(), hostname, hexlify(key.get_fingerprint())
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class RejectPolicy(MissingHostKeyPolicy):
|
||||
"""
|
||||
Policy for automatically rejecting the unknown hostname & key. This is
|
||||
used by `.SSHClient`.
|
||||
"""
|
||||
|
||||
def missing_host_key(self, client, hostname, key):
|
||||
client._log(
|
||||
DEBUG,
|
||||
"Rejecting {} host key for {}: {}".format(
|
||||
key.get_name(), hostname, hexlify(key.get_fingerprint())
|
||||
),
|
||||
)
|
||||
raise SSHException(
|
||||
"Server {!r} not found in known_hosts".format(hostname)
|
||||
)
|
||||
|
||||
|
||||
class WarningPolicy(MissingHostKeyPolicy):
|
||||
"""
|
||||
Policy for logging a Python-style warning for an unknown host key, but
|
||||
accepting it. This is used by `.SSHClient`.
|
||||
"""
|
||||
|
||||
def missing_host_key(self, client, hostname, key):
|
||||
warnings.warn(
|
||||
"Unknown {} host key for {}: {}".format(
|
||||
key.get_name(), hostname, hexlify(key.get_fingerprint())
|
||||
)
|
||||
)
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/client.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/client.pyc
Normal file
Binary file not shown.
239
.ve/lib/python2.7/site-packages/paramiko/common.py
Normal file
239
.ve/lib/python2.7/site-packages/paramiko/common.py
Normal file
@@ -0,0 +1,239 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Common constants and global variables.
|
||||
"""
|
||||
import logging
|
||||
from paramiko.py3compat import byte_chr, PY2, bytes_types, text_type, long
|
||||
|
||||
(
|
||||
MSG_DISCONNECT,
|
||||
MSG_IGNORE,
|
||||
MSG_UNIMPLEMENTED,
|
||||
MSG_DEBUG,
|
||||
MSG_SERVICE_REQUEST,
|
||||
MSG_SERVICE_ACCEPT,
|
||||
) = range(1, 7)
|
||||
(MSG_KEXINIT, MSG_NEWKEYS) = range(20, 22)
|
||||
(
|
||||
MSG_USERAUTH_REQUEST,
|
||||
MSG_USERAUTH_FAILURE,
|
||||
MSG_USERAUTH_SUCCESS,
|
||||
MSG_USERAUTH_BANNER,
|
||||
) = range(50, 54)
|
||||
MSG_USERAUTH_PK_OK = 60
|
||||
(MSG_USERAUTH_INFO_REQUEST, MSG_USERAUTH_INFO_RESPONSE) = range(60, 62)
|
||||
(MSG_USERAUTH_GSSAPI_RESPONSE, MSG_USERAUTH_GSSAPI_TOKEN) = range(60, 62)
|
||||
(
|
||||
MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE,
|
||||
MSG_USERAUTH_GSSAPI_ERROR,
|
||||
MSG_USERAUTH_GSSAPI_ERRTOK,
|
||||
MSG_USERAUTH_GSSAPI_MIC,
|
||||
) = range(63, 67)
|
||||
HIGHEST_USERAUTH_MESSAGE_ID = 79
|
||||
(MSG_GLOBAL_REQUEST, MSG_REQUEST_SUCCESS, MSG_REQUEST_FAILURE) = range(80, 83)
|
||||
(
|
||||
MSG_CHANNEL_OPEN,
|
||||
MSG_CHANNEL_OPEN_SUCCESS,
|
||||
MSG_CHANNEL_OPEN_FAILURE,
|
||||
MSG_CHANNEL_WINDOW_ADJUST,
|
||||
MSG_CHANNEL_DATA,
|
||||
MSG_CHANNEL_EXTENDED_DATA,
|
||||
MSG_CHANNEL_EOF,
|
||||
MSG_CHANNEL_CLOSE,
|
||||
MSG_CHANNEL_REQUEST,
|
||||
MSG_CHANNEL_SUCCESS,
|
||||
MSG_CHANNEL_FAILURE,
|
||||
) = range(90, 101)
|
||||
|
||||
cMSG_DISCONNECT = byte_chr(MSG_DISCONNECT)
|
||||
cMSG_IGNORE = byte_chr(MSG_IGNORE)
|
||||
cMSG_UNIMPLEMENTED = byte_chr(MSG_UNIMPLEMENTED)
|
||||
cMSG_DEBUG = byte_chr(MSG_DEBUG)
|
||||
cMSG_SERVICE_REQUEST = byte_chr(MSG_SERVICE_REQUEST)
|
||||
cMSG_SERVICE_ACCEPT = byte_chr(MSG_SERVICE_ACCEPT)
|
||||
cMSG_KEXINIT = byte_chr(MSG_KEXINIT)
|
||||
cMSG_NEWKEYS = byte_chr(MSG_NEWKEYS)
|
||||
cMSG_USERAUTH_REQUEST = byte_chr(MSG_USERAUTH_REQUEST)
|
||||
cMSG_USERAUTH_FAILURE = byte_chr(MSG_USERAUTH_FAILURE)
|
||||
cMSG_USERAUTH_SUCCESS = byte_chr(MSG_USERAUTH_SUCCESS)
|
||||
cMSG_USERAUTH_BANNER = byte_chr(MSG_USERAUTH_BANNER)
|
||||
cMSG_USERAUTH_PK_OK = byte_chr(MSG_USERAUTH_PK_OK)
|
||||
cMSG_USERAUTH_INFO_REQUEST = byte_chr(MSG_USERAUTH_INFO_REQUEST)
|
||||
cMSG_USERAUTH_INFO_RESPONSE = byte_chr(MSG_USERAUTH_INFO_RESPONSE)
|
||||
cMSG_USERAUTH_GSSAPI_RESPONSE = byte_chr(MSG_USERAUTH_GSSAPI_RESPONSE)
|
||||
cMSG_USERAUTH_GSSAPI_TOKEN = byte_chr(MSG_USERAUTH_GSSAPI_TOKEN)
|
||||
cMSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE = byte_chr(
|
||||
MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE
|
||||
)
|
||||
cMSG_USERAUTH_GSSAPI_ERROR = byte_chr(MSG_USERAUTH_GSSAPI_ERROR)
|
||||
cMSG_USERAUTH_GSSAPI_ERRTOK = byte_chr(MSG_USERAUTH_GSSAPI_ERRTOK)
|
||||
cMSG_USERAUTH_GSSAPI_MIC = byte_chr(MSG_USERAUTH_GSSAPI_MIC)
|
||||
cMSG_GLOBAL_REQUEST = byte_chr(MSG_GLOBAL_REQUEST)
|
||||
cMSG_REQUEST_SUCCESS = byte_chr(MSG_REQUEST_SUCCESS)
|
||||
cMSG_REQUEST_FAILURE = byte_chr(MSG_REQUEST_FAILURE)
|
||||
cMSG_CHANNEL_OPEN = byte_chr(MSG_CHANNEL_OPEN)
|
||||
cMSG_CHANNEL_OPEN_SUCCESS = byte_chr(MSG_CHANNEL_OPEN_SUCCESS)
|
||||
cMSG_CHANNEL_OPEN_FAILURE = byte_chr(MSG_CHANNEL_OPEN_FAILURE)
|
||||
cMSG_CHANNEL_WINDOW_ADJUST = byte_chr(MSG_CHANNEL_WINDOW_ADJUST)
|
||||
cMSG_CHANNEL_DATA = byte_chr(MSG_CHANNEL_DATA)
|
||||
cMSG_CHANNEL_EXTENDED_DATA = byte_chr(MSG_CHANNEL_EXTENDED_DATA)
|
||||
cMSG_CHANNEL_EOF = byte_chr(MSG_CHANNEL_EOF)
|
||||
cMSG_CHANNEL_CLOSE = byte_chr(MSG_CHANNEL_CLOSE)
|
||||
cMSG_CHANNEL_REQUEST = byte_chr(MSG_CHANNEL_REQUEST)
|
||||
cMSG_CHANNEL_SUCCESS = byte_chr(MSG_CHANNEL_SUCCESS)
|
||||
cMSG_CHANNEL_FAILURE = byte_chr(MSG_CHANNEL_FAILURE)
|
||||
|
||||
# for debugging:
|
||||
MSG_NAMES = {
|
||||
MSG_DISCONNECT: "disconnect",
|
||||
MSG_IGNORE: "ignore",
|
||||
MSG_UNIMPLEMENTED: "unimplemented",
|
||||
MSG_DEBUG: "debug",
|
||||
MSG_SERVICE_REQUEST: "service-request",
|
||||
MSG_SERVICE_ACCEPT: "service-accept",
|
||||
MSG_KEXINIT: "kexinit",
|
||||
MSG_NEWKEYS: "newkeys",
|
||||
30: "kex30",
|
||||
31: "kex31",
|
||||
32: "kex32",
|
||||
33: "kex33",
|
||||
34: "kex34",
|
||||
40: "kex40",
|
||||
41: "kex41",
|
||||
MSG_USERAUTH_REQUEST: "userauth-request",
|
||||
MSG_USERAUTH_FAILURE: "userauth-failure",
|
||||
MSG_USERAUTH_SUCCESS: "userauth-success",
|
||||
MSG_USERAUTH_BANNER: "userauth--banner",
|
||||
MSG_USERAUTH_PK_OK: "userauth-60(pk-ok/info-request)",
|
||||
MSG_USERAUTH_INFO_RESPONSE: "userauth-info-response",
|
||||
MSG_GLOBAL_REQUEST: "global-request",
|
||||
MSG_REQUEST_SUCCESS: "request-success",
|
||||
MSG_REQUEST_FAILURE: "request-failure",
|
||||
MSG_CHANNEL_OPEN: "channel-open",
|
||||
MSG_CHANNEL_OPEN_SUCCESS: "channel-open-success",
|
||||
MSG_CHANNEL_OPEN_FAILURE: "channel-open-failure",
|
||||
MSG_CHANNEL_WINDOW_ADJUST: "channel-window-adjust",
|
||||
MSG_CHANNEL_DATA: "channel-data",
|
||||
MSG_CHANNEL_EXTENDED_DATA: "channel-extended-data",
|
||||
MSG_CHANNEL_EOF: "channel-eof",
|
||||
MSG_CHANNEL_CLOSE: "channel-close",
|
||||
MSG_CHANNEL_REQUEST: "channel-request",
|
||||
MSG_CHANNEL_SUCCESS: "channel-success",
|
||||
MSG_CHANNEL_FAILURE: "channel-failure",
|
||||
MSG_USERAUTH_GSSAPI_RESPONSE: "userauth-gssapi-response",
|
||||
MSG_USERAUTH_GSSAPI_TOKEN: "userauth-gssapi-token",
|
||||
MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE: "userauth-gssapi-exchange-complete",
|
||||
MSG_USERAUTH_GSSAPI_ERROR: "userauth-gssapi-error",
|
||||
MSG_USERAUTH_GSSAPI_ERRTOK: "userauth-gssapi-error-token",
|
||||
MSG_USERAUTH_GSSAPI_MIC: "userauth-gssapi-mic",
|
||||
}
|
||||
|
||||
|
||||
# authentication request return codes:
|
||||
AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED = range(3)
|
||||
|
||||
|
||||
# channel request failed reasons:
|
||||
(
|
||||
OPEN_SUCCEEDED,
|
||||
OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED,
|
||||
OPEN_FAILED_CONNECT_FAILED,
|
||||
OPEN_FAILED_UNKNOWN_CHANNEL_TYPE,
|
||||
OPEN_FAILED_RESOURCE_SHORTAGE,
|
||||
) = range(0, 5)
|
||||
|
||||
|
||||
CONNECTION_FAILED_CODE = {
|
||||
1: "Administratively prohibited",
|
||||
2: "Connect failed",
|
||||
3: "Unknown channel type",
|
||||
4: "Resource shortage",
|
||||
}
|
||||
|
||||
|
||||
(
|
||||
DISCONNECT_SERVICE_NOT_AVAILABLE,
|
||||
DISCONNECT_AUTH_CANCELLED_BY_USER,
|
||||
DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
|
||||
) = (7, 13, 14)
|
||||
|
||||
zero_byte = byte_chr(0)
|
||||
one_byte = byte_chr(1)
|
||||
four_byte = byte_chr(4)
|
||||
max_byte = byte_chr(0xff)
|
||||
cr_byte = byte_chr(13)
|
||||
linefeed_byte = byte_chr(10)
|
||||
crlf = cr_byte + linefeed_byte
|
||||
|
||||
if PY2:
|
||||
cr_byte_value = cr_byte
|
||||
linefeed_byte_value = linefeed_byte
|
||||
else:
|
||||
cr_byte_value = 13
|
||||
linefeed_byte_value = 10
|
||||
|
||||
|
||||
def asbytes(s):
|
||||
"""Coerce to bytes if possible or return unchanged."""
|
||||
if isinstance(s, bytes_types):
|
||||
return s
|
||||
if isinstance(s, text_type):
|
||||
# Accept text and encode as utf-8 for compatibility only.
|
||||
return s.encode("utf-8")
|
||||
asbytes = getattr(s, "asbytes", None)
|
||||
if asbytes is not None:
|
||||
return asbytes()
|
||||
# May be an object that implements the buffer api, let callers handle.
|
||||
return s
|
||||
|
||||
|
||||
xffffffff = long(0xffffffff)
|
||||
x80000000 = long(0x80000000)
|
||||
o666 = 438
|
||||
o660 = 432
|
||||
o644 = 420
|
||||
o600 = 384
|
||||
o777 = 511
|
||||
o700 = 448
|
||||
o70 = 56
|
||||
|
||||
DEBUG = logging.DEBUG
|
||||
INFO = logging.INFO
|
||||
WARNING = logging.WARNING
|
||||
ERROR = logging.ERROR
|
||||
CRITICAL = logging.CRITICAL
|
||||
|
||||
# Common IO/select/etc sleep period, in seconds
|
||||
io_sleep = 0.01
|
||||
|
||||
DEFAULT_WINDOW_SIZE = 64 * 2 ** 15
|
||||
DEFAULT_MAX_PACKET_SIZE = 2 ** 15
|
||||
|
||||
# lower bound on the max packet size we'll accept from the remote host
|
||||
# Minimum packet size is 32768 bytes according to
|
||||
# http://www.ietf.org/rfc/rfc4254.txt
|
||||
MIN_WINDOW_SIZE = 2 ** 15
|
||||
|
||||
# However, according to http://www.ietf.org/rfc/rfc4253.txt it is perfectly
|
||||
# legal to accept a size much smaller, as OpenSSH client does as size 16384.
|
||||
MIN_PACKET_SIZE = 2 ** 12
|
||||
|
||||
# Max windows size according to http://www.ietf.org/rfc/rfc4254.txt
|
||||
MAX_WINDOW_SIZE = 2 ** 32 - 1
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/common.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/common.pyc
Normal file
Binary file not shown.
40
.ve/lib/python2.7/site-packages/paramiko/compress.py
Normal file
40
.ve/lib/python2.7/site-packages/paramiko/compress.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Compression implementations for a Transport.
|
||||
"""
|
||||
|
||||
import zlib
|
||||
|
||||
|
||||
class ZlibCompressor(object):
|
||||
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(object):
|
||||
def __init__(self):
|
||||
self.z = zlib.decompressobj()
|
||||
|
||||
def __call__(self, data):
|
||||
return self.z.decompress(data)
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/compress.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/compress.pyc
Normal file
Binary file not shown.
293
.ve/lib/python2.7/site-packages/paramiko/config.py
Normal file
293
.ve/lib/python2.7/site-packages/paramiko/config.py
Normal file
@@ -0,0 +1,293 @@
|
||||
# Copyright (C) 2006-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright (C) 2012 Olle Lundberg <geek@nerd.sh>
|
||||
#
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Configuration file (aka ``ssh_config``) support.
|
||||
"""
|
||||
|
||||
import fnmatch
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import socket
|
||||
|
||||
SSH_PORT = 22
|
||||
|
||||
|
||||
class SSHConfig(object):
|
||||
"""
|
||||
Representation of config information as stored in the format used by
|
||||
OpenSSH. Queries can be made via `lookup`. The format is described in
|
||||
OpenSSH's ``ssh_config`` man page. This class is provided primarily as a
|
||||
convenience to posix users (since the OpenSSH format is a de-facto
|
||||
standard on posix) but should work fine on Windows too.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
SETTINGS_REGEX = re.compile(r"(\w+)(?:\s*=\s*|\s+)(.+)")
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Create a new OpenSSH config object.
|
||||
"""
|
||||
self._config = []
|
||||
|
||||
def parse(self, file_obj):
|
||||
"""
|
||||
Read an OpenSSH config from the given file object.
|
||||
|
||||
:param file_obj: a file-like object to read the config file from
|
||||
"""
|
||||
host = {"host": ["*"], "config": {}}
|
||||
for line in file_obj:
|
||||
# Strip any leading or trailing whitespace from the line.
|
||||
# Refer to https://github.com/paramiko/paramiko/issues/499
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
|
||||
match = re.match(self.SETTINGS_REGEX, line)
|
||||
if not match:
|
||||
raise Exception("Unparsable line {}".format(line))
|
||||
key = match.group(1).lower()
|
||||
value = match.group(2)
|
||||
|
||||
if key == "host":
|
||||
self._config.append(host)
|
||||
host = {"host": self._get_hosts(value), "config": {}}
|
||||
elif key == "proxycommand" and value.lower() == "none":
|
||||
# Store 'none' as None; prior to 3.x, it will get stripped out
|
||||
# at the end (for compatibility with issue #415). After 3.x, it
|
||||
# will simply not get stripped, leaving a nice explicit marker.
|
||||
host["config"][key] = None
|
||||
else:
|
||||
if value.startswith('"') and value.endswith('"'):
|
||||
value = value[1:-1]
|
||||
|
||||
# identityfile, localforward, remoteforward keys are special
|
||||
# cases, since they are allowed to be specified multiple times
|
||||
# and they should be tried in order of specification.
|
||||
if key in ["identityfile", "localforward", "remoteforward"]:
|
||||
if key in host["config"]:
|
||||
host["config"][key].append(value)
|
||||
else:
|
||||
host["config"][key] = [value]
|
||||
elif key not in host["config"]:
|
||||
host["config"][key] = value
|
||||
self._config.append(host)
|
||||
|
||||
def lookup(self, hostname):
|
||||
"""
|
||||
Return a dict of config options for a given hostname.
|
||||
|
||||
The host-matching rules of OpenSSH's ``ssh_config`` man page are used:
|
||||
For each parameter, the first obtained value will be used. The
|
||||
configuration files contain sections separated by ``Host``
|
||||
specifications, and that section is only applied for hosts that match
|
||||
one of the patterns given in the specification.
|
||||
|
||||
Since the first obtained value for each parameter is used, more host-
|
||||
specific declarations should be given near the beginning of the file,
|
||||
and general defaults at the end.
|
||||
|
||||
The keys in the returned dict are all normalized to lowercase (look for
|
||||
``"port"``, not ``"Port"``. The values are processed according to the
|
||||
rules for substitution variable expansion in ``ssh_config``.
|
||||
|
||||
:param str hostname: the hostname to lookup
|
||||
"""
|
||||
matches = [
|
||||
config
|
||||
for config in self._config
|
||||
if self._allowed(config["host"], hostname)
|
||||
]
|
||||
|
||||
ret = {}
|
||||
for match in matches:
|
||||
for key, value in match["config"].items():
|
||||
if key not in ret:
|
||||
# Create a copy of the original value,
|
||||
# else it will reference the original list
|
||||
# in self._config and update that value too
|
||||
# when the extend() is being called.
|
||||
ret[key] = value[:] if value is not None else value
|
||||
elif key == "identityfile":
|
||||
ret[key].extend(value)
|
||||
ret = self._expand_variables(ret, hostname)
|
||||
# TODO: remove in 3.x re #670
|
||||
if "proxycommand" in ret and ret["proxycommand"] is None:
|
||||
del ret["proxycommand"]
|
||||
return ret
|
||||
|
||||
def get_hostnames(self):
|
||||
"""
|
||||
Return the set of literal hostnames defined in the SSH config (both
|
||||
explicit hostnames and wildcard entries).
|
||||
"""
|
||||
hosts = set()
|
||||
for entry in self._config:
|
||||
hosts.update(entry["host"])
|
||||
return hosts
|
||||
|
||||
def _allowed(self, hosts, hostname):
|
||||
match = False
|
||||
for host in hosts:
|
||||
if host.startswith("!") and fnmatch.fnmatch(hostname, host[1:]):
|
||||
return False
|
||||
elif fnmatch.fnmatch(hostname, host):
|
||||
match = True
|
||||
return match
|
||||
|
||||
def _expand_variables(self, config, hostname):
|
||||
"""
|
||||
Return a dict of config options with expanded substitutions
|
||||
for a given hostname.
|
||||
|
||||
Please refer to man ``ssh_config`` for the parameters that
|
||||
are replaced.
|
||||
|
||||
:param dict config: the config for the hostname
|
||||
:param str hostname: the hostname that the config belongs to
|
||||
"""
|
||||
|
||||
if "hostname" in config:
|
||||
config["hostname"] = config["hostname"].replace("%h", hostname)
|
||||
else:
|
||||
config["hostname"] = hostname
|
||||
|
||||
if "port" in config:
|
||||
port = config["port"]
|
||||
else:
|
||||
port = SSH_PORT
|
||||
|
||||
user = os.getenv("USER")
|
||||
if "user" in config:
|
||||
remoteuser = config["user"]
|
||||
else:
|
||||
remoteuser = user
|
||||
|
||||
host = socket.gethostname().split(".")[0]
|
||||
fqdn = LazyFqdn(config, host)
|
||||
homedir = os.path.expanduser("~")
|
||||
replacements = {
|
||||
"controlpath": [
|
||||
("%h", config["hostname"]),
|
||||
("%l", fqdn),
|
||||
("%L", host),
|
||||
("%n", hostname),
|
||||
("%p", port),
|
||||
("%r", remoteuser),
|
||||
("%u", user),
|
||||
],
|
||||
"identityfile": [
|
||||
("~", homedir),
|
||||
("%d", homedir),
|
||||
("%h", config["hostname"]),
|
||||
("%l", fqdn),
|
||||
("%u", user),
|
||||
("%r", remoteuser),
|
||||
],
|
||||
"proxycommand": [
|
||||
("~", homedir),
|
||||
("%h", config["hostname"]),
|
||||
("%p", port),
|
||||
("%r", remoteuser),
|
||||
],
|
||||
}
|
||||
|
||||
for k in config:
|
||||
if config[k] is None:
|
||||
continue
|
||||
if k in replacements:
|
||||
for find, replace in replacements[k]:
|
||||
if isinstance(config[k], list):
|
||||
for item in range(len(config[k])):
|
||||
if find in config[k][item]:
|
||||
config[k][item] = config[k][item].replace(
|
||||
find, str(replace)
|
||||
)
|
||||
else:
|
||||
if find in config[k]:
|
||||
config[k] = config[k].replace(find, str(replace))
|
||||
return config
|
||||
|
||||
def _get_hosts(self, host):
|
||||
"""
|
||||
Return a list of host_names from host value.
|
||||
"""
|
||||
try:
|
||||
return shlex.split(host)
|
||||
except ValueError:
|
||||
raise Exception("Unparsable host {}".format(host))
|
||||
|
||||
|
||||
class LazyFqdn(object):
|
||||
"""
|
||||
Returns the host's fqdn on request as string.
|
||||
"""
|
||||
|
||||
def __init__(self, config, host=None):
|
||||
self.fqdn = None
|
||||
self.config = config
|
||||
self.host = host
|
||||
|
||||
def __str__(self):
|
||||
if self.fqdn is None:
|
||||
#
|
||||
# If the SSH config contains AddressFamily, use that when
|
||||
# determining the local host's FQDN. Using socket.getfqdn() from
|
||||
# the standard library is the most general solution, but can
|
||||
# result in noticeable delays on some platforms when IPv6 is
|
||||
# misconfigured or not available, as it calls getaddrinfo with no
|
||||
# address family specified, so both IPv4 and IPv6 are checked.
|
||||
#
|
||||
|
||||
# Handle specific option
|
||||
fqdn = None
|
||||
address_family = self.config.get("addressfamily", "any").lower()
|
||||
if address_family != "any":
|
||||
try:
|
||||
family = socket.AF_INET6
|
||||
if address_family == "inet":
|
||||
socket.AF_INET
|
||||
results = socket.getaddrinfo(
|
||||
self.host,
|
||||
None,
|
||||
family,
|
||||
socket.SOCK_DGRAM,
|
||||
socket.IPPROTO_IP,
|
||||
socket.AI_CANONNAME,
|
||||
)
|
||||
for res in results:
|
||||
af, socktype, proto, canonname, sa = res
|
||||
if canonname and "." in canonname:
|
||||
fqdn = canonname
|
||||
break
|
||||
# giaerror -> socket.getaddrinfo() can't resolve self.host
|
||||
# (which is from socket.gethostname()). Fall back to the
|
||||
# getfqdn() call below.
|
||||
except socket.gaierror:
|
||||
pass
|
||||
# Handle 'any' / unspecified
|
||||
if fqdn is None:
|
||||
fqdn = socket.getfqdn()
|
||||
# Cache
|
||||
self.fqdn = fqdn
|
||||
return self.fqdn
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/config.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/config.pyc
Normal file
Binary file not shown.
247
.ve/lib/python2.7/site-packages/paramiko/dsskey.py
Normal file
247
.ve/lib/python2.7/site-packages/paramiko/dsskey.py
Normal file
@@ -0,0 +1,247 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
DSS keys.
|
||||
"""
|
||||
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import dsa
|
||||
from cryptography.hazmat.primitives.asymmetric.utils import (
|
||||
decode_dss_signature,
|
||||
encode_dss_signature,
|
||||
)
|
||||
|
||||
from paramiko import util
|
||||
from paramiko.common import zero_byte
|
||||
from paramiko.ssh_exception import SSHException
|
||||
from paramiko.message import Message
|
||||
from paramiko.ber import BER, BERException
|
||||
from paramiko.pkey import PKey
|
||||
|
||||
|
||||
class DSSKey(PKey):
|
||||
"""
|
||||
Representation of a DSS key which can be used to sign an verify SSH2
|
||||
data.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
msg=None,
|
||||
data=None,
|
||||
filename=None,
|
||||
password=None,
|
||||
vals=None,
|
||||
file_obj=None,
|
||||
):
|
||||
self.p = None
|
||||
self.q = None
|
||||
self.g = None
|
||||
self.y = None
|
||||
self.x = 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.p, self.q, self.g, self.y = vals
|
||||
else:
|
||||
self._check_type_and_load_cert(
|
||||
msg=msg,
|
||||
key_type="ssh-dss",
|
||||
cert_type="ssh-dss-cert-v01@openssh.com",
|
||||
)
|
||||
self.p = msg.get_mpint()
|
||||
self.q = msg.get_mpint()
|
||||
self.g = msg.get_mpint()
|
||||
self.y = msg.get_mpint()
|
||||
self.size = util.bit_length(self.p)
|
||||
|
||||
def asbytes(self):
|
||||
m = Message()
|
||||
m.add_string("ssh-dss")
|
||||
m.add_mpint(self.p)
|
||||
m.add_mpint(self.q)
|
||||
m.add_mpint(self.g)
|
||||
m.add_mpint(self.y)
|
||||
return m.asbytes()
|
||||
|
||||
def __str__(self):
|
||||
return self.asbytes()
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.get_name(), self.p, self.q, self.g, self.y))
|
||||
|
||||
def get_name(self):
|
||||
return "ssh-dss"
|
||||
|
||||
def get_bits(self):
|
||||
return self.size
|
||||
|
||||
def can_sign(self):
|
||||
return self.x is not None
|
||||
|
||||
def sign_ssh_data(self, data):
|
||||
key = dsa.DSAPrivateNumbers(
|
||||
x=self.x,
|
||||
public_numbers=dsa.DSAPublicNumbers(
|
||||
y=self.y,
|
||||
parameter_numbers=dsa.DSAParameterNumbers(
|
||||
p=self.p, q=self.q, g=self.g
|
||||
),
|
||||
),
|
||||
).private_key(backend=default_backend())
|
||||
sig = key.sign(data, hashes.SHA1())
|
||||
r, s = decode_dss_signature(sig)
|
||||
|
||||
m = Message()
|
||||
m.add_string("ssh-dss")
|
||||
# apparently, in rare cases, r or s may be shorter than 20 bytes!
|
||||
rstr = util.deflate_long(r, 0)
|
||||
sstr = util.deflate_long(s, 0)
|
||||
if len(rstr) < 20:
|
||||
rstr = zero_byte * (20 - len(rstr)) + rstr
|
||||
if len(sstr) < 20:
|
||||
sstr = zero_byte * (20 - len(sstr)) + sstr
|
||||
m.add_string(rstr + sstr)
|
||||
return m
|
||||
|
||||
def verify_ssh_sig(self, data, msg):
|
||||
if len(msg.asbytes()) == 40:
|
||||
# spies.com bug: signature has no header
|
||||
sig = msg.asbytes()
|
||||
else:
|
||||
kind = msg.get_text()
|
||||
if kind != "ssh-dss":
|
||||
return 0
|
||||
sig = msg.get_binary()
|
||||
|
||||
# pull out (r, s) which are NOT encoded as mpints
|
||||
sigR = util.inflate_long(sig[:20], 1)
|
||||
sigS = util.inflate_long(sig[20:], 1)
|
||||
|
||||
signature = encode_dss_signature(sigR, sigS)
|
||||
|
||||
key = dsa.DSAPublicNumbers(
|
||||
y=self.y,
|
||||
parameter_numbers=dsa.DSAParameterNumbers(
|
||||
p=self.p, q=self.q, g=self.g
|
||||
),
|
||||
).public_key(backend=default_backend())
|
||||
try:
|
||||
key.verify(signature, data, hashes.SHA1())
|
||||
except InvalidSignature:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def write_private_key_file(self, filename, password=None):
|
||||
key = dsa.DSAPrivateNumbers(
|
||||
x=self.x,
|
||||
public_numbers=dsa.DSAPublicNumbers(
|
||||
y=self.y,
|
||||
parameter_numbers=dsa.DSAParameterNumbers(
|
||||
p=self.p, q=self.q, g=self.g
|
||||
),
|
||||
),
|
||||
).private_key(backend=default_backend())
|
||||
|
||||
self._write_private_key_file(
|
||||
filename,
|
||||
key,
|
||||
serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
password=password,
|
||||
)
|
||||
|
||||
def write_private_key(self, file_obj, password=None):
|
||||
key = dsa.DSAPrivateNumbers(
|
||||
x=self.x,
|
||||
public_numbers=dsa.DSAPublicNumbers(
|
||||
y=self.y,
|
||||
parameter_numbers=dsa.DSAParameterNumbers(
|
||||
p=self.p, q=self.q, g=self.g
|
||||
),
|
||||
),
|
||||
).private_key(backend=default_backend())
|
||||
|
||||
self._write_private_key(
|
||||
file_obj,
|
||||
key,
|
||||
serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
password=password,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def generate(bits=1024, progress_func=None):
|
||||
"""
|
||||
Generate a new private DSS 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 `.DSSKey` private key
|
||||
"""
|
||||
numbers = dsa.generate_private_key(
|
||||
bits, backend=default_backend()
|
||||
).private_numbers()
|
||||
key = DSSKey(
|
||||
vals=(
|
||||
numbers.public_numbers.parameter_numbers.p,
|
||||
numbers.public_numbers.parameter_numbers.q,
|
||||
numbers.public_numbers.parameter_numbers.g,
|
||||
numbers.public_numbers.y,
|
||||
)
|
||||
)
|
||||
key.x = numbers.x
|
||||
return key
|
||||
|
||||
# ...internals...
|
||||
|
||||
def _from_private_key_file(self, filename, password):
|
||||
data = self._read_private_key_file("DSA", filename, password)
|
||||
self._decode_key(data)
|
||||
|
||||
def _from_private_key(self, file_obj, password):
|
||||
data = self._read_private_key("DSA", file_obj, password)
|
||||
self._decode_key(data)
|
||||
|
||||
def _decode_key(self, data):
|
||||
# private key file contains:
|
||||
# DSAPrivateKey = { version = 0, p, q, g, y, x }
|
||||
try:
|
||||
keylist = BER(data).decode()
|
||||
except BERException as e:
|
||||
raise SSHException("Unable to parse key file: " + str(e))
|
||||
if type(keylist) is not list or len(keylist) < 6 or keylist[0] != 0:
|
||||
raise SSHException(
|
||||
"not a valid DSA private key file (bad ber encoding)"
|
||||
)
|
||||
self.p = keylist[1]
|
||||
self.q = keylist[2]
|
||||
self.g = keylist[3]
|
||||
self.y = keylist[4]
|
||||
self.x = keylist[5]
|
||||
self.size = util.bit_length(self.p)
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/dsskey.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/dsskey.pyc
Normal file
Binary file not shown.
308
.ve/lib/python2.7/site-packages/paramiko/ecdsakey.py
Normal file
308
.ve/lib/python2.7/site-packages/paramiko/ecdsakey.py
Normal file
@@ -0,0 +1,308 @@
|
||||
# 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 distrubuted 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
ECDSA keys
|
||||
"""
|
||||
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
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(object):
|
||||
"""
|
||||
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(object):
|
||||
"""
|
||||
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,
|
||||
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:
|
||||
numbers = ec.EllipticCurvePublicNumbers.from_encoded_point(
|
||||
self.ecdsa_curve.curve_class(), pointinfo
|
||||
)
|
||||
except ValueError:
|
||||
raise SSHException("Invalid public key")
|
||||
self.verifying_key = numbers.public_key(backend=default_backend())
|
||||
|
||||
@classmethod
|
||||
def supported_key_format_identifiers(cls):
|
||||
return cls._ECDSA_CURVES.get_key_format_identifier_list()
|
||||
|
||||
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()
|
||||
|
||||
def __hash__(self):
|
||||
return hash(
|
||||
(
|
||||
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):
|
||||
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):
|
||||
try:
|
||||
key = serialization.load_der_private_key(
|
||||
data, password=None, backend=default_backend()
|
||||
)
|
||||
except (ValueError, AssertionError) as e:
|
||||
raise SSHException(str(e))
|
||||
|
||||
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
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/ecdsakey.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/ecdsakey.pyc
Normal file
Binary file not shown.
226
.ve/lib/python2.7/site-packages/paramiko/ed25519key.py
Normal file
226
.ve/lib/python2.7/site-packages/paramiko/ed25519key.py
Normal file
@@ -0,0 +1,226 @@
|
||||
# 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 distrubuted 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
import bcrypt
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher
|
||||
|
||||
import nacl.signing
|
||||
|
||||
import six
|
||||
|
||||
from paramiko.message import Message
|
||||
from paramiko.pkey import PKey
|
||||
from paramiko.py3compat import b
|
||||
from paramiko.ssh_exception import SSHException, PasswordRequiredException
|
||||
|
||||
|
||||
OPENSSH_AUTH_MAGIC = b"openssh-key-v1\x00"
|
||||
|
||||
|
||||
def unpad(data):
|
||||
# At the moment, this is only used for unpadding private keys on disk. This
|
||||
# really ought to be made constant time (possibly by upstreaming this logic
|
||||
# into pyca/cryptography).
|
||||
padding_length = six.indexbytes(data, -1)
|
||||
if padding_length > 16:
|
||||
raise SSHException("Invalid key")
|
||||
for i in range(1, padding_length + 1):
|
||||
if six.indexbytes(data, -i) != (padding_length - i + 1):
|
||||
raise SSHException("Invalid key")
|
||||
return data[:-padding_length]
|
||||
|
||||
|
||||
class Ed25519Key(PKey):
|
||||
"""
|
||||
Representation of an `Ed25519 <https://ed25519.cr.yp.to/>`_ key.
|
||||
|
||||
.. note::
|
||||
Ed25519 key support was added to OpenSSH in version 6.5.
|
||||
|
||||
.. versionadded:: 2.2
|
||||
.. versionchanged:: 2.3
|
||||
Added a ``file_obj`` parameter to match other key classes.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, msg=None, data=None, filename=None, password=None, file_obj=None
|
||||
):
|
||||
self.public_blob = None
|
||||
verifying_key = signing_key = None
|
||||
if msg is None and data is not None:
|
||||
msg = Message(data)
|
||||
if msg is not None:
|
||||
self._check_type_and_load_cert(
|
||||
msg=msg,
|
||||
key_type="ssh-ed25519",
|
||||
cert_type="ssh-ed25519-cert-v01@openssh.com",
|
||||
)
|
||||
verifying_key = nacl.signing.VerifyKey(msg.get_binary())
|
||||
elif filename is not None:
|
||||
with open(filename, "r") as f:
|
||||
data = self._read_private_key("OPENSSH", f)
|
||||
elif file_obj is not None:
|
||||
data = self._read_private_key("OPENSSH", file_obj)
|
||||
|
||||
if filename or file_obj:
|
||||
signing_key = self._parse_signing_key_data(data, password)
|
||||
|
||||
if signing_key is None and verifying_key is None:
|
||||
raise ValueError("need a key")
|
||||
|
||||
self._signing_key = signing_key
|
||||
self._verifying_key = verifying_key
|
||||
|
||||
def _parse_signing_key_data(self, data, password):
|
||||
from paramiko.transport import Transport
|
||||
|
||||
# We may eventually want this to be usable for other key types, as
|
||||
# OpenSSH moves to it, but for now this is just for Ed25519 keys.
|
||||
# This format is described here:
|
||||
# https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
|
||||
# The description isn't totally complete, and I had to refer to the
|
||||
# source for a full implementation.
|
||||
message = Message(data)
|
||||
if message.get_bytes(len(OPENSSH_AUTH_MAGIC)) != OPENSSH_AUTH_MAGIC:
|
||||
raise SSHException("Invalid key")
|
||||
|
||||
ciphername = message.get_text()
|
||||
kdfname = message.get_text()
|
||||
kdfoptions = message.get_binary()
|
||||
num_keys = message.get_int()
|
||||
|
||||
if kdfname == "none":
|
||||
# kdfname of "none" must have an empty kdfoptions, the ciphername
|
||||
# must be "none"
|
||||
if kdfoptions or ciphername != "none":
|
||||
raise SSHException("Invalid key")
|
||||
elif kdfname == "bcrypt":
|
||||
if not password:
|
||||
raise PasswordRequiredException(
|
||||
"Private key file is encrypted"
|
||||
)
|
||||
kdf = Message(kdfoptions)
|
||||
bcrypt_salt = kdf.get_binary()
|
||||
bcrypt_rounds = kdf.get_int()
|
||||
else:
|
||||
raise SSHException("Invalid key")
|
||||
|
||||
if ciphername != "none" and ciphername not in Transport._cipher_info:
|
||||
raise SSHException("Invalid key")
|
||||
|
||||
public_keys = []
|
||||
for _ in range(num_keys):
|
||||
pubkey = Message(message.get_binary())
|
||||
if pubkey.get_text() != "ssh-ed25519":
|
||||
raise SSHException("Invalid key")
|
||||
public_keys.append(pubkey.get_binary())
|
||||
|
||||
private_ciphertext = message.get_binary()
|
||||
if ciphername == "none":
|
||||
private_data = private_ciphertext
|
||||
else:
|
||||
cipher = Transport._cipher_info[ciphername]
|
||||
key = bcrypt.kdf(
|
||||
password=b(password),
|
||||
salt=bcrypt_salt,
|
||||
desired_key_bytes=cipher["key-size"] + cipher["block-size"],
|
||||
rounds=bcrypt_rounds,
|
||||
# We can't control how many rounds are on disk, so no sense
|
||||
# warning about it.
|
||||
ignore_few_rounds=True,
|
||||
)
|
||||
decryptor = Cipher(
|
||||
cipher["class"](key[: cipher["key-size"]]),
|
||||
cipher["mode"](key[cipher["key-size"] :]),
|
||||
backend=default_backend(),
|
||||
).decryptor()
|
||||
private_data = (
|
||||
decryptor.update(private_ciphertext) + decryptor.finalize()
|
||||
)
|
||||
|
||||
message = Message(unpad(private_data))
|
||||
if message.get_int() != message.get_int():
|
||||
raise SSHException("Invalid key")
|
||||
|
||||
signing_keys = []
|
||||
for i in range(num_keys):
|
||||
if message.get_text() != "ssh-ed25519":
|
||||
raise SSHException("Invalid key")
|
||||
# A copy of the public key, again, ignore.
|
||||
public = message.get_binary()
|
||||
key_data = message.get_binary()
|
||||
# The second half of the key data is yet another copy of the public
|
||||
# key...
|
||||
signing_key = nacl.signing.SigningKey(key_data[:32])
|
||||
# Verify that all the public keys are the same...
|
||||
assert (
|
||||
signing_key.verify_key.encode()
|
||||
== public
|
||||
== public_keys[i]
|
||||
== key_data[32:]
|
||||
)
|
||||
signing_keys.append(signing_key)
|
||||
# Comment, ignore.
|
||||
message.get_binary()
|
||||
|
||||
if len(signing_keys) != 1:
|
||||
raise SSHException("Invalid key")
|
||||
return signing_keys[0]
|
||||
|
||||
def asbytes(self):
|
||||
if self.can_sign():
|
||||
v = self._signing_key.verify_key
|
||||
else:
|
||||
v = self._verifying_key
|
||||
m = Message()
|
||||
m.add_string("ssh-ed25519")
|
||||
m.add_string(v.encode())
|
||||
return m.asbytes()
|
||||
|
||||
def __hash__(self):
|
||||
if self.can_sign():
|
||||
v = self._signing_key.verify_key
|
||||
else:
|
||||
v = self._verifying_key
|
||||
return hash((self.get_name(), v))
|
||||
|
||||
def get_name(self):
|
||||
return "ssh-ed25519"
|
||||
|
||||
def get_bits(self):
|
||||
return 256
|
||||
|
||||
def can_sign(self):
|
||||
return self._signing_key is not None
|
||||
|
||||
def sign_ssh_data(self, data):
|
||||
m = Message()
|
||||
m.add_string("ssh-ed25519")
|
||||
m.add_string(self._signing_key.sign(data).signature)
|
||||
return m
|
||||
|
||||
def verify_ssh_sig(self, data, msg):
|
||||
if msg.get_text() != "ssh-ed25519":
|
||||
return False
|
||||
|
||||
try:
|
||||
self._verifying_key.verify(data, msg.get_binary())
|
||||
except nacl.exceptions.BadSignatureError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/ed25519key.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/ed25519key.pyc
Normal file
Binary file not shown.
545
.ve/lib/python2.7/site-packages/paramiko/file.py
Normal file
545
.ve/lib/python2.7/site-packages/paramiko/file.py
Normal file
@@ -0,0 +1,545 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
from paramiko.common import (
|
||||
linefeed_byte_value,
|
||||
crlf,
|
||||
cr_byte,
|
||||
linefeed_byte,
|
||||
cr_byte_value,
|
||||
)
|
||||
from paramiko.py3compat import BytesIO, PY2, u, bytes_types, text_type
|
||||
|
||||
from paramiko.util import ClosingContextManager
|
||||
|
||||
|
||||
class BufferedFile(ClosingContextManager):
|
||||
"""
|
||||
Reusable base class to implement Python-style file buffering around a
|
||||
simpler stream.
|
||||
"""
|
||||
|
||||
_DEFAULT_BUFSIZE = 8192
|
||||
|
||||
SEEK_SET = 0
|
||||
SEEK_CUR = 1
|
||||
SEEK_END = 2
|
||||
|
||||
FLAG_READ = 0x1
|
||||
FLAG_WRITE = 0x2
|
||||
FLAG_APPEND = 0x4
|
||||
FLAG_BINARY = 0x10
|
||||
FLAG_BUFFERED = 0x20
|
||||
FLAG_LINE_BUFFERED = 0x40
|
||||
FLAG_UNIVERSAL_NEWLINE = 0x80
|
||||
|
||||
def __init__(self):
|
||||
self.newlines = None
|
||||
self._flags = 0
|
||||
self._bufsize = self._DEFAULT_BUFSIZE
|
||||
self._wbuffer = BytesIO()
|
||||
self._rbuffer = bytes()
|
||||
self._at_trailing_cr = False
|
||||
self._closed = False
|
||||
# pos - position within the file, according to the user
|
||||
# realpos - position according the OS
|
||||
# (these may be different because we buffer for line reading)
|
||||
self._pos = self._realpos = 0
|
||||
# size only matters for seekable files
|
||||
self._size = 0
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
Returns an iterator that can be used to iterate over the lines in this
|
||||
file. This iterator happens to return the file itself, since a file is
|
||||
its own iterator.
|
||||
|
||||
:raises: ``ValueError`` -- if the file is closed.
|
||||
"""
|
||||
if self._closed:
|
||||
raise ValueError("I/O operation on closed file")
|
||||
return self
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the file. Future read and write operations will fail.
|
||||
"""
|
||||
self.flush()
|
||||
self._closed = True
|
||||
|
||||
def flush(self):
|
||||
"""
|
||||
Write out any data in the write buffer. This may do nothing if write
|
||||
buffering is not turned on.
|
||||
"""
|
||||
self._write_all(self._wbuffer.getvalue())
|
||||
self._wbuffer = BytesIO()
|
||||
return
|
||||
|
||||
if PY2:
|
||||
|
||||
def next(self):
|
||||
"""
|
||||
Returns the next line from the input, or raises
|
||||
``StopIteration`` when EOF is hit. Unlike Python file
|
||||
objects, it's okay to mix calls to `next` and `readline`.
|
||||
|
||||
:raises: ``StopIteration`` -- when the end of the file is reached.
|
||||
|
||||
:returns: a line (`str`) read from the file.
|
||||
"""
|
||||
line = self.readline()
|
||||
if not line:
|
||||
raise StopIteration
|
||||
return line
|
||||
|
||||
else:
|
||||
|
||||
def __next__(self):
|
||||
"""
|
||||
Returns the next line from the input, or raises ``StopIteration``
|
||||
when EOF is hit. Unlike python file objects, it's okay to mix
|
||||
calls to `.next` and `.readline`.
|
||||
|
||||
:raises: ``StopIteration`` -- when the end of the file is reached.
|
||||
|
||||
:returns: a line (`str`) read from the file.
|
||||
"""
|
||||
line = self.readline()
|
||||
if not line:
|
||||
raise StopIteration
|
||||
return line
|
||||
|
||||
def readable(self):
|
||||
"""
|
||||
Check if the file can be read from.
|
||||
|
||||
:returns:
|
||||
`True` if the file can be read from. If `False`, `read` will raise
|
||||
an exception.
|
||||
"""
|
||||
return (self._flags & self.FLAG_READ) == self.FLAG_READ
|
||||
|
||||
def writable(self):
|
||||
"""
|
||||
Check if the file can be written to.
|
||||
|
||||
:returns:
|
||||
`True` if the file can be written to. If `False`, `write` will
|
||||
raise an exception.
|
||||
"""
|
||||
return (self._flags & self.FLAG_WRITE) == self.FLAG_WRITE
|
||||
|
||||
def seekable(self):
|
||||
"""
|
||||
Check if the file supports random access.
|
||||
|
||||
:returns:
|
||||
`True` if the file supports random access. If `False`, `seek` will
|
||||
raise an exception.
|
||||
"""
|
||||
return False
|
||||
|
||||
def readinto(self, buff):
|
||||
"""
|
||||
Read up to ``len(buff)`` bytes into ``bytearray`` *buff* and return the
|
||||
number of bytes read.
|
||||
|
||||
:returns:
|
||||
The number of bytes read.
|
||||
"""
|
||||
data = self.read(len(buff))
|
||||
buff[: len(data)] = data
|
||||
return len(data)
|
||||
|
||||
def read(self, size=None):
|
||||
"""
|
||||
Read at most ``size`` bytes from the file (less if we hit the end of
|
||||
the file first). If the ``size`` argument is negative or omitted,
|
||||
read all the remaining data in the file.
|
||||
|
||||
.. note::
|
||||
``'b'`` mode flag is ignored (``self.FLAG_BINARY`` in
|
||||
``self._flags``), because SSH treats all files as binary, since we
|
||||
have no idea what encoding the file is in, or even if the file is
|
||||
text data.
|
||||
|
||||
:param int size: maximum number of bytes to read
|
||||
:returns:
|
||||
data read from the file (as bytes), or an empty string if EOF was
|
||||
encountered immediately
|
||||
"""
|
||||
if self._closed:
|
||||
raise IOError("File is closed")
|
||||
if not (self._flags & self.FLAG_READ):
|
||||
raise IOError("File is not open for reading")
|
||||
if (size is None) or (size < 0):
|
||||
# go for broke
|
||||
result = self._rbuffer
|
||||
self._rbuffer = bytes()
|
||||
self._pos += len(result)
|
||||
while True:
|
||||
try:
|
||||
new_data = self._read(self._DEFAULT_BUFSIZE)
|
||||
except EOFError:
|
||||
new_data = None
|
||||
if (new_data is None) or (len(new_data) == 0):
|
||||
break
|
||||
result += new_data
|
||||
self._realpos += len(new_data)
|
||||
self._pos += len(new_data)
|
||||
return result
|
||||
if size <= len(self._rbuffer):
|
||||
result = self._rbuffer[:size]
|
||||
self._rbuffer = self._rbuffer[size:]
|
||||
self._pos += len(result)
|
||||
return result
|
||||
while len(self._rbuffer) < size:
|
||||
read_size = size - len(self._rbuffer)
|
||||
if self._flags & self.FLAG_BUFFERED:
|
||||
read_size = max(self._bufsize, read_size)
|
||||
try:
|
||||
new_data = self._read(read_size)
|
||||
except EOFError:
|
||||
new_data = None
|
||||
if (new_data is None) or (len(new_data) == 0):
|
||||
break
|
||||
self._rbuffer += new_data
|
||||
self._realpos += len(new_data)
|
||||
result = self._rbuffer[:size]
|
||||
self._rbuffer = self._rbuffer[size:]
|
||||
self._pos += len(result)
|
||||
return result
|
||||
|
||||
def readline(self, size=None):
|
||||
"""
|
||||
Read one entire line from the file. A trailing newline character is
|
||||
kept in the string (but may be absent when a file ends with an
|
||||
incomplete line). If the size argument is present and non-negative, it
|
||||
is a maximum byte count (including the trailing newline) and an
|
||||
incomplete line may be returned. An empty string is returned only when
|
||||
EOF is encountered immediately.
|
||||
|
||||
.. note::
|
||||
Unlike stdio's ``fgets``, the returned string contains null
|
||||
characters (``'\\0'``) if they occurred in the input.
|
||||
|
||||
:param int size: maximum length of returned string.
|
||||
:returns:
|
||||
next line of the file, or an empty string if the end of the
|
||||
file has been reached.
|
||||
|
||||
If the file was opened in binary (``'b'``) mode: bytes are returned
|
||||
Else: the encoding of the file is assumed to be UTF-8 and character
|
||||
strings (`str`) are returned
|
||||
"""
|
||||
# it's almost silly how complex this function is.
|
||||
if self._closed:
|
||||
raise IOError("File is closed")
|
||||
if not (self._flags & self.FLAG_READ):
|
||||
raise IOError("File not open for reading")
|
||||
line = self._rbuffer
|
||||
truncated = False
|
||||
while True:
|
||||
if (
|
||||
self._at_trailing_cr
|
||||
and self._flags & self.FLAG_UNIVERSAL_NEWLINE
|
||||
and len(line) > 0
|
||||
):
|
||||
# edge case: the newline may be '\r\n' and we may have read
|
||||
# only the first '\r' last time.
|
||||
if line[0] == linefeed_byte_value:
|
||||
line = line[1:]
|
||||
self._record_newline(crlf)
|
||||
else:
|
||||
self._record_newline(cr_byte)
|
||||
self._at_trailing_cr = False
|
||||
# check size before looking for a linefeed, in case we already have
|
||||
# enough.
|
||||
if (size is not None) and (size >= 0):
|
||||
if len(line) >= size:
|
||||
# truncate line
|
||||
self._rbuffer = line[size:]
|
||||
line = line[:size]
|
||||
truncated = True
|
||||
break
|
||||
n = size - len(line)
|
||||
else:
|
||||
n = self._bufsize
|
||||
if linefeed_byte in line or (
|
||||
self._flags & self.FLAG_UNIVERSAL_NEWLINE and cr_byte in line
|
||||
):
|
||||
break
|
||||
try:
|
||||
new_data = self._read(n)
|
||||
except EOFError:
|
||||
new_data = None
|
||||
if (new_data is None) or (len(new_data) == 0):
|
||||
self._rbuffer = bytes()
|
||||
self._pos += len(line)
|
||||
return line if self._flags & self.FLAG_BINARY else u(line)
|
||||
line += new_data
|
||||
self._realpos += len(new_data)
|
||||
# find the newline
|
||||
pos = line.find(linefeed_byte)
|
||||
if self._flags & self.FLAG_UNIVERSAL_NEWLINE:
|
||||
rpos = line.find(cr_byte)
|
||||
if (rpos >= 0) and (rpos < pos or pos < 0):
|
||||
pos = rpos
|
||||
if pos == -1:
|
||||
# we couldn't find a newline in the truncated string, return it
|
||||
self._pos += len(line)
|
||||
return line if self._flags & self.FLAG_BINARY else u(line)
|
||||
xpos = pos + 1
|
||||
if (
|
||||
line[pos] == cr_byte_value
|
||||
and xpos < len(line)
|
||||
and line[xpos] == linefeed_byte_value
|
||||
):
|
||||
xpos += 1
|
||||
# if the string was truncated, _rbuffer needs to have the string after
|
||||
# the newline character plus the truncated part of the line we stored
|
||||
# earlier in _rbuffer
|
||||
if truncated:
|
||||
self._rbuffer = line[xpos:] + self._rbuffer
|
||||
else:
|
||||
self._rbuffer = line[xpos:]
|
||||
|
||||
lf = line[pos:xpos]
|
||||
line = line[:pos] + linefeed_byte
|
||||
if (len(self._rbuffer) == 0) and (lf == cr_byte):
|
||||
# we could read the line up to a '\r' and there could still be a
|
||||
# '\n' following that we read next time. note that and eat it.
|
||||
self._at_trailing_cr = True
|
||||
else:
|
||||
self._record_newline(lf)
|
||||
self._pos += len(line)
|
||||
return line if self._flags & self.FLAG_BINARY else u(line)
|
||||
|
||||
def readlines(self, sizehint=None):
|
||||
"""
|
||||
Read all remaining lines using `readline` and return them as a list.
|
||||
If the optional ``sizehint`` argument is present, instead of reading up
|
||||
to EOF, whole lines totalling approximately sizehint bytes (possibly
|
||||
after rounding up to an internal buffer size) are read.
|
||||
|
||||
:param int sizehint: desired maximum number of bytes to read.
|
||||
:returns: list of lines read from the file.
|
||||
"""
|
||||
lines = []
|
||||
byte_count = 0
|
||||
while True:
|
||||
line = self.readline()
|
||||
if len(line) == 0:
|
||||
break
|
||||
lines.append(line)
|
||||
byte_count += len(line)
|
||||
if (sizehint is not None) and (byte_count >= sizehint):
|
||||
break
|
||||
return lines
|
||||
|
||||
def seek(self, offset, whence=0):
|
||||
"""
|
||||
Set the file's current position, like stdio's ``fseek``. Not all file
|
||||
objects support seeking.
|
||||
|
||||
.. note::
|
||||
If a file is opened in append mode (``'a'`` or ``'a+'``), any seek
|
||||
operations will be undone at the next write (as the file position
|
||||
will move back to the end of the file).
|
||||
|
||||
:param int offset:
|
||||
position to move to within the file, relative to ``whence``.
|
||||
:param int whence:
|
||||
type of movement: 0 = absolute; 1 = relative to the current
|
||||
position; 2 = relative to the end of the file.
|
||||
|
||||
:raises: ``IOError`` -- if the file doesn't support random access.
|
||||
"""
|
||||
raise IOError("File does not support seeking.")
|
||||
|
||||
def tell(self):
|
||||
"""
|
||||
Return the file's current position. This may not be accurate or
|
||||
useful if the underlying file doesn't support random access, or was
|
||||
opened in append mode.
|
||||
|
||||
:returns: file position (`number <int>` of bytes).
|
||||
"""
|
||||
return self._pos
|
||||
|
||||
def write(self, data):
|
||||
"""
|
||||
Write data to the file. If write buffering is on (``bufsize`` was
|
||||
specified and non-zero), some or all of the data may not actually be
|
||||
written yet. (Use `flush` or `close` to force buffered data to be
|
||||
written out.)
|
||||
|
||||
:param data: ``str``/``bytes`` data to write
|
||||
"""
|
||||
if isinstance(data, text_type):
|
||||
# Accept text and encode as utf-8 for compatibility only.
|
||||
data = data.encode("utf-8")
|
||||
if self._closed:
|
||||
raise IOError("File is closed")
|
||||
if not (self._flags & self.FLAG_WRITE):
|
||||
raise IOError("File not open for writing")
|
||||
if not (self._flags & self.FLAG_BUFFERED):
|
||||
self._write_all(data)
|
||||
return
|
||||
self._wbuffer.write(data)
|
||||
if self._flags & self.FLAG_LINE_BUFFERED:
|
||||
# only scan the new data for linefeed, to avoid wasting time.
|
||||
last_newline_pos = data.rfind(linefeed_byte)
|
||||
if last_newline_pos >= 0:
|
||||
wbuf = self._wbuffer.getvalue()
|
||||
last_newline_pos += len(wbuf) - len(data)
|
||||
self._write_all(wbuf[: last_newline_pos + 1])
|
||||
self._wbuffer = BytesIO()
|
||||
self._wbuffer.write(wbuf[last_newline_pos + 1 :])
|
||||
return
|
||||
# even if we're line buffering, if the buffer has grown past the
|
||||
# buffer size, force a flush.
|
||||
if self._wbuffer.tell() >= self._bufsize:
|
||||
self.flush()
|
||||
return
|
||||
|
||||
def writelines(self, sequence):
|
||||
"""
|
||||
Write a sequence of strings to the file. The sequence can be any
|
||||
iterable object producing strings, typically a list of strings. (The
|
||||
name is intended to match `readlines`; `writelines` does not add line
|
||||
separators.)
|
||||
|
||||
:param sequence: an iterable sequence of strings.
|
||||
"""
|
||||
for line in sequence:
|
||||
self.write(line)
|
||||
return
|
||||
|
||||
def xreadlines(self):
|
||||
"""
|
||||
Identical to ``iter(f)``. This is a deprecated file interface that
|
||||
predates Python iterator support.
|
||||
"""
|
||||
return self
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self._closed
|
||||
|
||||
# ...overrides...
|
||||
|
||||
def _read(self, size):
|
||||
"""
|
||||
(subclass override)
|
||||
Read data from the stream. Return ``None`` or raise ``EOFError`` to
|
||||
indicate EOF.
|
||||
"""
|
||||
raise EOFError()
|
||||
|
||||
def _write(self, data):
|
||||
"""
|
||||
(subclass override)
|
||||
Write data into the stream.
|
||||
"""
|
||||
raise IOError("write not implemented")
|
||||
|
||||
def _get_size(self):
|
||||
"""
|
||||
(subclass override)
|
||||
Return the size of the file. This is called from within `_set_mode`
|
||||
if the file is opened in append mode, so the file position can be
|
||||
tracked and `seek` and `tell` will work correctly. If the file is
|
||||
a stream that can't be randomly accessed, you don't need to override
|
||||
this method,
|
||||
"""
|
||||
return 0
|
||||
|
||||
# ...internals...
|
||||
|
||||
def _set_mode(self, mode="r", bufsize=-1):
|
||||
"""
|
||||
Subclasses call this method to initialize the BufferedFile.
|
||||
"""
|
||||
# set bufsize in any event, because it's used for readline().
|
||||
self._bufsize = self._DEFAULT_BUFSIZE
|
||||
if bufsize < 0:
|
||||
# do no buffering by default, because otherwise writes will get
|
||||
# buffered in a way that will probably confuse people.
|
||||
bufsize = 0
|
||||
if bufsize == 1:
|
||||
# apparently, line buffering only affects writes. reads are only
|
||||
# buffered if you call readline (directly or indirectly: iterating
|
||||
# over a file will indirectly call readline).
|
||||
self._flags |= self.FLAG_BUFFERED | self.FLAG_LINE_BUFFERED
|
||||
elif bufsize > 1:
|
||||
self._bufsize = bufsize
|
||||
self._flags |= self.FLAG_BUFFERED
|
||||
self._flags &= ~self.FLAG_LINE_BUFFERED
|
||||
elif bufsize == 0:
|
||||
# unbuffered
|
||||
self._flags &= ~(self.FLAG_BUFFERED | self.FLAG_LINE_BUFFERED)
|
||||
|
||||
if ("r" in mode) or ("+" in mode):
|
||||
self._flags |= self.FLAG_READ
|
||||
if ("w" in mode) or ("+" in mode):
|
||||
self._flags |= self.FLAG_WRITE
|
||||
if "a" in mode:
|
||||
self._flags |= self.FLAG_WRITE | self.FLAG_APPEND
|
||||
self._size = self._get_size()
|
||||
self._pos = self._realpos = self._size
|
||||
if "b" in mode:
|
||||
self._flags |= self.FLAG_BINARY
|
||||
if "U" in mode:
|
||||
self._flags |= self.FLAG_UNIVERSAL_NEWLINE
|
||||
# built-in file objects have this attribute to store which kinds of
|
||||
# line terminations they've seen:
|
||||
# <http://www.python.org/doc/current/lib/built-in-funcs.html>
|
||||
self.newlines = None
|
||||
|
||||
def _write_all(self, data):
|
||||
# the underlying stream may be something that does partial writes (like
|
||||
# a socket).
|
||||
while len(data) > 0:
|
||||
count = self._write(data)
|
||||
data = data[count:]
|
||||
if self._flags & self.FLAG_APPEND:
|
||||
self._size += count
|
||||
self._pos = self._realpos = self._size
|
||||
else:
|
||||
self._pos += count
|
||||
self._realpos += count
|
||||
return None
|
||||
|
||||
def _record_newline(self, newline):
|
||||
# silliness about tracking what kinds of newlines we've seen.
|
||||
# i don't understand why it can be None, a string, or a tuple, instead
|
||||
# of just always being a tuple, but we'll emulate that behavior anyway.
|
||||
if not (self._flags & self.FLAG_UNIVERSAL_NEWLINE):
|
||||
return
|
||||
if self.newlines is None:
|
||||
self.newlines = newline
|
||||
elif self.newlines != newline and isinstance(
|
||||
self.newlines, bytes_types
|
||||
):
|
||||
self.newlines = (self.newlines, newline)
|
||||
elif newline not in self.newlines:
|
||||
self.newlines += (newline,)
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/file.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/file.pyc
Normal file
Binary file not shown.
387
.ve/lib/python2.7/site-packages/paramiko/hostkeys.py
Normal file
387
.ve/lib/python2.7/site-packages/paramiko/hostkeys.py
Normal file
@@ -0,0 +1,387 @@
|
||||
# Copyright (C) 2006-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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
|
||||
import binascii
|
||||
import os
|
||||
|
||||
from collections import MutableMapping
|
||||
from hashlib import sha1
|
||||
from hmac import HMAC
|
||||
|
||||
from paramiko.py3compat import b, u, encodebytes, decodebytes
|
||||
|
||||
from paramiko.dsskey import DSSKey
|
||||
from paramiko.rsakey import RSAKey
|
||||
from paramiko.util import get_logger, constant_time_bytes_eq
|
||||
from paramiko.ecdsakey import ECDSAKey
|
||||
from paramiko.ed25519key import Ed25519Key
|
||||
from paramiko.ssh_exception import SSHException
|
||||
|
||||
|
||||
class HostKeys(MutableMapping):
|
||||
"""
|
||||
Representation of an OpenSSH-style "known hosts" file. Host keys can be
|
||||
read from one or more files, and then individual hosts can be looked up to
|
||||
verify server keys during SSH negotiation.
|
||||
|
||||
A `.HostKeys` object can be treated like a dict; any dict lookup is
|
||||
equivalent to calling `lookup`.
|
||||
|
||||
.. versionadded:: 1.5.3
|
||||
"""
|
||||
|
||||
def __init__(self, filename=None):
|
||||
"""
|
||||
Create a new HostKeys object, optionally loading keys from an OpenSSH
|
||||
style host-key file.
|
||||
|
||||
:param str filename: filename to load host keys from, or ``None``
|
||||
"""
|
||||
# emulate a dict of { hostname: { keytype: PKey } }
|
||||
self._entries = []
|
||||
if filename is not None:
|
||||
self.load(filename)
|
||||
|
||||
def add(self, hostname, keytype, key):
|
||||
"""
|
||||
Add a host key entry to the table. Any existing entry for a
|
||||
``(hostname, keytype)`` pair will be replaced.
|
||||
|
||||
:param str hostname: the hostname (or IP) to add
|
||||
:param str keytype: key type (``"ssh-rsa"`` or ``"ssh-dss"``)
|
||||
:param .PKey key: the key to add
|
||||
"""
|
||||
for e in self._entries:
|
||||
if (hostname in e.hostnames) and (e.key.get_name() == keytype):
|
||||
e.key = key
|
||||
return
|
||||
self._entries.append(HostKeyEntry([hostname], key))
|
||||
|
||||
def load(self, filename):
|
||||
"""
|
||||
Read a file of known SSH host keys, in the format used by OpenSSH.
|
||||
This type of file unfortunately doesn't exist on Windows, but on
|
||||
posix, it will usually be stored in
|
||||
``os.path.expanduser("~/.ssh/known_hosts")``.
|
||||
|
||||
If this method is called multiple times, the host keys are merged,
|
||||
not cleared. So multiple calls to `load` will just call `add`,
|
||||
replacing any existing entries and adding new ones.
|
||||
|
||||
:param str filename: name of the file to read host keys from
|
||||
|
||||
:raises: ``IOError`` -- if there was an error reading the file
|
||||
"""
|
||||
with open(filename, "r") as f:
|
||||
for lineno, line in enumerate(f, 1):
|
||||
line = line.strip()
|
||||
if (len(line) == 0) or (line[0] == "#"):
|
||||
continue
|
||||
try:
|
||||
e = HostKeyEntry.from_line(line, lineno)
|
||||
except SSHException:
|
||||
continue
|
||||
if e is not None:
|
||||
_hostnames = e.hostnames
|
||||
for h in _hostnames:
|
||||
if self.check(h, e.key):
|
||||
e.hostnames.remove(h)
|
||||
if len(e.hostnames):
|
||||
self._entries.append(e)
|
||||
|
||||
def save(self, filename):
|
||||
"""
|
||||
Save host keys into a file, in the format used by OpenSSH. The order
|
||||
of keys in the file will be preserved when possible (if these keys were
|
||||
loaded from a file originally). The single exception is that combined
|
||||
lines will be split into individual key lines, which is arguably a bug.
|
||||
|
||||
:param str filename: name of the file to write
|
||||
|
||||
:raises: ``IOError`` -- if there was an error writing the file
|
||||
|
||||
.. versionadded:: 1.6.1
|
||||
"""
|
||||
with open(filename, "w") as f:
|
||||
for e in self._entries:
|
||||
line = e.to_line()
|
||||
if line:
|
||||
f.write(line)
|
||||
|
||||
def lookup(self, hostname):
|
||||
"""
|
||||
Find a hostkey entry for a given hostname or IP. If no entry is found,
|
||||
``None`` is returned. Otherwise a dictionary of keytype to key is
|
||||
returned. The keytype will be either ``"ssh-rsa"`` or ``"ssh-dss"``.
|
||||
|
||||
:param str hostname: the hostname (or IP) to lookup
|
||||
:return: dict of `str` -> `.PKey` keys associated with this host
|
||||
(or ``None``)
|
||||
"""
|
||||
|
||||
class SubDict(MutableMapping):
|
||||
def __init__(self, hostname, entries, hostkeys):
|
||||
self._hostname = hostname
|
||||
self._entries = entries
|
||||
self._hostkeys = hostkeys
|
||||
|
||||
def __iter__(self):
|
||||
for k in self.keys():
|
||||
yield k
|
||||
|
||||
def __len__(self):
|
||||
return len(self.keys())
|
||||
|
||||
def __delitem__(self, key):
|
||||
for e in list(self._entries):
|
||||
if e.key.get_name() == key:
|
||||
self._entries.remove(e)
|
||||
else:
|
||||
raise KeyError(key)
|
||||
|
||||
def __getitem__(self, key):
|
||||
for e in self._entries:
|
||||
if e.key.get_name() == key:
|
||||
return e.key
|
||||
raise KeyError(key)
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
for e in self._entries:
|
||||
if e.key is None:
|
||||
continue
|
||||
if e.key.get_name() == key:
|
||||
# replace
|
||||
e.key = val
|
||||
break
|
||||
else:
|
||||
# add a new one
|
||||
e = HostKeyEntry([hostname], val)
|
||||
self._entries.append(e)
|
||||
self._hostkeys._entries.append(e)
|
||||
|
||||
def keys(self):
|
||||
return [
|
||||
e.key.get_name()
|
||||
for e in self._entries
|
||||
if e.key is not None
|
||||
]
|
||||
|
||||
entries = []
|
||||
for e in self._entries:
|
||||
if self._hostname_matches(hostname, e):
|
||||
entries.append(e)
|
||||
if len(entries) == 0:
|
||||
return None
|
||||
return SubDict(hostname, entries, self)
|
||||
|
||||
def _hostname_matches(self, hostname, entry):
|
||||
"""
|
||||
Tests whether ``hostname`` string matches given SubDict ``entry``.
|
||||
|
||||
:returns bool:
|
||||
"""
|
||||
for h in entry.hostnames:
|
||||
if (
|
||||
h == hostname
|
||||
or h.startswith("|1|")
|
||||
and not hostname.startswith("|1|")
|
||||
and constant_time_bytes_eq(self.hash_host(hostname, h), h)
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def check(self, hostname, key):
|
||||
"""
|
||||
Return True if the given key is associated with the given hostname
|
||||
in this dictionary.
|
||||
|
||||
:param str hostname: hostname (or IP) of the SSH server
|
||||
:param .PKey key: the key to check
|
||||
:return:
|
||||
``True`` if the key is associated with the hostname; else ``False``
|
||||
"""
|
||||
k = self.lookup(hostname)
|
||||
if k is None:
|
||||
return False
|
||||
host_key = k.get(key.get_name(), None)
|
||||
if host_key is None:
|
||||
return False
|
||||
return host_key.asbytes() == key.asbytes()
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Remove all host keys from the dictionary.
|
||||
"""
|
||||
self._entries = []
|
||||
|
||||
def __iter__(self):
|
||||
for k in self.keys():
|
||||
yield k
|
||||
|
||||
def __len__(self):
|
||||
return len(self.keys())
|
||||
|
||||
def __getitem__(self, key):
|
||||
ret = self.lookup(key)
|
||||
if ret is None:
|
||||
raise KeyError(key)
|
||||
return ret
|
||||
|
||||
def __delitem__(self, key):
|
||||
index = None
|
||||
for i, entry in enumerate(self._entries):
|
||||
if self._hostname_matches(key, entry):
|
||||
index = i
|
||||
break
|
||||
if index is None:
|
||||
raise KeyError(key)
|
||||
self._entries.pop(index)
|
||||
|
||||
def __setitem__(self, hostname, entry):
|
||||
# don't use this please.
|
||||
if len(entry) == 0:
|
||||
self._entries.append(HostKeyEntry([hostname], None))
|
||||
return
|
||||
for key_type in entry.keys():
|
||||
found = False
|
||||
for e in self._entries:
|
||||
if (hostname in e.hostnames) and e.key.get_name() == key_type:
|
||||
# replace
|
||||
e.key = entry[key_type]
|
||||
found = True
|
||||
if not found:
|
||||
self._entries.append(HostKeyEntry([hostname], entry[key_type]))
|
||||
|
||||
def keys(self):
|
||||
# Python 2.4 sets would be nice here.
|
||||
ret = []
|
||||
for e in self._entries:
|
||||
for h in e.hostnames:
|
||||
if h not in ret:
|
||||
ret.append(h)
|
||||
return ret
|
||||
|
||||
def values(self):
|
||||
ret = []
|
||||
for k in self.keys():
|
||||
ret.append(self.lookup(k))
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def hash_host(hostname, salt=None):
|
||||
"""
|
||||
Return a "hashed" form of the hostname, as used by OpenSSH when storing
|
||||
hashed hostnames in the known_hosts file.
|
||||
|
||||
:param str hostname: the hostname to hash
|
||||
:param str salt: optional salt to use when hashing
|
||||
(must be 20 bytes long)
|
||||
:return: the hashed hostname as a `str`
|
||||
"""
|
||||
if salt is None:
|
||||
salt = os.urandom(sha1().digest_size)
|
||||
else:
|
||||
if salt.startswith("|1|"):
|
||||
salt = salt.split("|")[2]
|
||||
salt = decodebytes(b(salt))
|
||||
assert len(salt) == sha1().digest_size
|
||||
hmac = HMAC(salt, b(hostname), sha1).digest()
|
||||
hostkey = "|1|{}|{}".format(u(encodebytes(salt)), u(encodebytes(hmac)))
|
||||
return hostkey.replace("\n", "")
|
||||
|
||||
|
||||
class InvalidHostKey(Exception):
|
||||
def __init__(self, line, exc):
|
||||
self.line = line
|
||||
self.exc = exc
|
||||
self.args = (line, exc)
|
||||
|
||||
|
||||
class HostKeyEntry:
|
||||
"""
|
||||
Representation of a line in an OpenSSH-style "known hosts" file.
|
||||
"""
|
||||
|
||||
def __init__(self, hostnames=None, key=None):
|
||||
self.valid = (hostnames is not None) and (key is not None)
|
||||
self.hostnames = hostnames
|
||||
self.key = key
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, line, lineno=None):
|
||||
"""
|
||||
Parses the given line of text to find the names for the host,
|
||||
the type of key, and the key data. The line is expected to be in the
|
||||
format used by the OpenSSH known_hosts file.
|
||||
|
||||
Lines are expected to not have leading or trailing whitespace.
|
||||
We don't bother to check for comments or empty lines. All of
|
||||
that should be taken care of before sending the line to us.
|
||||
|
||||
:param str line: a line from an OpenSSH known_hosts file
|
||||
"""
|
||||
log = get_logger("paramiko.hostkeys")
|
||||
fields = line.split(" ")
|
||||
if len(fields) < 3:
|
||||
# Bad number of fields
|
||||
msg = "Not enough fields found in known_hosts in line {} ({!r})"
|
||||
log.info(msg.format(lineno, line))
|
||||
return None
|
||||
fields = fields[:3]
|
||||
|
||||
names, keytype, key = fields
|
||||
names = names.split(",")
|
||||
|
||||
# Decide what kind of key we're looking at and create an object
|
||||
# to hold it accordingly.
|
||||
try:
|
||||
key = b(key)
|
||||
if keytype == "ssh-rsa":
|
||||
key = RSAKey(data=decodebytes(key))
|
||||
elif keytype == "ssh-dss":
|
||||
key = DSSKey(data=decodebytes(key))
|
||||
elif keytype in ECDSAKey.supported_key_format_identifiers():
|
||||
key = ECDSAKey(data=decodebytes(key), validate_point=False)
|
||||
elif keytype == "ssh-ed25519":
|
||||
key = Ed25519Key(data=decodebytes(key))
|
||||
else:
|
||||
log.info("Unable to handle key of type {}".format(keytype))
|
||||
return None
|
||||
|
||||
except binascii.Error as e:
|
||||
raise InvalidHostKey(line, e)
|
||||
|
||||
return cls(names, key)
|
||||
|
||||
def to_line(self):
|
||||
"""
|
||||
Returns a string in OpenSSH known_hosts file format, or None if
|
||||
the object is not in a valid state. A trailing newline is
|
||||
included.
|
||||
"""
|
||||
if self.valid:
|
||||
return "{} {} {}\n".format(
|
||||
",".join(self.hostnames),
|
||||
self.key.get_name(),
|
||||
self.key.get_base64(),
|
||||
)
|
||||
return None
|
||||
|
||||
def __repr__(self):
|
||||
return "<HostKeyEntry {!r}: {!r}>".format(self.hostnames, self.key)
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/hostkeys.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/hostkeys.pyc
Normal file
Binary file not shown.
128
.ve/lib/python2.7/site-packages/paramiko/kex_ecdh_nist.py
Normal file
128
.ve/lib/python2.7/site-packages/paramiko/kex_ecdh_nist.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""
|
||||
Ephemeral Elliptic Curve Diffie-Hellman (ECDH) key exchange
|
||||
RFC 5656, Section 4
|
||||
"""
|
||||
|
||||
from hashlib import sha256, sha384, sha512
|
||||
from paramiko.message import Message
|
||||
from paramiko.py3compat import byte_chr, long
|
||||
from paramiko.ssh_exception import SSHException
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from binascii import hexlify
|
||||
|
||||
_MSG_KEXECDH_INIT, _MSG_KEXECDH_REPLY = range(30, 32)
|
||||
c_MSG_KEXECDH_INIT, c_MSG_KEXECDH_REPLY = [byte_chr(c) for c in range(30, 32)]
|
||||
|
||||
|
||||
class KexNistp256:
|
||||
|
||||
name = "ecdh-sha2-nistp256"
|
||||
hash_algo = sha256
|
||||
curve = ec.SECP256R1()
|
||||
|
||||
def __init__(self, transport):
|
||||
self.transport = transport
|
||||
# private key, client public and server public keys
|
||||
self.P = long(0)
|
||||
self.Q_C = None
|
||||
self.Q_S = None
|
||||
|
||||
def start_kex(self):
|
||||
self._generate_key_pair()
|
||||
if self.transport.server_mode:
|
||||
self.transport._expect_packet(_MSG_KEXECDH_INIT)
|
||||
return
|
||||
m = Message()
|
||||
m.add_byte(c_MSG_KEXECDH_INIT)
|
||||
# SEC1: V2.0 2.3.3 Elliptic-Curve-Point-to-Octet-String Conversion
|
||||
m.add_string(self.Q_C.public_numbers().encode_point())
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(_MSG_KEXECDH_REPLY)
|
||||
|
||||
def parse_next(self, ptype, m):
|
||||
if self.transport.server_mode and (ptype == _MSG_KEXECDH_INIT):
|
||||
return self._parse_kexecdh_init(m)
|
||||
elif not self.transport.server_mode and (ptype == _MSG_KEXECDH_REPLY):
|
||||
return self._parse_kexecdh_reply(m)
|
||||
raise SSHException(
|
||||
"KexECDH asked to handle packet type {:d}".format(ptype)
|
||||
)
|
||||
|
||||
def _generate_key_pair(self):
|
||||
self.P = ec.generate_private_key(self.curve, default_backend())
|
||||
if self.transport.server_mode:
|
||||
self.Q_S = self.P.public_key()
|
||||
return
|
||||
self.Q_C = self.P.public_key()
|
||||
|
||||
def _parse_kexecdh_init(self, m):
|
||||
Q_C_bytes = m.get_string()
|
||||
self.Q_C = ec.EllipticCurvePublicNumbers.from_encoded_point(
|
||||
self.curve, Q_C_bytes
|
||||
)
|
||||
K_S = self.transport.get_server_key().asbytes()
|
||||
K = self.P.exchange(ec.ECDH(), self.Q_C.public_key(default_backend()))
|
||||
K = long(hexlify(K), 16)
|
||||
# compute exchange hash
|
||||
hm = Message()
|
||||
hm.add(
|
||||
self.transport.remote_version,
|
||||
self.transport.local_version,
|
||||
self.transport.remote_kex_init,
|
||||
self.transport.local_kex_init,
|
||||
)
|
||||
hm.add_string(K_S)
|
||||
hm.add_string(Q_C_bytes)
|
||||
# SEC1: V2.0 2.3.3 Elliptic-Curve-Point-to-Octet-String Conversion
|
||||
hm.add_string(self.Q_S.public_numbers().encode_point())
|
||||
hm.add_mpint(long(K))
|
||||
H = self.hash_algo(hm.asbytes()).digest()
|
||||
self.transport._set_K_H(K, H)
|
||||
sig = self.transport.get_server_key().sign_ssh_data(H)
|
||||
# construct reply
|
||||
m = Message()
|
||||
m.add_byte(c_MSG_KEXECDH_REPLY)
|
||||
m.add_string(K_S)
|
||||
m.add_string(self.Q_S.public_numbers().encode_point())
|
||||
m.add_string(sig)
|
||||
self.transport._send_message(m)
|
||||
self.transport._activate_outbound()
|
||||
|
||||
def _parse_kexecdh_reply(self, m):
|
||||
K_S = m.get_string()
|
||||
Q_S_bytes = m.get_string()
|
||||
self.Q_S = ec.EllipticCurvePublicNumbers.from_encoded_point(
|
||||
self.curve, Q_S_bytes
|
||||
)
|
||||
sig = m.get_binary()
|
||||
K = self.P.exchange(ec.ECDH(), self.Q_S.public_key(default_backend()))
|
||||
K = long(hexlify(K), 16)
|
||||
# compute exchange hash and verify signature
|
||||
hm = Message()
|
||||
hm.add(
|
||||
self.transport.local_version,
|
||||
self.transport.remote_version,
|
||||
self.transport.local_kex_init,
|
||||
self.transport.remote_kex_init,
|
||||
)
|
||||
hm.add_string(K_S)
|
||||
# SEC1: V2.0 2.3.3 Elliptic-Curve-Point-to-Octet-String Conversion
|
||||
hm.add_string(self.Q_C.public_numbers().encode_point())
|
||||
hm.add_string(Q_S_bytes)
|
||||
hm.add_mpint(K)
|
||||
self.transport._set_K_H(K, self.hash_algo(hm.asbytes()).digest())
|
||||
self.transport._verify_key(K_S, sig)
|
||||
self.transport._activate_outbound()
|
||||
|
||||
|
||||
class KexNistp384(KexNistp256):
|
||||
name = "ecdh-sha2-nistp384"
|
||||
hash_algo = sha384
|
||||
curve = ec.SECP384R1()
|
||||
|
||||
|
||||
class KexNistp521(KexNistp256):
|
||||
name = "ecdh-sha2-nistp521"
|
||||
hash_algo = sha512
|
||||
curve = ec.SECP521R1()
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/kex_ecdh_nist.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/kex_ecdh_nist.pyc
Normal file
Binary file not shown.
287
.ve/lib/python2.7/site-packages/paramiko/kex_gex.py
Normal file
287
.ve/lib/python2.7/site-packages/paramiko/kex_gex.py
Normal file
@@ -0,0 +1,287 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Variant on `KexGroup1 <paramiko.kex_group1.KexGroup1>` where the prime "p" and
|
||||
generator "g" are provided by the server. A bit more work is required on the
|
||||
client side, and a **lot** more on the server side.
|
||||
"""
|
||||
|
||||
import os
|
||||
from hashlib import sha1, sha256
|
||||
|
||||
from paramiko import util
|
||||
from paramiko.common import DEBUG
|
||||
from paramiko.message import Message
|
||||
from paramiko.py3compat import byte_chr, byte_ord, byte_mask
|
||||
from paramiko.ssh_exception import SSHException
|
||||
|
||||
|
||||
(
|
||||
_MSG_KEXDH_GEX_REQUEST_OLD,
|
||||
_MSG_KEXDH_GEX_GROUP,
|
||||
_MSG_KEXDH_GEX_INIT,
|
||||
_MSG_KEXDH_GEX_REPLY,
|
||||
_MSG_KEXDH_GEX_REQUEST,
|
||||
) = range(30, 35)
|
||||
|
||||
(
|
||||
c_MSG_KEXDH_GEX_REQUEST_OLD,
|
||||
c_MSG_KEXDH_GEX_GROUP,
|
||||
c_MSG_KEXDH_GEX_INIT,
|
||||
c_MSG_KEXDH_GEX_REPLY,
|
||||
c_MSG_KEXDH_GEX_REQUEST,
|
||||
) = [byte_chr(c) for c in range(30, 35)]
|
||||
|
||||
|
||||
class KexGex(object):
|
||||
|
||||
name = "diffie-hellman-group-exchange-sha1"
|
||||
min_bits = 1024
|
||||
max_bits = 8192
|
||||
preferred_bits = 2048
|
||||
hash_algo = sha1
|
||||
|
||||
def __init__(self, transport):
|
||||
self.transport = transport
|
||||
self.p = None
|
||||
self.q = None
|
||||
self.g = None
|
||||
self.x = None
|
||||
self.e = None
|
||||
self.f = None
|
||||
self.old_style = False
|
||||
|
||||
def start_kex(self, _test_old_style=False):
|
||||
if self.transport.server_mode:
|
||||
self.transport._expect_packet(
|
||||
_MSG_KEXDH_GEX_REQUEST, _MSG_KEXDH_GEX_REQUEST_OLD
|
||||
)
|
||||
return
|
||||
# request a bit range: we accept (min_bits) to (max_bits), but prefer
|
||||
# (preferred_bits). according to the spec, we shouldn't pull the
|
||||
# minimum up above 1024.
|
||||
m = Message()
|
||||
if _test_old_style:
|
||||
# only used for unit tests: we shouldn't ever send this
|
||||
m.add_byte(c_MSG_KEXDH_GEX_REQUEST_OLD)
|
||||
m.add_int(self.preferred_bits)
|
||||
self.old_style = True
|
||||
else:
|
||||
m.add_byte(c_MSG_KEXDH_GEX_REQUEST)
|
||||
m.add_int(self.min_bits)
|
||||
m.add_int(self.preferred_bits)
|
||||
m.add_int(self.max_bits)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(_MSG_KEXDH_GEX_GROUP)
|
||||
|
||||
def parse_next(self, ptype, m):
|
||||
if ptype == _MSG_KEXDH_GEX_REQUEST:
|
||||
return self._parse_kexdh_gex_request(m)
|
||||
elif ptype == _MSG_KEXDH_GEX_GROUP:
|
||||
return self._parse_kexdh_gex_group(m)
|
||||
elif ptype == _MSG_KEXDH_GEX_INIT:
|
||||
return self._parse_kexdh_gex_init(m)
|
||||
elif ptype == _MSG_KEXDH_GEX_REPLY:
|
||||
return self._parse_kexdh_gex_reply(m)
|
||||
elif ptype == _MSG_KEXDH_GEX_REQUEST_OLD:
|
||||
return self._parse_kexdh_gex_request_old(m)
|
||||
msg = "KexGex {} asked to handle packet type {:d}"
|
||||
raise SSHException(msg.format(self.name, ptype))
|
||||
|
||||
# ...internals...
|
||||
|
||||
def _generate_x(self):
|
||||
# generate an "x" (1 < x < (p-1)/2).
|
||||
q = (self.p - 1) // 2
|
||||
qnorm = util.deflate_long(q, 0)
|
||||
qhbyte = byte_ord(qnorm[0])
|
||||
byte_count = len(qnorm)
|
||||
qmask = 0xff
|
||||
while not (qhbyte & 0x80):
|
||||
qhbyte <<= 1
|
||||
qmask >>= 1
|
||||
while True:
|
||||
x_bytes = os.urandom(byte_count)
|
||||
x_bytes = byte_mask(x_bytes[0], qmask) + x_bytes[1:]
|
||||
x = util.inflate_long(x_bytes, 1)
|
||||
if (x > 1) and (x < q):
|
||||
break
|
||||
self.x = x
|
||||
|
||||
def _parse_kexdh_gex_request(self, m):
|
||||
minbits = m.get_int()
|
||||
preferredbits = m.get_int()
|
||||
maxbits = m.get_int()
|
||||
# smoosh the user's preferred size into our own limits
|
||||
if preferredbits > self.max_bits:
|
||||
preferredbits = self.max_bits
|
||||
if preferredbits < self.min_bits:
|
||||
preferredbits = self.min_bits
|
||||
# fix min/max if they're inconsistent. technically, we could just pout
|
||||
# and hang up, but there's no harm in giving them the benefit of the
|
||||
# doubt and just picking a bitsize for them.
|
||||
if minbits > preferredbits:
|
||||
minbits = preferredbits
|
||||
if maxbits < preferredbits:
|
||||
maxbits = preferredbits
|
||||
# now save a copy
|
||||
self.min_bits = minbits
|
||||
self.preferred_bits = preferredbits
|
||||
self.max_bits = maxbits
|
||||
# generate prime
|
||||
pack = self.transport._get_modulus_pack()
|
||||
if pack is None:
|
||||
raise SSHException("Can't do server-side gex with no modulus pack")
|
||||
self.transport._log(
|
||||
DEBUG,
|
||||
"Picking p ({} <= {} <= {} bits)".format(
|
||||
minbits, preferredbits, maxbits
|
||||
),
|
||||
)
|
||||
self.g, self.p = pack.get_modulus(minbits, preferredbits, maxbits)
|
||||
m = Message()
|
||||
m.add_byte(c_MSG_KEXDH_GEX_GROUP)
|
||||
m.add_mpint(self.p)
|
||||
m.add_mpint(self.g)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(_MSG_KEXDH_GEX_INIT)
|
||||
|
||||
def _parse_kexdh_gex_request_old(self, m):
|
||||
# same as above, but without min_bits or max_bits (used by older
|
||||
# clients like putty)
|
||||
self.preferred_bits = m.get_int()
|
||||
# smoosh the user's preferred size into our own limits
|
||||
if self.preferred_bits > self.max_bits:
|
||||
self.preferred_bits = self.max_bits
|
||||
if self.preferred_bits < self.min_bits:
|
||||
self.preferred_bits = self.min_bits
|
||||
# generate prime
|
||||
pack = self.transport._get_modulus_pack()
|
||||
if pack is None:
|
||||
raise SSHException("Can't do server-side gex with no modulus pack")
|
||||
self.transport._log(
|
||||
DEBUG, "Picking p (~ {} bits)".format(self.preferred_bits)
|
||||
)
|
||||
self.g, self.p = pack.get_modulus(
|
||||
self.min_bits, self.preferred_bits, self.max_bits
|
||||
)
|
||||
m = Message()
|
||||
m.add_byte(c_MSG_KEXDH_GEX_GROUP)
|
||||
m.add_mpint(self.p)
|
||||
m.add_mpint(self.g)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(_MSG_KEXDH_GEX_INIT)
|
||||
self.old_style = True
|
||||
|
||||
def _parse_kexdh_gex_group(self, m):
|
||||
self.p = m.get_mpint()
|
||||
self.g = m.get_mpint()
|
||||
# reject if p's bit length < 1024 or > 8192
|
||||
bitlen = util.bit_length(self.p)
|
||||
if (bitlen < 1024) or (bitlen > 8192):
|
||||
raise SSHException(
|
||||
"Server-generated gex p (don't ask) is out of range "
|
||||
"({} bits)".format(bitlen)
|
||||
)
|
||||
self.transport._log(DEBUG, "Got server p ({} bits)".format(bitlen))
|
||||
self._generate_x()
|
||||
# now compute e = g^x mod p
|
||||
self.e = pow(self.g, self.x, self.p)
|
||||
m = Message()
|
||||
m.add_byte(c_MSG_KEXDH_GEX_INIT)
|
||||
m.add_mpint(self.e)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(_MSG_KEXDH_GEX_REPLY)
|
||||
|
||||
def _parse_kexdh_gex_init(self, m):
|
||||
self.e = m.get_mpint()
|
||||
if (self.e < 1) or (self.e > self.p - 1):
|
||||
raise SSHException('Client kex "e" is out of range')
|
||||
self._generate_x()
|
||||
self.f = pow(self.g, self.x, self.p)
|
||||
K = pow(self.e, self.x, self.p)
|
||||
key = self.transport.get_server_key().asbytes()
|
||||
# okay, build up the hash H of
|
||||
# (V_C || V_S || I_C || I_S || K_S || min || n || max || p || g || e || f || K) # noqa
|
||||
hm = Message()
|
||||
hm.add(
|
||||
self.transport.remote_version,
|
||||
self.transport.local_version,
|
||||
self.transport.remote_kex_init,
|
||||
self.transport.local_kex_init,
|
||||
key,
|
||||
)
|
||||
if not self.old_style:
|
||||
hm.add_int(self.min_bits)
|
||||
hm.add_int(self.preferred_bits)
|
||||
if not self.old_style:
|
||||
hm.add_int(self.max_bits)
|
||||
hm.add_mpint(self.p)
|
||||
hm.add_mpint(self.g)
|
||||
hm.add_mpint(self.e)
|
||||
hm.add_mpint(self.f)
|
||||
hm.add_mpint(K)
|
||||
H = self.hash_algo(hm.asbytes()).digest()
|
||||
self.transport._set_K_H(K, H)
|
||||
# sign it
|
||||
sig = self.transport.get_server_key().sign_ssh_data(H)
|
||||
# send reply
|
||||
m = Message()
|
||||
m.add_byte(c_MSG_KEXDH_GEX_REPLY)
|
||||
m.add_string(key)
|
||||
m.add_mpint(self.f)
|
||||
m.add_string(sig)
|
||||
self.transport._send_message(m)
|
||||
self.transport._activate_outbound()
|
||||
|
||||
def _parse_kexdh_gex_reply(self, m):
|
||||
host_key = m.get_string()
|
||||
self.f = m.get_mpint()
|
||||
sig = m.get_string()
|
||||
if (self.f < 1) or (self.f > self.p - 1):
|
||||
raise SSHException('Server kex "f" is out of range')
|
||||
K = pow(self.f, self.x, self.p)
|
||||
# okay, build up the hash H of
|
||||
# (V_C || V_S || I_C || I_S || K_S || min || n || max || p || g || e || f || K) # noqa
|
||||
hm = Message()
|
||||
hm.add(
|
||||
self.transport.local_version,
|
||||
self.transport.remote_version,
|
||||
self.transport.local_kex_init,
|
||||
self.transport.remote_kex_init,
|
||||
host_key,
|
||||
)
|
||||
if not self.old_style:
|
||||
hm.add_int(self.min_bits)
|
||||
hm.add_int(self.preferred_bits)
|
||||
if not self.old_style:
|
||||
hm.add_int(self.max_bits)
|
||||
hm.add_mpint(self.p)
|
||||
hm.add_mpint(self.g)
|
||||
hm.add_mpint(self.e)
|
||||
hm.add_mpint(self.f)
|
||||
hm.add_mpint(K)
|
||||
self.transport._set_K_H(K, self.hash_algo(hm.asbytes()).digest())
|
||||
self.transport._verify_key(host_key, sig)
|
||||
self.transport._activate_outbound()
|
||||
|
||||
|
||||
class KexGexSHA256(KexGex):
|
||||
name = "diffie-hellman-group-exchange-sha256"
|
||||
hash_algo = sha256
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/kex_gex.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/kex_gex.pyc
Normal file
Binary file not shown.
154
.ve/lib/python2.7/site-packages/paramiko/kex_group1.py
Normal file
154
.ve/lib/python2.7/site-packages/paramiko/kex_group1.py
Normal file
@@ -0,0 +1,154 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of
|
||||
1024 bit key halves, using a known "p" prime and "g" generator.
|
||||
"""
|
||||
|
||||
import os
|
||||
from hashlib import sha1
|
||||
|
||||
from paramiko import util
|
||||
from paramiko.common import max_byte, zero_byte
|
||||
from paramiko.message import Message
|
||||
from paramiko.py3compat import byte_chr, long, byte_mask
|
||||
from paramiko.ssh_exception import SSHException
|
||||
|
||||
|
||||
_MSG_KEXDH_INIT, _MSG_KEXDH_REPLY = range(30, 32)
|
||||
c_MSG_KEXDH_INIT, c_MSG_KEXDH_REPLY = [byte_chr(c) for c in range(30, 32)]
|
||||
|
||||
b7fffffffffffffff = byte_chr(0x7f) + max_byte * 7
|
||||
b0000000000000000 = zero_byte * 8
|
||||
|
||||
|
||||
class KexGroup1(object):
|
||||
|
||||
# draft-ietf-secsh-transport-09.txt, page 17
|
||||
P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF # noqa
|
||||
G = 2
|
||||
|
||||
name = "diffie-hellman-group1-sha1"
|
||||
hash_algo = sha1
|
||||
|
||||
def __init__(self, transport):
|
||||
self.transport = transport
|
||||
self.x = long(0)
|
||||
self.e = long(0)
|
||||
self.f = long(0)
|
||||
|
||||
def start_kex(self):
|
||||
self._generate_x()
|
||||
if self.transport.server_mode:
|
||||
# compute f = g^x mod p, but don't send it yet
|
||||
self.f = pow(self.G, self.x, self.P)
|
||||
self.transport._expect_packet(_MSG_KEXDH_INIT)
|
||||
return
|
||||
# compute e = g^x mod p (where g=2), and send it
|
||||
self.e = pow(self.G, self.x, self.P)
|
||||
m = Message()
|
||||
m.add_byte(c_MSG_KEXDH_INIT)
|
||||
m.add_mpint(self.e)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(_MSG_KEXDH_REPLY)
|
||||
|
||||
def parse_next(self, ptype, m):
|
||||
if self.transport.server_mode and (ptype == _MSG_KEXDH_INIT):
|
||||
return self._parse_kexdh_init(m)
|
||||
elif not self.transport.server_mode and (ptype == _MSG_KEXDH_REPLY):
|
||||
return self._parse_kexdh_reply(m)
|
||||
msg = "KexGroup1 asked to handle packet type {:d}"
|
||||
raise SSHException(msg.format(ptype))
|
||||
|
||||
# ...internals...
|
||||
|
||||
def _generate_x(self):
|
||||
# generate an "x" (1 < x < q), where q is (p-1)/2.
|
||||
# p is a 128-byte (1024-bit) number, where the first 64 bits are 1.
|
||||
# therefore q can be approximated as a 2^1023. we drop the subset of
|
||||
# potential x where the first 63 bits are 1, because some of those
|
||||
# will be larger than q (but this is a tiny tiny subset of
|
||||
# potential x).
|
||||
while 1:
|
||||
x_bytes = os.urandom(128)
|
||||
x_bytes = byte_mask(x_bytes[0], 0x7f) + x_bytes[1:]
|
||||
if (
|
||||
x_bytes[:8] != b7fffffffffffffff
|
||||
and x_bytes[:8] != b0000000000000000
|
||||
):
|
||||
break
|
||||
self.x = util.inflate_long(x_bytes)
|
||||
|
||||
def _parse_kexdh_reply(self, m):
|
||||
# client mode
|
||||
host_key = m.get_string()
|
||||
self.f = m.get_mpint()
|
||||
if (self.f < 1) or (self.f > self.P - 1):
|
||||
raise SSHException('Server kex "f" is out of range')
|
||||
sig = m.get_binary()
|
||||
K = pow(self.f, self.x, self.P)
|
||||
# okay, build up the hash H of
|
||||
# (V_C || V_S || I_C || I_S || K_S || e || f || K)
|
||||
hm = Message()
|
||||
hm.add(
|
||||
self.transport.local_version,
|
||||
self.transport.remote_version,
|
||||
self.transport.local_kex_init,
|
||||
self.transport.remote_kex_init,
|
||||
)
|
||||
hm.add_string(host_key)
|
||||
hm.add_mpint(self.e)
|
||||
hm.add_mpint(self.f)
|
||||
hm.add_mpint(K)
|
||||
self.transport._set_K_H(K, sha1(hm.asbytes()).digest())
|
||||
self.transport._verify_key(host_key, sig)
|
||||
self.transport._activate_outbound()
|
||||
|
||||
def _parse_kexdh_init(self, m):
|
||||
# server mode
|
||||
self.e = m.get_mpint()
|
||||
if (self.e < 1) or (self.e > self.P - 1):
|
||||
raise SSHException('Client kex "e" is out of range')
|
||||
K = pow(self.e, self.x, self.P)
|
||||
key = self.transport.get_server_key().asbytes()
|
||||
# okay, build up the hash H of
|
||||
# (V_C || V_S || I_C || I_S || K_S || e || f || K)
|
||||
hm = Message()
|
||||
hm.add(
|
||||
self.transport.remote_version,
|
||||
self.transport.local_version,
|
||||
self.transport.remote_kex_init,
|
||||
self.transport.local_kex_init,
|
||||
)
|
||||
hm.add_string(key)
|
||||
hm.add_mpint(self.e)
|
||||
hm.add_mpint(self.f)
|
||||
hm.add_mpint(K)
|
||||
H = sha1(hm.asbytes()).digest()
|
||||
self.transport._set_K_H(K, H)
|
||||
# sign it
|
||||
sig = self.transport.get_server_key().sign_ssh_data(H)
|
||||
# send reply
|
||||
m = Message()
|
||||
m.add_byte(c_MSG_KEXDH_REPLY)
|
||||
m.add_string(key)
|
||||
m.add_mpint(self.f)
|
||||
m.add_string(sig)
|
||||
self.transport._send_message(m)
|
||||
self.transport._activate_outbound()
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/kex_group1.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/kex_group1.pyc
Normal file
Binary file not shown.
35
.ve/lib/python2.7/site-packages/paramiko/kex_group14.py
Normal file
35
.ve/lib/python2.7/site-packages/paramiko/kex_group14.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# Copyright (C) 2013 Torsten Landschoff <torsten@debian.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 distrubuted 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of
|
||||
2048 bit key halves, using a known "p" prime and "g" generator.
|
||||
"""
|
||||
|
||||
from paramiko.kex_group1 import KexGroup1
|
||||
from hashlib import sha1
|
||||
|
||||
|
||||
class KexGroup14(KexGroup1):
|
||||
|
||||
# http://tools.ietf.org/html/rfc3526#section-3
|
||||
P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF # noqa
|
||||
G = 2
|
||||
|
||||
name = "diffie-hellman-group14-sha1"
|
||||
hash_algo = sha1
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/kex_group14.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/kex_group14.pyc
Normal file
Binary file not shown.
680
.ve/lib/python2.7/site-packages/paramiko/kex_gss.py
Normal file
680
.ve/lib/python2.7/site-packages/paramiko/kex_gss.py
Normal file
@@ -0,0 +1,680 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright (C) 2013-2014 science + computing ag
|
||||
# Author: Sebastian Deiss <sebastian.deiss@t-online.de>
|
||||
#
|
||||
#
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
|
||||
"""
|
||||
This module provides GSS-API / SSPI Key Exchange as defined in :rfc:`4462`.
|
||||
|
||||
.. note:: Credential delegation is not supported in server mode.
|
||||
|
||||
.. note::
|
||||
`RFC 4462 Section 2.2
|
||||
<https://tools.ietf.org/html/rfc4462.html#section-2.2>`_ says we are not
|
||||
required to implement GSS-API error messages. Thus, in many methods within
|
||||
this module, if an error occurs an exception will be thrown and the
|
||||
connection will be terminated.
|
||||
|
||||
.. seealso:: :doc:`/api/ssh_gss`
|
||||
|
||||
.. versionadded:: 1.15
|
||||
"""
|
||||
|
||||
import os
|
||||
from hashlib import sha1
|
||||
|
||||
from paramiko.common import DEBUG, max_byte, zero_byte
|
||||
from paramiko import util
|
||||
from paramiko.message import Message
|
||||
from paramiko.py3compat import byte_chr, byte_mask, byte_ord
|
||||
from paramiko.ssh_exception import SSHException
|
||||
|
||||
|
||||
(
|
||||
MSG_KEXGSS_INIT,
|
||||
MSG_KEXGSS_CONTINUE,
|
||||
MSG_KEXGSS_COMPLETE,
|
||||
MSG_KEXGSS_HOSTKEY,
|
||||
MSG_KEXGSS_ERROR,
|
||||
) = range(30, 35)
|
||||
(MSG_KEXGSS_GROUPREQ, MSG_KEXGSS_GROUP) = range(40, 42)
|
||||
(
|
||||
c_MSG_KEXGSS_INIT,
|
||||
c_MSG_KEXGSS_CONTINUE,
|
||||
c_MSG_KEXGSS_COMPLETE,
|
||||
c_MSG_KEXGSS_HOSTKEY,
|
||||
c_MSG_KEXGSS_ERROR,
|
||||
) = [byte_chr(c) for c in range(30, 35)]
|
||||
(c_MSG_KEXGSS_GROUPREQ, c_MSG_KEXGSS_GROUP) = [
|
||||
byte_chr(c) for c in range(40, 42)
|
||||
]
|
||||
|
||||
|
||||
class KexGSSGroup1(object):
|
||||
"""
|
||||
GSS-API / SSPI Authenticated Diffie-Hellman Key Exchange as defined in `RFC
|
||||
4462 Section 2 <https://tools.ietf.org/html/rfc4462.html#section-2>`_
|
||||
"""
|
||||
|
||||
# draft-ietf-secsh-transport-09.txt, page 17
|
||||
P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF # noqa
|
||||
G = 2
|
||||
b7fffffffffffffff = byte_chr(0x7f) + max_byte * 7 # noqa
|
||||
b0000000000000000 = zero_byte * 8 # noqa
|
||||
NAME = "gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g=="
|
||||
|
||||
def __init__(self, transport):
|
||||
self.transport = transport
|
||||
self.kexgss = self.transport.kexgss_ctxt
|
||||
self.gss_host = None
|
||||
self.x = 0
|
||||
self.e = 0
|
||||
self.f = 0
|
||||
|
||||
def start_kex(self):
|
||||
"""
|
||||
Start the GSS-API / SSPI Authenticated Diffie-Hellman Key Exchange.
|
||||
"""
|
||||
self._generate_x()
|
||||
if self.transport.server_mode:
|
||||
# compute f = g^x mod p, but don't send it yet
|
||||
self.f = pow(self.G, self.x, self.P)
|
||||
self.transport._expect_packet(MSG_KEXGSS_INIT)
|
||||
return
|
||||
# compute e = g^x mod p (where g=2), and send it
|
||||
self.e = pow(self.G, self.x, self.P)
|
||||
# Initialize GSS-API Key Exchange
|
||||
self.gss_host = self.transport.gss_host
|
||||
m = Message()
|
||||
m.add_byte(c_MSG_KEXGSS_INIT)
|
||||
m.add_string(self.kexgss.ssh_init_sec_context(target=self.gss_host))
|
||||
m.add_mpint(self.e)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(
|
||||
MSG_KEXGSS_HOSTKEY,
|
||||
MSG_KEXGSS_CONTINUE,
|
||||
MSG_KEXGSS_COMPLETE,
|
||||
MSG_KEXGSS_ERROR,
|
||||
)
|
||||
|
||||
def parse_next(self, ptype, m):
|
||||
"""
|
||||
Parse the next packet.
|
||||
|
||||
:param ptype: The (string) type of the incoming packet
|
||||
:param `.Message` m: The paket content
|
||||
"""
|
||||
if self.transport.server_mode and (ptype == MSG_KEXGSS_INIT):
|
||||
return self._parse_kexgss_init(m)
|
||||
elif not self.transport.server_mode and (ptype == MSG_KEXGSS_HOSTKEY):
|
||||
return self._parse_kexgss_hostkey(m)
|
||||
elif self.transport.server_mode and (ptype == MSG_KEXGSS_CONTINUE):
|
||||
return self._parse_kexgss_continue(m)
|
||||
elif not self.transport.server_mode and (ptype == MSG_KEXGSS_COMPLETE):
|
||||
return self._parse_kexgss_complete(m)
|
||||
elif ptype == MSG_KEXGSS_ERROR:
|
||||
return self._parse_kexgss_error(m)
|
||||
msg = "GSS KexGroup1 asked to handle packet type {:d}"
|
||||
raise SSHException(msg.format(ptype))
|
||||
|
||||
# ## internals...
|
||||
|
||||
def _generate_x(self):
|
||||
"""
|
||||
generate an "x" (1 < x < q), where q is (p-1)/2.
|
||||
p is a 128-byte (1024-bit) number, where the first 64 bits are 1.
|
||||
therefore q can be approximated as a 2^1023. we drop the subset of
|
||||
potential x where the first 63 bits are 1, because some of those will
|
||||
be larger than q (but this is a tiny tiny subset of potential x).
|
||||
"""
|
||||
while 1:
|
||||
x_bytes = os.urandom(128)
|
||||
x_bytes = byte_mask(x_bytes[0], 0x7f) + x_bytes[1:]
|
||||
first = x_bytes[:8]
|
||||
if first not in (self.b7fffffffffffffff, self.b0000000000000000):
|
||||
break
|
||||
self.x = util.inflate_long(x_bytes)
|
||||
|
||||
def _parse_kexgss_hostkey(self, m):
|
||||
"""
|
||||
Parse the SSH2_MSG_KEXGSS_HOSTKEY message (client mode).
|
||||
|
||||
:param `.Message` m: The content of the SSH2_MSG_KEXGSS_HOSTKEY message
|
||||
"""
|
||||
# client mode
|
||||
host_key = m.get_string()
|
||||
self.transport.host_key = host_key
|
||||
sig = m.get_string()
|
||||
self.transport._verify_key(host_key, sig)
|
||||
self.transport._expect_packet(MSG_KEXGSS_CONTINUE, MSG_KEXGSS_COMPLETE)
|
||||
|
||||
def _parse_kexgss_continue(self, m):
|
||||
"""
|
||||
Parse the SSH2_MSG_KEXGSS_CONTINUE message.
|
||||
|
||||
:param `.Message` m: The content of the SSH2_MSG_KEXGSS_CONTINUE
|
||||
message
|
||||
"""
|
||||
if not self.transport.server_mode:
|
||||
srv_token = m.get_string()
|
||||
m = Message()
|
||||
m.add_byte(c_MSG_KEXGSS_CONTINUE)
|
||||
m.add_string(
|
||||
self.kexgss.ssh_init_sec_context(
|
||||
target=self.gss_host, recv_token=srv_token
|
||||
)
|
||||
)
|
||||
self.transport.send_message(m)
|
||||
self.transport._expect_packet(
|
||||
MSG_KEXGSS_CONTINUE, MSG_KEXGSS_COMPLETE, MSG_KEXGSS_ERROR
|
||||
)
|
||||
else:
|
||||
pass
|
||||
|
||||
def _parse_kexgss_complete(self, m):
|
||||
"""
|
||||
Parse the SSH2_MSG_KEXGSS_COMPLETE message (client mode).
|
||||
|
||||
:param `.Message` m: The content of the
|
||||
SSH2_MSG_KEXGSS_COMPLETE message
|
||||
"""
|
||||
# client mode
|
||||
if self.transport.host_key is None:
|
||||
self.transport.host_key = NullHostKey()
|
||||
self.f = m.get_mpint()
|
||||
if (self.f < 1) or (self.f > self.P - 1):
|
||||
raise SSHException('Server kex "f" is out of range')
|
||||
mic_token = m.get_string()
|
||||
# This must be TRUE, if there is a GSS-API token in this message.
|
||||
bool = m.get_boolean()
|
||||
srv_token = None
|
||||
if bool:
|
||||
srv_token = m.get_string()
|
||||
K = pow(self.f, self.x, self.P)
|
||||
# okay, build up the hash H of
|
||||
# (V_C || V_S || I_C || I_S || K_S || e || f || K)
|
||||
hm = Message()
|
||||
hm.add(
|
||||
self.transport.local_version,
|
||||
self.transport.remote_version,
|
||||
self.transport.local_kex_init,
|
||||
self.transport.remote_kex_init,
|
||||
)
|
||||
hm.add_string(self.transport.host_key.__str__())
|
||||
hm.add_mpint(self.e)
|
||||
hm.add_mpint(self.f)
|
||||
hm.add_mpint(K)
|
||||
H = sha1(str(hm)).digest()
|
||||
self.transport._set_K_H(K, H)
|
||||
if srv_token is not None:
|
||||
self.kexgss.ssh_init_sec_context(
|
||||
target=self.gss_host, recv_token=srv_token
|
||||
)
|
||||
self.kexgss.ssh_check_mic(mic_token, H)
|
||||
else:
|
||||
self.kexgss.ssh_check_mic(mic_token, H)
|
||||
self.transport.gss_kex_used = True
|
||||
self.transport._activate_outbound()
|
||||
|
||||
def _parse_kexgss_init(self, m):
|
||||
"""
|
||||
Parse the SSH2_MSG_KEXGSS_INIT message (server mode).
|
||||
|
||||
:param `.Message` m: The content of the SSH2_MSG_KEXGSS_INIT message
|
||||
"""
|
||||
# server mode
|
||||
client_token = m.get_string()
|
||||
self.e = m.get_mpint()
|
||||
if (self.e < 1) or (self.e > self.P - 1):
|
||||
raise SSHException('Client kex "e" is out of range')
|
||||
K = pow(self.e, self.x, self.P)
|
||||
self.transport.host_key = NullHostKey()
|
||||
key = self.transport.host_key.__str__()
|
||||
# okay, build up the hash H of
|
||||
# (V_C || V_S || I_C || I_S || K_S || e || f || K)
|
||||
hm = Message()
|
||||
hm.add(
|
||||
self.transport.remote_version,
|
||||
self.transport.local_version,
|
||||
self.transport.remote_kex_init,
|
||||
self.transport.local_kex_init,
|
||||
)
|
||||
hm.add_string(key)
|
||||
hm.add_mpint(self.e)
|
||||
hm.add_mpint(self.f)
|
||||
hm.add_mpint(K)
|
||||
H = sha1(hm.asbytes()).digest()
|
||||
self.transport._set_K_H(K, H)
|
||||
srv_token = self.kexgss.ssh_accept_sec_context(
|
||||
self.gss_host, client_token
|
||||
)
|
||||
m = Message()
|
||||
if self.kexgss._gss_srv_ctxt_status:
|
||||
mic_token = self.kexgss.ssh_get_mic(
|
||||
self.transport.session_id, gss_kex=True
|
||||
)
|
||||
m.add_byte(c_MSG_KEXGSS_COMPLETE)
|
||||
m.add_mpint(self.f)
|
||||
m.add_string(mic_token)
|
||||
if srv_token is not None:
|
||||
m.add_boolean(True)
|
||||
m.add_string(srv_token)
|
||||
else:
|
||||
m.add_boolean(False)
|
||||
self.transport._send_message(m)
|
||||
self.transport.gss_kex_used = True
|
||||
self.transport._activate_outbound()
|
||||
else:
|
||||
m.add_byte(c_MSG_KEXGSS_CONTINUE)
|
||||
m.add_string(srv_token)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(
|
||||
MSG_KEXGSS_CONTINUE, MSG_KEXGSS_COMPLETE, MSG_KEXGSS_ERROR
|
||||
)
|
||||
|
||||
def _parse_kexgss_error(self, m):
|
||||
"""
|
||||
Parse the SSH2_MSG_KEXGSS_ERROR message (client mode).
|
||||
The server may send a GSS-API error message. if it does, we display
|
||||
the error by throwing an exception (client mode).
|
||||
|
||||
:param `.Message` m: The content of the SSH2_MSG_KEXGSS_ERROR message
|
||||
:raise SSHException: Contains GSS-API major and minor status as well as
|
||||
the error message and the language tag of the
|
||||
message
|
||||
"""
|
||||
maj_status = m.get_int()
|
||||
min_status = m.get_int()
|
||||
err_msg = m.get_string()
|
||||
m.get_string() # we don't care about the language!
|
||||
raise SSHException(
|
||||
"""GSS-API Error:
|
||||
Major Status: {}
|
||||
Minor Status: {}
|
||||
Error Message: {}
|
||||
""".format(
|
||||
maj_status, min_status, err_msg
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class KexGSSGroup14(KexGSSGroup1):
|
||||
"""
|
||||
GSS-API / SSPI Authenticated Diffie-Hellman Group14 Key Exchange as defined
|
||||
in `RFC 4462 Section 2
|
||||
<https://tools.ietf.org/html/rfc4462.html#section-2>`_
|
||||
"""
|
||||
|
||||
P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF # noqa
|
||||
G = 2
|
||||
NAME = "gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g=="
|
||||
|
||||
|
||||
class KexGSSGex(object):
|
||||
"""
|
||||
GSS-API / SSPI Authenticated Diffie-Hellman Group Exchange as defined in
|
||||
`RFC 4462 Section 2 <https://tools.ietf.org/html/rfc4462.html#section-2>`_
|
||||
"""
|
||||
|
||||
NAME = "gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g=="
|
||||
min_bits = 1024
|
||||
max_bits = 8192
|
||||
preferred_bits = 2048
|
||||
|
||||
def __init__(self, transport):
|
||||
self.transport = transport
|
||||
self.kexgss = self.transport.kexgss_ctxt
|
||||
self.gss_host = None
|
||||
self.p = None
|
||||
self.q = None
|
||||
self.g = None
|
||||
self.x = None
|
||||
self.e = None
|
||||
self.f = None
|
||||
self.old_style = False
|
||||
|
||||
def start_kex(self):
|
||||
"""
|
||||
Start the GSS-API / SSPI Authenticated Diffie-Hellman Group Exchange
|
||||
"""
|
||||
if self.transport.server_mode:
|
||||
self.transport._expect_packet(MSG_KEXGSS_GROUPREQ)
|
||||
return
|
||||
# request a bit range: we accept (min_bits) to (max_bits), but prefer
|
||||
# (preferred_bits). according to the spec, we shouldn't pull the
|
||||
# minimum up above 1024.
|
||||
self.gss_host = self.transport.gss_host
|
||||
m = Message()
|
||||
m.add_byte(c_MSG_KEXGSS_GROUPREQ)
|
||||
m.add_int(self.min_bits)
|
||||
m.add_int(self.preferred_bits)
|
||||
m.add_int(self.max_bits)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(MSG_KEXGSS_GROUP)
|
||||
|
||||
def parse_next(self, ptype, m):
|
||||
"""
|
||||
Parse the next packet.
|
||||
|
||||
:param ptype: The (string) type of the incoming packet
|
||||
:param `.Message` m: The paket content
|
||||
"""
|
||||
if ptype == MSG_KEXGSS_GROUPREQ:
|
||||
return self._parse_kexgss_groupreq(m)
|
||||
elif ptype == MSG_KEXGSS_GROUP:
|
||||
return self._parse_kexgss_group(m)
|
||||
elif ptype == MSG_KEXGSS_INIT:
|
||||
return self._parse_kexgss_gex_init(m)
|
||||
elif ptype == MSG_KEXGSS_HOSTKEY:
|
||||
return self._parse_kexgss_hostkey(m)
|
||||
elif ptype == MSG_KEXGSS_CONTINUE:
|
||||
return self._parse_kexgss_continue(m)
|
||||
elif ptype == MSG_KEXGSS_COMPLETE:
|
||||
return self._parse_kexgss_complete(m)
|
||||
elif ptype == MSG_KEXGSS_ERROR:
|
||||
return self._parse_kexgss_error(m)
|
||||
msg = "KexGex asked to handle packet type {:d}"
|
||||
raise SSHException(msg.format(ptype))
|
||||
|
||||
# ## internals...
|
||||
|
||||
def _generate_x(self):
|
||||
# generate an "x" (1 < x < (p-1)/2).
|
||||
q = (self.p - 1) // 2
|
||||
qnorm = util.deflate_long(q, 0)
|
||||
qhbyte = byte_ord(qnorm[0])
|
||||
byte_count = len(qnorm)
|
||||
qmask = 0xff
|
||||
while not (qhbyte & 0x80):
|
||||
qhbyte <<= 1
|
||||
qmask >>= 1
|
||||
while True:
|
||||
x_bytes = os.urandom(byte_count)
|
||||
x_bytes = byte_mask(x_bytes[0], qmask) + x_bytes[1:]
|
||||
x = util.inflate_long(x_bytes, 1)
|
||||
if (x > 1) and (x < q):
|
||||
break
|
||||
self.x = x
|
||||
|
||||
def _parse_kexgss_groupreq(self, m):
|
||||
"""
|
||||
Parse the SSH2_MSG_KEXGSS_GROUPREQ message (server mode).
|
||||
|
||||
:param `.Message` m: The content of the
|
||||
SSH2_MSG_KEXGSS_GROUPREQ message
|
||||
"""
|
||||
minbits = m.get_int()
|
||||
preferredbits = m.get_int()
|
||||
maxbits = m.get_int()
|
||||
# smoosh the user's preferred size into our own limits
|
||||
if preferredbits > self.max_bits:
|
||||
preferredbits = self.max_bits
|
||||
if preferredbits < self.min_bits:
|
||||
preferredbits = self.min_bits
|
||||
# fix min/max if they're inconsistent. technically, we could just pout
|
||||
# and hang up, but there's no harm in giving them the benefit of the
|
||||
# doubt and just picking a bitsize for them.
|
||||
if minbits > preferredbits:
|
||||
minbits = preferredbits
|
||||
if maxbits < preferredbits:
|
||||
maxbits = preferredbits
|
||||
# now save a copy
|
||||
self.min_bits = minbits
|
||||
self.preferred_bits = preferredbits
|
||||
self.max_bits = maxbits
|
||||
# generate prime
|
||||
pack = self.transport._get_modulus_pack()
|
||||
if pack is None:
|
||||
raise SSHException("Can't do server-side gex with no modulus pack")
|
||||
self.transport._log(
|
||||
DEBUG, # noqa
|
||||
"Picking p ({} <= {} <= {} bits)".format(
|
||||
minbits, preferredbits, maxbits
|
||||
),
|
||||
)
|
||||
self.g, self.p = pack.get_modulus(minbits, preferredbits, maxbits)
|
||||
m = Message()
|
||||
m.add_byte(c_MSG_KEXGSS_GROUP)
|
||||
m.add_mpint(self.p)
|
||||
m.add_mpint(self.g)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(MSG_KEXGSS_INIT)
|
||||
|
||||
def _parse_kexgss_group(self, m):
|
||||
"""
|
||||
Parse the SSH2_MSG_KEXGSS_GROUP message (client mode).
|
||||
|
||||
:param `Message` m: The content of the SSH2_MSG_KEXGSS_GROUP message
|
||||
"""
|
||||
self.p = m.get_mpint()
|
||||
self.g = m.get_mpint()
|
||||
# reject if p's bit length < 1024 or > 8192
|
||||
bitlen = util.bit_length(self.p)
|
||||
if (bitlen < 1024) or (bitlen > 8192):
|
||||
raise SSHException(
|
||||
"Server-generated gex p (don't ask) is out of range "
|
||||
"({} bits)".format(bitlen)
|
||||
)
|
||||
self.transport._log(
|
||||
DEBUG, "Got server p ({} bits)".format(bitlen)
|
||||
) # noqa
|
||||
self._generate_x()
|
||||
# now compute e = g^x mod p
|
||||
self.e = pow(self.g, self.x, self.p)
|
||||
m = Message()
|
||||
m.add_byte(c_MSG_KEXGSS_INIT)
|
||||
m.add_string(self.kexgss.ssh_init_sec_context(target=self.gss_host))
|
||||
m.add_mpint(self.e)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(
|
||||
MSG_KEXGSS_HOSTKEY,
|
||||
MSG_KEXGSS_CONTINUE,
|
||||
MSG_KEXGSS_COMPLETE,
|
||||
MSG_KEXGSS_ERROR,
|
||||
)
|
||||
|
||||
def _parse_kexgss_gex_init(self, m):
|
||||
"""
|
||||
Parse the SSH2_MSG_KEXGSS_INIT message (server mode).
|
||||
|
||||
:param `Message` m: The content of the SSH2_MSG_KEXGSS_INIT message
|
||||
"""
|
||||
client_token = m.get_string()
|
||||
self.e = m.get_mpint()
|
||||
if (self.e < 1) or (self.e > self.p - 1):
|
||||
raise SSHException('Client kex "e" is out of range')
|
||||
self._generate_x()
|
||||
self.f = pow(self.g, self.x, self.p)
|
||||
K = pow(self.e, self.x, self.p)
|
||||
self.transport.host_key = NullHostKey()
|
||||
key = self.transport.host_key.__str__()
|
||||
# okay, build up the hash H of
|
||||
# (V_C || V_S || I_C || I_S || K_S || min || n || max || p || g || e || f || K) # noqa
|
||||
hm = Message()
|
||||
hm.add(
|
||||
self.transport.remote_version,
|
||||
self.transport.local_version,
|
||||
self.transport.remote_kex_init,
|
||||
self.transport.local_kex_init,
|
||||
key,
|
||||
)
|
||||
hm.add_int(self.min_bits)
|
||||
hm.add_int(self.preferred_bits)
|
||||
hm.add_int(self.max_bits)
|
||||
hm.add_mpint(self.p)
|
||||
hm.add_mpint(self.g)
|
||||
hm.add_mpint(self.e)
|
||||
hm.add_mpint(self.f)
|
||||
hm.add_mpint(K)
|
||||
H = sha1(hm.asbytes()).digest()
|
||||
self.transport._set_K_H(K, H)
|
||||
srv_token = self.kexgss.ssh_accept_sec_context(
|
||||
self.gss_host, client_token
|
||||
)
|
||||
m = Message()
|
||||
if self.kexgss._gss_srv_ctxt_status:
|
||||
mic_token = self.kexgss.ssh_get_mic(
|
||||
self.transport.session_id, gss_kex=True
|
||||
)
|
||||
m.add_byte(c_MSG_KEXGSS_COMPLETE)
|
||||
m.add_mpint(self.f)
|
||||
m.add_string(mic_token)
|
||||
if srv_token is not None:
|
||||
m.add_boolean(True)
|
||||
m.add_string(srv_token)
|
||||
else:
|
||||
m.add_boolean(False)
|
||||
self.transport._send_message(m)
|
||||
self.transport.gss_kex_used = True
|
||||
self.transport._activate_outbound()
|
||||
else:
|
||||
m.add_byte(c_MSG_KEXGSS_CONTINUE)
|
||||
m.add_string(srv_token)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(
|
||||
MSG_KEXGSS_CONTINUE, MSG_KEXGSS_COMPLETE, MSG_KEXGSS_ERROR
|
||||
)
|
||||
|
||||
def _parse_kexgss_hostkey(self, m):
|
||||
"""
|
||||
Parse the SSH2_MSG_KEXGSS_HOSTKEY message (client mode).
|
||||
|
||||
:param `Message` m: The content of the SSH2_MSG_KEXGSS_HOSTKEY message
|
||||
"""
|
||||
# client mode
|
||||
host_key = m.get_string()
|
||||
self.transport.host_key = host_key
|
||||
sig = m.get_string()
|
||||
self.transport._verify_key(host_key, sig)
|
||||
self.transport._expect_packet(MSG_KEXGSS_CONTINUE, MSG_KEXGSS_COMPLETE)
|
||||
|
||||
def _parse_kexgss_continue(self, m):
|
||||
"""
|
||||
Parse the SSH2_MSG_KEXGSS_CONTINUE message.
|
||||
|
||||
:param `Message` m: The content of the SSH2_MSG_KEXGSS_CONTINUE message
|
||||
"""
|
||||
if not self.transport.server_mode:
|
||||
srv_token = m.get_string()
|
||||
m = Message()
|
||||
m.add_byte(c_MSG_KEXGSS_CONTINUE)
|
||||
m.add_string(
|
||||
self.kexgss.ssh_init_sec_context(
|
||||
target=self.gss_host, recv_token=srv_token
|
||||
)
|
||||
)
|
||||
self.transport.send_message(m)
|
||||
self.transport._expect_packet(
|
||||
MSG_KEXGSS_CONTINUE, MSG_KEXGSS_COMPLETE, MSG_KEXGSS_ERROR
|
||||
)
|
||||
else:
|
||||
pass
|
||||
|
||||
def _parse_kexgss_complete(self, m):
|
||||
"""
|
||||
Parse the SSH2_MSG_KEXGSS_COMPLETE message (client mode).
|
||||
|
||||
:param `Message` m: The content of the SSH2_MSG_KEXGSS_COMPLETE message
|
||||
"""
|
||||
if self.transport.host_key is None:
|
||||
self.transport.host_key = NullHostKey()
|
||||
self.f = m.get_mpint()
|
||||
mic_token = m.get_string()
|
||||
# This must be TRUE, if there is a GSS-API token in this message.
|
||||
bool = m.get_boolean()
|
||||
srv_token = None
|
||||
if bool:
|
||||
srv_token = m.get_string()
|
||||
if (self.f < 1) or (self.f > self.p - 1):
|
||||
raise SSHException('Server kex "f" is out of range')
|
||||
K = pow(self.f, self.x, self.p)
|
||||
# okay, build up the hash H of
|
||||
# (V_C || V_S || I_C || I_S || K_S || min || n || max || p || g || e || f || K) # noqa
|
||||
hm = Message()
|
||||
hm.add(
|
||||
self.transport.local_version,
|
||||
self.transport.remote_version,
|
||||
self.transport.local_kex_init,
|
||||
self.transport.remote_kex_init,
|
||||
self.transport.host_key.__str__(),
|
||||
)
|
||||
if not self.old_style:
|
||||
hm.add_int(self.min_bits)
|
||||
hm.add_int(self.preferred_bits)
|
||||
if not self.old_style:
|
||||
hm.add_int(self.max_bits)
|
||||
hm.add_mpint(self.p)
|
||||
hm.add_mpint(self.g)
|
||||
hm.add_mpint(self.e)
|
||||
hm.add_mpint(self.f)
|
||||
hm.add_mpint(K)
|
||||
H = sha1(hm.asbytes()).digest()
|
||||
self.transport._set_K_H(K, H)
|
||||
if srv_token is not None:
|
||||
self.kexgss.ssh_init_sec_context(
|
||||
target=self.gss_host, recv_token=srv_token
|
||||
)
|
||||
self.kexgss.ssh_check_mic(mic_token, H)
|
||||
else:
|
||||
self.kexgss.ssh_check_mic(mic_token, H)
|
||||
self.transport.gss_kex_used = True
|
||||
self.transport._activate_outbound()
|
||||
|
||||
def _parse_kexgss_error(self, m):
|
||||
"""
|
||||
Parse the SSH2_MSG_KEXGSS_ERROR message (client mode).
|
||||
The server may send a GSS-API error message. if it does, we display
|
||||
the error by throwing an exception (client mode).
|
||||
|
||||
:param `Message` m: The content of the SSH2_MSG_KEXGSS_ERROR message
|
||||
:raise SSHException: Contains GSS-API major and minor status as well as
|
||||
the error message and the language tag of the
|
||||
message
|
||||
"""
|
||||
maj_status = m.get_int()
|
||||
min_status = m.get_int()
|
||||
err_msg = m.get_string()
|
||||
m.get_string() # we don't care about the language (lang_tag)!
|
||||
raise SSHException(
|
||||
"""GSS-API Error:
|
||||
Major Status: {}
|
||||
Minor Status: {}
|
||||
Error Message: {}
|
||||
""".format(
|
||||
maj_status, min_status, err_msg
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class NullHostKey(object):
|
||||
"""
|
||||
This class represents the Null Host Key for GSS-API Key Exchange as defined
|
||||
in `RFC 4462 Section 5
|
||||
<https://tools.ietf.org/html/rfc4462.html#section-5>`_
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.key = ""
|
||||
|
||||
def __str__(self):
|
||||
return self.key
|
||||
|
||||
def get_name(self):
|
||||
return self.key
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/kex_gss.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/kex_gss.pyc
Normal file
Binary file not shown.
310
.ve/lib/python2.7/site-packages/paramiko/message.py
Normal file
310
.ve/lib/python2.7/site-packages/paramiko/message.py
Normal file
@@ -0,0 +1,310 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Implementation of an SSH2 "message".
|
||||
"""
|
||||
|
||||
import struct
|
||||
|
||||
from paramiko import util
|
||||
from paramiko.common import zero_byte, max_byte, one_byte, asbytes
|
||||
from paramiko.py3compat import long, BytesIO, u, integer_types
|
||||
|
||||
|
||||
class Message(object):
|
||||
"""
|
||||
An SSH2 message is a stream of bytes that encodes some combination of
|
||||
strings, integers, bools, and infinite-precision integers (known in Python
|
||||
as longs). This class builds or breaks down such a byte stream.
|
||||
|
||||
Normally you don't need to deal with anything this low-level, but it's
|
||||
exposed for people implementing custom extensions, or features that
|
||||
paramiko doesn't support yet.
|
||||
"""
|
||||
|
||||
big_int = long(0xff000000)
|
||||
|
||||
def __init__(self, content=None):
|
||||
"""
|
||||
Create a new SSH2 message.
|
||||
|
||||
:param str content:
|
||||
the byte stream to use as the message content (passed in only when
|
||||
decomposing a message).
|
||||
"""
|
||||
if content is not None:
|
||||
self.packet = BytesIO(content)
|
||||
else:
|
||||
self.packet = BytesIO()
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Return the byte stream content of this message, as a string/bytes obj.
|
||||
"""
|
||||
return self.asbytes()
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Returns a string representation of this object, for debugging.
|
||||
"""
|
||||
return "paramiko.Message(" + repr(self.packet.getvalue()) + ")"
|
||||
|
||||
def asbytes(self):
|
||||
"""
|
||||
Return the byte stream content of this Message, as bytes.
|
||||
"""
|
||||
return self.packet.getvalue()
|
||||
|
||||
def rewind(self):
|
||||
"""
|
||||
Rewind the message to the beginning as if no items had been parsed
|
||||
out of it yet.
|
||||
"""
|
||||
self.packet.seek(0)
|
||||
|
||||
def get_remainder(self):
|
||||
"""
|
||||
Return the bytes (as a `str`) of this message that haven't already been
|
||||
parsed and returned.
|
||||
"""
|
||||
position = self.packet.tell()
|
||||
remainder = self.packet.read()
|
||||
self.packet.seek(position)
|
||||
return remainder
|
||||
|
||||
def get_so_far(self):
|
||||
"""
|
||||
Returns the `str` bytes of this message that have been parsed and
|
||||
returned. The string passed into a message's constructor can be
|
||||
regenerated by concatenating ``get_so_far`` and `get_remainder`.
|
||||
"""
|
||||
position = self.packet.tell()
|
||||
self.rewind()
|
||||
return self.packet.read(position)
|
||||
|
||||
def get_bytes(self, n):
|
||||
"""
|
||||
Return the next ``n`` bytes of the message (as a `str`), without
|
||||
decomposing into an int, decoded string, etc. Just the raw bytes are
|
||||
returned. Returns a string of ``n`` zero bytes if there weren't ``n``
|
||||
bytes remaining in the message.
|
||||
"""
|
||||
b = self.packet.read(n)
|
||||
max_pad_size = 1 << 20 # Limit padding to 1 MB
|
||||
if len(b) < n < max_pad_size:
|
||||
return b + zero_byte * (n - len(b))
|
||||
return b
|
||||
|
||||
def get_byte(self):
|
||||
"""
|
||||
Return the next byte of the message, without decomposing it. This
|
||||
is equivalent to `get_bytes(1) <get_bytes>`.
|
||||
|
||||
:return:
|
||||
the next (`str`) byte of the message, or ``'\000'`` if there aren't
|
||||
any bytes remaining.
|
||||
"""
|
||||
return self.get_bytes(1)
|
||||
|
||||
def get_boolean(self):
|
||||
"""
|
||||
Fetch a boolean from the stream.
|
||||
"""
|
||||
b = self.get_bytes(1)
|
||||
return b != zero_byte
|
||||
|
||||
def get_adaptive_int(self):
|
||||
"""
|
||||
Fetch an int from the stream.
|
||||
|
||||
:return: a 32-bit unsigned `int`.
|
||||
"""
|
||||
byte = self.get_bytes(1)
|
||||
if byte == max_byte:
|
||||
return util.inflate_long(self.get_binary())
|
||||
byte += self.get_bytes(3)
|
||||
return struct.unpack(">I", byte)[0]
|
||||
|
||||
def get_int(self):
|
||||
"""
|
||||
Fetch an int from the stream.
|
||||
"""
|
||||
return struct.unpack(">I", self.get_bytes(4))[0]
|
||||
|
||||
def get_int64(self):
|
||||
"""
|
||||
Fetch a 64-bit int from the stream.
|
||||
|
||||
:return: a 64-bit unsigned integer (`long`).
|
||||
"""
|
||||
return struct.unpack(">Q", self.get_bytes(8))[0]
|
||||
|
||||
def get_mpint(self):
|
||||
"""
|
||||
Fetch a long int (mpint) from the stream.
|
||||
|
||||
:return: an arbitrary-length integer (`long`).
|
||||
"""
|
||||
return util.inflate_long(self.get_binary())
|
||||
|
||||
def get_string(self):
|
||||
"""
|
||||
Fetch a `str` from the stream. This could be a byte string and may
|
||||
contain unprintable characters. (It's not unheard of for a string to
|
||||
contain another byte-stream message.)
|
||||
"""
|
||||
return self.get_bytes(self.get_int())
|
||||
|
||||
def get_text(self):
|
||||
"""
|
||||
Fetch a Unicode string from the stream.
|
||||
"""
|
||||
return u(self.get_string())
|
||||
|
||||
def get_binary(self):
|
||||
"""
|
||||
Fetch a string from the stream. This could be a byte string and may
|
||||
contain unprintable characters. (It's not unheard of for a string to
|
||||
contain another byte-stream Message.)
|
||||
"""
|
||||
return self.get_bytes(self.get_int())
|
||||
|
||||
def get_list(self):
|
||||
"""
|
||||
Fetch a list of `strings <str>` from the stream.
|
||||
|
||||
These are trivially encoded as comma-separated values in a string.
|
||||
"""
|
||||
return self.get_text().split(",")
|
||||
|
||||
def add_bytes(self, b):
|
||||
"""
|
||||
Write bytes to the stream, without any formatting.
|
||||
|
||||
:param str b: bytes to add
|
||||
"""
|
||||
self.packet.write(b)
|
||||
return self
|
||||
|
||||
def add_byte(self, b):
|
||||
"""
|
||||
Write a single byte to the stream, without any formatting.
|
||||
|
||||
:param str b: byte to add
|
||||
"""
|
||||
self.packet.write(b)
|
||||
return self
|
||||
|
||||
def add_boolean(self, b):
|
||||
"""
|
||||
Add a boolean value to the stream.
|
||||
|
||||
:param bool b: boolean value to add
|
||||
"""
|
||||
if b:
|
||||
self.packet.write(one_byte)
|
||||
else:
|
||||
self.packet.write(zero_byte)
|
||||
return self
|
||||
|
||||
def add_int(self, n):
|
||||
"""
|
||||
Add an integer to the stream.
|
||||
|
||||
:param int n: integer to add
|
||||
"""
|
||||
self.packet.write(struct.pack(">I", n))
|
||||
return self
|
||||
|
||||
def add_adaptive_int(self, n):
|
||||
"""
|
||||
Add an integer to the stream.
|
||||
|
||||
:param int n: integer to add
|
||||
"""
|
||||
if n >= Message.big_int:
|
||||
self.packet.write(max_byte)
|
||||
self.add_string(util.deflate_long(n))
|
||||
else:
|
||||
self.packet.write(struct.pack(">I", n))
|
||||
return self
|
||||
|
||||
def add_int64(self, n):
|
||||
"""
|
||||
Add a 64-bit int to the stream.
|
||||
|
||||
:param long n: long int to add
|
||||
"""
|
||||
self.packet.write(struct.pack(">Q", n))
|
||||
return self
|
||||
|
||||
def add_mpint(self, z):
|
||||
"""
|
||||
Add a long int to the stream, encoded as an infinite-precision
|
||||
integer. This method only works on positive numbers.
|
||||
|
||||
:param long z: long int to add
|
||||
"""
|
||||
self.add_string(util.deflate_long(z))
|
||||
return self
|
||||
|
||||
def add_string(self, s):
|
||||
"""
|
||||
Add a string to the stream.
|
||||
|
||||
:param str s: string to add
|
||||
"""
|
||||
s = asbytes(s)
|
||||
self.add_int(len(s))
|
||||
self.packet.write(s)
|
||||
return self
|
||||
|
||||
def add_list(self, l):
|
||||
"""
|
||||
Add a list of strings to the stream. They are encoded identically to
|
||||
a single string of values separated by commas. (Yes, really, that's
|
||||
how SSH2 does it.)
|
||||
|
||||
:param l: list of strings to add
|
||||
"""
|
||||
self.add_string(",".join(l))
|
||||
return self
|
||||
|
||||
def _add(self, i):
|
||||
if type(i) is bool:
|
||||
return self.add_boolean(i)
|
||||
elif isinstance(i, integer_types):
|
||||
return self.add_adaptive_int(i)
|
||||
elif type(i) is list:
|
||||
return self.add_list(i)
|
||||
else:
|
||||
return self.add_string(i)
|
||||
|
||||
def add(self, *seq):
|
||||
"""
|
||||
Add a sequence of items to the stream. The values are encoded based
|
||||
on their type: str, int, bool, list, or long.
|
||||
|
||||
.. warning::
|
||||
Longs are encoded non-deterministically. Don't use this method.
|
||||
|
||||
:param seq: the sequence of items
|
||||
"""
|
||||
for item in seq:
|
||||
self._add(item)
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/message.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/message.pyc
Normal file
Binary file not shown.
596
.ve/lib/python2.7/site-packages/paramiko/packet.py
Normal file
596
.ve/lib/python2.7/site-packages/paramiko/packet.py
Normal file
@@ -0,0 +1,596 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Packet handling
|
||||
"""
|
||||
|
||||
import errno
|
||||
import os
|
||||
import socket
|
||||
import struct
|
||||
import threading
|
||||
import time
|
||||
from hmac import HMAC
|
||||
|
||||
from paramiko import util
|
||||
from paramiko.common import (
|
||||
linefeed_byte,
|
||||
cr_byte_value,
|
||||
asbytes,
|
||||
MSG_NAMES,
|
||||
DEBUG,
|
||||
xffffffff,
|
||||
zero_byte,
|
||||
)
|
||||
from paramiko.py3compat import u, byte_ord
|
||||
from paramiko.ssh_exception import SSHException, ProxyCommandFailure
|
||||
from paramiko.message import Message
|
||||
|
||||
|
||||
def compute_hmac(key, message, digest_class):
|
||||
return HMAC(key, message, digest_class).digest()
|
||||
|
||||
|
||||
class NeedRekeyException(Exception):
|
||||
"""
|
||||
Exception indicating a rekey is needed.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def first_arg(e):
|
||||
arg = None
|
||||
if type(e.args) is tuple and len(e.args) > 0:
|
||||
arg = e.args[0]
|
||||
return arg
|
||||
|
||||
|
||||
class Packetizer(object):
|
||||
"""
|
||||
Implementation of the base SSH packet protocol.
|
||||
"""
|
||||
|
||||
# READ the secsh RFC's before raising these values. if anything,
|
||||
# they should probably be lower.
|
||||
REKEY_PACKETS = pow(2, 29)
|
||||
REKEY_BYTES = pow(2, 29)
|
||||
|
||||
# Allow receiving this many packets after a re-key request before
|
||||
# terminating
|
||||
REKEY_PACKETS_OVERFLOW_MAX = pow(2, 29)
|
||||
# Allow receiving this many bytes after a re-key request before terminating
|
||||
REKEY_BYTES_OVERFLOW_MAX = pow(2, 29)
|
||||
|
||||
def __init__(self, socket):
|
||||
self.__socket = socket
|
||||
self.__logger = None
|
||||
self.__closed = False
|
||||
self.__dump_packets = False
|
||||
self.__need_rekey = False
|
||||
self.__init_count = 0
|
||||
self.__remainder = bytes()
|
||||
|
||||
# used for noticing when to re-key:
|
||||
self.__sent_bytes = 0
|
||||
self.__sent_packets = 0
|
||||
self.__received_bytes = 0
|
||||
self.__received_packets = 0
|
||||
self.__received_bytes_overflow = 0
|
||||
self.__received_packets_overflow = 0
|
||||
|
||||
# current inbound/outbound ciphering:
|
||||
self.__block_size_out = 8
|
||||
self.__block_size_in = 8
|
||||
self.__mac_size_out = 0
|
||||
self.__mac_size_in = 0
|
||||
self.__block_engine_out = None
|
||||
self.__block_engine_in = None
|
||||
self.__sdctr_out = False
|
||||
self.__mac_engine_out = None
|
||||
self.__mac_engine_in = None
|
||||
self.__mac_key_out = bytes()
|
||||
self.__mac_key_in = bytes()
|
||||
self.__compress_engine_out = None
|
||||
self.__compress_engine_in = None
|
||||
self.__sequence_number_out = 0
|
||||
self.__sequence_number_in = 0
|
||||
|
||||
# lock around outbound writes (packet computation)
|
||||
self.__write_lock = threading.RLock()
|
||||
|
||||
# keepalives:
|
||||
self.__keepalive_interval = 0
|
||||
self.__keepalive_last = time.time()
|
||||
self.__keepalive_callback = None
|
||||
|
||||
self.__timer = None
|
||||
self.__handshake_complete = False
|
||||
self.__timer_expired = False
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self.__closed
|
||||
|
||||
def set_log(self, log):
|
||||
"""
|
||||
Set the Python log object to use for logging.
|
||||
"""
|
||||
self.__logger = log
|
||||
|
||||
def set_outbound_cipher(
|
||||
self,
|
||||
block_engine,
|
||||
block_size,
|
||||
mac_engine,
|
||||
mac_size,
|
||||
mac_key,
|
||||
sdctr=False,
|
||||
):
|
||||
"""
|
||||
Switch outbound data cipher.
|
||||
"""
|
||||
self.__block_engine_out = block_engine
|
||||
self.__sdctr_out = sdctr
|
||||
self.__block_size_out = block_size
|
||||
self.__mac_engine_out = mac_engine
|
||||
self.__mac_size_out = mac_size
|
||||
self.__mac_key_out = mac_key
|
||||
self.__sent_bytes = 0
|
||||
self.__sent_packets = 0
|
||||
# wait until the reset happens in both directions before clearing
|
||||
# rekey flag
|
||||
self.__init_count |= 1
|
||||
if self.__init_count == 3:
|
||||
self.__init_count = 0
|
||||
self.__need_rekey = False
|
||||
|
||||
def set_inbound_cipher(
|
||||
self, block_engine, block_size, mac_engine, mac_size, mac_key
|
||||
):
|
||||
"""
|
||||
Switch inbound data cipher.
|
||||
"""
|
||||
self.__block_engine_in = block_engine
|
||||
self.__block_size_in = block_size
|
||||
self.__mac_engine_in = mac_engine
|
||||
self.__mac_size_in = mac_size
|
||||
self.__mac_key_in = mac_key
|
||||
self.__received_bytes = 0
|
||||
self.__received_packets = 0
|
||||
self.__received_bytes_overflow = 0
|
||||
self.__received_packets_overflow = 0
|
||||
# wait until the reset happens in both directions before clearing
|
||||
# rekey flag
|
||||
self.__init_count |= 2
|
||||
if self.__init_count == 3:
|
||||
self.__init_count = 0
|
||||
self.__need_rekey = False
|
||||
|
||||
def set_outbound_compressor(self, compressor):
|
||||
self.__compress_engine_out = compressor
|
||||
|
||||
def set_inbound_compressor(self, compressor):
|
||||
self.__compress_engine_in = compressor
|
||||
|
||||
def close(self):
|
||||
self.__closed = True
|
||||
self.__socket.close()
|
||||
|
||||
def set_hexdump(self, hexdump):
|
||||
self.__dump_packets = hexdump
|
||||
|
||||
def get_hexdump(self):
|
||||
return self.__dump_packets
|
||||
|
||||
def get_mac_size_in(self):
|
||||
return self.__mac_size_in
|
||||
|
||||
def get_mac_size_out(self):
|
||||
return self.__mac_size_out
|
||||
|
||||
def need_rekey(self):
|
||||
"""
|
||||
Returns ``True`` if a new set of keys needs to be negotiated. This
|
||||
will be triggered during a packet read or write, so it should be
|
||||
checked after every read or write, or at least after every few.
|
||||
"""
|
||||
return self.__need_rekey
|
||||
|
||||
def set_keepalive(self, interval, callback):
|
||||
"""
|
||||
Turn on/off the callback keepalive. If ``interval`` seconds pass with
|
||||
no data read from or written to the socket, the callback will be
|
||||
executed and the timer will be reset.
|
||||
"""
|
||||
self.__keepalive_interval = interval
|
||||
self.__keepalive_callback = callback
|
||||
self.__keepalive_last = time.time()
|
||||
|
||||
def read_timer(self):
|
||||
self.__timer_expired = True
|
||||
|
||||
def start_handshake(self, timeout):
|
||||
"""
|
||||
Tells `Packetizer` that the handshake process started.
|
||||
Starts a book keeping timer that can signal a timeout in the
|
||||
handshake process.
|
||||
|
||||
:param float timeout: amount of seconds to wait before timing out
|
||||
"""
|
||||
if not self.__timer:
|
||||
self.__timer = threading.Timer(float(timeout), self.read_timer)
|
||||
self.__timer.start()
|
||||
|
||||
def handshake_timed_out(self):
|
||||
"""
|
||||
Checks if the handshake has timed out.
|
||||
|
||||
If `start_handshake` wasn't called before the call to this function,
|
||||
the return value will always be `False`. If the handshake completed
|
||||
before a timeout was reached, the return value will be `False`
|
||||
|
||||
:return: handshake time out status, as a `bool`
|
||||
"""
|
||||
if not self.__timer:
|
||||
return False
|
||||
if self.__handshake_complete:
|
||||
return False
|
||||
return self.__timer_expired
|
||||
|
||||
def complete_handshake(self):
|
||||
"""
|
||||
Tells `Packetizer` that the handshake has completed.
|
||||
"""
|
||||
if self.__timer:
|
||||
self.__timer.cancel()
|
||||
self.__timer_expired = False
|
||||
self.__handshake_complete = True
|
||||
|
||||
def read_all(self, n, check_rekey=False):
|
||||
"""
|
||||
Read as close to N bytes as possible, blocking as long as necessary.
|
||||
|
||||
:param int n: number of bytes to read
|
||||
:return: the data read, as a `str`
|
||||
|
||||
:raises:
|
||||
``EOFError`` -- if the socket was closed before all the bytes could
|
||||
be read
|
||||
"""
|
||||
out = bytes()
|
||||
# handle over-reading from reading the banner line
|
||||
if len(self.__remainder) > 0:
|
||||
out = self.__remainder[:n]
|
||||
self.__remainder = self.__remainder[n:]
|
||||
n -= len(out)
|
||||
while n > 0:
|
||||
got_timeout = False
|
||||
if self.handshake_timed_out():
|
||||
raise EOFError()
|
||||
try:
|
||||
x = self.__socket.recv(n)
|
||||
if len(x) == 0:
|
||||
raise EOFError()
|
||||
out += x
|
||||
n -= len(x)
|
||||
except socket.timeout:
|
||||
got_timeout = True
|
||||
except socket.error as e:
|
||||
# on Linux, sometimes instead of socket.timeout, we get
|
||||
# EAGAIN. this is a bug in recent (> 2.6.9) kernels but
|
||||
# we need to work around it.
|
||||
arg = first_arg(e)
|
||||
if arg == errno.EAGAIN:
|
||||
got_timeout = True
|
||||
elif arg == errno.EINTR:
|
||||
# syscall interrupted; try again
|
||||
pass
|
||||
elif self.__closed:
|
||||
raise EOFError()
|
||||
else:
|
||||
raise
|
||||
if got_timeout:
|
||||
if self.__closed:
|
||||
raise EOFError()
|
||||
if check_rekey and (len(out) == 0) and self.__need_rekey:
|
||||
raise NeedRekeyException()
|
||||
self._check_keepalive()
|
||||
return out
|
||||
|
||||
def write_all(self, out):
|
||||
self.__keepalive_last = time.time()
|
||||
iteration_with_zero_as_return_value = 0
|
||||
while len(out) > 0:
|
||||
retry_write = False
|
||||
try:
|
||||
n = self.__socket.send(out)
|
||||
except socket.timeout:
|
||||
retry_write = True
|
||||
except socket.error as e:
|
||||
arg = first_arg(e)
|
||||
if arg == errno.EAGAIN:
|
||||
retry_write = True
|
||||
elif arg == errno.EINTR:
|
||||
# syscall interrupted; try again
|
||||
retry_write = True
|
||||
else:
|
||||
n = -1
|
||||
except ProxyCommandFailure:
|
||||
raise # so it doesn't get swallowed by the below catchall
|
||||
except Exception:
|
||||
# could be: (32, 'Broken pipe')
|
||||
n = -1
|
||||
if retry_write:
|
||||
n = 0
|
||||
if self.__closed:
|
||||
n = -1
|
||||
else:
|
||||
if n == 0 and iteration_with_zero_as_return_value > 10:
|
||||
# We shouldn't retry the write, but we didn't
|
||||
# manage to send anything over the socket. This might be an
|
||||
# indication that we have lost contact with the remote
|
||||
# side, but are yet to receive an EOFError or other socket
|
||||
# errors. Let's give it some iteration to try and catch up.
|
||||
n = -1
|
||||
iteration_with_zero_as_return_value += 1
|
||||
if n < 0:
|
||||
raise EOFError()
|
||||
if n == len(out):
|
||||
break
|
||||
out = out[n:]
|
||||
return
|
||||
|
||||
def readline(self, timeout):
|
||||
"""
|
||||
Read a line from the socket. We assume no data is pending after the
|
||||
line, so it's okay to attempt large reads.
|
||||
"""
|
||||
buf = self.__remainder
|
||||
while linefeed_byte not in buf:
|
||||
buf += self._read_timeout(timeout)
|
||||
n = buf.index(linefeed_byte)
|
||||
self.__remainder = buf[n + 1 :]
|
||||
buf = buf[:n]
|
||||
if (len(buf) > 0) and (buf[-1] == cr_byte_value):
|
||||
buf = buf[:-1]
|
||||
return u(buf)
|
||||
|
||||
def send_message(self, data):
|
||||
"""
|
||||
Write a block of data using the current cipher, as an SSH block.
|
||||
"""
|
||||
# encrypt this sucka
|
||||
data = asbytes(data)
|
||||
cmd = byte_ord(data[0])
|
||||
if cmd in MSG_NAMES:
|
||||
cmd_name = MSG_NAMES[cmd]
|
||||
else:
|
||||
cmd_name = "${:x}".format(cmd)
|
||||
orig_len = len(data)
|
||||
self.__write_lock.acquire()
|
||||
try:
|
||||
if self.__compress_engine_out is not None:
|
||||
data = self.__compress_engine_out(data)
|
||||
packet = self._build_packet(data)
|
||||
if self.__dump_packets:
|
||||
self._log(
|
||||
DEBUG,
|
||||
"Write packet <{}>, length {}".format(cmd_name, orig_len),
|
||||
)
|
||||
self._log(DEBUG, util.format_binary(packet, "OUT: "))
|
||||
if self.__block_engine_out is not None:
|
||||
out = self.__block_engine_out.update(packet)
|
||||
else:
|
||||
out = packet
|
||||
# + mac
|
||||
if self.__block_engine_out is not None:
|
||||
payload = (
|
||||
struct.pack(">I", self.__sequence_number_out) + packet
|
||||
)
|
||||
out += compute_hmac(
|
||||
self.__mac_key_out, payload, self.__mac_engine_out
|
||||
)[: self.__mac_size_out]
|
||||
self.__sequence_number_out = (
|
||||
self.__sequence_number_out + 1
|
||||
) & xffffffff
|
||||
self.write_all(out)
|
||||
|
||||
self.__sent_bytes += len(out)
|
||||
self.__sent_packets += 1
|
||||
sent_too_much = (
|
||||
self.__sent_packets >= self.REKEY_PACKETS
|
||||
or self.__sent_bytes >= self.REKEY_BYTES
|
||||
)
|
||||
if sent_too_much and not self.__need_rekey:
|
||||
# only ask once for rekeying
|
||||
msg = "Rekeying (hit {} packets, {} bytes sent)"
|
||||
self._log(
|
||||
DEBUG, msg.format(self.__sent_packets, self.__sent_bytes)
|
||||
)
|
||||
self.__received_bytes_overflow = 0
|
||||
self.__received_packets_overflow = 0
|
||||
self._trigger_rekey()
|
||||
finally:
|
||||
self.__write_lock.release()
|
||||
|
||||
def read_message(self):
|
||||
"""
|
||||
Only one thread should ever be in this function (no other locking is
|
||||
done).
|
||||
|
||||
:raises: `.SSHException` -- if the packet is mangled
|
||||
:raises: `.NeedRekeyException` -- if the transport should rekey
|
||||
"""
|
||||
header = self.read_all(self.__block_size_in, check_rekey=True)
|
||||
if self.__block_engine_in is not None:
|
||||
header = self.__block_engine_in.update(header)
|
||||
if self.__dump_packets:
|
||||
self._log(DEBUG, util.format_binary(header, "IN: "))
|
||||
packet_size = struct.unpack(">I", header[:4])[0]
|
||||
# leftover contains decrypted bytes from the first block (after the
|
||||
# length field)
|
||||
leftover = header[4:]
|
||||
if (packet_size - len(leftover)) % self.__block_size_in != 0:
|
||||
raise SSHException("Invalid packet blocking")
|
||||
buf = self.read_all(packet_size + self.__mac_size_in - len(leftover))
|
||||
packet = buf[: packet_size - len(leftover)]
|
||||
post_packet = buf[packet_size - len(leftover) :]
|
||||
if self.__block_engine_in is not None:
|
||||
packet = self.__block_engine_in.update(packet)
|
||||
if self.__dump_packets:
|
||||
self._log(DEBUG, util.format_binary(packet, "IN: "))
|
||||
packet = leftover + packet
|
||||
|
||||
if self.__mac_size_in > 0:
|
||||
mac = post_packet[: self.__mac_size_in]
|
||||
mac_payload = (
|
||||
struct.pack(">II", self.__sequence_number_in, packet_size)
|
||||
+ packet
|
||||
)
|
||||
my_mac = compute_hmac(
|
||||
self.__mac_key_in, mac_payload, self.__mac_engine_in
|
||||
)[: self.__mac_size_in]
|
||||
if not util.constant_time_bytes_eq(my_mac, mac):
|
||||
raise SSHException("Mismatched MAC")
|
||||
padding = byte_ord(packet[0])
|
||||
payload = packet[1 : packet_size - padding]
|
||||
|
||||
if self.__dump_packets:
|
||||
self._log(
|
||||
DEBUG,
|
||||
"Got payload ({} bytes, {} padding)".format(
|
||||
packet_size, padding
|
||||
),
|
||||
)
|
||||
|
||||
if self.__compress_engine_in is not None:
|
||||
payload = self.__compress_engine_in(payload)
|
||||
|
||||
msg = Message(payload[1:])
|
||||
msg.seqno = self.__sequence_number_in
|
||||
self.__sequence_number_in = (self.__sequence_number_in + 1) & xffffffff
|
||||
|
||||
# check for rekey
|
||||
raw_packet_size = packet_size + self.__mac_size_in + 4
|
||||
self.__received_bytes += raw_packet_size
|
||||
self.__received_packets += 1
|
||||
if self.__need_rekey:
|
||||
# we've asked to rekey -- give them some packets to comply before
|
||||
# dropping the connection
|
||||
self.__received_bytes_overflow += raw_packet_size
|
||||
self.__received_packets_overflow += 1
|
||||
if (
|
||||
self.__received_packets_overflow
|
||||
>= self.REKEY_PACKETS_OVERFLOW_MAX
|
||||
) or (
|
||||
self.__received_bytes_overflow >= self.REKEY_BYTES_OVERFLOW_MAX
|
||||
):
|
||||
raise SSHException(
|
||||
"Remote transport is ignoring rekey requests"
|
||||
)
|
||||
elif (self.__received_packets >= self.REKEY_PACKETS) or (
|
||||
self.__received_bytes >= self.REKEY_BYTES
|
||||
):
|
||||
# only ask once for rekeying
|
||||
err = "Rekeying (hit {} packets, {} bytes received)"
|
||||
self._log(
|
||||
DEBUG,
|
||||
err.format(self.__received_packets, self.__received_bytes),
|
||||
)
|
||||
self.__received_bytes_overflow = 0
|
||||
self.__received_packets_overflow = 0
|
||||
self._trigger_rekey()
|
||||
|
||||
cmd = byte_ord(payload[0])
|
||||
if cmd in MSG_NAMES:
|
||||
cmd_name = MSG_NAMES[cmd]
|
||||
else:
|
||||
cmd_name = "${:x}".format(cmd)
|
||||
if self.__dump_packets:
|
||||
self._log(
|
||||
DEBUG,
|
||||
"Read packet <{}>, length {}".format(cmd_name, len(payload)),
|
||||
)
|
||||
return cmd, msg
|
||||
|
||||
# ...protected...
|
||||
|
||||
def _log(self, level, msg):
|
||||
if self.__logger is None:
|
||||
return
|
||||
if issubclass(type(msg), list):
|
||||
for m in msg:
|
||||
self.__logger.log(level, m)
|
||||
else:
|
||||
self.__logger.log(level, msg)
|
||||
|
||||
def _check_keepalive(self):
|
||||
if (
|
||||
not self.__keepalive_interval
|
||||
or not self.__block_engine_out
|
||||
or self.__need_rekey
|
||||
):
|
||||
# wait till we're encrypting, and not in the middle of rekeying
|
||||
return
|
||||
now = time.time()
|
||||
if now > self.__keepalive_last + self.__keepalive_interval:
|
||||
self.__keepalive_callback()
|
||||
self.__keepalive_last = now
|
||||
|
||||
def _read_timeout(self, timeout):
|
||||
start = time.time()
|
||||
while True:
|
||||
try:
|
||||
x = self.__socket.recv(128)
|
||||
if len(x) == 0:
|
||||
raise EOFError()
|
||||
break
|
||||
except socket.timeout:
|
||||
pass
|
||||
except EnvironmentError as e:
|
||||
if first_arg(e) == errno.EINTR:
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
if self.__closed:
|
||||
raise EOFError()
|
||||
now = time.time()
|
||||
if now - start >= timeout:
|
||||
raise socket.timeout()
|
||||
return x
|
||||
|
||||
def _build_packet(self, payload):
|
||||
# pad up at least 4 bytes, to nearest block-size (usually 8)
|
||||
bsize = self.__block_size_out
|
||||
padding = 3 + bsize - ((len(payload) + 8) % bsize)
|
||||
packet = struct.pack(">IB", len(payload) + padding + 1, padding)
|
||||
packet += payload
|
||||
if self.__sdctr_out or self.__block_engine_out is None:
|
||||
# cute trick i caught openssh doing: if we're not encrypting or
|
||||
# SDCTR mode (RFC4344),
|
||||
# don't waste random bytes for the padding
|
||||
packet += zero_byte * padding
|
||||
else:
|
||||
packet += os.urandom(padding)
|
||||
return packet
|
||||
|
||||
def _trigger_rekey(self):
|
||||
# outside code should check for this flag
|
||||
self.__need_rekey = True
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/packet.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/packet.pyc
Normal file
Binary file not shown.
148
.ve/lib/python2.7/site-packages/paramiko/pipe.py
Normal file
148
.ve/lib/python2.7/site-packages/paramiko/pipe.py
Normal file
@@ -0,0 +1,148 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Abstraction of a one-way pipe where the read end can be used in
|
||||
`select.select`. Normally this is trivial, but Windows makes it nearly
|
||||
impossible.
|
||||
|
||||
The pipe acts like an Event, which can be set or cleared. When set, the pipe
|
||||
will trigger as readable in `select <select.select>`.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import socket
|
||||
|
||||
|
||||
def make_pipe():
|
||||
if sys.platform[:3] != "win":
|
||||
p = PosixPipe()
|
||||
else:
|
||||
p = WindowsPipe()
|
||||
return p
|
||||
|
||||
|
||||
class PosixPipe(object):
|
||||
def __init__(self):
|
||||
self._rfd, self._wfd = os.pipe()
|
||||
self._set = False
|
||||
self._forever = False
|
||||
self._closed = False
|
||||
|
||||
def close(self):
|
||||
os.close(self._rfd)
|
||||
os.close(self._wfd)
|
||||
# used for unit tests:
|
||||
self._closed = True
|
||||
|
||||
def fileno(self):
|
||||
return self._rfd
|
||||
|
||||
def clear(self):
|
||||
if not self._set or self._forever:
|
||||
return
|
||||
os.read(self._rfd, 1)
|
||||
self._set = False
|
||||
|
||||
def set(self):
|
||||
if self._set or self._closed:
|
||||
return
|
||||
self._set = True
|
||||
os.write(self._wfd, b"*")
|
||||
|
||||
def set_forever(self):
|
||||
self._forever = True
|
||||
self.set()
|
||||
|
||||
|
||||
class WindowsPipe(object):
|
||||
"""
|
||||
On Windows, only an OS-level "WinSock" may be used in select(), but reads
|
||||
and writes must be to the actual socket object.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
serv.bind(("127.0.0.1", 0))
|
||||
serv.listen(1)
|
||||
|
||||
# need to save sockets in _rsock/_wsock so they don't get closed
|
||||
self._rsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._rsock.connect(("127.0.0.1", serv.getsockname()[1]))
|
||||
|
||||
self._wsock, addr = serv.accept()
|
||||
serv.close()
|
||||
self._set = False
|
||||
self._forever = False
|
||||
self._closed = False
|
||||
|
||||
def close(self):
|
||||
self._rsock.close()
|
||||
self._wsock.close()
|
||||
# used for unit tests:
|
||||
self._closed = True
|
||||
|
||||
def fileno(self):
|
||||
return self._rsock.fileno()
|
||||
|
||||
def clear(self):
|
||||
if not self._set or self._forever:
|
||||
return
|
||||
self._rsock.recv(1)
|
||||
self._set = False
|
||||
|
||||
def set(self):
|
||||
if self._set or self._closed:
|
||||
return
|
||||
self._set = True
|
||||
self._wsock.send(b"*")
|
||||
|
||||
def set_forever(self):
|
||||
self._forever = True
|
||||
self.set()
|
||||
|
||||
|
||||
class OrPipe(object):
|
||||
def __init__(self, pipe):
|
||||
self._set = False
|
||||
self._partner = None
|
||||
self._pipe = pipe
|
||||
|
||||
def set(self):
|
||||
self._set = True
|
||||
if not self._partner._set:
|
||||
self._pipe.set()
|
||||
|
||||
def clear(self):
|
||||
self._set = False
|
||||
if not self._partner._set:
|
||||
self._pipe.clear()
|
||||
|
||||
|
||||
def make_or_pipe(pipe):
|
||||
"""
|
||||
wraps a pipe into two pipe-like objects which are "or"d together to
|
||||
affect the real pipe. if either returned pipe is set, the wrapped pipe
|
||||
is set. when both are cleared, the wrapped pipe is cleared.
|
||||
"""
|
||||
p1 = OrPipe(pipe)
|
||||
p2 = OrPipe(pipe)
|
||||
p1._partner = p2
|
||||
p2._partner = p1
|
||||
return p1, p2
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/pipe.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/pipe.pyc
Normal file
Binary file not shown.
539
.ve/lib/python2.7/site-packages/paramiko/pkey.py
Normal file
539
.ve/lib/python2.7/site-packages/paramiko/pkey.py
Normal file
@@ -0,0 +1,539 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Common API for all public keys.
|
||||
"""
|
||||
|
||||
import base64
|
||||
from binascii import unhexlify
|
||||
import os
|
||||
from hashlib import md5
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.ciphers import algorithms, modes, Cipher
|
||||
|
||||
from paramiko import util
|
||||
from paramiko.common import o600
|
||||
from paramiko.py3compat import u, encodebytes, decodebytes, b, string_types
|
||||
from paramiko.ssh_exception import SSHException, PasswordRequiredException
|
||||
from paramiko.message import Message
|
||||
|
||||
|
||||
class PKey(object):
|
||||
"""
|
||||
Base class for public keys.
|
||||
"""
|
||||
|
||||
# known encryption types for private key files:
|
||||
_CIPHER_TABLE = {
|
||||
"AES-128-CBC": {
|
||||
"cipher": algorithms.AES,
|
||||
"keysize": 16,
|
||||
"blocksize": 16,
|
||||
"mode": modes.CBC,
|
||||
},
|
||||
"AES-256-CBC": {
|
||||
"cipher": algorithms.AES,
|
||||
"keysize": 32,
|
||||
"blocksize": 16,
|
||||
"mode": modes.CBC,
|
||||
},
|
||||
"DES-EDE3-CBC": {
|
||||
"cipher": algorithms.TripleDES,
|
||||
"keysize": 24,
|
||||
"blocksize": 8,
|
||||
"mode": modes.CBC,
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, msg=None, data=None):
|
||||
"""
|
||||
Create a new instance of this public key type. If ``msg`` is given,
|
||||
the key's public part(s) will be filled in from the message. If
|
||||
``data`` is given, the key's public part(s) will be filled in from
|
||||
the string.
|
||||
|
||||
:param .Message msg:
|
||||
an optional SSH `.Message` containing a public key of this type.
|
||||
:param str data: an optional string containing a public key
|
||||
of this type
|
||||
|
||||
:raises: `.SSHException` --
|
||||
if a key cannot be created from the ``data`` or ``msg`` given, or
|
||||
no key was passed in.
|
||||
"""
|
||||
pass
|
||||
|
||||
def asbytes(self):
|
||||
"""
|
||||
Return a string of an SSH `.Message` made up of the public part(s) of
|
||||
this key. This string is suitable for passing to `__init__` to
|
||||
re-create the key object later.
|
||||
"""
|
||||
return bytes()
|
||||
|
||||
def __str__(self):
|
||||
return self.asbytes()
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
# TODO: The comparison functions should be removed as per:
|
||||
# https://docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons
|
||||
def __cmp__(self, other):
|
||||
"""
|
||||
Compare this key to another. Returns 0 if this key is equivalent to
|
||||
the given key, or non-0 if they are different. Only the public parts
|
||||
of the key are compared, so a public key will compare equal to its
|
||||
corresponding private key.
|
||||
|
||||
:param .PKey other: key to compare to.
|
||||
"""
|
||||
hs = hash(self)
|
||||
ho = hash(other)
|
||||
if hs != ho:
|
||||
return cmp(hs, ho) # noqa
|
||||
return cmp(self.asbytes(), other.asbytes()) # noqa
|
||||
|
||||
def __eq__(self, other):
|
||||
return hash(self) == hash(other)
|
||||
|
||||
def get_name(self):
|
||||
"""
|
||||
Return the name of this private key implementation.
|
||||
|
||||
:return:
|
||||
name of this private key type, in SSH terminology, as a `str` (for
|
||||
example, ``"ssh-rsa"``).
|
||||
"""
|
||||
return ""
|
||||
|
||||
def get_bits(self):
|
||||
"""
|
||||
Return the number of significant bits in this key. This is useful
|
||||
for judging the relative security of a key.
|
||||
|
||||
:return: bits in the key (as an `int`)
|
||||
"""
|
||||
return 0
|
||||
|
||||
def can_sign(self):
|
||||
"""
|
||||
Return ``True`` if this key has the private part necessary for signing
|
||||
data.
|
||||
"""
|
||||
return False
|
||||
|
||||
def get_fingerprint(self):
|
||||
"""
|
||||
Return an MD5 fingerprint of the public part of this key. Nothing
|
||||
secret is revealed.
|
||||
|
||||
:return:
|
||||
a 16-byte `string <str>` (binary) of the MD5 fingerprint, in SSH
|
||||
format.
|
||||
"""
|
||||
return md5(self.asbytes()).digest()
|
||||
|
||||
def get_base64(self):
|
||||
"""
|
||||
Return a base64 string containing the public part of this key. Nothing
|
||||
secret is revealed. This format is compatible with that used to store
|
||||
public key files or recognized host keys.
|
||||
|
||||
:return: a base64 `string <str>` containing the public part of the key.
|
||||
"""
|
||||
return u(encodebytes(self.asbytes())).replace("\n", "")
|
||||
|
||||
def sign_ssh_data(self, data):
|
||||
"""
|
||||
Sign a blob of data with this private key, and return a `.Message`
|
||||
representing an SSH signature message.
|
||||
|
||||
:param str data: the data to sign.
|
||||
:return: an SSH signature `message <.Message>`.
|
||||
"""
|
||||
return bytes()
|
||||
|
||||
def verify_ssh_sig(self, data, msg):
|
||||
"""
|
||||
Given a blob of data, and an SSH message representing a signature of
|
||||
that data, verify that it was signed with this key.
|
||||
|
||||
:param str data: the data that was signed.
|
||||
:param .Message msg: an SSH signature message
|
||||
:return:
|
||||
``True`` if the signature verifies correctly; ``False`` otherwise.
|
||||
"""
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def from_private_key_file(cls, filename, password=None):
|
||||
"""
|
||||
Create a key object by reading a private key file. If the private
|
||||
key is encrypted and ``password`` is not ``None``, the given password
|
||||
will be used to decrypt the key (otherwise `.PasswordRequiredException`
|
||||
is thrown). Through the magic of Python, this factory method will
|
||||
exist in all subclasses of PKey (such as `.RSAKey` or `.DSSKey`), but
|
||||
is useless on the abstract PKey class.
|
||||
|
||||
:param str filename: name of the file to read
|
||||
:param str password:
|
||||
an optional password to use to decrypt the key file, if it's
|
||||
encrypted
|
||||
:return: a new `.PKey` based on the given private key
|
||||
|
||||
:raises: ``IOError`` -- if there was an error reading the file
|
||||
:raises: `.PasswordRequiredException` -- if the private key file is
|
||||
encrypted, and ``password`` is ``None``
|
||||
:raises: `.SSHException` -- if the key file is invalid
|
||||
"""
|
||||
key = cls(filename=filename, password=password)
|
||||
return key
|
||||
|
||||
@classmethod
|
||||
def from_private_key(cls, file_obj, password=None):
|
||||
"""
|
||||
Create a key object by reading a private key from a file (or file-like)
|
||||
object. If the private key is encrypted and ``password`` is not
|
||||
``None``, the given password will be used to decrypt the key (otherwise
|
||||
`.PasswordRequiredException` is thrown).
|
||||
|
||||
:param file_obj: the file-like object to read from
|
||||
:param str password:
|
||||
an optional password to use to decrypt the key, if it's encrypted
|
||||
:return: a new `.PKey` based on the given private key
|
||||
|
||||
:raises: ``IOError`` -- if there was an error reading the key
|
||||
:raises: `.PasswordRequiredException` --
|
||||
if the private key file is encrypted, and ``password`` is ``None``
|
||||
:raises: `.SSHException` -- if the key file is invalid
|
||||
"""
|
||||
key = cls(file_obj=file_obj, password=password)
|
||||
return key
|
||||
|
||||
def write_private_key_file(self, filename, password=None):
|
||||
"""
|
||||
Write private key contents into a file. If the password is not
|
||||
``None``, the key is encrypted before writing.
|
||||
|
||||
:param str filename: name of the file to write
|
||||
:param str password:
|
||||
an optional password to use to encrypt the key file
|
||||
|
||||
:raises: ``IOError`` -- if there was an error writing the file
|
||||
:raises: `.SSHException` -- if the key is invalid
|
||||
"""
|
||||
raise Exception("Not implemented in PKey")
|
||||
|
||||
def write_private_key(self, file_obj, password=None):
|
||||
"""
|
||||
Write private key contents into a file (or file-like) object. If the
|
||||
password is not ``None``, the key is encrypted before writing.
|
||||
|
||||
:param file_obj: the file-like object to write into
|
||||
:param str password: an optional password to use to encrypt the key
|
||||
|
||||
:raises: ``IOError`` -- if there was an error writing to the file
|
||||
:raises: `.SSHException` -- if the key is invalid
|
||||
"""
|
||||
raise Exception("Not implemented in PKey")
|
||||
|
||||
def _read_private_key_file(self, tag, filename, password=None):
|
||||
"""
|
||||
Read an SSH2-format private key file, looking for a string of the type
|
||||
``"BEGIN xxx PRIVATE KEY"`` for some ``xxx``, base64-decode the text we
|
||||
find, and return it as a string. If the private key is encrypted and
|
||||
``password`` is not ``None``, the given password will be used to
|
||||
decrypt the key (otherwise `.PasswordRequiredException` is thrown).
|
||||
|
||||
:param str tag: ``"RSA"`` or ``"DSA"``, the tag used to mark the
|
||||
data block.
|
||||
:param str filename: name of the file to read.
|
||||
:param str password:
|
||||
an optional password to use to decrypt the key file, if it's
|
||||
encrypted.
|
||||
:return: data blob (`str`) that makes up the private key.
|
||||
|
||||
:raises: ``IOError`` -- if there was an error reading the file.
|
||||
:raises: `.PasswordRequiredException` -- if the private key file is
|
||||
encrypted, and ``password`` is ``None``.
|
||||
:raises: `.SSHException` -- if the key file is invalid.
|
||||
"""
|
||||
with open(filename, "r") as f:
|
||||
data = self._read_private_key(tag, f, password)
|
||||
return data
|
||||
|
||||
def _read_private_key(self, tag, f, password=None):
|
||||
lines = f.readlines()
|
||||
start = 0
|
||||
beginning_of_key = "-----BEGIN " + tag + " PRIVATE KEY-----"
|
||||
while start < len(lines) and lines[start].strip() != beginning_of_key:
|
||||
start += 1
|
||||
if start >= len(lines):
|
||||
raise SSHException("not a valid " + tag + " private key file")
|
||||
# parse any headers first
|
||||
headers = {}
|
||||
start += 1
|
||||
while start < len(lines):
|
||||
l = lines[start].split(": ")
|
||||
if len(l) == 1:
|
||||
break
|
||||
headers[l[0].lower()] = l[1].strip()
|
||||
start += 1
|
||||
# find end
|
||||
end = start
|
||||
ending_of_key = "-----END " + tag + " PRIVATE KEY-----"
|
||||
while end < len(lines) and lines[end].strip() != ending_of_key:
|
||||
end += 1
|
||||
# if we trudged to the end of the file, just try to cope.
|
||||
try:
|
||||
data = decodebytes(b("".join(lines[start:end])))
|
||||
except base64.binascii.Error as e:
|
||||
raise SSHException("base64 decoding error: " + str(e))
|
||||
if "proc-type" not in headers:
|
||||
# unencryped: done
|
||||
return data
|
||||
# encrypted keyfile: will need a password
|
||||
proc_type = headers["proc-type"]
|
||||
if proc_type != "4,ENCRYPTED":
|
||||
raise SSHException(
|
||||
'Unknown private key structure "{}"'.format(proc_type)
|
||||
)
|
||||
try:
|
||||
encryption_type, saltstr = headers["dek-info"].split(",")
|
||||
except:
|
||||
raise SSHException("Can't parse DEK-info in private key file")
|
||||
if encryption_type not in self._CIPHER_TABLE:
|
||||
raise SSHException(
|
||||
'Unknown private key cipher "{}"'.format(encryption_type)
|
||||
)
|
||||
# if no password was passed in,
|
||||
# raise an exception pointing out that we need one
|
||||
if password is None:
|
||||
raise PasswordRequiredException("Private key file is encrypted")
|
||||
cipher = self._CIPHER_TABLE[encryption_type]["cipher"]
|
||||
keysize = self._CIPHER_TABLE[encryption_type]["keysize"]
|
||||
mode = self._CIPHER_TABLE[encryption_type]["mode"]
|
||||
salt = unhexlify(b(saltstr))
|
||||
key = util.generate_key_bytes(md5, salt, password, keysize)
|
||||
decryptor = Cipher(
|
||||
cipher(key), mode(salt), backend=default_backend()
|
||||
).decryptor()
|
||||
return decryptor.update(data) + decryptor.finalize()
|
||||
|
||||
def _write_private_key_file(self, filename, key, format, password=None):
|
||||
"""
|
||||
Write an SSH2-format private key file in a form that can be read by
|
||||
paramiko or openssh. If no password is given, the key is written in
|
||||
a trivially-encoded format (base64) which is completely insecure. If
|
||||
a password is given, DES-EDE3-CBC is used.
|
||||
|
||||
:param str tag:
|
||||
``"RSA"`` or ``"DSA"``, the tag used to mark the data block.
|
||||
:param filename: name of the file to write.
|
||||
:param str data: data blob that makes up the private key.
|
||||
:param str password: an optional password to use to encrypt the file.
|
||||
|
||||
:raises: ``IOError`` -- if there was an error writing the file.
|
||||
"""
|
||||
with open(filename, "w") as f:
|
||||
os.chmod(filename, o600)
|
||||
self._write_private_key(f, key, format, password=password)
|
||||
|
||||
def _write_private_key(self, f, key, format, password=None):
|
||||
if password is None:
|
||||
encryption = serialization.NoEncryption()
|
||||
else:
|
||||
encryption = serialization.BestAvailableEncryption(b(password))
|
||||
|
||||
f.write(
|
||||
key.private_bytes(
|
||||
serialization.Encoding.PEM, format, encryption
|
||||
).decode()
|
||||
)
|
||||
|
||||
def _check_type_and_load_cert(self, msg, key_type, cert_type):
|
||||
"""
|
||||
Perform message type-checking & optional certificate loading.
|
||||
|
||||
This includes fast-forwarding cert ``msg`` objects past the nonce, so
|
||||
that the subsequent fields are the key numbers; thus the caller may
|
||||
expect to treat the message as key material afterwards either way.
|
||||
|
||||
The obtained key type is returned for classes which need to know what
|
||||
it was (e.g. ECDSA.)
|
||||
"""
|
||||
# Normalization; most classes have a single key type and give a string,
|
||||
# but eg ECDSA is a 1:N mapping.
|
||||
key_types = key_type
|
||||
cert_types = cert_type
|
||||
if isinstance(key_type, string_types):
|
||||
key_types = [key_types]
|
||||
if isinstance(cert_types, string_types):
|
||||
cert_types = [cert_types]
|
||||
# Can't do much with no message, that should've been handled elsewhere
|
||||
if msg is None:
|
||||
raise SSHException("Key object may not be empty")
|
||||
# First field is always key type, in either kind of object. (make sure
|
||||
# we rewind before grabbing it - sometimes caller had to do their own
|
||||
# introspection first!)
|
||||
msg.rewind()
|
||||
type_ = msg.get_text()
|
||||
# Regular public key - nothing special to do besides the implicit
|
||||
# type check.
|
||||
if type_ in key_types:
|
||||
pass
|
||||
# OpenSSH-compatible certificate - store full copy as .public_blob
|
||||
# (so signing works correctly) and then fast-forward past the
|
||||
# nonce.
|
||||
elif type_ in cert_types:
|
||||
# This seems the cleanest way to 'clone' an already-being-read
|
||||
# message; they're *IO objects at heart and their .getvalue()
|
||||
# always returns the full value regardless of pointer position.
|
||||
self.load_certificate(Message(msg.asbytes()))
|
||||
# Read out nonce as it comes before the public numbers.
|
||||
# TODO: usefully interpret it & other non-public-number fields
|
||||
# (requires going back into per-type subclasses.)
|
||||
msg.get_string()
|
||||
else:
|
||||
err = "Invalid key (class: {}, data type: {}"
|
||||
raise SSHException(err.format(self.__class__.__name__, type_))
|
||||
|
||||
def load_certificate(self, value):
|
||||
"""
|
||||
Supplement the private key contents with data loaded from an OpenSSH
|
||||
public key (``.pub``) or certificate (``-cert.pub``) file, a string
|
||||
containing such a file, or a `.Message` object.
|
||||
|
||||
The .pub contents adds no real value, since the private key
|
||||
file includes sufficient information to derive the public
|
||||
key info. For certificates, however, this can be used on
|
||||
the client side to offer authentication requests to the server
|
||||
based on certificate instead of raw public key.
|
||||
|
||||
See:
|
||||
https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.certkeys
|
||||
|
||||
Note: very little effort is made to validate the certificate contents,
|
||||
that is for the server to decide if it is good enough to authenticate
|
||||
successfully.
|
||||
"""
|
||||
if isinstance(value, Message):
|
||||
constructor = "from_message"
|
||||
elif os.path.isfile(value):
|
||||
constructor = "from_file"
|
||||
else:
|
||||
constructor = "from_string"
|
||||
blob = getattr(PublicBlob, constructor)(value)
|
||||
if not blob.key_type.startswith(self.get_name()):
|
||||
err = "PublicBlob type {} incompatible with key type {}"
|
||||
raise ValueError(err.format(blob.key_type, self.get_name()))
|
||||
self.public_blob = blob
|
||||
|
||||
|
||||
# General construct for an OpenSSH style Public Key blob
|
||||
# readable from a one-line file of the format:
|
||||
# <key-name> <base64-blob> [<comment>]
|
||||
# Of little value in the case of standard public keys
|
||||
# {ssh-rsa, ssh-dss, ssh-ecdsa, ssh-ed25519}, but should
|
||||
# provide rudimentary support for {*-cert.v01}
|
||||
class PublicBlob(object):
|
||||
"""
|
||||
OpenSSH plain public key or OpenSSH signed public key (certificate).
|
||||
|
||||
Tries to be as dumb as possible and barely cares about specific
|
||||
per-key-type data.
|
||||
|
||||
..note::
|
||||
Most of the time you'll want to call `from_file`, `from_string` or
|
||||
`from_message` for useful instantiation, the main constructor is
|
||||
basically "I should be using ``attrs`` for this."
|
||||
"""
|
||||
|
||||
def __init__(self, type_, blob, comment=None):
|
||||
"""
|
||||
Create a new public blob of given type and contents.
|
||||
|
||||
:param str type_: Type indicator, eg ``ssh-rsa``.
|
||||
:param blob: The blob bytes themselves.
|
||||
:param str comment: A comment, if one was given (e.g. file-based.)
|
||||
"""
|
||||
self.key_type = type_
|
||||
self.key_blob = blob
|
||||
self.comment = comment
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, filename):
|
||||
"""
|
||||
Create a public blob from a ``-cert.pub``-style file on disk.
|
||||
"""
|
||||
with open(filename) as f:
|
||||
string = f.read()
|
||||
return cls.from_string(string)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, string):
|
||||
"""
|
||||
Create a public blob from a ``-cert.pub``-style string.
|
||||
"""
|
||||
fields = string.split(None, 2)
|
||||
if len(fields) < 2:
|
||||
msg = "Not enough fields for public blob: {}"
|
||||
raise ValueError(msg.format(fields))
|
||||
key_type = fields[0]
|
||||
key_blob = decodebytes(b(fields[1]))
|
||||
try:
|
||||
comment = fields[2].strip()
|
||||
except IndexError:
|
||||
comment = None
|
||||
# Verify that the blob message first (string) field matches the
|
||||
# key_type
|
||||
m = Message(key_blob)
|
||||
blob_type = m.get_text()
|
||||
if blob_type != key_type:
|
||||
deets = "key type={!r}, but blob type={!r}".format(
|
||||
key_type, blob_type
|
||||
)
|
||||
raise ValueError("Invalid PublicBlob contents: {}".format(deets))
|
||||
# All good? All good.
|
||||
return cls(type_=key_type, blob=key_blob, comment=comment)
|
||||
|
||||
@classmethod
|
||||
def from_message(cls, message):
|
||||
"""
|
||||
Create a public blob from a network `.Message`.
|
||||
|
||||
Specifically, a cert-bearing pubkey auth packet, because by definition
|
||||
OpenSSH-style certificates 'are' their own network representation."
|
||||
"""
|
||||
type_ = message.get_text()
|
||||
return cls(type_=type_, blob=message.asbytes())
|
||||
|
||||
def __str__(self):
|
||||
ret = "{} public key/certificate".format(self.key_type)
|
||||
if self.comment:
|
||||
ret += "- {}".format(self.comment)
|
||||
return ret
|
||||
|
||||
def __eq__(self, other):
|
||||
# Just piggyback on Message/BytesIO, since both of these should be one.
|
||||
return self and other and self.key_blob == other.key_blob
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/pkey.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/pkey.pyc
Normal file
Binary file not shown.
148
.ve/lib/python2.7/site-packages/paramiko/primes.py
Normal file
148
.ve/lib/python2.7/site-packages/paramiko/primes.py
Normal file
@@ -0,0 +1,148 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Utility functions for dealing with primes.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from paramiko import util
|
||||
from paramiko.py3compat import byte_mask, long
|
||||
from paramiko.ssh_exception import SSHException
|
||||
|
||||
|
||||
def _roll_random(n):
|
||||
"""returns a random # from 0 to N-1"""
|
||||
bits = util.bit_length(n - 1)
|
||||
byte_count = (bits + 7) // 8
|
||||
hbyte_mask = pow(2, bits % 8) - 1
|
||||
|
||||
# so here's the plan:
|
||||
# we fetch as many random bits as we'd need to fit N-1, and if the
|
||||
# generated number is >= N, we try again. in the worst case (N-1 is a
|
||||
# power of 2), we have slightly better than 50% odds of getting one that
|
||||
# fits, so i can't guarantee that this loop will ever finish, but the odds
|
||||
# of it looping forever should be infinitesimal.
|
||||
while True:
|
||||
x = os.urandom(byte_count)
|
||||
if hbyte_mask > 0:
|
||||
x = byte_mask(x[0], hbyte_mask) + x[1:]
|
||||
num = util.inflate_long(x, 1)
|
||||
if num < n:
|
||||
break
|
||||
return num
|
||||
|
||||
|
||||
class ModulusPack(object):
|
||||
"""
|
||||
convenience object for holding the contents of the /etc/ssh/moduli file,
|
||||
on systems that have such a file.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# pack is a hash of: bits -> [ (generator, modulus) ... ]
|
||||
self.pack = {}
|
||||
self.discarded = []
|
||||
|
||||
def _parse_modulus(self, line):
|
||||
(
|
||||
timestamp,
|
||||
mod_type,
|
||||
tests,
|
||||
tries,
|
||||
size,
|
||||
generator,
|
||||
modulus,
|
||||
) = line.split()
|
||||
mod_type = int(mod_type)
|
||||
tests = int(tests)
|
||||
tries = int(tries)
|
||||
size = int(size)
|
||||
generator = int(generator)
|
||||
modulus = long(modulus, 16)
|
||||
|
||||
# weed out primes that aren't at least:
|
||||
# type 2 (meets basic structural requirements)
|
||||
# test 4 (more than just a small-prime sieve)
|
||||
# tries < 100 if test & 4 (at least 100 tries of miller-rabin)
|
||||
if (
|
||||
mod_type < 2
|
||||
or tests < 4
|
||||
or (tests & 4 and tests < 8 and tries < 100)
|
||||
):
|
||||
self.discarded.append(
|
||||
(modulus, "does not meet basic requirements")
|
||||
)
|
||||
return
|
||||
if generator == 0:
|
||||
generator = 2
|
||||
|
||||
# there's a bug in the ssh "moduli" file (yeah, i know: shock! dismay!
|
||||
# call cnn!) where it understates the bit lengths of these primes by 1.
|
||||
# this is okay.
|
||||
bl = util.bit_length(modulus)
|
||||
if (bl != size) and (bl != size + 1):
|
||||
self.discarded.append(
|
||||
(modulus, "incorrectly reported bit length {}".format(size))
|
||||
)
|
||||
return
|
||||
if bl not in self.pack:
|
||||
self.pack[bl] = []
|
||||
self.pack[bl].append((generator, modulus))
|
||||
|
||||
def read_file(self, filename):
|
||||
"""
|
||||
:raises IOError: passed from any file operations that fail.
|
||||
"""
|
||||
self.pack = {}
|
||||
with open(filename, "r") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if (len(line) == 0) or (line[0] == "#"):
|
||||
continue
|
||||
try:
|
||||
self._parse_modulus(line)
|
||||
except:
|
||||
continue
|
||||
|
||||
def get_modulus(self, min, prefer, max):
|
||||
bitsizes = sorted(self.pack.keys())
|
||||
if len(bitsizes) == 0:
|
||||
raise SSHException("no moduli available")
|
||||
good = -1
|
||||
# find nearest bitsize >= preferred
|
||||
for b in bitsizes:
|
||||
if (b >= prefer) and (b <= max) and (b < good or good == -1):
|
||||
good = b
|
||||
# if that failed, find greatest bitsize >= min
|
||||
if good == -1:
|
||||
for b in bitsizes:
|
||||
if (b >= min) and (b <= max) and (b > good):
|
||||
good = b
|
||||
if good == -1:
|
||||
# their entire (min, max) range has no intersection with our range.
|
||||
# if their range is below ours, pick the smallest. otherwise pick
|
||||
# the largest. it'll be out of their range requirement either way,
|
||||
# but we'll be sending them the closest one we have.
|
||||
good = bitsizes[0]
|
||||
if min > good:
|
||||
good = bitsizes[-1]
|
||||
# now pick a random modulus of this bitsize
|
||||
n = _roll_random(len(self.pack[good]))
|
||||
return self.pack[good][n]
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/primes.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/primes.pyc
Normal file
Binary file not shown.
124
.ve/lib/python2.7/site-packages/paramiko/proxy.py
Normal file
124
.ve/lib/python2.7/site-packages/paramiko/proxy.py
Normal file
@@ -0,0 +1,124 @@
|
||||
# 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 distrubuted 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
|
||||
import os
|
||||
from shlex import split as shlsplit
|
||||
import signal
|
||||
from select import select
|
||||
import socket
|
||||
import time
|
||||
|
||||
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.
|
||||
"""
|
||||
# NOTE: subprocess import done lazily so platforms without it (e.g.
|
||||
# GAE) can still import us during overall Paramiko load.
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
self.cmd = shlsplit(command_line)
|
||||
self.process = Popen(
|
||||
self.cmd, stdin=PIPE, stdout=PIPE, stderr=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
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/proxy.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/proxy.pyc
Normal file
Binary file not shown.
178
.ve/lib/python2.7/site-packages/paramiko/py3compat.py
Normal file
178
.ve/lib/python2.7/site-packages/paramiko/py3compat.py
Normal file
@@ -0,0 +1,178 @@
|
||||
import sys
|
||||
import base64
|
||||
|
||||
__all__ = [
|
||||
"PY2",
|
||||
"string_types",
|
||||
"integer_types",
|
||||
"text_type",
|
||||
"bytes_types",
|
||||
"bytes",
|
||||
"long",
|
||||
"input",
|
||||
"decodebytes",
|
||||
"encodebytes",
|
||||
"bytestring",
|
||||
"byte_ord",
|
||||
"byte_chr",
|
||||
"byte_mask",
|
||||
"b",
|
||||
"u",
|
||||
"b2s",
|
||||
"StringIO",
|
||||
"BytesIO",
|
||||
"is_callable",
|
||||
"MAXSIZE",
|
||||
"next",
|
||||
"builtins",
|
||||
]
|
||||
|
||||
PY2 = sys.version_info[0] < 3
|
||||
|
||||
if PY2:
|
||||
string_types = basestring # NOQA
|
||||
text_type = unicode # NOQA
|
||||
bytes_types = str
|
||||
bytes = str
|
||||
integer_types = (int, long) # NOQA
|
||||
long = long # NOQA
|
||||
input = raw_input # NOQA
|
||||
decodebytes = base64.decodestring
|
||||
encodebytes = base64.encodestring
|
||||
|
||||
import __builtin__ as builtins
|
||||
|
||||
def bytestring(s): # NOQA
|
||||
if isinstance(s, unicode): # NOQA
|
||||
return s.encode("utf-8")
|
||||
return s
|
||||
|
||||
byte_ord = ord # NOQA
|
||||
byte_chr = chr # NOQA
|
||||
|
||||
def byte_mask(c, mask):
|
||||
return chr(ord(c) & mask)
|
||||
|
||||
def b(s, encoding="utf8"): # NOQA
|
||||
"""cast unicode or bytes to bytes"""
|
||||
if isinstance(s, str):
|
||||
return s
|
||||
elif isinstance(s, unicode): # NOQA
|
||||
return s.encode(encoding)
|
||||
elif isinstance(s, buffer): # NOQA
|
||||
return s
|
||||
else:
|
||||
raise TypeError("Expected unicode or bytes, got {!r}".format(s))
|
||||
|
||||
def u(s, encoding="utf8"): # NOQA
|
||||
"""cast bytes or unicode to unicode"""
|
||||
if isinstance(s, str):
|
||||
return s.decode(encoding)
|
||||
elif isinstance(s, unicode): # NOQA
|
||||
return s
|
||||
elif isinstance(s, buffer): # NOQA
|
||||
return s.decode(encoding)
|
||||
else:
|
||||
raise TypeError("Expected unicode or bytes, got {!r}".format(s))
|
||||
|
||||
def b2s(s):
|
||||
return s
|
||||
|
||||
import cStringIO
|
||||
|
||||
StringIO = cStringIO.StringIO
|
||||
BytesIO = StringIO
|
||||
|
||||
def is_callable(c): # NOQA
|
||||
return callable(c)
|
||||
|
||||
def get_next(c): # NOQA
|
||||
return c.next
|
||||
|
||||
def next(c):
|
||||
return c.next()
|
||||
|
||||
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
|
||||
class X(object):
|
||||
def __len__(self):
|
||||
return 1 << 31
|
||||
|
||||
try:
|
||||
len(X())
|
||||
except OverflowError:
|
||||
# 32-bit
|
||||
MAXSIZE = int((1 << 31) - 1) # NOQA
|
||||
else:
|
||||
# 64-bit
|
||||
MAXSIZE = int((1 << 63) - 1) # NOQA
|
||||
del X
|
||||
else:
|
||||
import collections
|
||||
import struct
|
||||
import builtins
|
||||
|
||||
string_types = str
|
||||
text_type = str
|
||||
bytes = bytes
|
||||
bytes_types = bytes
|
||||
integer_types = int
|
||||
|
||||
class long(int):
|
||||
pass
|
||||
|
||||
input = input
|
||||
decodebytes = base64.decodebytes
|
||||
encodebytes = base64.encodebytes
|
||||
|
||||
def bytestring(s):
|
||||
return s
|
||||
|
||||
def byte_ord(c):
|
||||
# In case we're handed a string instead of an int.
|
||||
if not isinstance(c, int):
|
||||
c = ord(c)
|
||||
return c
|
||||
|
||||
def byte_chr(c):
|
||||
assert isinstance(c, int)
|
||||
return struct.pack("B", c)
|
||||
|
||||
def byte_mask(c, mask):
|
||||
assert isinstance(c, int)
|
||||
return struct.pack("B", c & mask)
|
||||
|
||||
def b(s, encoding="utf8"):
|
||||
"""cast unicode or bytes to bytes"""
|
||||
if isinstance(s, bytes):
|
||||
return s
|
||||
elif isinstance(s, str):
|
||||
return s.encode(encoding)
|
||||
else:
|
||||
raise TypeError("Expected unicode or bytes, got {!r}".format(s))
|
||||
|
||||
def u(s, encoding="utf8"):
|
||||
"""cast bytes or unicode to unicode"""
|
||||
if isinstance(s, bytes):
|
||||
return s.decode(encoding)
|
||||
elif isinstance(s, str):
|
||||
return s
|
||||
else:
|
||||
raise TypeError("Expected unicode or bytes, got {!r}".format(s))
|
||||
|
||||
def b2s(s):
|
||||
return s.decode() if isinstance(s, bytes) else s
|
||||
|
||||
import io
|
||||
|
||||
StringIO = io.StringIO # NOQA
|
||||
BytesIO = io.BytesIO # NOQA
|
||||
|
||||
def is_callable(c):
|
||||
return isinstance(c, collections.Callable)
|
||||
|
||||
def get_next(c):
|
||||
return c.__next__
|
||||
|
||||
next = next
|
||||
|
||||
MAXSIZE = sys.maxsize # NOQA
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/py3compat.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/py3compat.pyc
Normal file
Binary file not shown.
191
.ve/lib/python2.7/site-packages/paramiko/rsakey.py
Normal file
191
.ve/lib/python2.7/site-packages/paramiko/rsakey.py
Normal file
@@ -0,0 +1,191 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
RSA keys.
|
||||
"""
|
||||
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
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.py3compat import PY2
|
||||
from paramiko.ssh_exception import SSHException
|
||||
|
||||
|
||||
class RSAKey(PKey):
|
||||
"""
|
||||
Representation of an RSA key which can be used to sign and verify SSH2
|
||||
data.
|
||||
"""
|
||||
|
||||
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,
|
||||
key_type="ssh-rsa",
|
||||
cert_type="ssh-rsa-cert-v01@openssh.com",
|
||||
)
|
||||
self.key = rsa.RSAPublicNumbers(
|
||||
e=msg.get_mpint(), n=msg.get_mpint()
|
||||
).public_key(default_backend())
|
||||
|
||||
@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("ssh-rsa")
|
||||
m.add_mpint(self.public_numbers.e)
|
||||
m.add_mpint(self.public_numbers.n)
|
||||
return m.asbytes()
|
||||
|
||||
def __str__(self):
|
||||
# NOTE: as per inane commentary in #853, this appears to be the least
|
||||
# crummy way to get a representation that prints identical to Python
|
||||
# 2's previous behavior, on both interpreters.
|
||||
# TODO: replace with a nice clean fingerprint display or something
|
||||
if PY2:
|
||||
# Can't just return the .decode below for Py2 because stuff still
|
||||
# tries stuffing it into ASCII for whatever godforsaken reason
|
||||
return self.asbytes()
|
||||
else:
|
||||
return self.asbytes().decode("utf8", errors="ignore")
|
||||
|
||||
def __hash__(self):
|
||||
return hash(
|
||||
(self.get_name(), self.public_numbers.e, self.public_numbers.n)
|
||||
)
|
||||
|
||||
def get_name(self):
|
||||
return "ssh-rsa"
|
||||
|
||||
def get_bits(self):
|
||||
return self.size
|
||||
|
||||
def can_sign(self):
|
||||
return isinstance(self.key, rsa.RSAPrivateKey)
|
||||
|
||||
def sign_ssh_data(self, data):
|
||||
sig = self.key.sign(
|
||||
data, padding=padding.PKCS1v15(), algorithm=hashes.SHA1()
|
||||
)
|
||||
|
||||
m = Message()
|
||||
m.add_string("ssh-rsa")
|
||||
m.add_string(sig)
|
||||
return m
|
||||
|
||||
def verify_ssh_sig(self, data, msg):
|
||||
if msg.get_text() != "ssh-rsa":
|
||||
return False
|
||||
key = self.key
|
||||
if isinstance(key, rsa.RSAPrivateKey):
|
||||
key = key.public_key()
|
||||
|
||||
try:
|
||||
key.verify(
|
||||
msg.get_binary(), data, padding.PKCS1v15(), hashes.SHA1()
|
||||
)
|
||||
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):
|
||||
try:
|
||||
key = serialization.load_der_private_key(
|
||||
data, password=None, backend=default_backend()
|
||||
)
|
||||
except ValueError as e:
|
||||
raise SSHException(str(e))
|
||||
|
||||
assert isinstance(key, rsa.RSAPrivateKey)
|
||||
self.key = key
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/rsakey.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/rsakey.pyc
Normal file
Binary file not shown.
730
.ve/lib/python2.7/site-packages/paramiko/server.py
Normal file
730
.ve/lib/python2.7/site-packages/paramiko/server.py
Normal file
@@ -0,0 +1,730 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
`.ServerInterface` is an interface to override for server support.
|
||||
"""
|
||||
|
||||
import threading
|
||||
from paramiko import util
|
||||
from paramiko.common import (
|
||||
DEBUG,
|
||||
ERROR,
|
||||
OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED,
|
||||
AUTH_FAILED,
|
||||
AUTH_SUCCESSFUL,
|
||||
)
|
||||
from paramiko.py3compat import string_types
|
||||
|
||||
|
||||
class ServerInterface(object):
|
||||
"""
|
||||
This class defines an interface for controlling the behavior of Paramiko
|
||||
in server mode.
|
||||
|
||||
Methods on this class are called from Paramiko's primary thread, so you
|
||||
shouldn't do too much work in them. (Certainly nothing that blocks or
|
||||
sleeps.)
|
||||
"""
|
||||
|
||||
def check_channel_request(self, kind, chanid):
|
||||
"""
|
||||
Determine if a channel request of a given type will be granted, and
|
||||
return ``OPEN_SUCCEEDED`` or an error code. This method is
|
||||
called in server mode when the client requests a channel, after
|
||||
authentication is complete.
|
||||
|
||||
If you allow channel requests (and an ssh server that didn't would be
|
||||
useless), you should also override some of the channel request methods
|
||||
below, which are used to determine which services will be allowed on
|
||||
a given channel:
|
||||
|
||||
- `check_channel_pty_request`
|
||||
- `check_channel_shell_request`
|
||||
- `check_channel_subsystem_request`
|
||||
- `check_channel_window_change_request`
|
||||
- `check_channel_x11_request`
|
||||
- `check_channel_forward_agent_request`
|
||||
|
||||
The ``chanid`` parameter is a small number that uniquely identifies the
|
||||
channel within a `.Transport`. A `.Channel` object is not created
|
||||
unless this method returns ``OPEN_SUCCEEDED`` -- once a
|
||||
`.Channel` object is created, you can call `.Channel.get_id` to
|
||||
retrieve the channel ID.
|
||||
|
||||
The return value should either be ``OPEN_SUCCEEDED`` (or
|
||||
``0``) to allow the channel request, or one of the following error
|
||||
codes to reject it:
|
||||
|
||||
- ``OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED``
|
||||
- ``OPEN_FAILED_CONNECT_FAILED``
|
||||
- ``OPEN_FAILED_UNKNOWN_CHANNEL_TYPE``
|
||||
- ``OPEN_FAILED_RESOURCE_SHORTAGE``
|
||||
|
||||
The default implementation always returns
|
||||
``OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED``.
|
||||
|
||||
:param str kind:
|
||||
the kind of channel the client would like to open (usually
|
||||
``"session"``).
|
||||
:param int chanid: ID of the channel
|
||||
:return: an `int` success or failure code (listed above)
|
||||
"""
|
||||
return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
||||
|
||||
def get_allowed_auths(self, username):
|
||||
"""
|
||||
Return a list of authentication methods supported by the server.
|
||||
This list is sent to clients attempting to authenticate, to inform them
|
||||
of authentication methods that might be successful.
|
||||
|
||||
The "list" is actually a string of comma-separated names of types of
|
||||
authentication. Possible values are ``"password"``, ``"publickey"``,
|
||||
and ``"none"``.
|
||||
|
||||
The default implementation always returns ``"password"``.
|
||||
|
||||
:param str username: the username requesting authentication.
|
||||
:return: a comma-separated `str` of authentication types
|
||||
"""
|
||||
return "password"
|
||||
|
||||
def check_auth_none(self, username):
|
||||
"""
|
||||
Determine if a client may open channels with no (further)
|
||||
authentication.
|
||||
|
||||
Return ``AUTH_FAILED`` if the client must authenticate, or
|
||||
``AUTH_SUCCESSFUL`` if it's okay for the client to not
|
||||
authenticate.
|
||||
|
||||
The default implementation always returns ``AUTH_FAILED``.
|
||||
|
||||
:param str username: the username of the client.
|
||||
:return:
|
||||
``AUTH_FAILED`` if the authentication fails; ``AUTH_SUCCESSFUL`` if
|
||||
it succeeds.
|
||||
:rtype: int
|
||||
"""
|
||||
return AUTH_FAILED
|
||||
|
||||
def check_auth_password(self, username, password):
|
||||
"""
|
||||
Determine if a given username and password supplied by the client is
|
||||
acceptable for use in authentication.
|
||||
|
||||
Return ``AUTH_FAILED`` if the password is not accepted,
|
||||
``AUTH_SUCCESSFUL`` if the password is accepted and completes
|
||||
the authentication, or ``AUTH_PARTIALLY_SUCCESSFUL`` if your
|
||||
authentication is stateful, and this key is accepted for
|
||||
authentication, but more authentication is required. (In this latter
|
||||
case, `get_allowed_auths` will be called to report to the client what
|
||||
options it has for continuing the authentication.)
|
||||
|
||||
The default implementation always returns ``AUTH_FAILED``.
|
||||
|
||||
:param str username: the username of the authenticating client.
|
||||
:param str password: the password given by the client.
|
||||
:return:
|
||||
``AUTH_FAILED`` if the authentication fails; ``AUTH_SUCCESSFUL`` if
|
||||
it succeeds; ``AUTH_PARTIALLY_SUCCESSFUL`` if the password auth is
|
||||
successful, but authentication must continue.
|
||||
:rtype: int
|
||||
"""
|
||||
return AUTH_FAILED
|
||||
|
||||
def check_auth_publickey(self, username, key):
|
||||
"""
|
||||
Determine if a given key supplied by the client is acceptable for use
|
||||
in authentication. You should override this method in server mode to
|
||||
check the username and key and decide if you would accept a signature
|
||||
made using this key.
|
||||
|
||||
Return ``AUTH_FAILED`` if the key is not accepted,
|
||||
``AUTH_SUCCESSFUL`` if the key is accepted and completes the
|
||||
authentication, or ``AUTH_PARTIALLY_SUCCESSFUL`` if your
|
||||
authentication is stateful, and this password is accepted for
|
||||
authentication, but more authentication is required. (In this latter
|
||||
case, `get_allowed_auths` will be called to report to the client what
|
||||
options it has for continuing the authentication.)
|
||||
|
||||
Note that you don't have to actually verify any key signtature here.
|
||||
If you're willing to accept the key, Paramiko will do the work of
|
||||
verifying the client's signature.
|
||||
|
||||
The default implementation always returns ``AUTH_FAILED``.
|
||||
|
||||
:param str username: the username of the authenticating client
|
||||
:param .PKey key: the key object provided by the client
|
||||
:return:
|
||||
``AUTH_FAILED`` if the client can't authenticate with this key;
|
||||
``AUTH_SUCCESSFUL`` if it can; ``AUTH_PARTIALLY_SUCCESSFUL`` if it
|
||||
can authenticate with this key but must continue with
|
||||
authentication
|
||||
:rtype: int
|
||||
"""
|
||||
return AUTH_FAILED
|
||||
|
||||
def check_auth_interactive(self, username, submethods):
|
||||
"""
|
||||
Begin an interactive authentication challenge, if supported. You
|
||||
should override this method in server mode if you want to support the
|
||||
``"keyboard-interactive"`` auth type, which requires you to send a
|
||||
series of questions for the client to answer.
|
||||
|
||||
Return ``AUTH_FAILED`` if this auth method isn't supported. Otherwise,
|
||||
you should return an `.InteractiveQuery` object containing the prompts
|
||||
and instructions for the user. The response will be sent via a call
|
||||
to `check_auth_interactive_response`.
|
||||
|
||||
The default implementation always returns ``AUTH_FAILED``.
|
||||
|
||||
:param str username: the username of the authenticating client
|
||||
:param str submethods:
|
||||
a comma-separated list of methods preferred by the client (usually
|
||||
empty)
|
||||
:return:
|
||||
``AUTH_FAILED`` if this auth method isn't supported; otherwise an
|
||||
object containing queries for the user
|
||||
:rtype: int or `.InteractiveQuery`
|
||||
"""
|
||||
return AUTH_FAILED
|
||||
|
||||
def check_auth_interactive_response(self, responses):
|
||||
"""
|
||||
Continue or finish an interactive authentication challenge, if
|
||||
supported. You should override this method in server mode if you want
|
||||
to support the ``"keyboard-interactive"`` auth type.
|
||||
|
||||
Return ``AUTH_FAILED`` if the responses are not accepted,
|
||||
``AUTH_SUCCESSFUL`` if the responses are accepted and complete
|
||||
the authentication, or ``AUTH_PARTIALLY_SUCCESSFUL`` if your
|
||||
authentication is stateful, and this set of responses is accepted for
|
||||
authentication, but more authentication is required. (In this latter
|
||||
case, `get_allowed_auths` will be called to report to the client what
|
||||
options it has for continuing the authentication.)
|
||||
|
||||
If you wish to continue interactive authentication with more questions,
|
||||
you may return an `.InteractiveQuery` object, which should cause the
|
||||
client to respond with more answers, calling this method again. This
|
||||
cycle can continue indefinitely.
|
||||
|
||||
The default implementation always returns ``AUTH_FAILED``.
|
||||
|
||||
:param responses: list of `str` responses from the client
|
||||
:return:
|
||||
``AUTH_FAILED`` if the authentication fails; ``AUTH_SUCCESSFUL`` if
|
||||
it succeeds; ``AUTH_PARTIALLY_SUCCESSFUL`` if the interactive auth
|
||||
is successful, but authentication must continue; otherwise an
|
||||
object containing queries for the user
|
||||
:rtype: int or `.InteractiveQuery`
|
||||
"""
|
||||
return AUTH_FAILED
|
||||
|
||||
def check_auth_gssapi_with_mic(
|
||||
self, username, gss_authenticated=AUTH_FAILED, cc_file=None
|
||||
):
|
||||
"""
|
||||
Authenticate the given user to the server if he is a valid krb5
|
||||
principal.
|
||||
|
||||
:param str username: The username of the authenticating client
|
||||
:param int gss_authenticated: The result of the krb5 authentication
|
||||
:param str cc_filename: The krb5 client credentials cache filename
|
||||
:return: ``AUTH_FAILED`` if the user is not authenticated otherwise
|
||||
``AUTH_SUCCESSFUL``
|
||||
:rtype: int
|
||||
:note: Kerberos credential delegation is not supported.
|
||||
:see: `.ssh_gss`
|
||||
:note: : We are just checking in L{AuthHandler} that the given user is
|
||||
a valid krb5 principal!
|
||||
We don't check if the krb5 principal is allowed to log in on
|
||||
the server, because there is no way to do that in python. So
|
||||
if you develop your own SSH server with paramiko for a cetain
|
||||
plattform like Linux, you should call C{krb5_kuserok()} in
|
||||
your local kerberos library to make sure that the
|
||||
krb5_principal has an account on the server and is allowed to
|
||||
log in as a user.
|
||||
:see: http://www.unix.com/man-page/all/3/krb5_kuserok/
|
||||
"""
|
||||
if gss_authenticated == AUTH_SUCCESSFUL:
|
||||
return AUTH_SUCCESSFUL
|
||||
return AUTH_FAILED
|
||||
|
||||
def check_auth_gssapi_keyex(
|
||||
self, username, gss_authenticated=AUTH_FAILED, cc_file=None
|
||||
):
|
||||
"""
|
||||
Authenticate the given user to the server if he is a valid krb5
|
||||
principal and GSS-API Key Exchange was performed.
|
||||
If GSS-API Key Exchange was not performed, this authentication method
|
||||
won't be available.
|
||||
|
||||
:param str username: The username of the authenticating client
|
||||
:param int gss_authenticated: The result of the krb5 authentication
|
||||
:param str cc_filename: The krb5 client credentials cache filename
|
||||
:return: ``AUTH_FAILED`` if the user is not authenticated otherwise
|
||||
``AUTH_SUCCESSFUL``
|
||||
:rtype: int
|
||||
:note: Kerberos credential delegation is not supported.
|
||||
:see: `.ssh_gss` `.kex_gss`
|
||||
:note: : We are just checking in L{AuthHandler} that the given user is
|
||||
a valid krb5 principal!
|
||||
We don't check if the krb5 principal is allowed to log in on
|
||||
the server, because there is no way to do that in python. So
|
||||
if you develop your own SSH server with paramiko for a cetain
|
||||
plattform like Linux, you should call C{krb5_kuserok()} in
|
||||
your local kerberos library to make sure that the
|
||||
krb5_principal has an account on the server and is allowed
|
||||
to log in as a user.
|
||||
:see: http://www.unix.com/man-page/all/3/krb5_kuserok/
|
||||
"""
|
||||
if gss_authenticated == AUTH_SUCCESSFUL:
|
||||
return AUTH_SUCCESSFUL
|
||||
return AUTH_FAILED
|
||||
|
||||
def enable_auth_gssapi(self):
|
||||
"""
|
||||
Overwrite this function in your SSH server to enable GSSAPI
|
||||
authentication.
|
||||
The default implementation always returns false.
|
||||
|
||||
:returns bool: Whether GSSAPI authentication is enabled.
|
||||
:see: `.ssh_gss`
|
||||
"""
|
||||
UseGSSAPI = False
|
||||
return UseGSSAPI
|
||||
|
||||
def check_port_forward_request(self, address, port):
|
||||
"""
|
||||
Handle a request for port forwarding. The client is asking that
|
||||
connections to the given address and port be forwarded back across
|
||||
this ssh connection. An address of ``"0.0.0.0"`` indicates a global
|
||||
address (any address associated with this server) and a port of ``0``
|
||||
indicates that no specific port is requested (usually the OS will pick
|
||||
a port).
|
||||
|
||||
The default implementation always returns ``False``, rejecting the
|
||||
port forwarding request. If the request is accepted, you should return
|
||||
the port opened for listening.
|
||||
|
||||
:param str address: the requested address
|
||||
:param int port: the requested port
|
||||
:return:
|
||||
the port number (`int`) that was opened for listening, or ``False``
|
||||
to reject
|
||||
"""
|
||||
return False
|
||||
|
||||
def cancel_port_forward_request(self, address, port):
|
||||
"""
|
||||
The client would like to cancel a previous port-forwarding request.
|
||||
If the given address and port is being forwarded across this ssh
|
||||
connection, the port should be closed.
|
||||
|
||||
:param str address: the forwarded address
|
||||
:param int port: the forwarded port
|
||||
"""
|
||||
pass
|
||||
|
||||
def check_global_request(self, kind, msg):
|
||||
"""
|
||||
Handle a global request of the given ``kind``. This method is called
|
||||
in server mode and client mode, whenever the remote host makes a global
|
||||
request. If there are any arguments to the request, they will be in
|
||||
``msg``.
|
||||
|
||||
There aren't any useful global requests defined, aside from port
|
||||
forwarding, so usually this type of request is an extension to the
|
||||
protocol.
|
||||
|
||||
If the request was successful and you would like to return contextual
|
||||
data to the remote host, return a tuple. Items in the tuple will be
|
||||
sent back with the successful result. (Note that the items in the
|
||||
tuple can only be strings, ints, longs, or bools.)
|
||||
|
||||
The default implementation always returns ``False``, indicating that it
|
||||
does not support any global requests.
|
||||
|
||||
.. note:: Port forwarding requests are handled separately, in
|
||||
`check_port_forward_request`.
|
||||
|
||||
:param str kind: the kind of global request being made.
|
||||
:param .Message msg: any extra arguments to the request.
|
||||
:return:
|
||||
``True`` or a `tuple` of data if the request was granted; ``False``
|
||||
otherwise.
|
||||
"""
|
||||
return False
|
||||
|
||||
# ...Channel requests...
|
||||
|
||||
def check_channel_pty_request(
|
||||
self, channel, term, width, height, pixelwidth, pixelheight, modes
|
||||
):
|
||||
"""
|
||||
Determine if a pseudo-terminal of the given dimensions (usually
|
||||
requested for shell access) can be provided on the given channel.
|
||||
|
||||
The default implementation always returns ``False``.
|
||||
|
||||
:param .Channel channel: the `.Channel` the pty request arrived on.
|
||||
:param str term: type of terminal requested (for example, ``"vt100"``).
|
||||
:param int width: width of screen in characters.
|
||||
:param int height: height of screen in characters.
|
||||
:param int pixelwidth:
|
||||
width of screen in pixels, if known (may be ``0`` if unknown).
|
||||
:param int pixelheight:
|
||||
height of screen in pixels, if known (may be ``0`` if unknown).
|
||||
:return:
|
||||
``True`` if the pseudo-terminal has been allocated; ``False``
|
||||
otherwise.
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_channel_shell_request(self, channel):
|
||||
"""
|
||||
Determine if a shell will be provided to the client on the given
|
||||
channel. If this method returns ``True``, the channel should be
|
||||
connected to the stdin/stdout of a shell (or something that acts like
|
||||
a shell).
|
||||
|
||||
The default implementation always returns ``False``.
|
||||
|
||||
:param .Channel channel: the `.Channel` the request arrived on.
|
||||
:return:
|
||||
``True`` if this channel is now hooked up to a shell; ``False`` if
|
||||
a shell can't or won't be provided.
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_channel_exec_request(self, channel, command):
|
||||
"""
|
||||
Determine if a shell command will be executed for the client. If this
|
||||
method returns ``True``, the channel should be connected to the stdin,
|
||||
stdout, and stderr of the shell command.
|
||||
|
||||
The default implementation always returns ``False``.
|
||||
|
||||
:param .Channel channel: the `.Channel` the request arrived on.
|
||||
:param str command: the command to execute.
|
||||
:return:
|
||||
``True`` if this channel is now hooked up to the stdin, stdout, and
|
||||
stderr of the executing command; ``False`` if the command will not
|
||||
be executed.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_channel_subsystem_request(self, channel, name):
|
||||
"""
|
||||
Determine if a requested subsystem will be provided to the client on
|
||||
the given channel. If this method returns ``True``, all future I/O
|
||||
through this channel will be assumed to be connected to the requested
|
||||
subsystem. An example of a subsystem is ``sftp``.
|
||||
|
||||
The default implementation checks for a subsystem handler assigned via
|
||||
`.Transport.set_subsystem_handler`.
|
||||
If one has been set, the handler is invoked and this method returns
|
||||
``True``. Otherwise it returns ``False``.
|
||||
|
||||
.. note:: Because the default implementation uses the `.Transport` to
|
||||
identify valid subsystems, you probably won't need to override this
|
||||
method.
|
||||
|
||||
:param .Channel channel: the `.Channel` the pty request arrived on.
|
||||
:param str name: name of the requested subsystem.
|
||||
:return:
|
||||
``True`` if this channel is now hooked up to the requested
|
||||
subsystem; ``False`` if that subsystem can't or won't be provided.
|
||||
"""
|
||||
transport = channel.get_transport()
|
||||
handler_class, larg, kwarg = transport._get_subsystem_handler(name)
|
||||
if handler_class is None:
|
||||
return False
|
||||
handler = handler_class(channel, name, self, *larg, **kwarg)
|
||||
handler.start()
|
||||
return True
|
||||
|
||||
def check_channel_window_change_request(
|
||||
self, channel, width, height, pixelwidth, pixelheight
|
||||
):
|
||||
"""
|
||||
Determine if the pseudo-terminal on the given channel can be resized.
|
||||
This only makes sense if a pty was previously allocated on it.
|
||||
|
||||
The default implementation always returns ``False``.
|
||||
|
||||
:param .Channel channel: the `.Channel` the pty request arrived on.
|
||||
:param int width: width of screen in characters.
|
||||
:param int height: height of screen in characters.
|
||||
:param int pixelwidth:
|
||||
width of screen in pixels, if known (may be ``0`` if unknown).
|
||||
:param int pixelheight:
|
||||
height of screen in pixels, if known (may be ``0`` if unknown).
|
||||
:return: ``True`` if the terminal was resized; ``False`` if not.
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_channel_x11_request(
|
||||
self,
|
||||
channel,
|
||||
single_connection,
|
||||
auth_protocol,
|
||||
auth_cookie,
|
||||
screen_number,
|
||||
):
|
||||
"""
|
||||
Determine if the client will be provided with an X11 session. If this
|
||||
method returns ``True``, X11 applications should be routed through new
|
||||
SSH channels, using `.Transport.open_x11_channel`.
|
||||
|
||||
The default implementation always returns ``False``.
|
||||
|
||||
:param .Channel channel: the `.Channel` the X11 request arrived on
|
||||
:param bool single_connection:
|
||||
``True`` if only a single X11 channel should be opened, else
|
||||
``False``.
|
||||
:param str auth_protocol: the protocol used for X11 authentication
|
||||
:param str auth_cookie: the cookie used to authenticate to X11
|
||||
:param int screen_number: the number of the X11 screen to connect to
|
||||
:return: ``True`` if the X11 session was opened; ``False`` if not
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_channel_forward_agent_request(self, channel):
|
||||
"""
|
||||
Determine if the client will be provided with an forward agent session.
|
||||
If this method returns ``True``, the server will allow SSH Agent
|
||||
forwarding.
|
||||
|
||||
The default implementation always returns ``False``.
|
||||
|
||||
:param .Channel channel: the `.Channel` the request arrived on
|
||||
:return: ``True`` if the AgentForward was loaded; ``False`` if not
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_channel_direct_tcpip_request(self, chanid, origin, destination):
|
||||
"""
|
||||
Determine if a local port forwarding channel will be granted, and
|
||||
return ``OPEN_SUCCEEDED`` or an error code. This method is
|
||||
called in server mode when the client requests a channel, after
|
||||
authentication is complete.
|
||||
|
||||
The ``chanid`` parameter is a small number that uniquely identifies the
|
||||
channel within a `.Transport`. A `.Channel` object is not created
|
||||
unless this method returns ``OPEN_SUCCEEDED`` -- once a
|
||||
`.Channel` object is created, you can call `.Channel.get_id` to
|
||||
retrieve the channel ID.
|
||||
|
||||
The origin and destination parameters are (ip_address, port) tuples
|
||||
that correspond to both ends of the TCP connection in the forwarding
|
||||
tunnel.
|
||||
|
||||
The return value should either be ``OPEN_SUCCEEDED`` (or
|
||||
``0``) to allow the channel request, or one of the following error
|
||||
codes to reject it:
|
||||
|
||||
- ``OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED``
|
||||
- ``OPEN_FAILED_CONNECT_FAILED``
|
||||
- ``OPEN_FAILED_UNKNOWN_CHANNEL_TYPE``
|
||||
- ``OPEN_FAILED_RESOURCE_SHORTAGE``
|
||||
|
||||
The default implementation always returns
|
||||
``OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED``.
|
||||
|
||||
:param int chanid: ID of the channel
|
||||
:param tuple origin:
|
||||
2-tuple containing the IP address and port of the originator
|
||||
(client side)
|
||||
:param tuple destination:
|
||||
2-tuple containing the IP address and port of the destination
|
||||
(server side)
|
||||
:return: an `int` success or failure code (listed above)
|
||||
"""
|
||||
return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
||||
|
||||
def check_channel_env_request(self, channel, name, value):
|
||||
"""
|
||||
Check whether a given environment variable can be specified for the
|
||||
given channel. This method should return ``True`` if the server
|
||||
is willing to set the specified environment variable. Note that
|
||||
some environment variables (e.g., PATH) can be exceedingly
|
||||
dangerous, so blindly allowing the client to set the environment
|
||||
is almost certainly not a good idea.
|
||||
|
||||
The default implementation always returns ``False``.
|
||||
|
||||
:param channel: the `.Channel` the env request arrived on
|
||||
:param str name: name
|
||||
:param str value: Channel value
|
||||
:returns: A boolean
|
||||
"""
|
||||
return False
|
||||
|
||||
def get_banner(self):
|
||||
"""
|
||||
A pre-login banner to display to the user. The message may span
|
||||
multiple lines separated by crlf pairs. The language should be in
|
||||
rfc3066 style, for example: en-US
|
||||
|
||||
The default implementation always returns ``(None, None)``.
|
||||
|
||||
:returns: A tuple containing the banner and language code.
|
||||
|
||||
.. versionadded:: 2.3
|
||||
"""
|
||||
return (None, None)
|
||||
|
||||
|
||||
class InteractiveQuery(object):
|
||||
"""
|
||||
A query (set of prompts) for a user during interactive authentication.
|
||||
"""
|
||||
|
||||
def __init__(self, name="", instructions="", *prompts):
|
||||
"""
|
||||
Create a new interactive query to send to the client. The name and
|
||||
instructions are optional, but are generally displayed to the end
|
||||
user. A list of prompts may be included, or they may be added via
|
||||
the `add_prompt` method.
|
||||
|
||||
:param str name: name of this query
|
||||
:param str instructions:
|
||||
user instructions (usually short) about this query
|
||||
:param str prompts: one or more authentication prompts
|
||||
"""
|
||||
self.name = name
|
||||
self.instructions = instructions
|
||||
self.prompts = []
|
||||
for x in prompts:
|
||||
if isinstance(x, string_types):
|
||||
self.add_prompt(x)
|
||||
else:
|
||||
self.add_prompt(x[0], x[1])
|
||||
|
||||
def add_prompt(self, prompt, echo=True):
|
||||
"""
|
||||
Add a prompt to this query. The prompt should be a (reasonably short)
|
||||
string. Multiple prompts can be added to the same query.
|
||||
|
||||
:param str prompt: the user prompt
|
||||
:param bool echo:
|
||||
``True`` (default) if the user's response should be echoed;
|
||||
``False`` if not (for a password or similar)
|
||||
"""
|
||||
self.prompts.append((prompt, echo))
|
||||
|
||||
|
||||
class SubsystemHandler(threading.Thread):
|
||||
"""
|
||||
Handler for a subsytem in server mode. If you create a subclass of this
|
||||
class and pass it to `.Transport.set_subsystem_handler`, an object of this
|
||||
class will be created for each request for this subsystem. Each new object
|
||||
will be executed within its own new thread by calling `start_subsystem`.
|
||||
When that method completes, the channel is closed.
|
||||
|
||||
For example, if you made a subclass ``MP3Handler`` and registered it as the
|
||||
handler for subsystem ``"mp3"``, then whenever a client has successfully
|
||||
authenticated and requests subsytem ``"mp3"``, an object of class
|
||||
``MP3Handler`` will be created, and `start_subsystem` will be called on
|
||||
it from a new thread.
|
||||
"""
|
||||
|
||||
def __init__(self, channel, name, server):
|
||||
"""
|
||||
Create a new handler for a channel. This is used by `.ServerInterface`
|
||||
to start up a new handler when a channel requests this subsystem. You
|
||||
don't need to override this method, but if you do, be sure to pass the
|
||||
``channel`` and ``name`` parameters through to the original
|
||||
``__init__`` method here.
|
||||
|
||||
:param .Channel channel: the channel associated with this
|
||||
subsystem request.
|
||||
:param str name: name of the requested subsystem.
|
||||
:param .ServerInterface server:
|
||||
the server object for the session that started this subsystem
|
||||
"""
|
||||
threading.Thread.__init__(self, target=self._run)
|
||||
self.__channel = channel
|
||||
self.__transport = channel.get_transport()
|
||||
self.__name = name
|
||||
self.__server = server
|
||||
|
||||
def get_server(self):
|
||||
"""
|
||||
Return the `.ServerInterface` object associated with this channel and
|
||||
subsystem.
|
||||
"""
|
||||
return self.__server
|
||||
|
||||
def _run(self):
|
||||
try:
|
||||
self.__transport._log(
|
||||
DEBUG, "Starting handler for subsystem {}".format(self.__name)
|
||||
)
|
||||
self.start_subsystem(self.__name, self.__transport, self.__channel)
|
||||
except Exception as e:
|
||||
self.__transport._log(
|
||||
ERROR,
|
||||
'Exception in subsystem handler for "{}": {}'.format(
|
||||
self.__name, e
|
||||
),
|
||||
)
|
||||
self.__transport._log(ERROR, util.tb_strings())
|
||||
try:
|
||||
self.finish_subsystem()
|
||||
except:
|
||||
pass
|
||||
|
||||
def start_subsystem(self, name, transport, channel):
|
||||
"""
|
||||
Process an ssh subsystem in server mode. This method is called on a
|
||||
new object (and in a new thread) for each subsystem request. It is
|
||||
assumed that all subsystem logic will take place here, and when the
|
||||
subsystem is finished, this method will return. After this method
|
||||
returns, the channel is closed.
|
||||
|
||||
The combination of ``transport`` and ``channel`` are unique; this
|
||||
handler corresponds to exactly one `.Channel` on one `.Transport`.
|
||||
|
||||
.. note::
|
||||
It is the responsibility of this method to exit if the underlying
|
||||
`.Transport` is closed. This can be done by checking
|
||||
`.Transport.is_active` or noticing an EOF on the `.Channel`. If
|
||||
this method loops forever without checking for this case, your
|
||||
Python interpreter may refuse to exit because this thread will
|
||||
still be running.
|
||||
|
||||
:param str name: name of the requested subsystem.
|
||||
:param .Transport transport: the server-mode `.Transport`.
|
||||
:param .Channel channel: the channel associated with this subsystem
|
||||
request.
|
||||
"""
|
||||
pass
|
||||
|
||||
def finish_subsystem(self):
|
||||
"""
|
||||
Perform any cleanup at the end of a subsystem. The default
|
||||
implementation just closes the channel.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
"""
|
||||
self.__channel.close()
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/server.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/server.pyc
Normal file
Binary file not shown.
213
.ve/lib/python2.7/site-packages/paramiko/sftp.py
Normal file
213
.ve/lib/python2.7/site-packages/paramiko/sftp.py
Normal file
@@ -0,0 +1,213 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
import select
|
||||
import socket
|
||||
import struct
|
||||
|
||||
from paramiko import util
|
||||
from paramiko.common import asbytes, DEBUG
|
||||
from paramiko.message import Message
|
||||
from paramiko.py3compat import byte_chr, byte_ord
|
||||
|
||||
|
||||
(
|
||||
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",
|
||||
}
|
||||
|
||||
|
||||
class SFTPError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BaseSFTP(object):
|
||||
def __init__(self):
|
||||
self.logger = util.get_logger("paramiko.sftp")
|
||||
self.sock = None
|
||||
self.ultra_debug = False
|
||||
|
||||
# ...internals...
|
||||
|
||||
def _send_version(self):
|
||||
self._send_packet(CMD_INIT, struct.pack(">I", _VERSION))
|
||||
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 = asbytes(packet)
|
||||
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()
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/sftp.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/sftp.pyc
Normal file
Binary file not shown.
243
.ve/lib/python2.7/site-packages/paramiko/sftp_attr.py
Normal file
243
.ve/lib/python2.7/site-packages/paramiko/sftp_attr.py
Normal file
@@ -0,0 +1,243 @@
|
||||
# Copyright (C) 2003-2006 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
import stat
|
||||
import time
|
||||
from paramiko.common import x80000000, o700, o70, xffffffff
|
||||
from paramiko.py3compat import long, b
|
||||
|
||||
|
||||
class SFTPAttributes(object):
|
||||
"""
|
||||
Representation of the attributes of a file (or proxied file) for SFTP in
|
||||
client or server mode. It attemps to mirror the object returned by
|
||||
`os.stat` as closely as possible, so it may have the following fields,
|
||||
with the same meanings as those returned by an `os.stat` object:
|
||||
|
||||
- ``st_size``
|
||||
- ``st_uid``
|
||||
- ``st_gid``
|
||||
- ``st_mode``
|
||||
- ``st_atime``
|
||||
- ``st_mtime``
|
||||
|
||||
Because SFTP allows flags to have other arbitrary named attributes, these
|
||||
are stored in a dict named ``attr``. Occasionally, the filename is also
|
||||
stored, in ``filename``.
|
||||
"""
|
||||
|
||||
FLAG_SIZE = 1
|
||||
FLAG_UIDGID = 2
|
||||
FLAG_PERMISSIONS = 4
|
||||
FLAG_AMTIME = 8
|
||||
FLAG_EXTENDED = x80000000
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Create a new (empty) SFTPAttributes object. All fields will be empty.
|
||||
"""
|
||||
self._flags = 0
|
||||
self.st_size = None
|
||||
self.st_uid = None
|
||||
self.st_gid = None
|
||||
self.st_mode = None
|
||||
self.st_atime = None
|
||||
self.st_mtime = None
|
||||
self.attr = {}
|
||||
|
||||
@classmethod
|
||||
def from_stat(cls, obj, filename=None):
|
||||
"""
|
||||
Create an `.SFTPAttributes` object from an existing ``stat`` object (an
|
||||
object returned by `os.stat`).
|
||||
|
||||
:param object obj: an object returned by `os.stat` (or equivalent).
|
||||
:param str filename: the filename associated with this file.
|
||||
:return: new `.SFTPAttributes` object with the same attribute fields.
|
||||
"""
|
||||
attr = cls()
|
||||
attr.st_size = obj.st_size
|
||||
attr.st_uid = obj.st_uid
|
||||
attr.st_gid = obj.st_gid
|
||||
attr.st_mode = obj.st_mode
|
||||
attr.st_atime = obj.st_atime
|
||||
attr.st_mtime = obj.st_mtime
|
||||
if filename is not None:
|
||||
attr.filename = filename
|
||||
return attr
|
||||
|
||||
def __repr__(self):
|
||||
return "<SFTPAttributes: {}>".format(self._debug_str())
|
||||
|
||||
# ...internals...
|
||||
@classmethod
|
||||
def _from_msg(cls, msg, filename=None, longname=None):
|
||||
attr = cls()
|
||||
attr._unpack(msg)
|
||||
if filename is not None:
|
||||
attr.filename = filename
|
||||
if longname is not None:
|
||||
attr.longname = longname
|
||||
return attr
|
||||
|
||||
def _unpack(self, msg):
|
||||
self._flags = msg.get_int()
|
||||
if self._flags & self.FLAG_SIZE:
|
||||
self.st_size = msg.get_int64()
|
||||
if self._flags & self.FLAG_UIDGID:
|
||||
self.st_uid = msg.get_int()
|
||||
self.st_gid = msg.get_int()
|
||||
if self._flags & self.FLAG_PERMISSIONS:
|
||||
self.st_mode = msg.get_int()
|
||||
if self._flags & self.FLAG_AMTIME:
|
||||
self.st_atime = msg.get_int()
|
||||
self.st_mtime = msg.get_int()
|
||||
if self._flags & self.FLAG_EXTENDED:
|
||||
count = msg.get_int()
|
||||
for i in range(count):
|
||||
self.attr[msg.get_string()] = msg.get_string()
|
||||
|
||||
def _pack(self, msg):
|
||||
self._flags = 0
|
||||
if self.st_size is not None:
|
||||
self._flags |= self.FLAG_SIZE
|
||||
if (self.st_uid is not None) and (self.st_gid is not None):
|
||||
self._flags |= self.FLAG_UIDGID
|
||||
if self.st_mode is not None:
|
||||
self._flags |= self.FLAG_PERMISSIONS
|
||||
if (self.st_atime is not None) and (self.st_mtime is not None):
|
||||
self._flags |= self.FLAG_AMTIME
|
||||
if len(self.attr) > 0:
|
||||
self._flags |= self.FLAG_EXTENDED
|
||||
msg.add_int(self._flags)
|
||||
if self._flags & self.FLAG_SIZE:
|
||||
msg.add_int64(self.st_size)
|
||||
if self._flags & self.FLAG_UIDGID:
|
||||
msg.add_int(self.st_uid)
|
||||
msg.add_int(self.st_gid)
|
||||
if self._flags & self.FLAG_PERMISSIONS:
|
||||
msg.add_int(self.st_mode)
|
||||
if self._flags & self.FLAG_AMTIME:
|
||||
# throw away any fractional seconds
|
||||
msg.add_int(long(self.st_atime))
|
||||
msg.add_int(long(self.st_mtime))
|
||||
if self._flags & self.FLAG_EXTENDED:
|
||||
msg.add_int(len(self.attr))
|
||||
for key, val in self.attr.items():
|
||||
msg.add_string(key)
|
||||
msg.add_string(val)
|
||||
return
|
||||
|
||||
def _debug_str(self):
|
||||
out = "[ "
|
||||
if self.st_size is not None:
|
||||
out += "size={} ".format(self.st_size)
|
||||
if (self.st_uid is not None) and (self.st_gid is not None):
|
||||
out += "uid={} gid={} ".format(self.st_uid, self.st_gid)
|
||||
if self.st_mode is not None:
|
||||
out += "mode=" + oct(self.st_mode) + " "
|
||||
if (self.st_atime is not None) and (self.st_mtime is not None):
|
||||
out += "atime={} mtime={} ".format(self.st_atime, self.st_mtime)
|
||||
for k, v in self.attr.items():
|
||||
out += '"{}"={!r} '.format(str(k), v)
|
||||
out += "]"
|
||||
return out
|
||||
|
||||
@staticmethod
|
||||
def _rwx(n, suid, sticky=False):
|
||||
if suid:
|
||||
suid = 2
|
||||
out = "-r"[n >> 2] + "-w"[(n >> 1) & 1]
|
||||
if sticky:
|
||||
out += "-xTt"[suid + (n & 1)]
|
||||
else:
|
||||
out += "-xSs"[suid + (n & 1)]
|
||||
return out
|
||||
|
||||
def __str__(self):
|
||||
"""create a unix-style long description of the file (like ls -l)"""
|
||||
if self.st_mode is not None:
|
||||
kind = stat.S_IFMT(self.st_mode)
|
||||
if kind == stat.S_IFIFO:
|
||||
ks = "p"
|
||||
elif kind == stat.S_IFCHR:
|
||||
ks = "c"
|
||||
elif kind == stat.S_IFDIR:
|
||||
ks = "d"
|
||||
elif kind == stat.S_IFBLK:
|
||||
ks = "b"
|
||||
elif kind == stat.S_IFREG:
|
||||
ks = "-"
|
||||
elif kind == stat.S_IFLNK:
|
||||
ks = "l"
|
||||
elif kind == stat.S_IFSOCK:
|
||||
ks = "s"
|
||||
else:
|
||||
ks = "?"
|
||||
ks += self._rwx(
|
||||
(self.st_mode & o700) >> 6, self.st_mode & stat.S_ISUID
|
||||
)
|
||||
ks += self._rwx(
|
||||
(self.st_mode & o70) >> 3, self.st_mode & stat.S_ISGID
|
||||
)
|
||||
ks += self._rwx(
|
||||
self.st_mode & 7, self.st_mode & stat.S_ISVTX, True
|
||||
)
|
||||
else:
|
||||
ks = "?---------"
|
||||
# compute display date
|
||||
if (self.st_mtime is None) or (self.st_mtime == xffffffff):
|
||||
# shouldn't really happen
|
||||
datestr = "(unknown date)"
|
||||
else:
|
||||
if abs(time.time() - self.st_mtime) > 15552000:
|
||||
# (15552000 = 6 months)
|
||||
datestr = time.strftime(
|
||||
"%d %b %Y", time.localtime(self.st_mtime)
|
||||
)
|
||||
else:
|
||||
datestr = time.strftime(
|
||||
"%d %b %H:%M", time.localtime(self.st_mtime)
|
||||
)
|
||||
filename = getattr(self, "filename", "?")
|
||||
|
||||
# not all servers support uid/gid
|
||||
uid = self.st_uid
|
||||
gid = self.st_gid
|
||||
size = self.st_size
|
||||
if uid is None:
|
||||
uid = 0
|
||||
if gid is None:
|
||||
gid = 0
|
||||
if size is None:
|
||||
size = 0
|
||||
|
||||
# TODO: not sure this actually worked as expected beforehand, leaving
|
||||
# it untouched for the time being, re: .format() upgrade, until someone
|
||||
# has time to doublecheck
|
||||
return "%s 1 %-8d %-8d %8d %-12s %s" % (
|
||||
ks,
|
||||
uid,
|
||||
gid,
|
||||
size,
|
||||
datestr,
|
||||
filename,
|
||||
)
|
||||
|
||||
def asbytes(self):
|
||||
return b(str(self))
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/sftp_attr.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/sftp_attr.pyc
Normal file
Binary file not shown.
921
.ve/lib/python2.7/site-packages/paramiko/sftp_client.py
Normal file
921
.ve/lib/python2.7/site-packages/paramiko/sftp_client.py
Normal file
@@ -0,0 +1,921 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
|
||||
from binascii import hexlify
|
||||
import errno
|
||||
import os
|
||||
import stat
|
||||
import threading
|
||||
import time
|
||||
import weakref
|
||||
from paramiko import util
|
||||
from paramiko.channel import Channel
|
||||
from paramiko.message import Message
|
||||
from paramiko.common import INFO, DEBUG, o777
|
||||
from paramiko.py3compat import bytestring, b, u, long
|
||||
from paramiko.sftp import (
|
||||
BaseSFTP,
|
||||
CMD_OPENDIR,
|
||||
CMD_HANDLE,
|
||||
SFTPError,
|
||||
CMD_READDIR,
|
||||
CMD_NAME,
|
||||
CMD_CLOSE,
|
||||
SFTP_FLAG_READ,
|
||||
SFTP_FLAG_WRITE,
|
||||
SFTP_FLAG_CREATE,
|
||||
SFTP_FLAG_TRUNC,
|
||||
SFTP_FLAG_APPEND,
|
||||
SFTP_FLAG_EXCL,
|
||||
CMD_OPEN,
|
||||
CMD_REMOVE,
|
||||
CMD_RENAME,
|
||||
CMD_MKDIR,
|
||||
CMD_RMDIR,
|
||||
CMD_STAT,
|
||||
CMD_ATTRS,
|
||||
CMD_LSTAT,
|
||||
CMD_SYMLINK,
|
||||
CMD_SETSTAT,
|
||||
CMD_READLINK,
|
||||
CMD_REALPATH,
|
||||
CMD_STATUS,
|
||||
CMD_EXTENDED,
|
||||
SFTP_OK,
|
||||
SFTP_EOF,
|
||||
SFTP_NO_SUCH_FILE,
|
||||
SFTP_PERMISSION_DENIED,
|
||||
)
|
||||
|
||||
from paramiko.sftp_attr import SFTPAttributes
|
||||
from paramiko.ssh_exception import SSHException
|
||||
from paramiko.sftp_file import SFTPFile
|
||||
from paramiko.util import ClosingContextManager
|
||||
|
||||
|
||||
def _to_unicode(s):
|
||||
"""
|
||||
decode a string as ascii or utf8 if possible (as required by the sftp
|
||||
protocol). if neither works, just return a byte string because the server
|
||||
probably doesn't know the filename's encoding.
|
||||
"""
|
||||
try:
|
||||
return s.encode("ascii")
|
||||
except (UnicodeError, AttributeError):
|
||||
try:
|
||||
return s.decode("utf-8")
|
||||
except UnicodeError:
|
||||
return s
|
||||
|
||||
|
||||
b_slash = b"/"
|
||||
|
||||
|
||||
class SFTPClient(BaseSFTP, ClosingContextManager):
|
||||
"""
|
||||
SFTP client object.
|
||||
|
||||
Used to open an SFTP session across an open SSH `.Transport` and perform
|
||||
remote file operations.
|
||||
|
||||
Instances of this class may be used as context managers.
|
||||
"""
|
||||
|
||||
def __init__(self, sock):
|
||||
"""
|
||||
Create an SFTP client from an existing `.Channel`. The channel
|
||||
should already have requested the ``"sftp"`` subsystem.
|
||||
|
||||
An alternate way to create an SFTP client context is by using
|
||||
`from_transport`.
|
||||
|
||||
:param .Channel sock: an open `.Channel` using the ``"sftp"`` subsystem
|
||||
|
||||
:raises:
|
||||
`.SSHException` -- if there's an exception while negotiating sftp
|
||||
"""
|
||||
BaseSFTP.__init__(self)
|
||||
self.sock = sock
|
||||
self.ultra_debug = False
|
||||
self.request_number = 1
|
||||
# lock for request_number
|
||||
self._lock = threading.Lock()
|
||||
self._cwd = None
|
||||
# request # -> SFTPFile
|
||||
self._expecting = weakref.WeakValueDictionary()
|
||||
if type(sock) is Channel:
|
||||
# override default logger
|
||||
transport = self.sock.get_transport()
|
||||
self.logger = util.get_logger(
|
||||
transport.get_log_channel() + ".sftp"
|
||||
)
|
||||
self.ultra_debug = transport.get_hexdump()
|
||||
try:
|
||||
server_version = self._send_version()
|
||||
except EOFError:
|
||||
raise SSHException("EOF during negotiation")
|
||||
self._log(
|
||||
INFO,
|
||||
"Opened sftp connection (server version {})".format(
|
||||
server_version
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_transport(cls, t, window_size=None, max_packet_size=None):
|
||||
"""
|
||||
Create an SFTP client channel from an open `.Transport`.
|
||||
|
||||
Setting the window and packet sizes might affect the transfer speed.
|
||||
The default settings in the `.Transport` class are the same as in
|
||||
OpenSSH and should work adequately for both files transfers and
|
||||
interactive sessions.
|
||||
|
||||
:param .Transport t: an open `.Transport` which is already
|
||||
authenticated
|
||||
:param int window_size:
|
||||
optional window size for the `.SFTPClient` session.
|
||||
:param int max_packet_size:
|
||||
optional max packet size for the `.SFTPClient` session..
|
||||
|
||||
:return:
|
||||
a new `.SFTPClient` object, referring to an sftp session (channel)
|
||||
across the transport
|
||||
|
||||
.. versionchanged:: 1.15
|
||||
Added the ``window_size`` and ``max_packet_size`` arguments.
|
||||
"""
|
||||
chan = t.open_session(
|
||||
window_size=window_size, max_packet_size=max_packet_size
|
||||
)
|
||||
if chan is None:
|
||||
return None
|
||||
chan.invoke_subsystem("sftp")
|
||||
return cls(chan)
|
||||
|
||||
def _log(self, level, msg, *args):
|
||||
if isinstance(msg, list):
|
||||
for m in msg:
|
||||
self._log(level, m, *args)
|
||||
else:
|
||||
# NOTE: these bits MUST continue using %-style format junk because
|
||||
# logging.Logger.log() explicitly requires it. Grump.
|
||||
# escape '%' in msg (they could come from file or directory names)
|
||||
# before logging
|
||||
msg = msg.replace("%", "%%")
|
||||
super(SFTPClient, self)._log(
|
||||
level,
|
||||
"[chan %s] " + msg,
|
||||
*([self.sock.get_name()] + list(args))
|
||||
)
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the SFTP session and its underlying channel.
|
||||
|
||||
.. versionadded:: 1.4
|
||||
"""
|
||||
self._log(INFO, "sftp session closed.")
|
||||
self.sock.close()
|
||||
|
||||
def get_channel(self):
|
||||
"""
|
||||
Return the underlying `.Channel` object for this SFTP session. This
|
||||
might be useful for doing things like setting a timeout on the channel.
|
||||
|
||||
.. versionadded:: 1.7.1
|
||||
"""
|
||||
return self.sock
|
||||
|
||||
def listdir(self, path="."):
|
||||
"""
|
||||
Return a list containing the names of the entries in the given
|
||||
``path``.
|
||||
|
||||
The list is in arbitrary order. It does not include the special
|
||||
entries ``'.'`` and ``'..'`` even if they are present in the folder.
|
||||
This method is meant to mirror ``os.listdir`` as closely as possible.
|
||||
For a list of full `.SFTPAttributes` objects, see `listdir_attr`.
|
||||
|
||||
:param str path: path to list (defaults to ``'.'``)
|
||||
"""
|
||||
return [f.filename for f in self.listdir_attr(path)]
|
||||
|
||||
def listdir_attr(self, path="."):
|
||||
"""
|
||||
Return a list containing `.SFTPAttributes` objects corresponding to
|
||||
files in the given ``path``. The list is in arbitrary order. It does
|
||||
not include the special entries ``'.'`` and ``'..'`` even if they are
|
||||
present in the folder.
|
||||
|
||||
The returned `.SFTPAttributes` objects will each have an additional
|
||||
field: ``longname``, which may contain a formatted string of the file's
|
||||
attributes, in unix format. The content of this string will probably
|
||||
depend on the SFTP server implementation.
|
||||
|
||||
:param str path: path to list (defaults to ``'.'``)
|
||||
:return: list of `.SFTPAttributes` objects
|
||||
|
||||
.. versionadded:: 1.2
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, "listdir({!r})".format(path))
|
||||
t, msg = self._request(CMD_OPENDIR, path)
|
||||
if t != CMD_HANDLE:
|
||||
raise SFTPError("Expected handle")
|
||||
handle = msg.get_binary()
|
||||
filelist = []
|
||||
while True:
|
||||
try:
|
||||
t, msg = self._request(CMD_READDIR, handle)
|
||||
except EOFError:
|
||||
# done with handle
|
||||
break
|
||||
if t != CMD_NAME:
|
||||
raise SFTPError("Expected name response")
|
||||
count = msg.get_int()
|
||||
for i in range(count):
|
||||
filename = msg.get_text()
|
||||
longname = msg.get_text()
|
||||
attr = SFTPAttributes._from_msg(msg, filename, longname)
|
||||
if (filename != ".") and (filename != ".."):
|
||||
filelist.append(attr)
|
||||
self._request(CMD_CLOSE, handle)
|
||||
return filelist
|
||||
|
||||
def listdir_iter(self, path=".", read_aheads=50):
|
||||
"""
|
||||
Generator version of `.listdir_attr`.
|
||||
|
||||
See the API docs for `.listdir_attr` for overall details.
|
||||
|
||||
This function adds one more kwarg on top of `.listdir_attr`:
|
||||
``read_aheads``, an integer controlling how many
|
||||
``SSH_FXP_READDIR`` requests are made to the server. The default of 50
|
||||
should suffice for most file listings as each request/response cycle
|
||||
may contain multiple files (dependent on server implementation.)
|
||||
|
||||
.. versionadded:: 1.15
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, "listdir({!r})".format(path))
|
||||
t, msg = self._request(CMD_OPENDIR, path)
|
||||
|
||||
if t != CMD_HANDLE:
|
||||
raise SFTPError("Expected handle")
|
||||
|
||||
handle = msg.get_string()
|
||||
|
||||
nums = list()
|
||||
while True:
|
||||
try:
|
||||
# Send out a bunch of readdir requests so that we can read the
|
||||
# responses later on Section 6.7 of the SSH file transfer RFC
|
||||
# explains this
|
||||
# http://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
|
||||
for i in range(read_aheads):
|
||||
num = self._async_request(type(None), CMD_READDIR, handle)
|
||||
nums.append(num)
|
||||
|
||||
# For each of our sent requests
|
||||
# Read and parse the corresponding packets
|
||||
# If we're at the end of our queued requests, then fire off
|
||||
# some more requests
|
||||
# Exit the loop when we've reached the end of the directory
|
||||
# handle
|
||||
for num in nums:
|
||||
t, pkt_data = self._read_packet()
|
||||
msg = Message(pkt_data)
|
||||
new_num = msg.get_int()
|
||||
if num == new_num:
|
||||
if t == CMD_STATUS:
|
||||
self._convert_status(msg)
|
||||
count = msg.get_int()
|
||||
for i in range(count):
|
||||
filename = msg.get_text()
|
||||
longname = msg.get_text()
|
||||
attr = SFTPAttributes._from_msg(
|
||||
msg, filename, longname
|
||||
)
|
||||
if (filename != ".") and (filename != ".."):
|
||||
yield attr
|
||||
|
||||
# If we've hit the end of our queued requests, reset nums.
|
||||
nums = list()
|
||||
|
||||
except EOFError:
|
||||
self._request(CMD_CLOSE, handle)
|
||||
return
|
||||
|
||||
def open(self, filename, mode="r", bufsize=-1):
|
||||
"""
|
||||
Open a file on the remote server. The arguments are the same as for
|
||||
Python's built-in `python:file` (aka `python:open`). A file-like
|
||||
object is returned, which closely mimics the behavior of a normal
|
||||
Python file object, including the ability to be used as a context
|
||||
manager.
|
||||
|
||||
The mode indicates how the file is to be opened: ``'r'`` for reading,
|
||||
``'w'`` for writing (truncating an existing file), ``'a'`` for
|
||||
appending, ``'r+'`` for reading/writing, ``'w+'`` for reading/writing
|
||||
(truncating an existing file), ``'a+'`` for reading/appending. The
|
||||
Python ``'b'`` flag is ignored, since SSH treats all files as binary.
|
||||
The ``'U'`` flag is supported in a compatible way.
|
||||
|
||||
Since 1.5.2, an ``'x'`` flag indicates that the operation should only
|
||||
succeed if the file was created and did not previously exist. This has
|
||||
no direct mapping to Python's file flags, but is commonly known as the
|
||||
``O_EXCL`` flag in posix.
|
||||
|
||||
The file will be buffered in standard Python style by default, but
|
||||
can be altered with the ``bufsize`` parameter. ``0`` turns off
|
||||
buffering, ``1`` uses line buffering, and any number greater than 1
|
||||
(``>1``) uses that specific buffer size.
|
||||
|
||||
:param str filename: name of the file to open
|
||||
:param str mode: mode (Python-style) to open in
|
||||
:param int bufsize: desired buffering (-1 = default buffer size)
|
||||
:return: an `.SFTPFile` object representing the open file
|
||||
|
||||
:raises: ``IOError`` -- if the file could not be opened.
|
||||
"""
|
||||
filename = self._adjust_cwd(filename)
|
||||
self._log(DEBUG, "open({!r}, {!r})".format(filename, mode))
|
||||
imode = 0
|
||||
if ("r" in mode) or ("+" in mode):
|
||||
imode |= SFTP_FLAG_READ
|
||||
if ("w" in mode) or ("+" in mode) or ("a" in mode):
|
||||
imode |= SFTP_FLAG_WRITE
|
||||
if "w" in mode:
|
||||
imode |= SFTP_FLAG_CREATE | SFTP_FLAG_TRUNC
|
||||
if "a" in mode:
|
||||
imode |= SFTP_FLAG_CREATE | SFTP_FLAG_APPEND
|
||||
if "x" in mode:
|
||||
imode |= SFTP_FLAG_CREATE | SFTP_FLAG_EXCL
|
||||
attrblock = SFTPAttributes()
|
||||
t, msg = self._request(CMD_OPEN, filename, imode, attrblock)
|
||||
if t != CMD_HANDLE:
|
||||
raise SFTPError("Expected handle")
|
||||
handle = msg.get_binary()
|
||||
self._log(
|
||||
DEBUG,
|
||||
"open({!r}, {!r}) -> {}".format(
|
||||
filename, mode, u(hexlify(handle))
|
||||
),
|
||||
)
|
||||
return SFTPFile(self, handle, mode, bufsize)
|
||||
|
||||
# Python continues to vacillate about "open" vs "file"...
|
||||
file = open
|
||||
|
||||
def remove(self, path):
|
||||
"""
|
||||
Remove the file at the given path. This only works on files; for
|
||||
removing folders (directories), use `rmdir`.
|
||||
|
||||
:param str path: path (absolute or relative) of the file to remove
|
||||
|
||||
:raises: ``IOError`` -- if the path refers to a folder (directory)
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, "remove({!r})".format(path))
|
||||
self._request(CMD_REMOVE, path)
|
||||
|
||||
unlink = remove
|
||||
|
||||
def rename(self, oldpath, newpath):
|
||||
"""
|
||||
Rename a file or folder from ``oldpath`` to ``newpath``.
|
||||
|
||||
.. note::
|
||||
This method implements 'standard' SFTP ``RENAME`` behavior; those
|
||||
seeking the OpenSSH "POSIX rename" extension behavior should use
|
||||
`posix_rename`.
|
||||
|
||||
:param str oldpath:
|
||||
existing name of the file or folder
|
||||
:param str newpath:
|
||||
new name for the file or folder, must not exist already
|
||||
|
||||
:raises:
|
||||
``IOError`` -- if ``newpath`` is a folder, or something else goes
|
||||
wrong
|
||||
"""
|
||||
oldpath = self._adjust_cwd(oldpath)
|
||||
newpath = self._adjust_cwd(newpath)
|
||||
self._log(DEBUG, "rename({!r}, {!r})".format(oldpath, newpath))
|
||||
self._request(CMD_RENAME, oldpath, newpath)
|
||||
|
||||
def posix_rename(self, oldpath, newpath):
|
||||
"""
|
||||
Rename a file or folder from ``oldpath`` to ``newpath``, following
|
||||
posix conventions.
|
||||
|
||||
:param str oldpath: existing name of the file or folder
|
||||
:param str newpath: new name for the file or folder, will be
|
||||
overwritten if it already exists
|
||||
|
||||
:raises:
|
||||
``IOError`` -- if ``newpath`` is a folder, posix-rename is not
|
||||
supported by the server or something else goes wrong
|
||||
|
||||
:versionadded: 2.2
|
||||
"""
|
||||
oldpath = self._adjust_cwd(oldpath)
|
||||
newpath = self._adjust_cwd(newpath)
|
||||
self._log(DEBUG, "posix_rename({!r}, {!r})".format(oldpath, newpath))
|
||||
self._request(
|
||||
CMD_EXTENDED, "posix-rename@openssh.com", oldpath, newpath
|
||||
)
|
||||
|
||||
def mkdir(self, path, mode=o777):
|
||||
"""
|
||||
Create a folder (directory) named ``path`` with numeric mode ``mode``.
|
||||
The default mode is 0777 (octal). On some systems, mode is ignored.
|
||||
Where it is used, the current umask value is first masked out.
|
||||
|
||||
:param str path: name of the folder to create
|
||||
:param int mode: permissions (posix-style) for the newly-created folder
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, "mkdir({!r}, {!r})".format(path, mode))
|
||||
attr = SFTPAttributes()
|
||||
attr.st_mode = mode
|
||||
self._request(CMD_MKDIR, path, attr)
|
||||
|
||||
def rmdir(self, path):
|
||||
"""
|
||||
Remove the folder named ``path``.
|
||||
|
||||
:param str path: name of the folder to remove
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, "rmdir({!r})".format(path))
|
||||
self._request(CMD_RMDIR, path)
|
||||
|
||||
def stat(self, path):
|
||||
"""
|
||||
Retrieve information about a file on the remote system. The return
|
||||
value is an object whose attributes correspond to the attributes of
|
||||
Python's ``stat`` structure as returned by ``os.stat``, except that it
|
||||
contains fewer fields. An SFTP server may return as much or as little
|
||||
info as it wants, so the results may vary from server to server.
|
||||
|
||||
Unlike a Python `python:stat` object, the result may not be accessed as
|
||||
a tuple. This is mostly due to the author's slack factor.
|
||||
|
||||
The fields supported are: ``st_mode``, ``st_size``, ``st_uid``,
|
||||
``st_gid``, ``st_atime``, and ``st_mtime``.
|
||||
|
||||
:param str path: the filename to stat
|
||||
:return:
|
||||
an `.SFTPAttributes` object containing attributes about the given
|
||||
file
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, "stat({!r})".format(path))
|
||||
t, msg = self._request(CMD_STAT, path)
|
||||
if t != CMD_ATTRS:
|
||||
raise SFTPError("Expected attributes")
|
||||
return SFTPAttributes._from_msg(msg)
|
||||
|
||||
def lstat(self, path):
|
||||
"""
|
||||
Retrieve information about a file on the remote system, without
|
||||
following symbolic links (shortcuts). This otherwise behaves exactly
|
||||
the same as `stat`.
|
||||
|
||||
:param str path: the filename to stat
|
||||
:return:
|
||||
an `.SFTPAttributes` object containing attributes about the given
|
||||
file
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, "lstat({!r})".format(path))
|
||||
t, msg = self._request(CMD_LSTAT, path)
|
||||
if t != CMD_ATTRS:
|
||||
raise SFTPError("Expected attributes")
|
||||
return SFTPAttributes._from_msg(msg)
|
||||
|
||||
def symlink(self, source, dest):
|
||||
"""
|
||||
Create a symbolic link to the ``source`` path at ``destination``.
|
||||
|
||||
:param str source: path of the original file
|
||||
:param str dest: path of the newly created symlink
|
||||
"""
|
||||
dest = self._adjust_cwd(dest)
|
||||
self._log(DEBUG, "symlink({!r}, {!r})".format(source, dest))
|
||||
source = bytestring(source)
|
||||
self._request(CMD_SYMLINK, source, dest)
|
||||
|
||||
def chmod(self, path, mode):
|
||||
"""
|
||||
Change the mode (permissions) of a file. The permissions are
|
||||
unix-style and identical to those used by Python's `os.chmod`
|
||||
function.
|
||||
|
||||
:param str path: path of the file to change the permissions of
|
||||
:param int mode: new permissions
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, "chmod({!r}, {!r})".format(path, mode))
|
||||
attr = SFTPAttributes()
|
||||
attr.st_mode = mode
|
||||
self._request(CMD_SETSTAT, path, attr)
|
||||
|
||||
def chown(self, path, uid, gid):
|
||||
"""
|
||||
Change the owner (``uid``) and group (``gid``) of a 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 str path: path of the file to change the owner and group of
|
||||
:param int uid: new owner's uid
|
||||
:param int gid: new group id
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, "chown({!r}, {!r}, {!r})".format(path, uid, gid))
|
||||
attr = SFTPAttributes()
|
||||
attr.st_uid, attr.st_gid = uid, gid
|
||||
self._request(CMD_SETSTAT, path, attr)
|
||||
|
||||
def utime(self, path, times):
|
||||
"""
|
||||
Set the access and modified times of the file specified by ``path``.
|
||||
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 str path: path of the file to modify
|
||||
:param tuple times:
|
||||
``None`` or a tuple of (access time, modified time) in standard
|
||||
internet epoch time (seconds since 01 January 1970 GMT)
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
if times is None:
|
||||
times = (time.time(), time.time())
|
||||
self._log(DEBUG, "utime({!r}, {!r})".format(path, times))
|
||||
attr = SFTPAttributes()
|
||||
attr.st_atime, attr.st_mtime = times
|
||||
self._request(CMD_SETSTAT, path, attr)
|
||||
|
||||
def truncate(self, path, size):
|
||||
"""
|
||||
Change the size of the file specified by ``path``. This usually
|
||||
extends or shrinks the size of the file, just like the `~file.truncate`
|
||||
method on Python file objects.
|
||||
|
||||
:param str path: path of the file to modify
|
||||
:param int size: the new size of the file
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, "truncate({!r}, {!r})".format(path, size))
|
||||
attr = SFTPAttributes()
|
||||
attr.st_size = size
|
||||
self._request(CMD_SETSTAT, path, attr)
|
||||
|
||||
def readlink(self, path):
|
||||
"""
|
||||
Return the target of a symbolic link (shortcut). You can use
|
||||
`symlink` to create these. The result may be either an absolute or
|
||||
relative pathname.
|
||||
|
||||
:param str path: path of the symbolic link file
|
||||
:return: target path, as a `str`
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, "readlink({!r})".format(path))
|
||||
t, msg = self._request(CMD_READLINK, path)
|
||||
if t != CMD_NAME:
|
||||
raise SFTPError("Expected name response")
|
||||
count = msg.get_int()
|
||||
if count == 0:
|
||||
return None
|
||||
if count != 1:
|
||||
raise SFTPError("Readlink returned {} results".format(count))
|
||||
return _to_unicode(msg.get_string())
|
||||
|
||||
def normalize(self, path):
|
||||
"""
|
||||
Return the normalized path (on the server) of a given path. This
|
||||
can be used to quickly resolve symbolic links or determine what the
|
||||
server is considering to be the "current folder" (by passing ``'.'``
|
||||
as ``path``).
|
||||
|
||||
:param str path: path to be normalized
|
||||
:return: normalized form of the given path (as a `str`)
|
||||
|
||||
:raises: ``IOError`` -- if the path can't be resolved on the server
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, "normalize({!r})".format(path))
|
||||
t, msg = self._request(CMD_REALPATH, path)
|
||||
if t != CMD_NAME:
|
||||
raise SFTPError("Expected name response")
|
||||
count = msg.get_int()
|
||||
if count != 1:
|
||||
raise SFTPError("Realpath returned {} results".format(count))
|
||||
return msg.get_text()
|
||||
|
||||
def chdir(self, path=None):
|
||||
"""
|
||||
Change the "current directory" of this SFTP session. Since SFTP
|
||||
doesn't really have the concept of a current working directory, this is
|
||||
emulated by Paramiko. Once you use this method to set a working
|
||||
directory, all operations on this `.SFTPClient` object will be relative
|
||||
to that path. You can pass in ``None`` to stop using a current working
|
||||
directory.
|
||||
|
||||
:param str path: new current working directory
|
||||
|
||||
:raises:
|
||||
``IOError`` -- if the requested path doesn't exist on the server
|
||||
|
||||
.. versionadded:: 1.4
|
||||
"""
|
||||
if path is None:
|
||||
self._cwd = None
|
||||
return
|
||||
if not stat.S_ISDIR(self.stat(path).st_mode):
|
||||
code = errno.ENOTDIR
|
||||
raise SFTPError(code, "{}: {}".format(os.strerror(code), path))
|
||||
self._cwd = b(self.normalize(path))
|
||||
|
||||
def getcwd(self):
|
||||
"""
|
||||
Return the "current working directory" for this SFTP session, as
|
||||
emulated by Paramiko. If no directory has been set with `chdir`,
|
||||
this method will return ``None``.
|
||||
|
||||
.. versionadded:: 1.4
|
||||
"""
|
||||
# TODO: make class initialize with self._cwd set to self.normalize('.')
|
||||
return self._cwd and u(self._cwd)
|
||||
|
||||
def _transfer_with_callback(self, reader, writer, file_size, callback):
|
||||
size = 0
|
||||
while True:
|
||||
data = reader.read(32768)
|
||||
writer.write(data)
|
||||
size += len(data)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if callback is not None:
|
||||
callback(size, file_size)
|
||||
return size
|
||||
|
||||
def putfo(self, fl, remotepath, file_size=0, callback=None, confirm=True):
|
||||
"""
|
||||
Copy the contents of an open file object (``fl``) to the SFTP server as
|
||||
``remotepath``. Any exception raised by operations will be passed
|
||||
through.
|
||||
|
||||
The SFTP operations use pipelining for speed.
|
||||
|
||||
:param fl: opened file or file-like object to copy
|
||||
:param str remotepath: the destination path on the SFTP server
|
||||
:param int file_size:
|
||||
optional size parameter passed to callback. If none is specified,
|
||||
size defaults to 0
|
||||
:param callable callback:
|
||||
optional callback function (form: ``func(int, int)``) that accepts
|
||||
the bytes transferred so far and the total bytes to be transferred
|
||||
(since 1.7.4)
|
||||
:param bool confirm:
|
||||
whether to do a stat() on the file afterwards to confirm the file
|
||||
size (since 1.7.7)
|
||||
|
||||
:return:
|
||||
an `.SFTPAttributes` object containing attributes about the given
|
||||
file.
|
||||
|
||||
.. versionadded:: 1.10
|
||||
"""
|
||||
with self.file(remotepath, "wb") as fr:
|
||||
fr.set_pipelined(True)
|
||||
size = self._transfer_with_callback(
|
||||
reader=fl, writer=fr, file_size=file_size, callback=callback
|
||||
)
|
||||
if confirm:
|
||||
s = self.stat(remotepath)
|
||||
if s.st_size != size:
|
||||
raise IOError(
|
||||
"size mismatch in put! {} != {}".format(s.st_size, size)
|
||||
)
|
||||
else:
|
||||
s = SFTPAttributes()
|
||||
return s
|
||||
|
||||
def put(self, localpath, remotepath, callback=None, confirm=True):
|
||||
"""
|
||||
Copy a local file (``localpath``) to the SFTP server as ``remotepath``.
|
||||
Any exception raised by operations will be passed through. This
|
||||
method is primarily provided as a convenience.
|
||||
|
||||
The SFTP operations use pipelining for speed.
|
||||
|
||||
:param str localpath: the local file to copy
|
||||
:param str remotepath: the destination path on the SFTP server. Note
|
||||
that the filename should be included. Only specifying a directory
|
||||
may result in an error.
|
||||
:param callable callback:
|
||||
optional callback function (form: ``func(int, int)``) that accepts
|
||||
the bytes transferred so far and the total bytes to be transferred
|
||||
:param bool confirm:
|
||||
whether to do a stat() on the file afterwards to confirm the file
|
||||
size
|
||||
|
||||
:return: an `.SFTPAttributes` object containing attributes about the
|
||||
given file
|
||||
|
||||
.. versionadded:: 1.4
|
||||
.. versionchanged:: 1.7.4
|
||||
``callback`` and rich attribute return value added.
|
||||
.. versionchanged:: 1.7.7
|
||||
``confirm`` param added.
|
||||
"""
|
||||
file_size = os.stat(localpath).st_size
|
||||
with open(localpath, "rb") as fl:
|
||||
return self.putfo(fl, remotepath, file_size, callback, confirm)
|
||||
|
||||
def getfo(self, remotepath, fl, callback=None):
|
||||
"""
|
||||
Copy a remote file (``remotepath``) from the SFTP server and write to
|
||||
an open file or file-like object, ``fl``. Any exception raised by
|
||||
operations will be passed through. This method is primarily provided
|
||||
as a convenience.
|
||||
|
||||
:param object remotepath: opened file or file-like object to copy to
|
||||
:param str fl:
|
||||
the destination path on the local host or open file object
|
||||
:param callable callback:
|
||||
optional callback function (form: ``func(int, int)``) that accepts
|
||||
the bytes transferred so far and the total bytes to be transferred
|
||||
:return: the `number <int>` of bytes written to the opened file object
|
||||
|
||||
.. versionadded:: 1.10
|
||||
"""
|
||||
file_size = self.stat(remotepath).st_size
|
||||
with self.open(remotepath, "rb") as fr:
|
||||
fr.prefetch(file_size)
|
||||
return self._transfer_with_callback(
|
||||
reader=fr, writer=fl, file_size=file_size, callback=callback
|
||||
)
|
||||
|
||||
def get(self, remotepath, localpath, callback=None):
|
||||
"""
|
||||
Copy a remote file (``remotepath``) from the SFTP server to the local
|
||||
host as ``localpath``. Any exception raised by operations will be
|
||||
passed through. This method is primarily provided as a convenience.
|
||||
|
||||
:param str remotepath: the remote file to copy
|
||||
:param str localpath: the destination path on the local host
|
||||
:param callable callback:
|
||||
optional callback function (form: ``func(int, int)``) that accepts
|
||||
the bytes transferred so far and the total bytes to be transferred
|
||||
|
||||
.. versionadded:: 1.4
|
||||
.. versionchanged:: 1.7.4
|
||||
Added the ``callback`` param
|
||||
"""
|
||||
with open(localpath, "wb") as fl:
|
||||
size = self.getfo(remotepath, fl, callback)
|
||||
s = os.stat(localpath)
|
||||
if s.st_size != size:
|
||||
raise IOError(
|
||||
"size mismatch in get! {} != {}".format(s.st_size, size)
|
||||
)
|
||||
|
||||
# ...internals...
|
||||
|
||||
def _request(self, t, *arg):
|
||||
num = self._async_request(type(None), t, *arg)
|
||||
return self._read_response(num)
|
||||
|
||||
def _async_request(self, fileobj, t, *arg):
|
||||
# this method may be called from other threads (prefetch)
|
||||
self._lock.acquire()
|
||||
try:
|
||||
msg = Message()
|
||||
msg.add_int(self.request_number)
|
||||
for item in arg:
|
||||
if isinstance(item, long):
|
||||
msg.add_int64(item)
|
||||
elif isinstance(item, int):
|
||||
msg.add_int(item)
|
||||
elif isinstance(item, SFTPAttributes):
|
||||
item._pack(msg)
|
||||
else:
|
||||
# For all other types, rely on as_string() to either coerce
|
||||
# to bytes before writing or raise a suitable exception.
|
||||
msg.add_string(item)
|
||||
num = self.request_number
|
||||
self._expecting[num] = fileobj
|
||||
self.request_number += 1
|
||||
finally:
|
||||
self._lock.release()
|
||||
self._send_packet(t, msg)
|
||||
return num
|
||||
|
||||
def _read_response(self, waitfor=None):
|
||||
while True:
|
||||
try:
|
||||
t, data = self._read_packet()
|
||||
except EOFError as e:
|
||||
raise SSHException("Server connection dropped: {}".format(e))
|
||||
msg = Message(data)
|
||||
num = msg.get_int()
|
||||
self._lock.acquire()
|
||||
try:
|
||||
if num not in self._expecting:
|
||||
# might be response for a file that was closed before
|
||||
# responses came back
|
||||
self._log(DEBUG, "Unexpected response #{}".format(num))
|
||||
if waitfor is None:
|
||||
# just doing a single check
|
||||
break
|
||||
continue
|
||||
fileobj = self._expecting[num]
|
||||
del self._expecting[num]
|
||||
finally:
|
||||
self._lock.release()
|
||||
if num == waitfor:
|
||||
# synchronous
|
||||
if t == CMD_STATUS:
|
||||
self._convert_status(msg)
|
||||
return t, msg
|
||||
|
||||
# can not rewrite this to deal with E721, either as a None check
|
||||
# nor as not an instance of None or NoneType
|
||||
if fileobj is not type(None): # noqa
|
||||
fileobj._async_response(t, msg, num)
|
||||
if waitfor is None:
|
||||
# just doing a single check
|
||||
break
|
||||
return None, None
|
||||
|
||||
def _finish_responses(self, fileobj):
|
||||
while fileobj in self._expecting.values():
|
||||
self._read_response()
|
||||
fileobj._check_exception()
|
||||
|
||||
def _convert_status(self, msg):
|
||||
"""
|
||||
Raises EOFError or IOError on error status; otherwise does nothing.
|
||||
"""
|
||||
code = msg.get_int()
|
||||
text = msg.get_text()
|
||||
if code == SFTP_OK:
|
||||
return
|
||||
elif code == SFTP_EOF:
|
||||
raise EOFError(text)
|
||||
elif code == SFTP_NO_SUCH_FILE:
|
||||
# clever idea from john a. meinel: map the error codes to errno
|
||||
raise IOError(errno.ENOENT, text)
|
||||
elif code == SFTP_PERMISSION_DENIED:
|
||||
raise IOError(errno.EACCES, text)
|
||||
else:
|
||||
raise IOError(text)
|
||||
|
||||
def _adjust_cwd(self, path):
|
||||
"""
|
||||
Return an adjusted path if we're emulating a "current working
|
||||
directory" for the server.
|
||||
"""
|
||||
path = b(path)
|
||||
if self._cwd is None:
|
||||
return path
|
||||
if len(path) and path[0:1] == b_slash:
|
||||
# absolute path
|
||||
return path
|
||||
if self._cwd == b_slash:
|
||||
return self._cwd + path
|
||||
return self._cwd + b_slash + path
|
||||
|
||||
|
||||
class SFTP(SFTPClient):
|
||||
"""
|
||||
An alias for `.SFTPClient` for backwards compatibility.
|
||||
"""
|
||||
|
||||
pass
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/sftp_client.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/sftp_client.pyc
Normal file
Binary file not shown.
570
.ve/lib/python2.7/site-packages/paramiko/sftp_file.py
Normal file
570
.ve/lib/python2.7/site-packages/paramiko/sftp_file.py
Normal file
@@ -0,0 +1,570 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
SFTP file object
|
||||
"""
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
from binascii import hexlify
|
||||
from collections import deque
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
from paramiko.common import DEBUG
|
||||
|
||||
from paramiko.file import BufferedFile
|
||||
from paramiko.py3compat import u, long
|
||||
from paramiko.sftp import (
|
||||
CMD_CLOSE,
|
||||
CMD_READ,
|
||||
CMD_DATA,
|
||||
SFTPError,
|
||||
CMD_WRITE,
|
||||
CMD_STATUS,
|
||||
CMD_FSTAT,
|
||||
CMD_ATTRS,
|
||||
CMD_FSETSTAT,
|
||||
CMD_EXTENDED,
|
||||
)
|
||||
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, long(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,
|
||||
long(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,
|
||||
long(offset),
|
||||
long(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):
|
||||
"""
|
||||
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.
|
||||
|
||||
.. 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.
|
||||
"""
|
||||
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)
|
||||
|
||||
def readv(self, chunks):
|
||||
"""
|
||||
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
|
||||
:return: a list of blocks read, in the same order as in ``chunks``
|
||||
|
||||
.. versionadded:: 1.5.4
|
||||
"""
|
||||
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)
|
||||
# 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):
|
||||
self._prefetching = True
|
||||
self._prefetch_done = False
|
||||
|
||||
t = threading.Thread(target=self._prefetch_thread, args=(chunks,))
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
def _prefetch_thread(self, chunks):
|
||||
# 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:
|
||||
num = self.sftp._async_request(
|
||||
self, CMD_READ, self.handle, long(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
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/sftp_file.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/sftp_file.pyc
Normal file
Binary file not shown.
196
.ve/lib/python2.7/site-packages/paramiko/sftp_handle.py
Normal file
196
.ve/lib/python2.7/site-packages/paramiko/sftp_handle.py
Normal file
@@ -0,0 +1,196 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Abstraction of an SFTP file handle (for server mode).
|
||||
"""
|
||||
|
||||
import os
|
||||
from paramiko.sftp import SFTP_OP_UNSUPPORTED, SFTP_OK
|
||||
from paramiko.util import ClosingContextManager
|
||||
|
||||
|
||||
class SFTPHandle(ClosingContextManager):
|
||||
"""
|
||||
Abstract object representing a handle to an open file (or folder) in an
|
||||
SFTP server implementation. Each handle has a string representation used
|
||||
by the client to refer to the underlying file.
|
||||
|
||||
Server implementations can (and should) subclass SFTPHandle to implement
|
||||
features of a file handle, like `stat` or `chattr`.
|
||||
|
||||
Instances of this class may be used as context managers.
|
||||
"""
|
||||
|
||||
def __init__(self, flags=0):
|
||||
"""
|
||||
Create a new file handle representing a local file being served over
|
||||
SFTP. If ``flags`` is passed in, it's used to determine if the file
|
||||
is open in append mode.
|
||||
|
||||
:param int flags: optional flags as passed to
|
||||
`.SFTPServerInterface.open`
|
||||
"""
|
||||
self.__flags = flags
|
||||
self.__name = None
|
||||
# only for handles to folders:
|
||||
self.__files = {}
|
||||
self.__tell = None
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
When a client closes a file, this method is called on the handle.
|
||||
Normally you would use this method to close the underlying OS level
|
||||
file object(s).
|
||||
|
||||
The default implementation checks for attributes on ``self`` named
|
||||
``readfile`` and/or ``writefile``, and if either or both are present,
|
||||
their ``close()`` methods are called. This means that if you are
|
||||
using the default implementations of `read` and `write`, this
|
||||
method's default implementation should be fine also.
|
||||
"""
|
||||
readfile = getattr(self, "readfile", None)
|
||||
if readfile is not None:
|
||||
readfile.close()
|
||||
writefile = getattr(self, "writefile", None)
|
||||
if writefile is not None:
|
||||
writefile.close()
|
||||
|
||||
def read(self, offset, length):
|
||||
"""
|
||||
Read up to ``length`` bytes from this file, starting at position
|
||||
``offset``. The offset may be a Python long, since SFTP allows it
|
||||
to be 64 bits.
|
||||
|
||||
If the end of the file has been reached, this method may return an
|
||||
empty string to signify EOF, or it may also return ``SFTP_EOF``.
|
||||
|
||||
The default implementation checks for an attribute on ``self`` named
|
||||
``readfile``, and if present, performs the read operation on the Python
|
||||
file-like object found there. (This is meant as a time saver for the
|
||||
common case where you are wrapping a Python file object.)
|
||||
|
||||
:param offset: position in the file to start reading from.
|
||||
:param int length: number of bytes to attempt to read.
|
||||
:return: data read from the file, or an SFTP error code, as a `str`.
|
||||
"""
|
||||
readfile = getattr(self, "readfile", None)
|
||||
if readfile is None:
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
try:
|
||||
if self.__tell is None:
|
||||
self.__tell = readfile.tell()
|
||||
if offset != self.__tell:
|
||||
readfile.seek(offset)
|
||||
self.__tell = offset
|
||||
data = readfile.read(length)
|
||||
except IOError as e:
|
||||
self.__tell = None
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
self.__tell += len(data)
|
||||
return data
|
||||
|
||||
def write(self, offset, data):
|
||||
"""
|
||||
Write ``data`` into this file at position ``offset``. Extending the
|
||||
file past its original end is expected. Unlike Python's normal
|
||||
``write()`` methods, this method cannot do a partial write: it must
|
||||
write all of ``data`` or else return an error.
|
||||
|
||||
The default implementation checks for an attribute on ``self`` named
|
||||
``writefile``, and if present, performs the write operation on the
|
||||
Python file-like object found there. The attribute is named
|
||||
differently from ``readfile`` to make it easy to implement read-only
|
||||
(or write-only) files, but if both attributes are present, they should
|
||||
refer to the same file.
|
||||
|
||||
:param offset: position in the file to start reading from.
|
||||
:param str data: data to write into the file.
|
||||
:return: an SFTP error code like ``SFTP_OK``.
|
||||
"""
|
||||
writefile = getattr(self, "writefile", None)
|
||||
if writefile is None:
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
try:
|
||||
# in append mode, don't care about seeking
|
||||
if (self.__flags & os.O_APPEND) == 0:
|
||||
if self.__tell is None:
|
||||
self.__tell = writefile.tell()
|
||||
if offset != self.__tell:
|
||||
writefile.seek(offset)
|
||||
self.__tell = offset
|
||||
writefile.write(data)
|
||||
writefile.flush()
|
||||
except IOError as e:
|
||||
self.__tell = None
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
if self.__tell is not None:
|
||||
self.__tell += len(data)
|
||||
return SFTP_OK
|
||||
|
||||
def stat(self):
|
||||
"""
|
||||
Return an `.SFTPAttributes` object referring to this open file, or an
|
||||
error code. This is equivalent to `.SFTPServerInterface.stat`, except
|
||||
it's called on an open file instead of a path.
|
||||
|
||||
:return:
|
||||
an attributes object for the given file, or an SFTP error code
|
||||
(like ``SFTP_PERMISSION_DENIED``).
|
||||
:rtype: `.SFTPAttributes` or error code
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def chattr(self, attr):
|
||||
"""
|
||||
Change the attributes of this 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 .SFTPAttributes attr: the attributes to change on this file.
|
||||
:return: an `int` error code like ``SFTP_OK``.
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
# ...internals...
|
||||
|
||||
def _set_files(self, files):
|
||||
"""
|
||||
Used by the SFTP server code to cache a directory listing. (In
|
||||
the SFTP protocol, listing a directory is a multi-stage process
|
||||
requiring a temporary handle.)
|
||||
"""
|
||||
self.__files = files
|
||||
|
||||
def _get_next_files(self):
|
||||
"""
|
||||
Used by the SFTP server code to retrieve a cached directory
|
||||
listing.
|
||||
"""
|
||||
fnlist = self.__files[:16]
|
||||
self.__files = self.__files[16:]
|
||||
return fnlist
|
||||
|
||||
def _get_name(self):
|
||||
return self.__name
|
||||
|
||||
def _set_name(self, name):
|
||||
self.__name = name
|
||||
|
||||
|
||||
from paramiko.sftp_server import SFTPServer
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/sftp_handle.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/sftp_handle.pyc
Normal file
Binary file not shown.
539
.ve/lib/python2.7/site-packages/paramiko/sftp_server.py
Normal file
539
.ve/lib/python2.7/site-packages/paramiko/sftp_server.py
Normal file
@@ -0,0 +1,539 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Server-mode SFTP support.
|
||||
"""
|
||||
|
||||
import os
|
||||
import errno
|
||||
import sys
|
||||
from hashlib import md5, sha1
|
||||
|
||||
from paramiko import util
|
||||
from paramiko.sftp import (
|
||||
BaseSFTP,
|
||||
Message,
|
||||
SFTP_FAILURE,
|
||||
SFTP_PERMISSION_DENIED,
|
||||
SFTP_NO_SUCH_FILE,
|
||||
)
|
||||
from paramiko.sftp_si import SFTPServerInterface
|
||||
from paramiko.sftp_attr import SFTPAttributes
|
||||
from paramiko.common import DEBUG
|
||||
from paramiko.py3compat import long, string_types, bytes_types, b
|
||||
from paramiko.server import SubsystemHandler
|
||||
|
||||
|
||||
# known hash algorithms for the "check-file" extension
|
||||
from paramiko.sftp import (
|
||||
CMD_HANDLE,
|
||||
SFTP_DESC,
|
||||
CMD_STATUS,
|
||||
SFTP_EOF,
|
||||
CMD_NAME,
|
||||
SFTP_BAD_MESSAGE,
|
||||
CMD_EXTENDED_REPLY,
|
||||
SFTP_FLAG_READ,
|
||||
SFTP_FLAG_WRITE,
|
||||
SFTP_FLAG_APPEND,
|
||||
SFTP_FLAG_CREATE,
|
||||
SFTP_FLAG_TRUNC,
|
||||
SFTP_FLAG_EXCL,
|
||||
CMD_NAMES,
|
||||
CMD_OPEN,
|
||||
CMD_CLOSE,
|
||||
SFTP_OK,
|
||||
CMD_READ,
|
||||
CMD_DATA,
|
||||
CMD_WRITE,
|
||||
CMD_REMOVE,
|
||||
CMD_RENAME,
|
||||
CMD_MKDIR,
|
||||
CMD_RMDIR,
|
||||
CMD_OPENDIR,
|
||||
CMD_READDIR,
|
||||
CMD_STAT,
|
||||
CMD_ATTRS,
|
||||
CMD_LSTAT,
|
||||
CMD_FSTAT,
|
||||
CMD_SETSTAT,
|
||||
CMD_FSETSTAT,
|
||||
CMD_READLINK,
|
||||
CMD_SYMLINK,
|
||||
CMD_REALPATH,
|
||||
CMD_EXTENDED,
|
||||
SFTP_OP_UNSUPPORTED,
|
||||
)
|
||||
|
||||
_hash_class = {"sha1": sha1, "md5": md5}
|
||||
|
||||
|
||||
class SFTPServer(BaseSFTP, SubsystemHandler):
|
||||
"""
|
||||
Server-side SFTP subsystem support. Since this is a `.SubsystemHandler`,
|
||||
it can be (and is meant to be) set as the handler for ``"sftp"`` requests.
|
||||
Use `.Transport.set_subsystem_handler` to activate this class.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
channel,
|
||||
name,
|
||||
server,
|
||||
sftp_si=SFTPServerInterface,
|
||||
*largs,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
The constructor for SFTPServer is meant to be called from within the
|
||||
`.Transport` as a subsystem handler. ``server`` and any additional
|
||||
parameters or keyword parameters are passed from the original call to
|
||||
`.Transport.set_subsystem_handler`.
|
||||
|
||||
:param .Channel channel: channel passed from the `.Transport`.
|
||||
:param str name: name of the requested subsystem.
|
||||
:param .ServerInterface server:
|
||||
the server object associated with this channel and subsystem
|
||||
:param sftp_si:
|
||||
a subclass of `.SFTPServerInterface` to use for handling individual
|
||||
requests.
|
||||
"""
|
||||
BaseSFTP.__init__(self)
|
||||
SubsystemHandler.__init__(self, channel, name, server)
|
||||
transport = channel.get_transport()
|
||||
self.logger = util.get_logger(transport.get_log_channel() + ".sftp")
|
||||
self.ultra_debug = transport.get_hexdump()
|
||||
self.next_handle = 1
|
||||
# map of handle-string to SFTPHandle for files & folders:
|
||||
self.file_table = {}
|
||||
self.folder_table = {}
|
||||
self.server = sftp_si(server, *largs, **kwargs)
|
||||
|
||||
def _log(self, level, msg):
|
||||
if issubclass(type(msg), list):
|
||||
for m in msg:
|
||||
super(SFTPServer, self)._log(
|
||||
level, "[chan " + self.sock.get_name() + "] " + m
|
||||
)
|
||||
else:
|
||||
super(SFTPServer, self)._log(
|
||||
level, "[chan " + self.sock.get_name() + "] " + msg
|
||||
)
|
||||
|
||||
def start_subsystem(self, name, transport, channel):
|
||||
self.sock = channel
|
||||
self._log(DEBUG, "Started sftp server on channel {!r}".format(channel))
|
||||
self._send_server_version()
|
||||
self.server.session_started()
|
||||
while True:
|
||||
try:
|
||||
t, data = self._read_packet()
|
||||
except EOFError:
|
||||
self._log(DEBUG, "EOF -- end of session")
|
||||
return
|
||||
except Exception as e:
|
||||
self._log(DEBUG, "Exception on channel: " + str(e))
|
||||
self._log(DEBUG, util.tb_strings())
|
||||
return
|
||||
msg = Message(data)
|
||||
request_number = msg.get_int()
|
||||
try:
|
||||
self._process(t, request_number, msg)
|
||||
except Exception as e:
|
||||
self._log(DEBUG, "Exception in server processing: " + str(e))
|
||||
self._log(DEBUG, util.tb_strings())
|
||||
# send some kind of failure message, at least
|
||||
try:
|
||||
self._send_status(request_number, SFTP_FAILURE)
|
||||
except:
|
||||
pass
|
||||
|
||||
def finish_subsystem(self):
|
||||
self.server.session_ended()
|
||||
super(SFTPServer, self).finish_subsystem()
|
||||
# close any file handles that were left open
|
||||
# (so we can return them to the OS quickly)
|
||||
for f in self.file_table.values():
|
||||
f.close()
|
||||
for f in self.folder_table.values():
|
||||
f.close()
|
||||
self.file_table = {}
|
||||
self.folder_table = {}
|
||||
|
||||
@staticmethod
|
||||
def convert_errno(e):
|
||||
"""
|
||||
Convert an errno value (as from an ``OSError`` or ``IOError``) into a
|
||||
standard SFTP result code. This is a convenience function for trapping
|
||||
exceptions in server code and returning an appropriate result.
|
||||
|
||||
:param int e: an errno code, as from ``OSError.errno``.
|
||||
:return: an `int` SFTP error code like ``SFTP_NO_SUCH_FILE``.
|
||||
"""
|
||||
if e == errno.EACCES:
|
||||
# permission denied
|
||||
return SFTP_PERMISSION_DENIED
|
||||
elif (e == errno.ENOENT) or (e == errno.ENOTDIR):
|
||||
# no such file
|
||||
return SFTP_NO_SUCH_FILE
|
||||
else:
|
||||
return SFTP_FAILURE
|
||||
|
||||
@staticmethod
|
||||
def set_file_attr(filename, attr):
|
||||
"""
|
||||
Change a file's attributes on the local filesystem. The contents of
|
||||
``attr`` are used to change the permissions, owner, group ownership,
|
||||
and/or modification & access time of the file, depending on which
|
||||
attributes are present in ``attr``.
|
||||
|
||||
This is meant to be a handy helper function for translating SFTP file
|
||||
requests into local file operations.
|
||||
|
||||
:param str filename:
|
||||
name of the file to alter (should usually be an absolute path).
|
||||
:param .SFTPAttributes attr: attributes to change.
|
||||
"""
|
||||
if sys.platform != "win32":
|
||||
# mode operations are meaningless on win32
|
||||
if attr._flags & attr.FLAG_PERMISSIONS:
|
||||
os.chmod(filename, attr.st_mode)
|
||||
if attr._flags & attr.FLAG_UIDGID:
|
||||
os.chown(filename, attr.st_uid, attr.st_gid)
|
||||
if attr._flags & attr.FLAG_AMTIME:
|
||||
os.utime(filename, (attr.st_atime, attr.st_mtime))
|
||||
if attr._flags & attr.FLAG_SIZE:
|
||||
with open(filename, "w+") as f:
|
||||
f.truncate(attr.st_size)
|
||||
|
||||
# ...internals...
|
||||
|
||||
def _response(self, request_number, t, *arg):
|
||||
msg = Message()
|
||||
msg.add_int(request_number)
|
||||
for item in arg:
|
||||
if isinstance(item, long):
|
||||
msg.add_int64(item)
|
||||
elif isinstance(item, int):
|
||||
msg.add_int(item)
|
||||
elif isinstance(item, (string_types, bytes_types)):
|
||||
msg.add_string(item)
|
||||
elif type(item) is SFTPAttributes:
|
||||
item._pack(msg)
|
||||
else:
|
||||
raise Exception(
|
||||
"unknown type for {!r} type {!r}".format(item, type(item))
|
||||
)
|
||||
self._send_packet(t, msg)
|
||||
|
||||
def _send_handle_response(self, request_number, handle, folder=False):
|
||||
if not issubclass(type(handle), SFTPHandle):
|
||||
# must be error code
|
||||
self._send_status(request_number, handle)
|
||||
return
|
||||
handle._set_name(b("hx{:d}".format(self.next_handle)))
|
||||
self.next_handle += 1
|
||||
if folder:
|
||||
self.folder_table[handle._get_name()] = handle
|
||||
else:
|
||||
self.file_table[handle._get_name()] = handle
|
||||
self._response(request_number, CMD_HANDLE, handle._get_name())
|
||||
|
||||
def _send_status(self, request_number, code, desc=None):
|
||||
if desc is None:
|
||||
try:
|
||||
desc = SFTP_DESC[code]
|
||||
except IndexError:
|
||||
desc = "Unknown"
|
||||
# some clients expect a "langauge" tag at the end
|
||||
# (but don't mind it being blank)
|
||||
self._response(request_number, CMD_STATUS, code, desc, "")
|
||||
|
||||
def _open_folder(self, request_number, path):
|
||||
resp = self.server.list_folder(path)
|
||||
if issubclass(type(resp), list):
|
||||
# got an actual list of filenames in the folder
|
||||
folder = SFTPHandle()
|
||||
folder._set_files(resp)
|
||||
self._send_handle_response(request_number, folder, True)
|
||||
return
|
||||
# must be an error code
|
||||
self._send_status(request_number, resp)
|
||||
|
||||
def _read_folder(self, request_number, folder):
|
||||
flist = folder._get_next_files()
|
||||
if len(flist) == 0:
|
||||
self._send_status(request_number, SFTP_EOF)
|
||||
return
|
||||
msg = Message()
|
||||
msg.add_int(request_number)
|
||||
msg.add_int(len(flist))
|
||||
for attr in flist:
|
||||
msg.add_string(attr.filename)
|
||||
msg.add_string(attr)
|
||||
attr._pack(msg)
|
||||
self._send_packet(CMD_NAME, msg)
|
||||
|
||||
def _check_file(self, request_number, msg):
|
||||
# this extension actually comes from v6 protocol, but since it's an
|
||||
# extension, i feel like we can reasonably support it backported.
|
||||
# it's very useful for verifying uploaded files or checking for
|
||||
# rsync-like differences between local and remote files.
|
||||
handle = msg.get_binary()
|
||||
alg_list = msg.get_list()
|
||||
start = msg.get_int64()
|
||||
length = msg.get_int64()
|
||||
block_size = msg.get_int()
|
||||
if handle not in self.file_table:
|
||||
self._send_status(
|
||||
request_number, SFTP_BAD_MESSAGE, "Invalid handle"
|
||||
)
|
||||
return
|
||||
f = self.file_table[handle]
|
||||
for x in alg_list:
|
||||
if x in _hash_class:
|
||||
algname = x
|
||||
alg = _hash_class[x]
|
||||
break
|
||||
else:
|
||||
self._send_status(
|
||||
request_number, SFTP_FAILURE, "No supported hash types found"
|
||||
)
|
||||
return
|
||||
if length == 0:
|
||||
st = f.stat()
|
||||
if not issubclass(type(st), SFTPAttributes):
|
||||
self._send_status(request_number, st, "Unable to stat file")
|
||||
return
|
||||
length = st.st_size - start
|
||||
if block_size == 0:
|
||||
block_size = length
|
||||
if block_size < 256:
|
||||
self._send_status(
|
||||
request_number, SFTP_FAILURE, "Block size too small"
|
||||
)
|
||||
return
|
||||
|
||||
sum_out = bytes()
|
||||
offset = start
|
||||
while offset < start + length:
|
||||
blocklen = min(block_size, start + length - offset)
|
||||
# don't try to read more than about 64KB at a time
|
||||
chunklen = min(blocklen, 65536)
|
||||
count = 0
|
||||
hash_obj = alg()
|
||||
while count < blocklen:
|
||||
data = f.read(offset, chunklen)
|
||||
if not isinstance(data, bytes_types):
|
||||
self._send_status(
|
||||
request_number, data, "Unable to hash file"
|
||||
)
|
||||
return
|
||||
hash_obj.update(data)
|
||||
count += len(data)
|
||||
offset += count
|
||||
sum_out += hash_obj.digest()
|
||||
|
||||
msg = Message()
|
||||
msg.add_int(request_number)
|
||||
msg.add_string("check-file")
|
||||
msg.add_string(algname)
|
||||
msg.add_bytes(sum_out)
|
||||
self._send_packet(CMD_EXTENDED_REPLY, msg)
|
||||
|
||||
def _convert_pflags(self, pflags):
|
||||
"""convert SFTP-style open() flags to Python's os.open() flags"""
|
||||
if (pflags & SFTP_FLAG_READ) and (pflags & SFTP_FLAG_WRITE):
|
||||
flags = os.O_RDWR
|
||||
elif pflags & SFTP_FLAG_WRITE:
|
||||
flags = os.O_WRONLY
|
||||
else:
|
||||
flags = os.O_RDONLY
|
||||
if pflags & SFTP_FLAG_APPEND:
|
||||
flags |= os.O_APPEND
|
||||
if pflags & SFTP_FLAG_CREATE:
|
||||
flags |= os.O_CREAT
|
||||
if pflags & SFTP_FLAG_TRUNC:
|
||||
flags |= os.O_TRUNC
|
||||
if pflags & SFTP_FLAG_EXCL:
|
||||
flags |= os.O_EXCL
|
||||
return flags
|
||||
|
||||
def _process(self, t, request_number, msg):
|
||||
self._log(DEBUG, "Request: {}".format(CMD_NAMES[t]))
|
||||
if t == CMD_OPEN:
|
||||
path = msg.get_text()
|
||||
flags = self._convert_pflags(msg.get_int())
|
||||
attr = SFTPAttributes._from_msg(msg)
|
||||
self._send_handle_response(
|
||||
request_number, self.server.open(path, flags, attr)
|
||||
)
|
||||
elif t == CMD_CLOSE:
|
||||
handle = msg.get_binary()
|
||||
if handle in self.folder_table:
|
||||
del self.folder_table[handle]
|
||||
self._send_status(request_number, SFTP_OK)
|
||||
return
|
||||
if handle in self.file_table:
|
||||
self.file_table[handle].close()
|
||||
del self.file_table[handle]
|
||||
self._send_status(request_number, SFTP_OK)
|
||||
return
|
||||
self._send_status(
|
||||
request_number, SFTP_BAD_MESSAGE, "Invalid handle"
|
||||
)
|
||||
elif t == CMD_READ:
|
||||
handle = msg.get_binary()
|
||||
offset = msg.get_int64()
|
||||
length = msg.get_int()
|
||||
if handle not in self.file_table:
|
||||
self._send_status(
|
||||
request_number, SFTP_BAD_MESSAGE, "Invalid handle"
|
||||
)
|
||||
return
|
||||
data = self.file_table[handle].read(offset, length)
|
||||
if isinstance(data, (bytes_types, string_types)):
|
||||
if len(data) == 0:
|
||||
self._send_status(request_number, SFTP_EOF)
|
||||
else:
|
||||
self._response(request_number, CMD_DATA, data)
|
||||
else:
|
||||
self._send_status(request_number, data)
|
||||
elif t == CMD_WRITE:
|
||||
handle = msg.get_binary()
|
||||
offset = msg.get_int64()
|
||||
data = msg.get_binary()
|
||||
if handle not in self.file_table:
|
||||
self._send_status(
|
||||
request_number, SFTP_BAD_MESSAGE, "Invalid handle"
|
||||
)
|
||||
return
|
||||
self._send_status(
|
||||
request_number, self.file_table[handle].write(offset, data)
|
||||
)
|
||||
elif t == CMD_REMOVE:
|
||||
path = msg.get_text()
|
||||
self._send_status(request_number, self.server.remove(path))
|
||||
elif t == CMD_RENAME:
|
||||
oldpath = msg.get_text()
|
||||
newpath = msg.get_text()
|
||||
self._send_status(
|
||||
request_number, self.server.rename(oldpath, newpath)
|
||||
)
|
||||
elif t == CMD_MKDIR:
|
||||
path = msg.get_text()
|
||||
attr = SFTPAttributes._from_msg(msg)
|
||||
self._send_status(request_number, self.server.mkdir(path, attr))
|
||||
elif t == CMD_RMDIR:
|
||||
path = msg.get_text()
|
||||
self._send_status(request_number, self.server.rmdir(path))
|
||||
elif t == CMD_OPENDIR:
|
||||
path = msg.get_text()
|
||||
self._open_folder(request_number, path)
|
||||
return
|
||||
elif t == CMD_READDIR:
|
||||
handle = msg.get_binary()
|
||||
if handle not in self.folder_table:
|
||||
self._send_status(
|
||||
request_number, SFTP_BAD_MESSAGE, "Invalid handle"
|
||||
)
|
||||
return
|
||||
folder = self.folder_table[handle]
|
||||
self._read_folder(request_number, folder)
|
||||
elif t == CMD_STAT:
|
||||
path = msg.get_text()
|
||||
resp = self.server.stat(path)
|
||||
if issubclass(type(resp), SFTPAttributes):
|
||||
self._response(request_number, CMD_ATTRS, resp)
|
||||
else:
|
||||
self._send_status(request_number, resp)
|
||||
elif t == CMD_LSTAT:
|
||||
path = msg.get_text()
|
||||
resp = self.server.lstat(path)
|
||||
if issubclass(type(resp), SFTPAttributes):
|
||||
self._response(request_number, CMD_ATTRS, resp)
|
||||
else:
|
||||
self._send_status(request_number, resp)
|
||||
elif t == CMD_FSTAT:
|
||||
handle = msg.get_binary()
|
||||
if handle not in self.file_table:
|
||||
self._send_status(
|
||||
request_number, SFTP_BAD_MESSAGE, "Invalid handle"
|
||||
)
|
||||
return
|
||||
resp = self.file_table[handle].stat()
|
||||
if issubclass(type(resp), SFTPAttributes):
|
||||
self._response(request_number, CMD_ATTRS, resp)
|
||||
else:
|
||||
self._send_status(request_number, resp)
|
||||
elif t == CMD_SETSTAT:
|
||||
path = msg.get_text()
|
||||
attr = SFTPAttributes._from_msg(msg)
|
||||
self._send_status(request_number, self.server.chattr(path, attr))
|
||||
elif t == CMD_FSETSTAT:
|
||||
handle = msg.get_binary()
|
||||
attr = SFTPAttributes._from_msg(msg)
|
||||
if handle not in self.file_table:
|
||||
self._response(
|
||||
request_number, SFTP_BAD_MESSAGE, "Invalid handle"
|
||||
)
|
||||
return
|
||||
self._send_status(
|
||||
request_number, self.file_table[handle].chattr(attr)
|
||||
)
|
||||
elif t == CMD_READLINK:
|
||||
path = msg.get_text()
|
||||
resp = self.server.readlink(path)
|
||||
if isinstance(resp, (bytes_types, string_types)):
|
||||
self._response(
|
||||
request_number, CMD_NAME, 1, resp, "", SFTPAttributes()
|
||||
)
|
||||
else:
|
||||
self._send_status(request_number, resp)
|
||||
elif t == CMD_SYMLINK:
|
||||
# the sftp 2 draft is incorrect here!
|
||||
# path always follows target_path
|
||||
target_path = msg.get_text()
|
||||
path = msg.get_text()
|
||||
self._send_status(
|
||||
request_number, self.server.symlink(target_path, path)
|
||||
)
|
||||
elif t == CMD_REALPATH:
|
||||
path = msg.get_text()
|
||||
rpath = self.server.canonicalize(path)
|
||||
self._response(
|
||||
request_number, CMD_NAME, 1, rpath, "", SFTPAttributes()
|
||||
)
|
||||
elif t == CMD_EXTENDED:
|
||||
tag = msg.get_text()
|
||||
if tag == "check-file":
|
||||
self._check_file(request_number, msg)
|
||||
elif tag == "posix-rename@openssh.com":
|
||||
oldpath = msg.get_text()
|
||||
newpath = msg.get_text()
|
||||
self._send_status(
|
||||
request_number, self.server.posix_rename(oldpath, newpath)
|
||||
)
|
||||
else:
|
||||
self._send_status(request_number, SFTP_OP_UNSUPPORTED)
|
||||
else:
|
||||
self._send_status(request_number, SFTP_OP_UNSUPPORTED)
|
||||
|
||||
|
||||
from paramiko.sftp_handle import SFTPHandle
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/sftp_server.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/sftp_server.pyc
Normal file
Binary file not shown.
316
.ve/lib/python2.7/site-packages/paramiko/sftp_si.py
Normal file
316
.ve/lib/python2.7/site-packages/paramiko/sftp_si.py
Normal file
@@ -0,0 +1,316 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
An interface to override for SFTP server support.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from paramiko.sftp import SFTP_OP_UNSUPPORTED
|
||||
|
||||
|
||||
class SFTPServerInterface(object):
|
||||
"""
|
||||
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, *largs, **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(SFTPServerInterface, self).__init__(*largs, **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
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/sftp_si.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/sftp_si.pyc
Normal file
Binary file not shown.
192
.ve/lib/python2.7/site-packages/paramiko/ssh_exception.py
Normal file
192
.ve/lib/python2.7/site-packages/paramiko/ssh_exception.py
Normal file
@@ -0,0 +1,192 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
import socket
|
||||
|
||||
|
||||
class SSHException(Exception):
|
||||
"""
|
||||
Exception raised by failures in SSH2 protocol negotiation or logic errors.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class AuthenticationException(SSHException):
|
||||
"""
|
||||
Exception raised when authentication failed for some reason. It may be
|
||||
possible to retry with different credentials. (Other classes specify more
|
||||
specific reasons.)
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PasswordRequiredException(AuthenticationException):
|
||||
"""
|
||||
Exception raised when a password is needed to unlock a private key file.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BadAuthenticationType(AuthenticationException):
|
||||
"""
|
||||
Exception raised when an authentication type (like password) is used, but
|
||||
the server isn't allowing that type. (It may only allow public-key, for
|
||||
example.)
|
||||
|
||||
.. versionadded:: 1.1
|
||||
"""
|
||||
|
||||
#: list of allowed authentication types provided by the server (possible
|
||||
#: values are: ``"none"``, ``"password"``, and ``"publickey"``).
|
||||
allowed_types = []
|
||||
|
||||
def __init__(self, explanation, types):
|
||||
AuthenticationException.__init__(self, explanation)
|
||||
self.allowed_types = types
|
||||
# for unpickling
|
||||
self.args = (explanation, types)
|
||||
|
||||
def __str__(self):
|
||||
return "{} (allowed_types={!r})".format(
|
||||
SSHException.__str__(self), self.allowed_types
|
||||
)
|
||||
|
||||
|
||||
class PartialAuthentication(AuthenticationException):
|
||||
"""
|
||||
An internal exception thrown in the case of partial authentication.
|
||||
"""
|
||||
|
||||
allowed_types = []
|
||||
|
||||
def __init__(self, types):
|
||||
AuthenticationException.__init__(self, "partial authentication")
|
||||
self.allowed_types = types
|
||||
# for unpickling
|
||||
self.args = (types,)
|
||||
|
||||
|
||||
class ChannelException(SSHException):
|
||||
"""
|
||||
Exception raised when an attempt to open a new `.Channel` fails.
|
||||
|
||||
:param int code: the error code returned by the server
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
def __init__(self, code, text):
|
||||
SSHException.__init__(self, text)
|
||||
self.code = code
|
||||
# for unpickling
|
||||
self.args = (code, text)
|
||||
|
||||
|
||||
class BadHostKeyException(SSHException):
|
||||
"""
|
||||
The host key given by the SSH server did not match what we were expecting.
|
||||
|
||||
:param str hostname: the hostname of the SSH server
|
||||
:param PKey got_key: the host key presented by the server
|
||||
:param PKey expected_key: the host key expected
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
def __init__(self, hostname, got_key, expected_key):
|
||||
message = (
|
||||
"Host key for server {} does not match: got {}, expected {}"
|
||||
) # noqa
|
||||
message = message.format(
|
||||
hostname, got_key.get_base64(), expected_key.get_base64()
|
||||
)
|
||||
SSHException.__init__(self, message)
|
||||
self.hostname = hostname
|
||||
self.key = got_key
|
||||
self.expected_key = expected_key
|
||||
# for unpickling
|
||||
self.args = (hostname, got_key, expected_key)
|
||||
|
||||
|
||||
class ProxyCommandFailure(SSHException):
|
||||
"""
|
||||
The "ProxyCommand" found in the .ssh/config file returned an error.
|
||||
|
||||
:param str command: The command line that is generating this exception.
|
||||
:param str error: The error captured from the proxy command output.
|
||||
"""
|
||||
|
||||
def __init__(self, command, error):
|
||||
SSHException.__init__(
|
||||
self,
|
||||
'"ProxyCommand ({})" returned non-zero exit status: {}'.format(
|
||||
command, error
|
||||
),
|
||||
)
|
||||
self.error = error
|
||||
# for unpickling
|
||||
self.args = (command, error)
|
||||
|
||||
|
||||
class NoValidConnectionsError(socket.error):
|
||||
"""
|
||||
Multiple connection attempts were made and no families succeeded.
|
||||
|
||||
This exception class wraps multiple "real" underlying connection errors,
|
||||
all of which represent failed connection attempts. Because these errors are
|
||||
not guaranteed to all be of the same error type (i.e. different errno,
|
||||
`socket.error` subclass, message, etc) we expose a single unified error
|
||||
message and a ``None`` errno so that instances of this class match most
|
||||
normal handling of `socket.error` objects.
|
||||
|
||||
To see the wrapped exception objects, access the ``errors`` attribute.
|
||||
``errors`` is a dict whose keys are address tuples (e.g. ``('127.0.0.1',
|
||||
22)``) and whose values are the exception encountered trying to connect to
|
||||
that address.
|
||||
|
||||
It is implied/assumed that all the errors given to a single instance of
|
||||
this class are from connecting to the same hostname + port (and thus that
|
||||
the differences are in the resolution of the hostname - e.g. IPv4 vs v6).
|
||||
|
||||
.. versionadded:: 1.16
|
||||
"""
|
||||
|
||||
def __init__(self, errors):
|
||||
"""
|
||||
:param dict errors:
|
||||
The errors dict to store, as described by class docstring.
|
||||
"""
|
||||
addrs = sorted(errors.keys())
|
||||
body = ", ".join([x[0] for x in addrs[:-1]])
|
||||
tail = addrs[-1][0]
|
||||
if body:
|
||||
msg = "Unable to connect to port {0} on {1} or {2}"
|
||||
else:
|
||||
msg = "Unable to connect to port {0} on {2}"
|
||||
super(NoValidConnectionsError, self).__init__(
|
||||
None, msg.format(addrs[0][1], body, tail) # stand-in for errno
|
||||
)
|
||||
self.errors = errors
|
||||
|
||||
def __reduce__(self):
|
||||
return (self.__class__, (self.errors,))
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/ssh_exception.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/ssh_exception.pyc
Normal file
Binary file not shown.
579
.ve/lib/python2.7/site-packages/paramiko/ssh_gss.py
Normal file
579
.ve/lib/python2.7/site-packages/paramiko/ssh_gss.py
Normal file
@@ -0,0 +1,579 @@
|
||||
# Copyright (C) 2013-2014 science + computing ag
|
||||
# Author: Sebastian Deiss <sebastian.deiss@t-online.de>
|
||||
#
|
||||
#
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
|
||||
"""
|
||||
This module provides GSS-API / SSPI authentication as defined in :rfc:`4462`.
|
||||
|
||||
.. note:: Credential delegation is not supported in server mode.
|
||||
|
||||
.. seealso:: :doc:`/api/kex_gss`
|
||||
|
||||
.. versionadded:: 1.15
|
||||
"""
|
||||
|
||||
import struct
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
#: A boolean constraint that indicates if GSS-API / SSPI is available.
|
||||
GSS_AUTH_AVAILABLE = True
|
||||
|
||||
|
||||
#: A tuple of the exception types used by the underlying GSSAPI implementation.
|
||||
GSS_EXCEPTIONS = ()
|
||||
|
||||
|
||||
from pyasn1.type.univ import ObjectIdentifier
|
||||
from pyasn1.codec.der import encoder, decoder
|
||||
|
||||
|
||||
#: :var str _API: Constraint for the used API
|
||||
_API = "MIT"
|
||||
|
||||
try:
|
||||
import gssapi
|
||||
|
||||
GSS_EXCEPTIONS = (gssapi.GSSException,)
|
||||
except (ImportError, OSError):
|
||||
try:
|
||||
import pywintypes
|
||||
import sspicon
|
||||
import sspi
|
||||
|
||||
_API = "SSPI"
|
||||
GSS_EXCEPTIONS = (pywintypes.error,)
|
||||
except ImportError:
|
||||
GSS_AUTH_AVAILABLE = False
|
||||
_API = None
|
||||
|
||||
from paramiko.common import MSG_USERAUTH_REQUEST
|
||||
from paramiko.ssh_exception import SSHException
|
||||
|
||||
|
||||
def GSSAuth(auth_method, gss_deleg_creds=True):
|
||||
"""
|
||||
Provide SSH2 GSS-API / SSPI authentication.
|
||||
|
||||
:param str auth_method: The name of the SSH authentication mechanism
|
||||
(gssapi-with-mic or gss-keyex)
|
||||
:param bool gss_deleg_creds: Delegate client credentials or not.
|
||||
We delegate credentials by default.
|
||||
:return: Either an `._SSH_GSSAPI` (Unix) object or an
|
||||
`_SSH_SSPI` (Windows) object
|
||||
|
||||
:raises: ``ImportError`` -- If no GSS-API / SSPI module could be imported.
|
||||
|
||||
:see: `RFC 4462 <http://www.ietf.org/rfc/rfc4462.txt>`_
|
||||
:note: Check for the available API and return either an `._SSH_GSSAPI`
|
||||
(MIT GSSAPI) object or an `._SSH_SSPI` (MS SSPI) object. If you
|
||||
get python-gssapi working on Windows, python-gssapi
|
||||
will be used and a `._SSH_GSSAPI` object will be returned.
|
||||
If there is no supported API available,
|
||||
``None`` will be returned.
|
||||
"""
|
||||
if _API == "MIT":
|
||||
return _SSH_GSSAPI(auth_method, gss_deleg_creds)
|
||||
elif _API == "SSPI" and os.name == "nt":
|
||||
return _SSH_SSPI(auth_method, gss_deleg_creds)
|
||||
else:
|
||||
raise ImportError("Unable to import a GSS-API / SSPI module!")
|
||||
|
||||
|
||||
class _SSH_GSSAuth(object):
|
||||
"""
|
||||
Contains the shared variables and methods of `._SSH_GSSAPI` and
|
||||
`._SSH_SSPI`.
|
||||
"""
|
||||
|
||||
def __init__(self, auth_method, gss_deleg_creds):
|
||||
"""
|
||||
:param str auth_method: The name of the SSH authentication mechanism
|
||||
(gssapi-with-mic or gss-keyex)
|
||||
:param bool gss_deleg_creds: Delegate client credentials or not
|
||||
"""
|
||||
self._auth_method = auth_method
|
||||
self._gss_deleg_creds = gss_deleg_creds
|
||||
self._gss_host = None
|
||||
self._username = None
|
||||
self._session_id = None
|
||||
self._service = "ssh-connection"
|
||||
"""
|
||||
OpenSSH supports Kerberos V5 mechanism only for GSS-API authentication,
|
||||
so we also support the krb5 mechanism only.
|
||||
"""
|
||||
self._krb5_mech = "1.2.840.113554.1.2.2"
|
||||
|
||||
# client mode
|
||||
self._gss_ctxt = None
|
||||
self._gss_ctxt_status = False
|
||||
|
||||
# server mode
|
||||
self._gss_srv_ctxt = None
|
||||
self._gss_srv_ctxt_status = False
|
||||
self.cc_file = None
|
||||
|
||||
def set_service(self, service):
|
||||
"""
|
||||
This is just a setter to use a non default service.
|
||||
I added this method, because RFC 4462 doesn't specify "ssh-connection"
|
||||
as the only service value.
|
||||
|
||||
:param str service: The desired SSH service
|
||||
"""
|
||||
if service.find("ssh-"):
|
||||
self._service = service
|
||||
|
||||
def set_username(self, username):
|
||||
"""
|
||||
Setter for C{username}. If GSS-API Key Exchange is performed, the
|
||||
username is not set by C{ssh_init_sec_context}.
|
||||
|
||||
:param str username: The name of the user who attempts to login
|
||||
"""
|
||||
self._username = username
|
||||
|
||||
def ssh_gss_oids(self, mode="client"):
|
||||
"""
|
||||
This method returns a single OID, because we only support the
|
||||
Kerberos V5 mechanism.
|
||||
|
||||
:param str mode: Client for client mode and server for server mode
|
||||
:return: A byte sequence containing the number of supported
|
||||
OIDs, the length of the OID and the actual OID encoded with
|
||||
DER
|
||||
:note: In server mode we just return the OID length and the DER encoded
|
||||
OID.
|
||||
"""
|
||||
OIDs = self._make_uint32(1)
|
||||
krb5_OID = encoder.encode(ObjectIdentifier(self._krb5_mech))
|
||||
OID_len = self._make_uint32(len(krb5_OID))
|
||||
if mode == "server":
|
||||
return OID_len + krb5_OID
|
||||
return OIDs + OID_len + krb5_OID
|
||||
|
||||
def ssh_check_mech(self, desired_mech):
|
||||
"""
|
||||
Check if the given OID is the Kerberos V5 OID (server mode).
|
||||
|
||||
:param str desired_mech: The desired GSS-API mechanism of the client
|
||||
:return: ``True`` if the given OID is supported, otherwise C{False}
|
||||
"""
|
||||
mech, __ = decoder.decode(desired_mech)
|
||||
if mech.__str__() != self._krb5_mech:
|
||||
return False
|
||||
return True
|
||||
|
||||
# Internals
|
||||
# -------------------------------------------------------------------------
|
||||
def _make_uint32(self, integer):
|
||||
"""
|
||||
Create a 32 bit unsigned integer (The byte sequence of an integer).
|
||||
|
||||
:param int integer: The integer value to convert
|
||||
:return: The byte sequence of an 32 bit integer
|
||||
"""
|
||||
return struct.pack("!I", integer)
|
||||
|
||||
def _ssh_build_mic(self, session_id, username, service, auth_method):
|
||||
"""
|
||||
Create the SSH2 MIC filed for gssapi-with-mic.
|
||||
|
||||
:param str session_id: The SSH session ID
|
||||
:param str username: The name of the user who attempts to login
|
||||
:param str service: The requested SSH service
|
||||
:param str auth_method: The requested SSH authentication mechanism
|
||||
:return: The MIC as defined in RFC 4462. The contents of the
|
||||
MIC field are:
|
||||
string session_identifier,
|
||||
byte SSH_MSG_USERAUTH_REQUEST,
|
||||
string user-name,
|
||||
string service (ssh-connection),
|
||||
string authentication-method
|
||||
(gssapi-with-mic or gssapi-keyex)
|
||||
"""
|
||||
mic = self._make_uint32(len(session_id))
|
||||
mic += session_id
|
||||
mic += struct.pack("B", MSG_USERAUTH_REQUEST)
|
||||
mic += self._make_uint32(len(username))
|
||||
mic += username.encode()
|
||||
mic += self._make_uint32(len(service))
|
||||
mic += service.encode()
|
||||
mic += self._make_uint32(len(auth_method))
|
||||
mic += auth_method.encode()
|
||||
return mic
|
||||
|
||||
|
||||
class _SSH_GSSAPI(_SSH_GSSAuth):
|
||||
"""
|
||||
Implementation of the GSS-API MIT Kerberos Authentication for SSH2.
|
||||
|
||||
:see: `.GSSAuth`
|
||||
"""
|
||||
|
||||
def __init__(self, auth_method, gss_deleg_creds):
|
||||
"""
|
||||
:param str auth_method: The name of the SSH authentication mechanism
|
||||
(gssapi-with-mic or gss-keyex)
|
||||
:param bool gss_deleg_creds: Delegate client credentials or not
|
||||
"""
|
||||
_SSH_GSSAuth.__init__(self, auth_method, gss_deleg_creds)
|
||||
|
||||
if self._gss_deleg_creds:
|
||||
self._gss_flags = (
|
||||
gssapi.C_PROT_READY_FLAG,
|
||||
gssapi.C_INTEG_FLAG,
|
||||
gssapi.C_MUTUAL_FLAG,
|
||||
gssapi.C_DELEG_FLAG,
|
||||
)
|
||||
else:
|
||||
self._gss_flags = (
|
||||
gssapi.C_PROT_READY_FLAG,
|
||||
gssapi.C_INTEG_FLAG,
|
||||
gssapi.C_MUTUAL_FLAG,
|
||||
)
|
||||
|
||||
def ssh_init_sec_context(
|
||||
self, target, desired_mech=None, username=None, recv_token=None
|
||||
):
|
||||
"""
|
||||
Initialize a GSS-API context.
|
||||
|
||||
:param str username: The name of the user who attempts to login
|
||||
:param str target: The hostname of the target to connect to
|
||||
:param str desired_mech: The negotiated GSS-API mechanism
|
||||
("pseudo negotiated" mechanism, because we
|
||||
support just the krb5 mechanism :-))
|
||||
:param str recv_token: The GSS-API token received from the Server
|
||||
:raises:
|
||||
`.SSHException` -- Is raised if the desired mechanism of the client
|
||||
is not supported
|
||||
:return: A ``String`` if the GSS-API has returned a token or
|
||||
``None`` if no token was returned
|
||||
"""
|
||||
self._username = username
|
||||
self._gss_host = target
|
||||
targ_name = gssapi.Name(
|
||||
"host@" + self._gss_host, gssapi.C_NT_HOSTBASED_SERVICE
|
||||
)
|
||||
ctx = gssapi.Context()
|
||||
ctx.flags = self._gss_flags
|
||||
if desired_mech is None:
|
||||
krb5_mech = gssapi.OID.mech_from_string(self._krb5_mech)
|
||||
else:
|
||||
mech, __ = decoder.decode(desired_mech)
|
||||
if mech.__str__() != self._krb5_mech:
|
||||
raise SSHException("Unsupported mechanism OID.")
|
||||
else:
|
||||
krb5_mech = gssapi.OID.mech_from_string(self._krb5_mech)
|
||||
token = None
|
||||
try:
|
||||
if recv_token is None:
|
||||
self._gss_ctxt = gssapi.InitContext(
|
||||
peer_name=targ_name,
|
||||
mech_type=krb5_mech,
|
||||
req_flags=ctx.flags,
|
||||
)
|
||||
token = self._gss_ctxt.step(token)
|
||||
else:
|
||||
token = self._gss_ctxt.step(recv_token)
|
||||
except gssapi.GSSException:
|
||||
message = "{} Target: {}".format(sys.exc_info()[1], self._gss_host)
|
||||
raise gssapi.GSSException(message)
|
||||
self._gss_ctxt_status = self._gss_ctxt.established
|
||||
return token
|
||||
|
||||
def ssh_get_mic(self, session_id, gss_kex=False):
|
||||
"""
|
||||
Create the MIC token for a SSH2 message.
|
||||
|
||||
:param str session_id: The SSH session ID
|
||||
:param bool gss_kex: Generate the MIC for GSS-API Key Exchange or not
|
||||
:return: gssapi-with-mic:
|
||||
Returns the MIC token from GSS-API for the message we created
|
||||
with ``_ssh_build_mic``.
|
||||
gssapi-keyex:
|
||||
Returns the MIC token from GSS-API with the SSH session ID as
|
||||
message.
|
||||
"""
|
||||
self._session_id = session_id
|
||||
if not gss_kex:
|
||||
mic_field = self._ssh_build_mic(
|
||||
self._session_id,
|
||||
self._username,
|
||||
self._service,
|
||||
self._auth_method,
|
||||
)
|
||||
mic_token = self._gss_ctxt.get_mic(mic_field)
|
||||
else:
|
||||
# for key exchange with gssapi-keyex
|
||||
mic_token = self._gss_srv_ctxt.get_mic(self._session_id)
|
||||
return mic_token
|
||||
|
||||
def ssh_accept_sec_context(self, hostname, recv_token, username=None):
|
||||
"""
|
||||
Accept a GSS-API context (server mode).
|
||||
|
||||
:param str hostname: The servers hostname
|
||||
:param str username: The name of the user who attempts to login
|
||||
:param str recv_token: The GSS-API Token received from the server,
|
||||
if it's not the initial call.
|
||||
:return: A ``String`` if the GSS-API has returned a token or ``None``
|
||||
if no token was returned
|
||||
"""
|
||||
# hostname and username are not required for GSSAPI, but for SSPI
|
||||
self._gss_host = hostname
|
||||
self._username = username
|
||||
if self._gss_srv_ctxt is None:
|
||||
self._gss_srv_ctxt = gssapi.AcceptContext()
|
||||
token = self._gss_srv_ctxt.step(recv_token)
|
||||
self._gss_srv_ctxt_status = self._gss_srv_ctxt.established
|
||||
return token
|
||||
|
||||
def ssh_check_mic(self, mic_token, session_id, username=None):
|
||||
"""
|
||||
Verify the MIC token for a SSH2 message.
|
||||
|
||||
:param str mic_token: The MIC token received from the client
|
||||
:param str session_id: The SSH session ID
|
||||
:param str username: The name of the user who attempts to login
|
||||
:return: None if the MIC check was successful
|
||||
:raises: ``gssapi.GSSException`` -- if the MIC check failed
|
||||
"""
|
||||
self._session_id = session_id
|
||||
self._username = username
|
||||
if self._username is not None:
|
||||
# server mode
|
||||
mic_field = self._ssh_build_mic(
|
||||
self._session_id,
|
||||
self._username,
|
||||
self._service,
|
||||
self._auth_method,
|
||||
)
|
||||
self._gss_srv_ctxt.verify_mic(mic_field, mic_token)
|
||||
else:
|
||||
# for key exchange with gssapi-keyex
|
||||
# client mode
|
||||
self._gss_ctxt.verify_mic(self._session_id, mic_token)
|
||||
|
||||
@property
|
||||
def credentials_delegated(self):
|
||||
"""
|
||||
Checks if credentials are delegated (server mode).
|
||||
|
||||
:return: ``True`` if credentials are delegated, otherwise ``False``
|
||||
"""
|
||||
if self._gss_srv_ctxt.delegated_cred is not None:
|
||||
return True
|
||||
return False
|
||||
|
||||
def save_client_creds(self, client_token):
|
||||
"""
|
||||
Save the Client token in a file. This is used by the SSH server
|
||||
to store the client credentials if credentials are delegated
|
||||
(server mode).
|
||||
|
||||
:param str client_token: The GSS-API token received form the client
|
||||
:raises:
|
||||
``NotImplementedError`` -- Credential delegation is currently not
|
||||
supported in server mode
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class _SSH_SSPI(_SSH_GSSAuth):
|
||||
"""
|
||||
Implementation of the Microsoft SSPI Kerberos Authentication for SSH2.
|
||||
|
||||
:see: `.GSSAuth`
|
||||
"""
|
||||
|
||||
def __init__(self, auth_method, gss_deleg_creds):
|
||||
"""
|
||||
:param str auth_method: The name of the SSH authentication mechanism
|
||||
(gssapi-with-mic or gss-keyex)
|
||||
:param bool gss_deleg_creds: Delegate client credentials or not
|
||||
"""
|
||||
_SSH_GSSAuth.__init__(self, auth_method, gss_deleg_creds)
|
||||
|
||||
if self._gss_deleg_creds:
|
||||
self._gss_flags = (
|
||||
sspicon.ISC_REQ_INTEGRITY
|
||||
| sspicon.ISC_REQ_MUTUAL_AUTH
|
||||
| sspicon.ISC_REQ_DELEGATE
|
||||
)
|
||||
else:
|
||||
self._gss_flags = (
|
||||
sspicon.ISC_REQ_INTEGRITY | sspicon.ISC_REQ_MUTUAL_AUTH
|
||||
)
|
||||
|
||||
def ssh_init_sec_context(
|
||||
self, target, desired_mech=None, username=None, recv_token=None
|
||||
):
|
||||
"""
|
||||
Initialize a SSPI context.
|
||||
|
||||
:param str username: The name of the user who attempts to login
|
||||
:param str target: The FQDN of the target to connect to
|
||||
:param str desired_mech: The negotiated SSPI mechanism
|
||||
("pseudo negotiated" mechanism, because we
|
||||
support just the krb5 mechanism :-))
|
||||
:param recv_token: The SSPI token received from the Server
|
||||
:raises:
|
||||
`.SSHException` -- Is raised if the desired mechanism of the client
|
||||
is not supported
|
||||
:return: A ``String`` if the SSPI has returned a token or ``None`` if
|
||||
no token was returned
|
||||
"""
|
||||
self._username = username
|
||||
self._gss_host = target
|
||||
error = 0
|
||||
targ_name = "host/" + self._gss_host
|
||||
if desired_mech is not None:
|
||||
mech, __ = decoder.decode(desired_mech)
|
||||
if mech.__str__() != self._krb5_mech:
|
||||
raise SSHException("Unsupported mechanism OID.")
|
||||
try:
|
||||
if recv_token is None:
|
||||
self._gss_ctxt = sspi.ClientAuth(
|
||||
"Kerberos", scflags=self._gss_flags, targetspn=targ_name
|
||||
)
|
||||
error, token = self._gss_ctxt.authorize(recv_token)
|
||||
token = token[0].Buffer
|
||||
except pywintypes.error as e:
|
||||
e.strerror += ", Target: {}".format(e, self._gss_host)
|
||||
raise
|
||||
|
||||
if error == 0:
|
||||
"""
|
||||
if the status is GSS_COMPLETE (error = 0) the context is fully
|
||||
established an we can set _gss_ctxt_status to True.
|
||||
"""
|
||||
self._gss_ctxt_status = True
|
||||
token = None
|
||||
"""
|
||||
You won't get another token if the context is fully established,
|
||||
so i set token to None instead of ""
|
||||
"""
|
||||
return token
|
||||
|
||||
def ssh_get_mic(self, session_id, gss_kex=False):
|
||||
"""
|
||||
Create the MIC token for a SSH2 message.
|
||||
|
||||
:param str session_id: The SSH session ID
|
||||
:param bool gss_kex: Generate the MIC for Key Exchange with SSPI or not
|
||||
:return: gssapi-with-mic:
|
||||
Returns the MIC token from SSPI for the message we created
|
||||
with ``_ssh_build_mic``.
|
||||
gssapi-keyex:
|
||||
Returns the MIC token from SSPI with the SSH session ID as
|
||||
message.
|
||||
"""
|
||||
self._session_id = session_id
|
||||
if not gss_kex:
|
||||
mic_field = self._ssh_build_mic(
|
||||
self._session_id,
|
||||
self._username,
|
||||
self._service,
|
||||
self._auth_method,
|
||||
)
|
||||
mic_token = self._gss_ctxt.sign(mic_field)
|
||||
else:
|
||||
# for key exchange with gssapi-keyex
|
||||
mic_token = self._gss_srv_ctxt.sign(self._session_id)
|
||||
return mic_token
|
||||
|
||||
def ssh_accept_sec_context(self, hostname, username, recv_token):
|
||||
"""
|
||||
Accept a SSPI context (server mode).
|
||||
|
||||
:param str hostname: The servers FQDN
|
||||
:param str username: The name of the user who attempts to login
|
||||
:param str recv_token: The SSPI Token received from the server,
|
||||
if it's not the initial call.
|
||||
:return: A ``String`` if the SSPI has returned a token or ``None`` if
|
||||
no token was returned
|
||||
"""
|
||||
self._gss_host = hostname
|
||||
self._username = username
|
||||
targ_name = "host/" + self._gss_host
|
||||
self._gss_srv_ctxt = sspi.ServerAuth("Kerberos", spn=targ_name)
|
||||
error, token = self._gss_srv_ctxt.authorize(recv_token)
|
||||
token = token[0].Buffer
|
||||
if error == 0:
|
||||
self._gss_srv_ctxt_status = True
|
||||
token = None
|
||||
return token
|
||||
|
||||
def ssh_check_mic(self, mic_token, session_id, username=None):
|
||||
"""
|
||||
Verify the MIC token for a SSH2 message.
|
||||
|
||||
:param str mic_token: The MIC token received from the client
|
||||
:param str session_id: The SSH session ID
|
||||
:param str username: The name of the user who attempts to login
|
||||
:return: None if the MIC check was successful
|
||||
:raises: ``sspi.error`` -- if the MIC check failed
|
||||
"""
|
||||
self._session_id = session_id
|
||||
self._username = username
|
||||
if username is not None:
|
||||
# server mode
|
||||
mic_field = self._ssh_build_mic(
|
||||
self._session_id,
|
||||
self._username,
|
||||
self._service,
|
||||
self._auth_method,
|
||||
)
|
||||
# Verifies data and its signature. If verification fails, an
|
||||
# sspi.error will be raised.
|
||||
self._gss_srv_ctxt.verify(mic_field, mic_token)
|
||||
else:
|
||||
# for key exchange with gssapi-keyex
|
||||
# client mode
|
||||
# Verifies data and its signature. If verification fails, an
|
||||
# sspi.error will be raised.
|
||||
self._gss_ctxt.verify(self._session_id, mic_token)
|
||||
|
||||
@property
|
||||
def credentials_delegated(self):
|
||||
"""
|
||||
Checks if credentials are delegated (server mode).
|
||||
|
||||
:return: ``True`` if credentials are delegated, otherwise ``False``
|
||||
"""
|
||||
return self._gss_flags & sspicon.ISC_REQ_DELEGATE and (
|
||||
self._gss_srv_ctxt_status or self._gss_flags
|
||||
)
|
||||
|
||||
def save_client_creds(self, client_token):
|
||||
"""
|
||||
Save the Client token in a file. This is used by the SSH server
|
||||
to store the client credentails if credentials are delegated
|
||||
(server mode).
|
||||
|
||||
:param str client_token: The SSPI token received form the client
|
||||
:raises:
|
||||
``NotImplementedError`` -- Credential delegation is currently not
|
||||
supported in server mode
|
||||
"""
|
||||
raise NotImplementedError
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/ssh_gss.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/ssh_gss.pyc
Normal file
Binary file not shown.
2928
.ve/lib/python2.7/site-packages/paramiko/transport.py
Normal file
2928
.ve/lib/python2.7/site-packages/paramiko/transport.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
.ve/lib/python2.7/site-packages/paramiko/transport.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/transport.pyc
Normal file
Binary file not shown.
305
.ve/lib/python2.7/site-packages/paramiko/util.py
Normal file
305
.ve/lib/python2.7/site-packages/paramiko/util.py
Normal file
@@ -0,0 +1,305 @@
|
||||
# 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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Useful functions used by the rest of paramiko.
|
||||
"""
|
||||
|
||||
from __future__ import generators
|
||||
|
||||
import errno
|
||||
import sys
|
||||
import struct
|
||||
import traceback
|
||||
import threading
|
||||
import logging
|
||||
|
||||
from paramiko.common import DEBUG, zero_byte, xffffffff, max_byte
|
||||
from paramiko.py3compat import PY2, long, byte_chr, byte_ord, b
|
||||
from paramiko.config import SSHConfig
|
||||
|
||||
|
||||
def inflate_long(s, always_positive=False):
|
||||
"""turns a normalized byte string into a long-int
|
||||
(adapted from Crypto.Util.number)"""
|
||||
out = long(0)
|
||||
negative = 0
|
||||
if not always_positive and (len(s) > 0) and (byte_ord(s[0]) >= 0x80):
|
||||
negative = 1
|
||||
if len(s) % 4:
|
||||
filler = zero_byte
|
||||
if negative:
|
||||
filler = max_byte
|
||||
# never convert this to ``s +=`` because this is a string, not a number
|
||||
# noinspection PyAugmentAssignment
|
||||
s = filler * (4 - len(s) % 4) + s
|
||||
for i in range(0, len(s), 4):
|
||||
out = (out << 32) + struct.unpack(">I", s[i : i + 4])[0]
|
||||
if negative:
|
||||
out -= long(1) << (8 * len(s))
|
||||
return out
|
||||
|
||||
|
||||
deflate_zero = zero_byte if PY2 else 0
|
||||
deflate_ff = max_byte if PY2 else 0xff
|
||||
|
||||
|
||||
def deflate_long(n, add_sign_padding=True):
|
||||
"""turns a long-int into a normalized byte string
|
||||
(adapted from Crypto.Util.number)"""
|
||||
# after much testing, this algorithm was deemed to be the fastest
|
||||
s = bytes()
|
||||
n = long(n)
|
||||
while (n != 0) and (n != -1):
|
||||
s = struct.pack(">I", n & xffffffff) + s
|
||||
n >>= 32
|
||||
# strip off leading zeros, FFs
|
||||
for i in enumerate(s):
|
||||
if (n == 0) and (i[1] != deflate_zero):
|
||||
break
|
||||
if (n == -1) and (i[1] != deflate_ff):
|
||||
break
|
||||
else:
|
||||
# degenerate case, n was either 0 or -1
|
||||
i = (0,)
|
||||
if n == 0:
|
||||
s = zero_byte
|
||||
else:
|
||||
s = max_byte
|
||||
s = s[i[0] :]
|
||||
if add_sign_padding:
|
||||
if (n == 0) and (byte_ord(s[0]) >= 0x80):
|
||||
s = zero_byte + s
|
||||
if (n == -1) and (byte_ord(s[0]) < 0x80):
|
||||
s = max_byte + s
|
||||
return s
|
||||
|
||||
|
||||
def format_binary(data, prefix=""):
|
||||
x = 0
|
||||
out = []
|
||||
while len(data) > x + 16:
|
||||
out.append(format_binary_line(data[x : x + 16]))
|
||||
x += 16
|
||||
if x < len(data):
|
||||
out.append(format_binary_line(data[x:]))
|
||||
return [prefix + line for line in out]
|
||||
|
||||
|
||||
def format_binary_line(data):
|
||||
left = " ".join(["{:02X}".format(byte_ord(c)) for c in data])
|
||||
right = "".join(
|
||||
[".{:c}..".format(byte_ord(c))[(byte_ord(c) + 63) // 95] for c in data]
|
||||
)
|
||||
return "{:50s} {}".format(left, right)
|
||||
|
||||
|
||||
def safe_string(s):
|
||||
out = b""
|
||||
for c in s:
|
||||
i = byte_ord(c)
|
||||
if 32 <= i <= 127:
|
||||
out += byte_chr(i)
|
||||
else:
|
||||
out += b("%{:02X}".format(i))
|
||||
return out
|
||||
|
||||
|
||||
def bit_length(n):
|
||||
try:
|
||||
return n.bit_length()
|
||||
except AttributeError:
|
||||
norm = deflate_long(n, False)
|
||||
hbyte = byte_ord(norm[0])
|
||||
if hbyte == 0:
|
||||
return 1
|
||||
bitlen = len(norm) * 8
|
||||
while not (hbyte & 0x80):
|
||||
hbyte <<= 1
|
||||
bitlen -= 1
|
||||
return bitlen
|
||||
|
||||
|
||||
def tb_strings():
|
||||
return "".join(traceback.format_exception(*sys.exc_info())).split("\n")
|
||||
|
||||
|
||||
def generate_key_bytes(hash_alg, salt, key, nbytes):
|
||||
"""
|
||||
Given a password, passphrase, or other human-source key, scramble it
|
||||
through a secure hash into some keyworthy bytes. This specific algorithm
|
||||
is used for encrypting/decrypting private key files.
|
||||
|
||||
:param function hash_alg: A function which creates a new hash object, such
|
||||
as ``hashlib.sha256``.
|
||||
:param salt: data to salt the hash with.
|
||||
:type salt: byte string
|
||||
:param str key: human-entered password or passphrase.
|
||||
:param int nbytes: number of bytes to generate.
|
||||
:return: Key data `str`
|
||||
"""
|
||||
keydata = bytes()
|
||||
digest = bytes()
|
||||
if len(salt) > 8:
|
||||
salt = salt[:8]
|
||||
while nbytes > 0:
|
||||
hash_obj = hash_alg()
|
||||
if len(digest) > 0:
|
||||
hash_obj.update(digest)
|
||||
hash_obj.update(b(key))
|
||||
hash_obj.update(salt)
|
||||
digest = hash_obj.digest()
|
||||
size = min(nbytes, len(digest))
|
||||
keydata += digest[:size]
|
||||
nbytes -= size
|
||||
return keydata
|
||||
|
||||
|
||||
def load_host_keys(filename):
|
||||
"""
|
||||
Read a file of known SSH host keys, in the format used by openssh, and
|
||||
return a compound dict of ``hostname -> keytype ->`` `PKey
|
||||
<paramiko.pkey.PKey>`. The hostname may be an IP address or DNS name. The
|
||||
keytype will be either ``"ssh-rsa"`` or ``"ssh-dss"``.
|
||||
|
||||
This type of file unfortunately doesn't exist on Windows, but on posix,
|
||||
it will usually be stored in ``os.path.expanduser("~/.ssh/known_hosts")``.
|
||||
|
||||
Since 1.5.3, this is just a wrapper around `.HostKeys`.
|
||||
|
||||
:param str filename: name of the file to read host keys from
|
||||
:return:
|
||||
nested dict of `.PKey` objects, indexed by hostname and then keytype
|
||||
"""
|
||||
from paramiko.hostkeys import HostKeys
|
||||
|
||||
return HostKeys(filename)
|
||||
|
||||
|
||||
def parse_ssh_config(file_obj):
|
||||
"""
|
||||
Provided only as a backward-compatible wrapper around `.SSHConfig`.
|
||||
"""
|
||||
config = SSHConfig()
|
||||
config.parse(file_obj)
|
||||
return config
|
||||
|
||||
|
||||
def lookup_ssh_host_config(hostname, config):
|
||||
"""
|
||||
Provided only as a backward-compatible wrapper around `.SSHConfig`.
|
||||
"""
|
||||
return config.lookup(hostname)
|
||||
|
||||
|
||||
def mod_inverse(x, m):
|
||||
# it's crazy how small Python can make this function.
|
||||
u1, u2, u3 = 1, 0, m
|
||||
v1, v2, v3 = 0, 1, x
|
||||
|
||||
while v3 > 0:
|
||||
q = u3 // v3
|
||||
u1, v1 = v1, u1 - v1 * q
|
||||
u2, v2 = v2, u2 - v2 * q
|
||||
u3, v3 = v3, u3 - v3 * q
|
||||
if u2 < 0:
|
||||
u2 += m
|
||||
return u2
|
||||
|
||||
|
||||
_g_thread_ids = {}
|
||||
_g_thread_counter = 0
|
||||
_g_thread_lock = threading.Lock()
|
||||
|
||||
|
||||
def get_thread_id():
|
||||
global _g_thread_ids, _g_thread_counter, _g_thread_lock
|
||||
tid = id(threading.currentThread())
|
||||
try:
|
||||
return _g_thread_ids[tid]
|
||||
except KeyError:
|
||||
_g_thread_lock.acquire()
|
||||
try:
|
||||
_g_thread_counter += 1
|
||||
ret = _g_thread_ids[tid] = _g_thread_counter
|
||||
finally:
|
||||
_g_thread_lock.release()
|
||||
return ret
|
||||
|
||||
|
||||
def log_to_file(filename, level=DEBUG):
|
||||
"""send paramiko logs to a logfile,
|
||||
if they're not already going somewhere"""
|
||||
l = logging.getLogger("paramiko")
|
||||
if len(l.handlers) > 0:
|
||||
return
|
||||
l.setLevel(level)
|
||||
f = open(filename, "a")
|
||||
lh = logging.StreamHandler(f)
|
||||
frm = "%(levelname)-.3s [%(asctime)s.%(msecs)03d] thr=%(_threadid)-3d"
|
||||
frm += " %(name)s: %(message)s"
|
||||
lh.setFormatter(logging.Formatter(frm, "%Y%m%d-%H:%M:%S"))
|
||||
l.addHandler(lh)
|
||||
|
||||
|
||||
# make only one filter object, so it doesn't get applied more than once
|
||||
class PFilter(object):
|
||||
def filter(self, record):
|
||||
record._threadid = get_thread_id()
|
||||
return True
|
||||
|
||||
|
||||
_pfilter = PFilter()
|
||||
|
||||
|
||||
def get_logger(name):
|
||||
l = logging.getLogger(name)
|
||||
l.addFilter(_pfilter)
|
||||
return l
|
||||
|
||||
|
||||
def retry_on_signal(function):
|
||||
"""Retries function until it doesn't raise an EINTR error"""
|
||||
while True:
|
||||
try:
|
||||
return function()
|
||||
except EnvironmentError as e:
|
||||
if e.errno != errno.EINTR:
|
||||
raise
|
||||
|
||||
|
||||
def constant_time_bytes_eq(a, b):
|
||||
if len(a) != len(b):
|
||||
return False
|
||||
res = 0
|
||||
# noinspection PyUnresolvedReferences
|
||||
for i in (xrange if PY2 else range)(len(a)): # noqa: F821
|
||||
res |= byte_ord(a[i]) ^ byte_ord(b[i])
|
||||
return res == 0
|
||||
|
||||
|
||||
class ClosingContextManager(object):
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.close()
|
||||
|
||||
|
||||
def clamp_value(minimum, val, maximum):
|
||||
return max(minimum, min(val, maximum))
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/util.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/util.pyc
Normal file
Binary file not shown.
141
.ve/lib/python2.7/site-packages/paramiko/win_pageant.py
Normal file
141
.ve/lib/python2.7/site-packages/paramiko/win_pageant.py
Normal file
@@ -0,0 +1,141 @@
|
||||
# Copyright (C) 2005 John Arbash-Meinel <john@arbash-meinel.com>
|
||||
# Modified up by: Todd Whiteman <ToddW@ActiveState.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.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Functions for communicating with Pageant, the basic windows ssh agent program.
|
||||
"""
|
||||
|
||||
import array
|
||||
import ctypes.wintypes
|
||||
import platform
|
||||
import struct
|
||||
from paramiko.common import zero_byte
|
||||
from paramiko.py3compat import b
|
||||
|
||||
try:
|
||||
import _thread as thread # Python 3.x
|
||||
except ImportError:
|
||||
import thread # Python 2.5-2.7
|
||||
|
||||
from . import _winapi
|
||||
|
||||
|
||||
_AGENT_COPYDATA_ID = 0x804e50ba
|
||||
_AGENT_MAX_MSGLEN = 8192
|
||||
# Note: The WM_COPYDATA value is pulled from win32con, as a workaround
|
||||
# so we do not have to import this huge library just for this one variable.
|
||||
win32con_WM_COPYDATA = 74
|
||||
|
||||
|
||||
def _get_pageant_window_object():
|
||||
return ctypes.windll.user32.FindWindowA(b"Pageant", b"Pageant")
|
||||
|
||||
|
||||
def can_talk_to_agent():
|
||||
"""
|
||||
Check to see if there is a "Pageant" agent we can talk to.
|
||||
|
||||
This checks both if we have the required libraries (win32all or ctypes)
|
||||
and if there is a Pageant currently running.
|
||||
"""
|
||||
return bool(_get_pageant_window_object())
|
||||
|
||||
|
||||
if platform.architecture()[0] == "64bit":
|
||||
ULONG_PTR = ctypes.c_uint64
|
||||
else:
|
||||
ULONG_PTR = ctypes.c_uint32
|
||||
|
||||
|
||||
class COPYDATASTRUCT(ctypes.Structure):
|
||||
"""
|
||||
ctypes implementation of
|
||||
http://msdn.microsoft.com/en-us/library/windows/desktop/ms649010%28v=vs.85%29.aspx
|
||||
"""
|
||||
|
||||
_fields_ = [
|
||||
("num_data", ULONG_PTR),
|
||||
("data_size", ctypes.wintypes.DWORD),
|
||||
("data_loc", ctypes.c_void_p),
|
||||
]
|
||||
|
||||
|
||||
def _query_pageant(msg):
|
||||
"""
|
||||
Communication with the Pageant process is done through a shared
|
||||
memory-mapped file.
|
||||
"""
|
||||
hwnd = _get_pageant_window_object()
|
||||
if not hwnd:
|
||||
# Raise a failure to connect exception, pageant isn't running anymore!
|
||||
return None
|
||||
|
||||
# create a name for the mmap
|
||||
map_name = "PageantRequest%08x" % thread.get_ident()
|
||||
|
||||
pymap = _winapi.MemoryMap(
|
||||
map_name, _AGENT_MAX_MSGLEN, _winapi.get_security_attributes_for_user()
|
||||
)
|
||||
with pymap:
|
||||
pymap.write(msg)
|
||||
# Create an array buffer containing the mapped filename
|
||||
char_buffer = array.array("b", b(map_name) + zero_byte) # noqa
|
||||
char_buffer_address, char_buffer_size = char_buffer.buffer_info()
|
||||
# Create a string to use for the SendMessage function call
|
||||
cds = COPYDATASTRUCT(
|
||||
_AGENT_COPYDATA_ID, char_buffer_size, char_buffer_address
|
||||
)
|
||||
|
||||
response = ctypes.windll.user32.SendMessageA(
|
||||
hwnd, win32con_WM_COPYDATA, ctypes.sizeof(cds), ctypes.byref(cds)
|
||||
)
|
||||
|
||||
if response > 0:
|
||||
pymap.seek(0)
|
||||
datalen = pymap.read(4)
|
||||
retlen = struct.unpack(">I", datalen)[0]
|
||||
return datalen + pymap.read(retlen)
|
||||
return None
|
||||
|
||||
|
||||
class PageantConnection(object):
|
||||
"""
|
||||
Mock "connection" to an agent which roughly approximates the behavior of
|
||||
a unix local-domain socket (as used by Agent). Requests are sent to the
|
||||
pageant daemon via special Windows magick, and responses are buffered back
|
||||
for subsequent reads.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._response = None
|
||||
|
||||
def send(self, data):
|
||||
self._response = _query_pageant(data)
|
||||
|
||||
def recv(self, n):
|
||||
if self._response is None:
|
||||
return ""
|
||||
ret = self._response[:n]
|
||||
self._response = self._response[n:]
|
||||
if self._response == "":
|
||||
self._response = None
|
||||
return ret
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
BIN
.ve/lib/python2.7/site-packages/paramiko/win_pageant.pyc
Normal file
BIN
.ve/lib/python2.7/site-packages/paramiko/win_pageant.pyc
Normal file
Binary file not shown.
Reference in New Issue
Block a user