Commit decc249a authored by Sam Rushing's avatar Sam Rushing

Eric's SSH implementation

parent a856267a
# if y < 1024, openssh will reject us: "bad server public DH value".
# y<1024 means f will be short, and of the form 2^y, so an observer
# could trivially derive our secret y from f. Openssh detects this
# and complains, so avoid creating such values by requiring y to be
# larger than ln2(self.p)
# TODO: we should also look at the value they send to us and reject
# insecure values of f (if g==2 and f has a single '1' bit while the
# rest are '0's, then they must have used a small y also).
# TODO: This could be computed when self.p is set up
# or do as openssh does and scan f for a single '1' bit instead
$Header: //prod/main/ap/ssh/README#1 $
python implementation of SSH2
Eric's Python SSH Library
=========================
Introduction
------------
This is a python implementation of the SSH2 protocol. No effort was made to support SSH1.
It uses Andrew Kuchling's pycrypto library (version 1.9a6).
This implementation is based on the following revisions of the IETF drafts. Future revisions may change certain parts of the protocol, but that is unlikely.
draft-ietf-secsh-architecture-15.txt
draft-ietf-secsh-assignednumbers-05.txt
draft-ietf-secsh-auth-kbdinteract-05.txt
draft-ietf-secsh-connect-18.txt
draft-ietf-secsh-dh-group-exchange-04.txt
draft-ietf-secsh-filexfer-04.txt
draft-ietf-secsh-fingerprint-01.txt
draft-ietf-secsh-gsskeyex-07.txt
draft-ietf-secsh-newmodes-00.txt
draft-ietf-secsh-newmodes-01.txt
draft-ietf-secsh-publickeyfile-04.txt
draft-ietf-secsh-transport-17.txt
draft-ietf-secsh-userauth-18.txt
Overview
--------
This is a very simple overview of the SSH protocol and how it maps to this library's source tree. The IETF secsh architecture document describes the basic architecture of the protocol.
The base-level protocol is called the "transport". You will find its implementation in ssh/transport/transport.py. A subclass of the transport is made to implement either a server or a client (currently only the client is implemented).
The transport is responsible for protocol negotiation, key exchange, encryption, compression, and message authenticity.
The transport may use any type of low-level transmission transports as long as they guarantee in-order delivery. TCP is a perfect example. To support different types of transmission types, the functionality is abstracted in the l4_transport directory (L4 meaning the 4th layer of the OSI network model). You may then use different socket libraries (select vs. poll) or even transmit over other media such as a serial cable (though serial does not offer guaranteed transmission, so it may be a poor choice).
The transport-layer features are abstracted in their respective directories:
cipher - Implements encryption/decryption.
compression - Implements compression.
keys - Formatting and handling of various key types.
key_exchange - The key exchange algorithm (only diffie-hellman).
mac - Message authentication codes.
Services
--------
The SSH transport layer supports different "services". Currently there are two services, "userauth" and "connection". Userauth provides the mechanism to authenticate a user. Connection is the service through which most data transfer is done. On the transport layer you send a message to ask if it is ok to use a service, and if so go ahead.
Userauth
--------
Userauth is a generic mechanism for authentication. It supports various different authentication mechanisms. Currently this library supports publickey and password. Host-based authentication could be trivially added if needed.
Connection
----------
The connection layer is a generic mechanism to have various different "channels". You can multiplex multiple channels over a single connection. The connection layer is also flow-controlled with finite sized windows.
Currently the only channel written is the interactive session channel. It executes the user's shell on the remote end.
Debugging
---------
There is a debugging facility to capture messages and selectively display them to the user. A transport instance has 1 instance of the debug class. The debug class receives messages and determines if the user wants to see them. You can subclass the debug class and change how the information is presented.
Naming Convention
-----------------
Any method that is used to handle incoming packets has the prefix 'msg_'.
All modules and methods are in lowercase_with_underscores.
All classes are in Capitalized_Words_With_Underscores. This is not a standard naming convention, and actually labelled as "ugly!" in the Python style guide. However, I've never liked CapitalizedWords without underscores because it is hard to read. Java style mixedCase has the exact same problem. I like using capitalized words because it is distinguished. I have never done this before, so this is an experiment with this library.
TODO
====
Important
---------
- more docstrings
- Make sure _fastmath and friends are getting installed.
- Add simple_client? See test_client.py.
- Go over every sentence in every spec to make sure this code adheres to it.
- Keys: Add debuging to keys.
- Diffie Hellman: When generating y, make sure that y > ln2(p) (or y > 1024)
- Diffie Hellman: Reject insecure values of f (if g==2 and f has a single '1' bit, then they must have used a small y also)
- Diffie Hellman: Make sure generation of f is good (no single 1 bit)
- CORO: There are problems reading from stdin with coro. test_coro_client doesn't work very well due to this fact.
- connect: I think there are some serious logic problems in the connect code. In some places it manually calls process_messages waiting for a specifc response to a specific message. However, this may cause it to miss other messages that are in the pipeline because there is no registry entry for them. I think to fix the main problem is to change the core process_messages() in the connect class to permanently register its messages.
- finish Remote_To_Local_Forward
- Files in test directory are out of date and broken.
- Update with latest specs (need to see what has changed).
Should Do
---------
- Handle new key exchange (after every gigabyte or every hour)
- More unittests.
- DH Group Exchange
- formalize exceptions with arguments
- exception handling (bad packets, etc.)
- Stir the random pool (see ssh.util.random.py)
- Userauth: Add more debugging.
- Compression support.
- Document how to extend the various components of this library.
- Finish README high-level documentation.
- Test proactive stuff (both successful guess and failures)
- In the userauth module, inspect auths_that_can_continue when getting an authentication failure so that we don't try subsequent authentication methods that we know will not work.
- Figure out what partial_success means in the userauth module.
- Check for invalid channel id's in the connect.py code when looking at self.local_channels.
- Change module definition at top of file to be a triple quoted string instead of a comment. This is the correct docstyle.
- Connect: No way to match channel requests with their response. Need to check latest specs.
Would Be Nice
-------------
- Write a server
- I18N of error messages.
- Handle SSH1.x protocol.
- Userauth: Support pipelining (sending multiple userauth requests without waiting for the server to respond).
- Userauth: Mechanism to remember which auth method succeeds on a per-host basis. Thus in future attempts to connect to the remote host, it can attempt the correct method first.
- BER: Make a complete ASN.1 decoder.
- BER: Break out the BER encodeder into a separate non-SSH specific module.
- BER: Add an encoder.
- Keys: Add key generator.
- Optimize...haven't yet done any performance testing.
Bugs
----
- Fix memory leaks (circular references)
# -*- Mode: Python -*-
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.auth
#
# This handles the various types of authentication systems.
#
import ssh.transport
class Authentication_System(ssh.transport.SSH_Service):
def authenticate(self, service_name):
"""authenticate(self, service_name) -> None
Attempts to authenticate with the remote side.
Assumes you have already confirmed that this authentication service
is OK to use by sending a SSH_MSG_SERVICE_REQUEST packet.
<service_name>: The name of the service that you want to use after
authenticating. Typically 'ssh-connection'.
"""
# XXX: userauth is currently the only defined authentication
# mechanism that I know if. userauth requires to know
# what the service is you are trying to authenticate for.
# This means that this generic API is specialized to include
# service_name only because Userauth is kinda designed weird.
# It is possible that other auth types don't care what service
# you want to run.
raise NotImplementedError
class Authentication_Error(Exception):
pass
This diff is collapsed.
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.cipher
#
# This is the interface to the encryption/decryption routines.
#
__version__ = '$Revision: #1 $'
class SSH_Cipher_Method:
"""SSH_Cipher_Method
Base class for any type of stream encryption.
"""
name = 'none'
key_size = 0
iv_size = 0
block_size = 1
IV = None
key = None
def encrypt(self, data):
raise NotImplementedError
def decrypt(self, data):
raise NotImplementedError
def set_encryption_key_and_iv(self, key, IV):
self.key = key
self.IV = IV
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.cipher.blowfish_cbc
#
# Implements the Blowfish cipher in CBC mode.
#
import ssh.cipher
from Crypto.Cipher import Blowfish
class Blowfish_CBC(ssh.cipher.SSH_Cipher_Method):
name = 'blowfish-cbc'
block_size = 8
key_size = 16
iv_size = 8
cipher = None
def encrypt(self, data):
return self.cipher.encrypt(data)
def descrypt(self, data):
return self.cipher.decrypt(data)
def set_encryption_key_and_iv(self, key, IV):
self.key = key
self.IV = IV
self.cipher = Blowfish.new(key, Blowfish.MODE_CBC, IV)
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.cipher.des3_cbc
#
# Implements the Triple DES cipher in CBC mode.
#
__version__ = '$Revision: #1 $'
import ssh.cipher
from Crypto.Cipher import DES3
class Triple_DES_CBC(ssh.cipher.SSH_Cipher_Method):
name = '3des-cbc'
block_size = 8
key_size = 24
iv_size = 8
cipher = None
def encrypt(self, data):
return self.cipher.encrypt(data)
def decrypt(self, data):
return self.cipher.decrypt(data)
def set_encryption_key_and_iv(self, key, IV):
self.key = key
self.IV = IV
self.cipher = DES3.new(key, DES3.MODE_CBC, IV)
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.cipher.none
#
# This is the 'none' cipher. It passes the data through without encryption.
#
__version__ = '$Revision: #1 $'
import ssh.cipher
class Cipher_None(ssh.cipher.SSH_Cipher_Method):
def encrypt(self, data):
return data
def decrypt(self, data):
return data
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.compression
#
# This is the interface to support compression of data over the SSH transport.
#
__version__ = '$Revision: #1 $'
class SSH_Compression_Method:
"""SSH_Compression_Method
Base class for any type of compression.
"""
name = 'none'
def compress(self, data):
raise NotImplementedError
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.compression.none
#
# This is the 'none' compression method.
# It passes the data through uncompressed.
#
__version__ = '$Revision: #1 $'
import ssh.compression
class Compression_None(ssh.compression.SSH_Compression_Method):
def compress(self, data):
return data
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
This diff is collapsed.
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.connection.connect
#
# This implements the SSH Connect service. This service can run interactive
# login sessions, remote execution of comments, forwarded TCP/IP connections,
# and forwarded X11 connections. These channels can be multiplexed into a
# single encrypted tunnel.
#
__version__ = '$Revision: #1 $'
import ssh.transport
import ssh.util.packet
import ssh.util.debug
from constants import *
class Connection_Service(ssh.transport.SSH_Service):
name = 'ssh-connection'
# This is a counter used to assign local channel ID's.
next_channel_id = 0
# This is a dictionary of channels.
# The key is the channel ID, the value is the Channel object.
local_channels = None
# This is a dictionary of remote channels.
# The key is the remote channel ID, the value is a Remote_Channel object.
remote_channels = None
def __init__(self, transport):
self.transport = transport
self.local_channels = {}
self.remote_channels = {}
callbacks = {SSH_MSG_GLOBAL_REQUEST: self.msg_global_request,
SSH_MSG_CHANNEL_WINDOW_ADJUST: self.msg_channel_window_adjust,
SSH_MSG_CHANNEL_DATA: self.msg_channel_data,
SSH_MSG_CHANNEL_EXTENDED_DATA: self.msg_channel_extended_data,
SSH_MSG_CHANNEL_EOF: self.msg_channel_eof,
SSH_MSG_CHANNEL_CLOSE: self.msg_channel_close,
SSH_MSG_CHANNEL_REQUEST: self.msg_channel_request,
SSH_MSG_CHANNEL_SUCCESS: self.msg_channel_success,
SSH_MSG_CHANNEL_FAILURE: self.msg_channel_failure,
SSH_MSG_CHANNEL_OPEN_CONFIRMATION: self.msg_channel_open_confirmation,
SSH_MSG_CHANNEL_OPEN_FAILURE: self.msg_channel_open_failure,
}
self.transport.register_callbacks('ssh-connection', callbacks)
def register_channel(self, channel):
"""register_channel(self, channel) -> None
When opening a channel, this function is called to add it to the
local_channels dictionary and to set the channel id.
"""
channel.channel_id = self.next_channel_id
assert not self.local_channels.has_key(channel.channel_id)
self.next_channel_id += 1 # XXX: Overflow?
self.local_channels[channel.channel_id] = channel
def msg_global_request(self, packet):
# XXX: finish this (it's a server thing)
data, offset = ssh.util.packet.unpack_payload_get_offset(SSH_MSG_GLOBAL_REQUEST_PAYLOAD, packet)
msg, request_name, want_reply = data
raise NotImplementedError
def msg_channel_window_adjust(self, packet):
msg, channel_id, bytes_to_add = ssh.util.packet.unpack_payload(SSH_MSG_CHANNEL_WINDOW_ADJUST_PAYLOAD, packet)
channel = self.local_channels[channel_id]
channel.remote_channel.window_data_left += bytes_to_add
self.transport.debug.write(ssh.util.debug.DEBUG_3, 'channel %i window increased by %i to %i', (channel.remote_channel.channel_id, bytes_to_add, channel.remote_channel.window_data_left))
channel.window_data_added_cv.wake_all()
def msg_channel_data(self, packet):
msg, channel_id, data = ssh.util.packet.unpack_payload(SSH_MSG_CHANNEL_DATA_PAYLOAD, packet)
channel = self.local_channels[channel_id]
# XXX: In theory, we should verify that len(data) <= channel.max_packet_size
if len(data) > channel.window_data_left:
self.transport.debug.write(ssh.util.debug.WARNING, 'channel %i %i bytes overflowed window of %i', (channel.channel_id, len(data), channel.remote_channel.window_data_left))
# Data is ignored.
else:
channel.window_data_left -= len(data)
channel.append_data_received(data)
def msg_channel_extended_data(self, packet):
msg, channel_id, data_type_code, data = ssh.util.packet.unpack_payload(SSH_MSG_CHANNEL_EXTENDED_DATA_PAYLOAD, packet)
channel = self.local_channels[channel_id]
if len(data) > channel.window_data_left:
self.transport.debug.write(ssh.util.debug.WARNING, 'channel %i %i bytes overflowed window of %i', (channel.channel_id, len(data), channel.remote_channel.window_data_left))
# Data is ignored.
else:
channel.window_data_left -= len(data)
channel.append_extended_data_received(data_type_code, data)
def msg_channel_eof(self, packet):
msg, channel_id = ssh.util.packet.unpack_payload(SSH_MSG_CHANNEL_EOF_PAYLOAD, packet)
channel = self.local_channels[channel_id]
# assert it is not already closed?
channel.set_eof()
def msg_channel_close(self, packet):
msg, channel_id = ssh.util.packet.unpack_payload(SSH_MSG_CHANNEL_CLOSE_PAYLOAD, packet)
channel = self.local_channels[channel_id]
del self.local_channels[channel_id]
del self.remote_channels[channel.remote_channel.channel_id]
# assert it is not already closed?
channel.closed = 1
if not channel.remote_channel.closed:
# Close the other side.
channel.close()
channel.set_eof()
def msg_channel_request(self, packet):
data, offset = ssh.util.packet.unpack_payload_get_offset(SSH_MSG_CHANNEL_REQUEST_PAYLOAD, packet)
msg, channel_id, request_type, want_reply = data
channel = self.local_channels[channel_id]
channel.handle_request(request_type, want_reply, packet[offset:])
def msg_channel_success(self, packet):
msg, channel_id = ssh.util.packet.unpack_payload(SSH_MSG_CHANNEL_SUCCESS_PAYLOAD, packet)
channel = self.local_channels[channel_id]
channel.channel_request_success()
def msg_channel_failure(self, packet):
msg, channel_id = ssh.util.packet.unpack_payload(SSH_MSG_CHANNEL_FAILURE_PAYLOAD, packet)
channel = self.local_channels[channel_id]
channel.channel_request_failure()
def msg_channel_open_confirmation(self, packet):
data, offset = ssh.util.packet.unpack_payload_get_offset(SSH_MSG_CHANNEL_OPEN_CONFIRMATION_PAYLOAD, packet)
msg, recipient_channel, sender_channel, window_size, max_packet_size = data
self.transport.debug.write(ssh.util.debug.DEBUG_1, 'channel %i open confirmation sender_channel=%i window_size=%i max_packet_size=%i', (recipient_channel, sender_channel, window_size, max_packet_size))
channel = self.local_channels[recipient_channel]
# XXX: Assert that the channel is not already open?
channel.closed = 0
channel.eof = 0
channel.remote_channel.closed = 0
channel.remote_channel.channel_id = sender_channel
assert not self.remote_channels.has_key(sender_channel)
self.remote_channels[sender_channel] = channel.remote_channel
channel.remote_channel.window_size = window_size
channel.remote_channel.window_data_left = window_size
channel.remote_channel.max_packet_size = max_packet_size
additional_data = ssh.util.packet.unpack_payload(channel.additional_packet_data_types, packet, offset)
channel.channel_open_success(additional_data)
def msg_channel_open_failure(self, packet):
msg, channel_id, reason_code, reason_text, language = ssh.util.packet.unpack_payload(SSH_MSG_CHANNEL_OPEN_FAILURE_PAYLOAD, packet)
channel = self.local_channels[channel_id]
# XXX: Assert that the channel is not already open?
channel.channel_open_failure(reason_code, reason_text, language)
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.connect.constants
#
# Constants used in the connect protocol.
__version__ = '$Revision: #1 $'
import ssh.util.packet
SSH_MSG_GLOBAL_REQUEST = 80
SSH_MSG_GLOBAL_REQUEST_SUCCESS = 81
SSH_MSG_GLOBAL_REQUEST_FAILURE = 82
SSH_MSG_CHANNEL_OPEN = 90
SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91
SSH_MSG_CHANNEL_OPEN_FAILURE = 92
SSH_MSG_CHANNEL_WINDOW_ADJUST = 93
SSH_MSG_CHANNEL_DATA = 94
SSH_MSG_CHANNEL_EXTENDED_DATA = 95
SSH_MSG_CHANNEL_EOF = 96
SSH_MSG_CHANNEL_CLOSE = 97
SSH_MSG_CHANNEL_REQUEST = 98
SSH_MSG_CHANNEL_SUCCESS = 99
SSH_MSG_CHANNEL_FAILURE = 100
# This is the basic open payload. Different session types may add
# additional information to this.
SSH_MSG_CHANNEL_OPEN_PAYLOAD = (ssh.util.packet.BYTE, # SSH_MSG_CHANNEL_OPEN
ssh.util.packet.STRING, # channel type
ssh.util.packet.UINT32, # sender channel
ssh.util.packet.UINT32, # initial window size
ssh.util.packet.UINT32) # maximum packet size
# This is the basic confirmation payload. Different session types may add
# addition information to this.
SSH_MSG_CHANNEL_OPEN_CONFIRMATION_PAYLOAD = (ssh.util.packet.BYTE, # SSH_MSG_CHANNEL_OPEN_CONFIRMATION,
ssh.util.packet.UINT32, # recipient channel
ssh.util.packet.UINT32, # sender channel
ssh.util.packet.UINT32, # initial window size
ssh.util.packet.UINT32) # maximum packet size
SSH_MSG_CHANNEL_OPEN_FAILURE_PAYLOAD = (ssh.util.packet.BYTE, # SSH_MSG_CHANNEL_OPEN_FAILURE
ssh.util.packet.UINT32, # recipient channel
ssh.util.packet.UINT32, # reason_code
ssh.util.packet.STRING, # reason_text
ssh.util.packet.STRING) # language
SSH_MSG_CHANNEL_CLOSE_PAYLOAD = (ssh.util.packet.BYTE, # SSH_MSG_CHANNEL_CLOSE
ssh.util.packet.UINT32) # recipient_channel
# This may contain additional request-specific data.
SSH_MSG_GLOBAL_REQUEST_PAYLOAD = (ssh.util.packet.BYTE, # SSH_MSG_GLOBAL_REQUEST
ssh.util.packet.STRING, # request name
ssh.util.packet.BOOLEAN) # want reply
# This may contain additional request-specific data.
SSH_MSG_GLOBAL_REQUEST_SUCCESS_PAYLOAD = (ssh.util.packet.BYTE) # SSH_MSG_GLOBAL_REQUEST_SUCCESS
SSH_MSG_GLOBAL_REQUEST_FAILURE_PAYLOAD = (ssh.util.packet.BYTE) # SSH_MSG_GLOBAL_REQUEST_FAILURE
SSH_MSG_CHANNEL_WINDOW_ADJUST_PAYLOAD = (ssh.util.packet.BYTE, # SSH_MSG_CHANNEL_WINDOW_ADJUST
ssh.util.packet.UINT32, # recipient channel
ssh.util.packet.UINT32) # bytes to add
SSH_MSG_CHANNEL_DATA_PAYLOAD = (ssh.util.packet.BYTE, # SSH_MSG_CHANNEL_DATA
ssh.util.packet.UINT32, # recipient channel
ssh.util.packet.STRING) # data
SSH_MSG_CHANNEL_EXTENDED_DATA_PAYLOAD = (ssh.util.packet.BYTE, # SSH_MSG_CHANNEL_EXTENDED_DATA
ssh.util.packet.UINT32, # recipient channel
ssh.util.packet.UINT32, # data_type_code
ssh.util.packet.STRING) # data
SSH_EXTENDED_DATA_STDERR = 1
SSH_MSG_CHANNEL_EOF_PAYLOAD = (ssh.util.packet.BYTE, # SSH_MSG_CHANNEL_EOF
ssh.util.packet.UINT32) # recipient channel
# This may contain addition request-specific data.
SSH_MSG_CHANNEL_REQUEST_PAYLOAD = (ssh.util.packet.BYTE, # SSH_MSG_CHANNEL_REQUEST
ssh.util.packet.UINT32, # recipient_channel
ssh.util.packet.STRING, # request type
ssh.util.packet.BOOLEAN) # want reply
SSH_MSG_CHANNEL_FAILURE_PAYLOAD = (ssh.util.packet.BYTE, # SSH_MSG_CHANNEL_FAILURE
ssh.util.packet.UINT32) # recipient_channel
SSH_MSG_CHANNEL_SUCCESS_PAYLOAD = (ssh.util.packet.BYTE, # SSH_MSG_CHANNEL_SUCCESS
ssh.util.packet.UINT32) # recipient_channel
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.connection.data_buffer
#
# This implements a simple buffer class that works like a FIFO.
# It is time-effecient since it tries to avoid string copies whenever
# possible.
__version__ = '$Revision: #1 $'
import coro_fifo
class Buffer:
def __init__(self):
self.fifo = coro_fifo.circular_fifo()
def __len__(self):
return len(self.fifo)
def write(self, data):
"""write(self, data) -> None
Writes data to the buffer.
"""
self.fifo.enqueue(data)
def pop(self):
"""pop(self) -> str
Pops the first string from the buffer.
"""
return self.fifo.dequeue()
def read_at_most(self, bytes):
"""read_at_most(self, bytes) -> str
Reads at most <bytes>.
May return less than <bytes> even if there is more data in the buffer.
Returns the empty string when the buffer is empty.
"""
while 1:
try:
data = self.fifo.peek()
except IndexError:
# Buffer empty.
self.fifo.cv.wait()
else:
break
if not data:
raise EOFError
if len(data) > bytes:
result = data[:bytes]
self.fifo.poke(data[bytes:])
return result
else:
return self.fifo.dequeue()
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.connect.interactive_session
#
# This implements the "session" channel of the ssh_connect service.
#
__version__ = '$Revision: #1 $'
import channel
import ssh.util.packet
from connect import *
class Interactive_Session(channel.Channel):
name = 'session'
def send_environment_variable(self, name, value):
self.send_channel_request('env', ENV_CHANNEL_REQUEST_PAYLOAD,
(name,
value))
class Interactive_Session_Client(Interactive_Session):
def open_pty(self, term='', width_char=0, height_char=0, width_pixels=0, height_pixels=0, modes=''):
self.send_channel_request('pty-req', PTY_CHANNEL_REQUEST_PAYLOAD,
(term,
width_char,
height_char,
width_pixels,
height_pixels,
modes))
def open_shell(self):
self.send_channel_request('shell', (), ())
def exec_command(self, command):
self.send_channel_request('exec', EXEC_CHANNEL_REQUEST_PAYLOAD, (command,))
class Interactive_Session_Server(Interactive_Session):
def handle_request(self, request_type, want_reply, type_specific_packet_data):
if self.request_handlers.has_key(request_type):
f = self.request_handlers[request_type]
f(want_reply, type_specific_packet_data)
else:
if want_reply:
packet = ssh.util.packet.pack_payload(SSH_MSG_CHANNEL_FAILURE_PAYLOAD, (self.remote_channel.channel_id,))
self.transport.send_packet(packet)
def handle_pty_request(self, want_reply, type_specific_packet_data):
term, width_char, height_char, width_pixels, height_pixels, modes = ssh.util.packet.unpack_payload(PTY_CHANNEL_REQUEST_PAYLOAD, type_specific_packet_data)
# XXX: NOT FINISHED
def handle_x11_request(self, want_reply, type_specific_packet_data):
single_connection, auth_protocol, auth_cookie, screen_number = ssh.util.packet.unpack_payload(X11_CHANNEL_REQUEST_PAYLOAD, type_specific_packet_data)
# XXX: NOT FINISHED
request_handlers = {'pty-req': handle_pty_request,
'x11-req': handle_x11_request,
}
PTY_CHANNEL_REQUEST_PAYLOAD = (ssh.util.packet.STRING, # TERM environment variable value (e.g., vt100)
ssh.util.packet.UINT32, # terminal width, characters (e.g., 80)
ssh.util.packet.UINT32, # terminal height, rows (e.g., 24)
ssh.util.packet.UINT32, # terminal width, pixels (e.g., 640)
ssh.util.packet.UINT32, # terminal height, pixels (e.g., 480)
ssh.util.packet.STRING) # encoded terminal modes
X11_CHANNEL_REQUEST_PAYLOAD = (ssh.util.packet.BOOLEAN, # single connection
ssh.util.packet.STRING, # x11 authentication protocol
ssh.util.packet.STRING, # x11 authentication cookie
ssh.util.packet.UINT32) # x11 screen number
ENV_CHANNEL_REQUEST_PAYLOAD = (ssh.util.packet.STRING, # variable name
ssh.util.packet.STRING) # variable value
EXEC_CHANNEL_REQUEST_PAYLOAD = (ssh.util.packet.STRING,) # command
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh_tty_modes
#
# List of TTY modes used for SSH Interactive Sessions.
#
__version__ = '$Revision: #1 $'
import struct
class Term_Mode_Builder:
def __init__(self):
self.ops = []
def set_mode(self, opcode, value):
self.ops.append(chr(opcode) + struct.pack('>I', value))
def get_mode(self):
return ''.join(self.ops)
TTY_OP_END = 0 # Indicates end of options.
VINTR = 1 # Interrupt character; 255 if none. Similarly for the
# other characters. Not all of these characters are
# supported on all systems.
VQUIT = 2 # The quit character (sends SIGQUIT signal on POSIX
# systems).
VERASE = 3 # Erase the character to left of the cursor.
VKILL = 4 # Kill the current input line.
VEOF = 5 # End-of-file character (sends EOF from the terminal).
VEOL = 6 # End-of-line character in addition to carriage return
# and/or linefeed.
VEOL2 = 7 # Additional end-of-line character.
VSTART = 8 # Continues paused output (normally control-Q).
VSTOP = 9 # Pauses output (normally control-S).
VSUSP = 10 # Suspends the current program.
VDSUSP = 11 # Another suspend character.
VREPRINT = 12 # Reprints the current input line.
VWERASE = 13 # Erases a word left of cursor.
VLNEXT = 14 # Enter the next character typed literally, even if it
# is a special character
VFLUSH = 15 # Character to flush output.
VSWTCH = 16 # Switch to a different shell layer.
VSTATUS = 17 # Prints system status line (load, command, pid etc).
VDISCARD = 18 # Toggles the flushing of terminal output.
IGNPAR = 30 # The ignore parity flag. The parameter SHOULD be 0 if
# this flag is FALSE set, and 1 if it is TRUE.
PARMRK = 31 # Mark parity and framing errors.
INPCK = 32 # Enable checking of parity errors.
ISTRIP = 33 # Strip 8th bit off characters.
INLCR = 34 # Map NL into CR on input.
IGNCR = 35 # Ignore CR on input.
ICRNL = 36 # Map CR to NL on input.
IUCLC = 37 # Translate uppercase characters to lowercase.
IXON = 38 # Enable output flow control.
IXANY = 39 # Any char will restart after stop.
IXOFF = 40 # Enable input flow control.
IMAXBEL = 41 # Ring bell on input queue full.
ISIG = 50 # Enable signals INTR, QUIT, [D]SUSP.
ICANON = 51 # Canonicalize input lines.
XCASE = 52 # Enable input and output of uppercase characters by
# preceding their lowercase equivalents with `\'.
ECHO = 53 # Enable echoing.
ECHOE = 54 # Visually erase chars.
ECHOK = 55 # Kill character discards current line.
ECHONL = 56 # Echo NL even if ECHO is off.
NOFLSH = 57 # Don't flush after interrupt.
TOSTOP = 58 # Stop background jobs from output.
IEXTEN = 59 # Enable extensions.
ECHOCTL = 60 # Echo control characters as ^(Char).
ECHOKE = 61 # Visual erase for line kill.
PENDIN = 62 # Retype pending input.
OPOST = 70 # Enable output processing.
OLCUC = 71 # Convert lowercase to uppercase.
ONLCR = 72 # Map NL to CR-NL.
OCRNL = 73 # Translate carriage return to newline (output).
ONOCR = 74 # Translate newline to carriage return-newline
ONLRET = 75 # Newline performs a carriage return (output).
CS7 = 90 # 7 bit mode.
CS8 = 91 # 8 bit mode.
PARENB = 92 # Parity enable.
PARODD = 93 # Odd parity, else even.
TTY_OP_ISPEED = 128 # Specifies the input baud rate in bits per second.
TTY_OP_OSPEED = 129 # Specifies the output baud rate in bits per second.
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.key_exchange
#
# This is the generic key exchange system.
# Currently the SSH spec only defines one key exchange method (diffie hellman),
# but in theory you could create your own method.
#
__version__ = '$Revision: #1 $'
import ssh.util.packet
import ssh.util.debug
class SSH_Key_Exchange:
"""SSH_Key_Exchange
Base class for any type of key exchange.
"""
name = 'none'
# What type of host key features this kex algorithm wants.
wants_signature_host_key = 0
wants_encryption_host_key = 0
shared_secret = None
exchange_hash = None
# session_id is the session identifier for this connection.
# It is the value of the very first exchange_hash. If new keys are
# exchanged, this will stay the same.
session_id = None
c2s_version_string = ''
s2c_version_string = ''
s2c_kexinit_packet = ''
c2s_kexinit_packet = ''
# The SSH_Transport.
transport = None
def __init__(self, transport):
self.supported_server_keys = []
self.transport = transport
def get_initial_client_kex_packet(self):
"""get_initial_client_kex_packet(self) -> packet
Get the very first packet of the key exchange to be sent by the client.
If the key exchange algorithm does not support the client sending
the first packet, then this function should return None.
"""
raise NotImplementedError
def get_initial_server_kex_packet(self):
"""get_initial_server_kex_packet(self) -> packet
Get the very first packet of the key exchange to be sent by the server.
If the key exchange algorithm does not support the server sending
the first packet, then this function should return None.
"""
raise NotImplementedError
def register_client_callbacks(self):
"""register_client_callbacks(self) -> None
Register callbacks necessary to handle the client side.
"""
raise NotImplementedError
def register_server_callbacks(self):
"""register_server_callbacks(self) -> None
Register callbacks necessary to handle the server side.
"""
raise NotImplementedError
def get_hash_object(self, *args):
"""get_hash_object(self, *args) -> hash_object
This returns a hash object.
This object must have the same API as the sha and md5 modules in
standard python:
- update(str)
- digest()
- hexdigest()
- copy()
Additional args are added to the hash object via update().
"""
hash_object = self._get_hash_object()
for arg in args:
hash_object.update(arg)
return hash_object
def _get_hash_object(self):
"""_get_hash_object(self) -> hash_object
Return a raw hash object (see get_hash_object).
"""
raise NotImplementedError
def get_encryption_key(self, letter, required_size):
"""get_encryption_key(self, letter, required_size) -> key
Computes an encryption key with the given letter.
<required_size> is the length of the key that you require (in bytes).
"""
shared_secret = ssh.util.packet.pack_payload((ssh.util.packet.MPINT,), (self.shared_secret,))
key = self.get_hash_object(
shared_secret,
self.exchange_hash,
letter,
self.session_id).digest()
if len(key) > required_size:
# Key is too big...return only what is needed.
key = key[:required_size]
elif len(key) < required_size:
# Key is not big enough...compute additional hashes until big enough.
# K1 = HASH(K || H || X || session_id) (X is e.g. "A")
# K2 = HASH(K || H || K1)
# K3 = HASH(K || H || K1 || K2)
# ...
# key = K1 || K2 || K3 || ...
self.transport.debug.write(ssh.util.debug.DEBUG_2, 'get_encryption_key: computed key is too small len(key)=%i required_size=%i', (len(key), required_size))
key_data = [key]
key_data_len = len(key)
while key_data_len < required_size:
additional_key_data = self.get_hash_object(shared_secret, self.exchange_hash, ''.join(key_data)).digest()
key_data.append(additional_key_data)
key_data_len += len(additional_key_data)
key = ''.join(key_data)[:required_size]
else:
# Key is just the right length.
pass
return key
def set_info(self, c2s_version_string, s2c_version_string, c2s_kexinit_packet, s2c_kexinit_packet, supported_server_keys):
self.c2s_version_string = c2s_version_string
self.s2c_version_string = s2c_version_string
self.c2s_kexinit_packet = c2s_kexinit_packet
self.s2c_kexinit_packet = s2c_kexinit_packet
self.supported_server_keys = supported_server_keys
def get_key_algorithm(self, key):
name = ssh.util.packet.unpack_payload( (ssh.util.packet.STRING,), key)[0]
for key_alg in self.supported_server_keys:
if key_alg.name == name:
return key_alg
raise ValueError, name
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.key_exchange.diffie_hellman
#
# This module implements the Diffie Hellman Group 1 SHA1 key exchange.
#
__version__ = '$Revision: #1 $'
import hashlib
import ssh.util.debug
import ssh.util.packet
import ssh.util.random
import ssh.key_exchange
import ssh.transport.constants
# 2**1024 - 2**960 - 1 + 2**64 * floor( 2**894 Pi + 129093 )
DH_PRIME = 179769313486231590770839156793787453197860296048756011706444423684197180216158519368947833795864925541502180565485980503646440548199239100050792877003355816639229553136239076508735759914822574862575007425302077447712589550957937778424442426617334727629299387668709205606050270810842907692932019128194467627007L
DH_GENERATOR = 2L
SSH_MSG_KEXDH_INIT = 30
SSH_MSG_KEXDH_REPLY = 31
class Diffie_Hellman_Group1_SHA1(ssh.key_exchange.SSH_Key_Exchange):
name = 'diffie-hellman-group1-sha1'
# What type of host key features this kex algorithm wants.
wants_signature_host_key = 1
wants_encryption_host_key = 0
client_random_value = '' # x
client_exchange_value = 0L # e
server_public_host_key = None # k_s
def get_initial_client_kex_packet(self):
self.transport.debug.write(ssh.util.debug.DEBUG_3, 'get_initial_kex_packet()')
# Send initial key.
# This is x.
self.client_random_value = ssh.util.random.get_random_number(512)
# p is large safe prime (DH_PRIME)
# g is a generator for a subgroup of GF(p) (DH_GENERATOR)
# compute e=g**x mod p
self.client_exchange_value = pow(DH_GENERATOR, self.client_random_value, DH_PRIME)
return ssh.util.packet.pack_payload(KEXDH_INIT_PAYLOAD,
(SSH_MSG_KEXDH_INIT,
self.client_exchange_value)
)
def get_initial_server_kex_packet(self):
raise NotImplementedError
def _get_hash_object(self):
"""_get_hash_object(self) -> hash_object
Return a raw hash object (see get_hash_object).
"""
return hashlib.sha1()
def register_client_callbacks(self):
callbacks = {SSH_MSG_KEXDH_REPLY: self.msg_kexdh_reply}
self.transport.register_callbacks(self.name, callbacks)
def register_server_callbacks(self):
raise NotImplementedError
def msg_kexdh_reply(self, packet):
# string server public host key and certificates (K_S)
# mpint f
# string signature of H
msg, public_host_key, server_exchange_value, signature_of_h = ssh.util.packet.unpack_payload(KEXDH_REPLY_PAYLOAD, packet)
# Create a SSH_Public_Private_Key instance from the packed string.
self.server_public_host_key = ssh.keys.parse_public_key(public_host_key)
# Verify that this is a known host key.
self.transport.verify_public_host_key(self.server_public_host_key)
# Make sure f is a valid number
if server_exchange_value <= 1 or server_exchange_value >= DH_PRIME-1:
self.transport.send_disconnect(ssh.transport.constants.SSH_DISCONNECT_KEY_EXCHANGE_FAILED, 'Key exchange did not succeed: Server exchange value not valid.')
# K = f**x mod p
self.shared_secret = pow(server_exchange_value, self.client_random_value, DH_PRIME)
# Verify hash.
# string V_C, the client's version string (CR and NL excluded)
# string V_S, the server's version string (CR and NL excluded)
# string I_C, the payload of the client's SSH_MSG_KEXINIT
# string I_S, the payload of the server's SSH_MSG_KEXINIT
# string K_S, the host key
# mpint e, exchange value sent by the client
# mpint f, exchange value sent by the server
# mpint K, the shared secret
H = ssh.util.packet.pack_payload(KEXDH_HASH_PAYLOAD,
(self.c2s_version_string,
self.s2c_version_string,
self.c2s_kexinit_packet,
self.s2c_kexinit_packet,
public_host_key,
self.client_exchange_value,
server_exchange_value,
self.shared_secret))
# Double check that the signature from the server matches our signature.
hash = hashlib.sha1(H)
self.exchange_hash = hash.digest()
if self.session_id is None:
# The session id is the first exchange hash.
self.session_id = self.exchange_hash
if not self.server_public_host_key.verify(self.exchange_hash, signature_of_h):
self.transport.send_disconnect(ssh.transport.constants.SSH_DISCONNECT_KEY_EXCHANGE_FAILED, 'Key exchange did not succeed: Signature did not match.')
# Finished...
self.transport.send_newkeys()
KEXDH_REPLY_PAYLOAD = (ssh.util.packet.BYTE,
ssh.util.packet.STRING, # public host key and certificates (K_S)
ssh.util.packet.MPINT, # f
ssh.util.packet.STRING # signature of H
)
KEXDH_INIT_PAYLOAD = (ssh.util.packet.BYTE,
ssh.util.packet.MPINT # e
)
KEXDH_HASH_PAYLOAD = (ssh.util.packet.STRING, # V_C, the client's version string (CR and NL excluded)
ssh.util.packet.STRING, # V_S, the server's version string (CR and NL excluded)
ssh.util.packet.STRING, # I_C, the payload of the client's SSH_MSG_KEXINIT
ssh.util.packet.STRING, # I_S, the payload of the server's SSH_MSG_KEXINIT
ssh.util.packet.STRING, # K_S, the host key
ssh.util.packet.MPINT, # e, exchange value sent by the client
ssh.util.packet.MPINT, # f, exchange value sent by the server
ssh.util.packet.MPINT # K, the shared secret
)
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.keys
#
# This implements the interface to parse and use various different key types.
#
__version__ = '$Revision: #1 $'
import ssh.util.packet
import dss
import rsa
# Map of supported key types.
keytypes = {
'ssh-dss': dss.SSH_DSS,
'ssh-rsa': rsa.SSH_RSA,
}
class Unknown_Key_Type(Exception):
def __init__(self, keytype):
self.keytype = keytype
Exception.__init__(self, keytype)
def __str__(self):
return '<Unknown_Key_Type: %r>' % self.keytype
def parse_public_key(public_key):
"""parse_public_key(public_key) -> SSH_Public_Private_Key instance
This takes a public key and generates an SSH_Public_Private_Key instance.
<public_key>: A packed public key. The format should be a packed string
with the first value being a string to identify the type.
"""
data, offset = ssh.util.packet.unpack_payload_get_offset((ssh.util.packet.STRING,), public_key)
keytype = data[0]
if not keytypes.has_key(keytype):
raise Unknown_Key_Type(keytype)
key_obj = keytypes[keytype]()
key_obj.set_public_key(public_key)
return key_obj
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.keys.ber
#
# Very simple ASN.1 BER decoder used for SSH private keys.
# This implements more than is necessary for SSH, but not the entire
# ASN.1 standard.
#
__version__ = '$Revision: #1 $'
# flags for BER tags
FLAGS_UNIVERSAL = 0x00
FLAGS_STRUCTURED = 0x20
FLAGS_APPLICATION = 0x40
FLAGS_CONTEXT = 0x80
FLAGS_PRIVATE = 0xC0
# universal BER tags
TAGS_INTEGER = 0x02
TAGS_OCTET_STRING = 0x04
TAGS_SEQUENCE = 0x10 | FLAGS_STRUCTURED
class DecodeError (Exception):
pass
class InsufficientData (DecodeError):
pass
class InvalidData (DecodeError):
pass
class UnknownTag (DecodeError):
pass
# SAFETY NOTE: it's important for each decoder to correctly handle length == zero.
def decode_string(s, pos, length):
# caller guarantees sufficient data in <s>
result = s[pos:pos+length]
pos += length
return result, pos
def decode_integer(s, pos, length):
if length == 0:
return 0, pos
else:
n = long(ord(s[pos]))
if n & 0x80:
# negative
n = n - 0x100
length -= 1
while length:
pos += 1
n = (n << 8) | ord(s[pos])
length -= 1
# advance past the last byte
pos += 1
return n, pos
def decode_structured(s, pos, length):
start = pos
end = start + length
result = []
if length:
while pos < end:
item, pos = decode (s, pos, end)
result.append(item)
return result, pos
# can an asn1 string *end* with a length? i.e., can we just do the
# length check once, at the front, and assume at least three bytes?
def decode(s, pos=0, eos=-1):
"""decode(s, pos=0, eos=-1) -> (value, pos)
Decodes a BER-encoded string.
Return value includes the position where decoding stopped.
<pos> start of scanning position.
<eos> end of scanning.
"""
if eos == -1:
eos = len(s)
# 1) get tag
tag = ord(s[pos])
pos += 1
# 2) get length
if pos > eos:
# assure at least one byte [valid for length == 0]
raise InsufficientData, pos
a = ord(s[pos])
if a < 0x80:
# one-byte length
length = a
pos += 1
elif pos + 1 >= eos:
# assure at least two bytes
raise InsufficientData, pos
elif a == 0x81:
# one-byte length (0x80 <= x <= 0xff)
length = ord(s[pos+1])
pos += 2
elif pos + 2 >= eos:
# assure at least three bytes
raise InsufficientData, pos
elif a == 0x82:
# two-byte length (0x80 <= x <= 0xffff)
length = ord(s[pos+1])
length = (length << 8) | ord(s[pos+2])
pos += 3
else:
# longer lengths allowed? >0x82?
raise InvalidData, pos
# 3) get value
# assure at least <length> bytes
if (pos + length) > eos:
raise InsufficientData, pos
elif tag == TAGS_OCTET_STRING:
return decode_string (s, pos, length)
elif tag == TAGS_INTEGER:
return decode_integer (s, pos, length)
elif tag == TAGS_SEQUENCE:
return decode_structured (s, pos, length)
else:
raise UnknownTag, tag
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.keys.dss
#
# Encapsulates the DSS key.
__version__ = '$Revision: #1 $'
import public_private_key
import hashlib
import ssh.util.packet
import ssh.util.random
from Crypto.PublicKey import DSA
from Crypto.Util import number
class SSH_DSS(public_private_key.SSH_Public_Private_Key):
# Features of this key type.
supports_signature = 1
supports_encryption = 0
name = 'ssh-dss'
# p, q, g, y
private_key = (0L, 0L, 0L, 0L, 0L)
# p, q, g, y, x
public_key = (0L, 0L, 0L, 0L)
def set_public_key(self, public_key):
dss, p, q, g, y = ssh.util.packet.unpack_payload(DSS_PUBLIC_KEY_PAYLOAD, public_key)
if dss != 'ssh-dss':
raise ValueError, dss
self.public_key = (p, q, g, y)
set_public_key.__doc__ = public_private_key.SSH_Public_Private_Key.set_public_key.__doc__
def set_private_key(self, private_key):
dss, p, q, g, y, x = ssh.util.packet.unpack_payload(DSS_PRIVATE_KEY_PAYLOAD, private_key)
if dss != 'ssh-dss':
raise ValueError, dss
self.private_key = (p, q, g, y, x)
set_private_key.__doc__ = public_private_key.SSH_Public_Private_Key.set_private_key.__doc__
def get_public_key_blob(self):
p, q, g, y = self.public_key
return ssh.util.packet.pack_payload(DSS_PUBLIC_KEY_PAYLOAD,
('ssh-dss',
p, q, g, y))
get_public_key_blob.__doc__ = public_private_key.SSH_Public_Private_Key.get_public_key_blob.__doc__
def get_private_key_blob(self):
p, q, g, y, x = self.public_key
return ssh.util.packet.pack_payload(DSS_PRIVATE_KEY_PAYLOAD,
('ssh-dss',
p, q, g, y, x))
get_private_key_blob.__doc__ = public_private_key.SSH_Public_Private_Key.get_private_key_blob.__doc__
def sign(self, message):
p, q, g, y, x = self.private_key
dsa_obj = DSA.construct( (y, g, p, q, x) )
message_hash = hashlib.sha1(message).digest()
# Get a random number that is greater than 2 and less than q.
random_number = ssh.util.random.get_random_number_from_range(2, q)
random_data = number.long_to_bytes(random_number)
r, s = dsa_obj.sign(message_hash, random_data)
signature = number.long_to_bytes(r, 20) + number.long_to_bytes(s, 20)
return ssh.util.packet.pack_payload(DSS_SIG_PAYLOAD,
('ssh-dss',
signature))
sign.__doc__ = public_private_key.SSH_Public_Private_Key.sign.__doc__
def verify(self, message, signature):
p, q, g, y = self.public_key
dss, blob = ssh.util.packet.unpack_payload(DSS_SIG_PAYLOAD, signature)
if dss != 'ssh-dss':
raise ValueError, dss
# blob is the concatenation of r and s
# r and s are 160-bit (20-byte) integers in network-byte-order
assert( len(blob) == 40 )
r = number.bytes_to_long(blob[:20])
s = number.bytes_to_long(blob[20:])
dsa_obj = DSA.construct( (y, g, p, q) )
hash_of_message = hashlib.sha1(message).digest()
return dsa_obj.verify(hash_of_message, (r, s))
verify.__doc__ = public_private_key.SSH_Public_Private_Key.verify.__doc__
DSS_PUBLIC_KEY_PAYLOAD = (ssh.util.packet.STRING, # "ssh-dss"
ssh.util.packet.MPINT, # p
ssh.util.packet.MPINT, # q
ssh.util.packet.MPINT, # g
ssh.util.packet.MPINT # y
)
DSS_PRIVATE_KEY_PAYLOAD = (ssh.util.packet.STRING, # "ssh-dss"
ssh.util.packet.MPINT, # p
ssh.util.packet.MPINT, # q
ssh.util.packet.MPINT, # g
ssh.util.packet.MPINT, # y
ssh.util.packet.MPINT, # x
)
DSS_SIG_PAYLOAD = (ssh.util.packet.STRING, # "ssh-dss"
ssh.util.packet.STRING # signature_key_blob
)
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.keys.key_storage
#
# This module loads and saves various types of SSH public/private keys.
#
__version__ = '$Revision: #1 $'
class Invalid_Server_Public_Host_Key(Exception):
"""Invalid_Server_Public_Host_Key(host_id, public_host_key)
This exception is raised when we have no knowledge of the server's
public key. Normally what happens is the user is asked if the fingerprint
of the public key is OK.
<host_id>: A Remote_Host_ID instance.
<public_host_key>: A SSH_Public_Private_Key instance.
"""
def __init__(self, host_id, public_host_key):
self.host_id = host_id
self.public_host_key = public_host_key
Exception.__init__(self, host_id, public_host_key)
def __str__(self):
return '<Invalid_Server_Public_host_Key host_id=%s>' % self.host_id
# XXX: include the filename and line number of the conflict.
class Host_Key_Changed_Error(Exception):
"""Host_Key_Changed_Error(host_id, location)
This exception is raised when the server's public host key does not match
our database.
<host_id>: A Remote_Host_ID instance
<location>: A string to direct the user to how to find the offending key
in their local database. May be the empty string if it is
not relevant.
"""
def __init__(self, host_id, location):
self.host_id = host_id
self.location = location
Exception.__init__(self, host_id, location)
def __str__(self):
return '<Host_Key_Changed_Error host_id=%s location=%s>' % (self.host_id, self.location)
class SSH_Key_Storage:
def load_keys(self, username=None, **kwargs):
"""load_keys(self, username=None, **kwargs) -> [private_public_key_obj, ...]
Loads the public and private keys.
<username> defaults to the current user.
Different key storage classes take different arguments.
Returns a list of SSH_Public_Private_Key objects.
Returns an empty list if the key is not available.
"""
raise NotImplementedError
def load_private_keys(self, username=None, **kwargs):
"""load_private_keys(self, username=None, **kwargs) -> [private_key_obj, ...]
Loads the private keys.
<username> defaults to the current user.
Different key storage classes take different arguments.
Returns a list of SSH_Public_Private_Key objects.
Returns an empty list if the key is not available.
"""
raise NotImplementedError
def load_public_keys(self, username=None, **kwargs):
"""load_public_keys(self, username=None, **kwargs) -> [public_key_obj, ...]
Loads the public keys.
<username> defaults to the current user.
Different key storage classes take different arguments.
Returns a list of SSH_Public_Private_Key objects.
Returns an empty list if the key is not available.
"""
raise NotImplementedError
def verify(self, host_id, server_key_types, public_host_key, username=None):
"""verify(self, host_id, server_key_types, public_host_key) -> boolean
This verifies that the given public host key is known.
Returns true if it is OK.
<username>: defaults to the current user.
<server_key_types>: A list of SSH_Public_Private_Key objects that we support.
<public_host_key>: A SSH_Public_Private_Key instance.
<host_id>: Remote_Host_ID instance.
"""
raise NotImplementedError
def update_known_hosts(self, host, public_host_key, username=None):
"""update_known_hosts(self, host, public_host_key, username=None) -> None
Updates the known hosts database for the given user.
<host>: The host string.
<public_host_key>: A SSH_Public_Private_Key instance.
<username>: Defaults to the current user.
"""
raise NotImplementedError
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.keys.openssh_authorized_keys
#
# This module handles the authorized_keys file.
#
__version__ = '$Revision: #1 $'
import os
import re
from rebuild import *
class DuplicateKeyError(Exception):
pass
# Keys are in two formats:
# SSH1:
# [options] bits exponent modulus comment
# SSH2:
# [options] keytype base64_key comment
#
# The options are optional. They never start with a number, nor do they
# contain spaces. It is a comma-separated list of values.
#
# The SSH1 key format is as follows. The bits is a number, typically
# something like 1024. The exponent is also a number, typically something
# like 35. The modulus is a very long string of numbers. The comment can be
# anything, but it is typically username@hostname.
#
# The SSH2 key format is as follows. The keytype is either ssh-dss or ssh-rsa.
# The key is a long string encoded in base-64. The comment can be anything,
# but it is typically username@hostname.
SPACE = '[ \t]'
OPTION_START = '[^ \t0-9]'
ATOM = '[^ \t]'
QUOTED_ATOM = '"[^"]*"'
WORD = OR(ATOM, QUOTED_ATOM)
OPTIONS = NAME('options', OPTION_START + SPLAT(WORD))
BITS = NAME('bits', '\d+')
EXPONENT = NAME('exponent', '\d+')
MODULUS = NAME('modulus', '\d+')
COMMENT = NAME('comment', '.*')
ssh1_key = CONCAT('^',
SPLAT(SPACE),
BITS, PLUS(SPACE),
EXPONENT, PLUS(SPACE),
MODULUS, SPLAT(SPACE),
COMMENT
)
ssh1_key_w_options = CONCAT('^',
SPLAT(SPACE),
OPTIONS, PLUS(SPACE),
BITS, PLUS(SPACE),
EXPONENT, PLUS(SPACE),
MODULUS, SPLAT(SPACE),
COMMENT
)
# The man page for OpenSSH specifies that "ssh-dss" and "ssh-rsa" are the only
# valid types, but the code actually checks for this list of types. Let's
# try to be as flexible as possible.
KEYTYPE = NAME('keytype', OR('ssh-dss', 'ssh-rsa', 'rsa1', 'rsa', 'dsa'))
# Not a very exact base64 regex, but should be good enough.
# OpenSSH seems to ignore spaces anywhere. Also, this doesn't check for
# a "partial" or truncated base64 string.
BASE64_KEY = NAME('base64_key', PLUS('[a-zA-Z0-9+/=]'))
ssh2_key = CONCAT('^',
SPLAT(SPACE),
KEYTYPE, PLUS(SPACE),
BASE64_KEY, SPLAT(SPACE),
COMMENT
)
ssh2_key_w_options = CONCAT('^',
SPLAT(SPACE),
OPTIONS, PLUS(SPACE),
KEYTYPE, PLUS(SPACE),
BASE64_KEY, SPLAT(SPACE),
COMMENT
)
ssh1_key_re = re.compile(ssh1_key)
ssh2_key_re = re.compile(ssh2_key)
ssh1_key_w_options_re = re.compile(ssh1_key_w_options)
ssh2_key_w_options_re = re.compile(ssh2_key_w_options)
class OpenSSH_Authorized_Keys:
"""OpenSSH_Authorized_Keys(filename)
This is a class that will represent an SSH authorized_keys file.
"""
def __init__(self, filename):
self.filename = filename
# This is a list of dictionary objects.
self.keys = []
self.read()
def read(self):
"""read() -> None
Reads the contents of the keyfile into memory.
If the file does not exist, then it does nothing.
"""
if os.path.exists(self.filename):
lines = open(self.filename).readlines()
else:
lines = []
for line in lines:
line = line.strip()
# ignore comment lines
if line and line[0] != '#':
try:
self.add_key(line)
except (DuplicateKeyError, ValueError):
# Ignore this entry.
# Maybe we should print an error or something?
pass
def add_key(self, key):
"""add_key(key) -> None
Adds the given key to the object.
<key> is a string.
Raises DuplicateKeyError if the key already exists.
Raises ValueError if the key does not appear to be a valid format.
"""
key = key.strip()
m = ssh1_key_re.match(key)
if not m:
m = ssh2_key_re.match(key)
if not m:
m = ssh1_key_w_options_re.match(key)
if not m:
m = ssh2_key_w_options_re.match(key)
if not m:
raise ValueError, key
values = m.groupdict()
if ((values.has_key('keytype') and not values['keytype']) or
(values.has_key('base64_key') and not values['base64_key']) or
(values.has_key('bits') and not values['bits']) or
(values.has_key('exponent') and not values['exponent']) or
(values.has_key('modulus') and not values['modulus'])
):
raise ValueError, key
self._value_strip(values)
if not values.has_key('options') or not values['options']:
# If it doesn't exist, or it exists as None, set it to the empty string.
values['options'] = ''
if not values['comment']:
values['comment'] = ''
self._duplicate_check(values)
self.keys.append(values)
def _value_strip(self, d):
"""_value_strip(d) -> None
Takes d, which is a dict, and calls strip() on all its values.
"""
for key, value in d.items():
if value:
d[key] = value.strip()
def _duplicate_check(self, key):
"""_duplicate_check(key) -> None
Checks if key (which is dict-format) is a duplicate.
Raises DuplicateKeyError if it is.
"""
if key.has_key('bits'):
# SSH1
for x in self.keys:
if (x.has_key('bits') and
x['bits'] == key['bits'] and
x['exponent'] == key['exponent'] and
x['modulus'] == key['modulus']):
raise DuplicateKeyError
else:
# SSH2
for x in self.keys:
if (x.has_key('keytype') and
x['keytype'] == key['keytype'] and
x['base64_key'] == key['base64_key']):
raise DuplicateKeyError
def write(self):
"""write() -> None
Writes the keyfile to disk, safely overwriting the keyfile that
already exists.
"""
# Avoid concurrent races here?
tmp_filename = self.filename + '.tmp'
fd = os.open(tmp_filename, os.O_WRONLY|os.O_CREAT|os.O_TRUNC, 0644)
write = lambda x,y=fd: os.write(y, x)
map(write, map(self.keydict_to_string, self.keys))
os.close(fd)
os.rename(tmp_filename, self.filename)
def keydict_to_string(self, key, short_output=0):
"""keydict_to_string(key, short_output=0) -> string
Converts an SSH dict-format key into a string.
<short_output> - Set to true if you want to exclude options and comment.
"""
if short_output:
options = ''
comment = ''
else:
options = key['options']
comment = key['comment']
if key.has_key('bits'):
# SSH1
bits = key['bits']
exponent = key['exponent']
modulus = key['modulus']
result = ' '.join([options, bits, exponent, modulus, comment])
else:
# SSH2
keytype = key['keytype']
base64_key = key['base64_key']
result = ' '.join([options, keytype, base64_key, comment])
return result.strip() + '\n'
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.keys.openssh_key_formats
#
# This module contains expressions for parsing and matching keys.
#
__version__ = '$Revision: #1 $'
import re
from rebuild import *
SPACE = '[ \t]'
NUMBER = '\d'
LIST_OF_HOSTS = NAME('list_of_hosts', PLUS('[^ \t]'))
COMMENT = OPTIONAL(
PLUS(SPACE),
NAME('comment', PLUS('.'))
)
ssh1_key = re.compile(
CONCAT('^',
LIST_OF_HOSTS,
PLUS(SPACE),
NAME('number_of_bits', PLUS(NUMBER)),
PLUS(SPACE),
NAME('exponent', PLUS(NUMBER)),
PLUS(SPACE),
NAME('modulus', PLUS(NUMBER)),
COMMENT
)
)
# The man page for OpenSSH specifies that "ssh-dss" and "ssh-rsa" are the only
# valid types, but the code actually checks for this list of types. Let's
# try to be as flexible as possible.
KEYTYPE = NAME('keytype', OR('ssh-dss', 'ssh-rsa', 'rsa1', 'rsa', 'dsa'))
# Not a very exact base64 regex, but should be good enough.
# OpenSSH seems to ignore spaces anywhere. Also, this doesn't check for
# a "partial" or truncated base64 string.
BASE64_KEY = NAME('base64_key', PLUS('[a-zA-Z0-9+/=]'))
ssh2_key = re.compile(
CONCAT('^',
KEYTYPE,
PLUS(SPACE),
BASE64_KEY,
COMMENT
)
)
ssh2_known_hosts_entry = re.compile(
CONCAT('^',
LIST_OF_HOSTS,
PLUS(SPACE),
KEYTYPE,
PLUS(SPACE),
BASE64_KEY,
COMMENT
)
)
This diff is collapsed.
This diff is collapsed.
# $Header: //prod/main/ap/ssh/ssh/keys/public_private_key.py#1 $
"""ssh.keys.public_private_key
This is the base public/private key object.
Specific key types subclass this for their implementation.
"""
__version__ = '$Revision: #1 $'
import hashlib
class SSH_Public_Private_Key:
"""SSH_Public_Private_Key
Base class for any type of public/private key.
"""
name = 'none'
# Features of this key type.
supports_signature = 0
supports_encryption = 0
# Keys are encoded according to the implementation.
private_key = None
public_key = None
def set_public_key(self, public_key):
"""set_public_key(self, public_key) -> None
Sets the public key. public_key is a string encoded according
to the algorithm.
"""
raise NotImplementedError
def set_private_key(self, private_key):
"""set_private_key(self, private_key) -> None
Sets the private key. private_key is a string encoded according
to the algorithm.
"""
raise NotImplementedError
def get_public_key_blob(self):
raise NotImplementedError
def get_private_key_blob(self):
raise NotImplementedError
def sign(self, message):
"""sign(self, message, ) -> signature
Signs a message with the given private_key.
<message> is a string of bytes.
The resulting signature is encoded as a payload of (string, bytes)
where string is the signature format identifier and byes is the
signature blob.
"""
raise NotImplementedError
def verify(self, message, signature):
"""verify(self, message, signature, public_key) -> boolean
Returns true or false if the signature is a match for the
signature of <message>.
<message> is a string of bytes.
<signature> is a payload encoded as (string, bytes).
"""
raise NotImplementedError
def public_key_fingerprint(self):
"""public_key_fingerprint(self) -> fingerprint string
Returns a fingerprint of the public key.
"""
m = hashlib.md5(self.get_public_key_blob())
# hexdigest returns lowercase already, but I just wanted to be careful.
fingerprint = m.hexdigest().lower()
pieces = [ fingerprint[x]+fingerprint[x+1] for x in xrange(0, len(fingerprint), 2) ]
return ':'.join(pieces)
# XXX: encrypt functions...
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# remote_host
#
# This module is the class that abstracts the ID of a host.
# Typically the ID is based on the IP or hostname of the remote host, but this
# allows you to use non-IP configurations.
#
class Remote_Host_ID:
pass
class IPv4_Remote_Host_ID(Remote_Host_ID):
"""IPv4_Remote_Host_ID
Represents the ID of the remote host.
<ip> is required.
<hostname> is optional.
"""
ip = ''
hostname = None
def __init__(self, ip, hostname):
self.ip = ip
self.hostname = hostname
def __repr__(self):
return '<IPv4_Remote_Host_ID instance ip=%r hostname=%r>' % (self.ip, self.hostname)
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.keys.rsa
#
# Encapsulates the RSA key.
__version__ = '$Revision: #1 $'
import hashlib
import public_private_key
import ssh.util.packet
import ssh.util.random
from Crypto.PublicKey import RSA
from Crypto.Util import number
# This is the DER encoding of the SHA1 identifier.
SHA1_Digest_Info = '\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'
class SSH_RSA(public_private_key.SSH_Public_Private_Key):
# Features of this key type.
supports_signature = 1
supports_encryption = 0
name = 'ssh-rsa'
private_key = (0L, 0L, 0L, 0L, 0L) # n, e, d, p, q
public_key = (0L, 0L) # e, n
def set_public_key(self, public_key):
rsa, e, n = ssh.util.packet.unpack_payload(RSA_PUBLIC_KEY_PAYLOAD, public_key)
if rsa != 'ssh-rsa':
raise ValueError, rsa
self.public_key = (e, n)
def set_private_key(self, private_key):
rsa, n, e, d, p, q = ssh.util.packet.unpack_payload(RSA_PRIVATE_KEY_PAYLOAD, private_key)
if rsa != 'ssh-rsa':
raise ValueError, rsa
self.public_key = (n, e, d, p, q)
def get_public_key_blob(self):
e, n = self.public_key
return ssh.util.packet.pack_payload(RSA_PUBLIC_KEY_PAYLOAD,
('ssh-rsa',
e, n))
def get_private_key_blob(self):
n, e, d, p, q = self.public_key
return ssh.util.packet.pack_payload(RSA_PRIVATE_KEY_PAYLOAD,
('ssh-rsa',
n, e, d, p, q))
def emsa_pkcs1_v1_5_encode(self, message, n_len):
"""emsa_pkcs1_v1_5_encode(self, message, n_len) -> encoded_message
Encodes the given string via the EMSA PKCS#1 version 1.5 method.
<message> - The string to encode.
<n_len> - The length (in octets) of the RSA modulus n.
"""
hash = hashlib.sha1(message).digest()
T = SHA1_Digest_Info + hash
if __debug__:
assert n_len >= len(T)+11
# PKCS spec says that it's -3...I do not understand why that doesn't work.
PS = '\xff'*(n_len - len(T) - 2)
if __debug__:
assert len(PS) >= 8
return '\x00\x01' + PS + '\x00' + T
def sign(self, message):
n, e, d, p, q = self.private_key
rsa_obj = RSA.construct( (n, e, d, p, q) )
modulus_n_length_in_octets = rsa_obj.size()/8
encoded_message = self.emsa_pkcs1_v1_5_encode(message, modulus_n_length_in_octets)
signature = rsa_obj.sign(encoded_message, '')[0] # Returns tuple of 1 element.
signature = number.long_to_bytes(signature)
return ssh.util.packet.pack_payload(RSA_SIG_PAYLOAD,
('ssh-rsa',
signature))
def verify(self, message, signature):
e, n = self.public_key
rsa, blob = ssh.util.packet.unpack_payload(RSA_SIG_PAYLOAD, signature)
if rsa != 'ssh-rsa':
raise ValueError, rsa
s = number.bytes_to_long(blob)
rsa_obj = RSA.construct( (n, e) )
modulus_n_length_in_octets = rsa_obj.size()/8
encoded_message = self.emsa_pkcs1_v1_5_encode(message, modulus_n_length_in_octets)
return rsa_obj.verify(encoded_message, (s,))
RSA_PUBLIC_KEY_PAYLOAD = (ssh.util.packet.STRING, # "ssh-rsa"
ssh.util.packet.MPINT, # e
ssh.util.packet.MPINT # n
)
RSA_PRIVATE_KEY_PAYLOAD = (ssh.util.packet.STRING, # "ssh-rsa"
ssh.util.packet.MPINT, # n
ssh.util.packet.MPINT, # e
ssh.util.packet.MPINT, # d
ssh.util.packet.MPINT, # p
ssh.util.packet.MPINT, # q
)
RSA_SIG_PAYLOAD = (ssh.util.packet.STRING, # "ssh-rsa"
ssh.util.packet.STRING # signature_key_blob
)
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.keys.static_key_storage
#
# This module is a key storage type where they keys are retained in memory.
#
__version__ = '$Revision: #1 $'
import os
import key_storage
import remote_host
import openssh_key_storage
class Static_Key_Storage(key_storage.SSH_Key_Storage):
"""Static_Key_Storage
A key storage mechanism where all keys are maintained in memory.
Note that this isn't terribly secure.
"""
key_types = ('dsa', 'rsa')
def __init__(self):
# The key is the username, the value is the key object.
self.public_key = {}
self.private_key = {}
# List of (hosts, (SSH_Public_Private_Key,...)) where hosts is a list of strings.
# Does not support any fancy meta-matching, sorry.
# Does not support user-specific known hosts.
self.known_hosts = []
def set_private_host_key(self, username, key_obj):
self.private_key[username] = key_obj
def set_public_host_key(self, username, key_obj):
self.public_key[username] = key_obj
def add_known_hosts(self, hosts, keystrings):
"""add_known_hosts(self, hosts, keystrings) -> None
Add a known host.
<hosts>: List of host strings.
<keystrings>: List of strings of the host's public key.
Must be in OpenSSH's standard format.
"""
key_storage = openssh_key_storage.OpenSSH_Key_Storage
key_objs = []
for keystring in keystrings:
key_obj = key_storage.parse_public_key(keystring)
key_objs.append(key_obj)
# XXX: Could merge host entries.
self.known_hosts.append((hosts, key_objs))
def load_keys(self, username=None):
if not self.public_key.has_key(username) or not self.private_key.has_key(username):
return None
if username is None:
username = os.getlogin()
key_obj = self.private_key[username]
public_key = self.public_key[username]
key_obj.public_key = public_key.public_key
return [key_obj]
load_keys.__doc__ = key_storage.SSH_Key_Storage.load_keys.__doc__
def load_private_keys(self, username=None):
if not self.private_key.has_key(username):
return []
if username is None:
username = os.getlogin()
return [self.private_key[username]]
load_private_keys.__doc__ = key_storage.SSH_Key_Storage.load_private_keys.__doc__
def load_public_keys(self, username=None):
if not self.public_key.has_key(username):
return []
if username is None:
username = os.getlogin()
return [self.public_key[username]]
load_public_keys.__doc__ = key_storage.SSH_Key_Storage.load_public_keys.__doc__
def verify(self, host_id, server_key_types, public_host_key, username=None):
if username is None:
username = os.getlogin()
for key in server_key_types:
if public_host_key.name == key.name:
# This is a supported key type.
if self._verify_contains(host_id, public_host_key, username):
return 1
return 0
verify.__doc__ = key_storage.SSH_Key_Storage.verify.__doc__
def _verify_contains(self, host_id, public_host_key, username):
__pychecker__ = 'unusednames=username'
# Currently only supported IPv4
if not isinstance(host_id, remote_host.IPv4_Remote_Host_ID):
return 0
for hosts, key_objs in self.known_hosts:
for key_obj in key_objs:
if key_obj.name == public_host_key.name:
for host in hosts:
if host == host_id.ip or host == host_id.hostname:
if key_obj.public_key == public_host_key.public_key:
return 1
return 0
def update_known_hosts(self, host, public_host_key, username=None):
__pychecker__ = 'unusednames=username'
self.known_hosts.append(([host], public_host_key))
update_known_hosts.__doc__ = key_storage.SSH_Key_Storage.update_known_hosts.__doc__
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.l4_transport
#
# Base transport class to implement networking.
#
__version__ = '$Revision: #1 $'
class Transport:
def connect(self):
raise NotImplementedError
def read(self, bytes):
raise NotImplementedError
def write(self, data):
raise NotImplementedError
def read_line(self):
raise NotImplementedError
def close(self):
raise NotImplementedError
def get_host_id(self):
"""get_host_id(self) -> Remote_Host_ID instance
Get's the Remote_Host_ID instance for the other side
of this connection.
"""
raise NotImplementedError
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.l4_transport.coro_socket_transport
#
# Socket transport used by SSH.
#
__version__ = '$Revision: #2 $'
import coro
import dnsqr
import dns_exceptions
import errno
import inet_utils
import ironutil
import socket
import ssh.l4_transport
import ssh.keys.remote_host
class coro_socket_transport(ssh.l4_transport.Transport):
# The socket
s = None
def __init__(self, ip, port=22, bind_ip=None, hostname=None):
assert inet_utils.is_ip(ip)
self.ip = ip
self.port = port
self.bind_ip = bind_ip
self.hostname = hostname
self.s = coro.make_socket(socket.AF_INET, socket.SOCK_STREAM)
def connect(self):
if self.bind_ip is not None:
self.s.bind((self.bind_ip, 0))
self.s.connect((self.ip, self.port))
def read(self, bytes):
# XXX: This could be made more efficient.
count = bytes
result = []
while count > 0:
try:
chunk = self.s.recv(count)
except OSError, why:
if why.errno == errno.EBADF:
raise EOFError
else:
raise
except coro.ClosedError:
raise EOFError
if len(chunk)==0:
raise EOFError
count -= len(chunk)
result.append(chunk)
return ''.join(result)
def write(self, bytes):
try:
return self.s.send(bytes)
except OSError, why:
if why.errno == errno.EBADF:
ironutil.raise_oserror(errno.EPIPE)
else:
raise
except coro.ClosedError:
ironutil.raise_oserror(errno.EPIPE)
def read_line(self):
# XXX: This should be made more efficient with buffering.
# However, the complexity and overhead of adding buffering just
# to support reading the line at the beginning of the protocol
# negotiation seems kinda silly.
result = []
while 1:
try:
it = self.s.recv(1)
except OSError, why:
if why.errno == errno.EBADF:
raise EOFError
else:
raise
except coro.ClosedError:
raise EOFError
if not it:
raise EOFError
if it == '\r':
# This is a part of CR LF line ending. Skip it.
pass
elif it == '\n':
break
else:
result.append(it)
return ''.join(result)
def close(self):
self.s.close()
def get_hostname(self):
if self.hostname is None:
try:
in_addr = inet_utils.to_in_addr(self.ip)
self.hostname = dnsqr.query (in_addr, 'PTR')[0][1]
except (dns_exceptions.DNS_Error, IndexError):
# XXX: Log debug message.
pass
return self.hostname
def get_host_id(self):
return ssh.keys.remote_host.IPv4_Remote_Host_ID(self.ip, self.get_hostname())
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.mac
#
# This implements the interface to support message authentication codes.
#
__version__ = '$Revision: #1 $'
class SSH_MAC_Method:
"""SSH_MAC_Method
Base class for any type of Message Authentication Code alogirthm.
"""
name = ''
block_size = 1
digest_size = 0
key_size = 0
key = None
def digest(self, sequence_number, data):
raise NotImplementedError
def set_key(self, key):
self.key = key
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh_hmac
#
# This implements the base HMAC hashing algorithm from RFC 2104.
#
__version__ = '$Revision: #1 $'
import ssh.mac
import ssh.util
import struct
class SSH_HMAC(ssh.mac.SSH_MAC_Method):
"""SSH_HMac
Base class of other HMAC algorithms.
See RFC 2104.
"""
def get_hash_object(self):
raise NotImplementedError
def set_key(self, key):
if __debug__:
# Insecure.
assert len(key) >= self.digest_size
if len(key) > self.block_size:
# Key is too big. Hash it and use the result as the key.
# This really isn't necessary because we're certain that the key
# is going to be the correct size. However, I put it in here
# for completeness with the HMAC spec.
import sys
sys.stderr.write('WARNING: Unexecpted HMAC key size!!!\n')
h = self.get_hash_object()
self.key = h.update(key).digest()
else:
self.key = key
ipad = '\x36' * self.block_size
opad = '\x5C' * self.block_size
padded_key = self.key + '\0' * (self.block_size-len(self.key))
self._enc_ipad = ssh.util.str_xor(padded_key, ipad)
self._enc_opad = ssh.util.str_xor(padded_key, opad)
def digest(self, sequence_number, data):
sequence_number = struct.pack('>L', sequence_number)
return self.hmac(sequence_number + data)
def hmac(self, data):
# H(K XOR opad, H(K XOR ipad, text))
hash = self.get_hash_object()
hash.update(self._enc_ipad)
hash.update(data)
b = hash.digest()
hash = self.get_hash_object()
hash.update(self._enc_opad)
hash.update(b)
return hash.digest()
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh_hmac_md5
#
# Implements the hmac-md5 SSH MAC algorithm.
__version__ = '$Revision: #1 $'
from hmac import SSH_HMAC
import hashlib
class HMAC_MD5(SSH_HMAC):
name = 'hmac-md5'
block_size = 64
digest_size = 16
key_size = 16
def get_hash_object(self):
return hashlib.md5()
import unittest
class ssh_hmac_md5_test_case(unittest.TestCase):
pass
class hmac_md5_test_case(ssh_hmac_md5_test_case):
def runTest(self):
# From RFC2104
a = HMAC_MD5()
a.set_key('\x0b'*16)
self.assertEqual(a.hmac('Hi There'), '\x92\x94\x72\x7a\x36\x38\xbb\x1c\x13\xf4\x8e\xf8\x15\x8b\xfc\x9d')
a = HMAC_MD5()
a.set_key('Jefe' + '\0'*12)
self.assertEqual(a.hmac('what do ya want for nothing?'), '\x75\x0c\x78\x3e\x6a\xb0\xb5\x03\xea\xa8\x6e\x31\x0a\x5d\xb7\x38')
a = HMAC_MD5()
a.set_key('\xAA'*16)
self.assertEqual(a.hmac('\xDD' * 50), '\x56\xbe\x34\x52\x1d\x14\x4c\x88\xdb\xb8\xc7\x33\xf0\xe8\xb3\xf6')
def suite():
suite = unittest.TestSuite()
suite.addTest(hmac_md5_test_case())
return suite
if __name__ == '__main__':
unittest.main(defaultTest='suite')
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh_hmac_sha1
#
# Implements the hmac-sha1 SSH MAC algorithm.
__version__ = '$Revision: #1 $'
from hmac import SSH_HMAC
import hashlib
class HMAC_SHA1(SSH_HMAC):
name = 'hmac-sha1'
block_size = 64
digest_size = 20
key_size = 20
def get_hash_object(self):
return hashlib.sha1()
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# ssh.mac.none
#
# This is the 'none' mac that returns an empty value.
#
__version__ = '$Revision: #1 $'
import ssh.mac
class MAC_None(ssh.mac.SSH_MAC_Method):
name = 'none'
def digest(self, sequence_number, data):
return ''
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""Secure Copy.
Overview
========
This implements the scp "protocol" for transferring files over SSH.
AFAIK, the SCP/RCP "protocol" is not documented anywhere. This implemented is
inferred from the BSD and OpenSSH source code.
Options
=======
Running the SCP process from the command line has the options defined in the
usage string in `ssh.scp.cli`.
The scp command connects to the remote host and runs the "scp" process
(assuming it is in your PATH somewhere).
If it is sending the file to the remote host, then it has the following
options:
scp [options] -t pathnames...
If it is getting a file from the remote host, then it has the following
options:
scp [options] -f target
A special option for running in remote mode is '-d'. This indicates that the
target should be a directory. This is only true if more than one source file
is specified.
The only other options supported by this implementation are "-p", "-r" and
"-v" which are user options defined in the usage string.
These options are not very consistent. For example, rcp in FreeBSD does not
support "-v", but it does support "-x" (which is for encryption and thus not
used in scp).
Messages
========
The SCP protocol involves one side (the client) sending commands to the other
side (the server). The commands are 1 letter, options for that command,
terminated by a newline character. The commands are:
- ``T``: Indicates the timestamp of the next file (only). The format is::
mtime_sec SPACE mtime_usec SPACE atime_sec SPACE atime_usec
The timestamps are POSIX timestamps in ASCII base-10. Most
implementations use 0 for the micro-second portions.
``mtime`` is the modified time. ``atime`` is the last access time.
This is only sent if the -p option is specified.
- ``C``: A file. The format is::
mode SPACE size SPACE filename
``mode`` is a 4-digit octal number in ASCII that is the mode of the file.
``size`` is a number in ASCII base-10 that indicates the size of the data
to follow.
``filename`` is the name of the file (with no path information).
The data immediately follows this entry.
- ``D``: A directory. The format is the same as a file (C), but the size
is zero. All following files will be in this specified directory.
- ``E``: The end of transmission. This has no options.
Care should be taken to make sure a filename does not contain a newline
character.
Responses
=========
Every command requires a response. A response is a message, with the following
codes:
- ``\0``: A successful response.
- ``\01``: An soft error occurred. The error string follows up to the
newline character. This does not stop the data stream.
- ``\02``: A hard error occurred. The error string follows up to the
newline character. This causes the receiving side (the server) to exit.
Any other character in a response is interpreted as a hard error.
Sending and receiving files has the following message/response timeline:
================= =================
Client Server
================= =================
Send C command
Wait for response
Send response
Got response
Send file
Receive file
Wait for response
Send response
Wait for response
Got response
Send response
Got response
================= =================
Transfer Modes
==============
There are various transfer modes that the scp program can run in. The
following is a description of those modes from the perspective of the person
initiating the scp command.
1. Local to local. This doesn't actually use SSH to transfer, and acts very
similar to cp.
2. Local to remote. The local side connects to the remote host with the -t
option and sends the file(s).
3. Remote to local. The local side connects to the remote host with the -f
option and requests file(s).
4. Remote to remote. The local side connects to the source remote host with
the -f option and connects to the target remote host with the -t option and
relays the data between the two hosts.
"""
__version__ = '$Revision: #1 $'
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""CLI interface.
This is the interface for running SCP and a command-line process.
"""
__version__ = '$Revision: #1 $'
import optparse
import sys
import ssh.scp.client
import ssh.scp.core
usage = """\
scp [options] file1 file2
scp [options] file1 ... directory
Each file or directory argument is either:
- A remote filename or directory in the format of
``username@hostname:pathname`` or ``hostname:pathname``.
- A local filename or directory. A local filename can not have any colons
before a slash.
If more than one source file is specified, the destination must be a directory
that exists.
The following options are available:
-p Preserve modification time, access time, and mode of the original file.
-r If any source filename is a directory, then transfer the directory and
all subfiles and directories to the remote host. The destination must
be a directory.
-v Produce verbose output. Up to 3 -v commands for more verbosity.
Note on file ownership and mode: If the destination is a file, and that file
already exists, then the mode and owner for the destination file is preserved.
If the destination is a directory or a filename that does not exist, then the
file will be created with the ownership of the remote user and the mode of the
original file modified by the umask of the remote user.
"""
class CLI:
def main(self, args=None):
"""The main entry point for the SCP cli program.
Calls sys.exit when finished.
:Parameters:
- `args`: The command line arguments as a list of strings. If
None, will use sys.argv.
"""
parser = optparse.OptionParser(usage=usage, add_help_option=False)
parser.add_option('-p', dest='preserve', action='store_true')
parser.add_option('-r', dest='recursive', action='store_true')
parser.add_option('-v', dest='verbosity', action='count')
parser.add_option('-t', dest='action_to', action='store_true')
parser.add_option('-f', dest='action_from', action='store_true')
parser.add_option('-d', dest='target_should_be_dir', action='store_true')
parser.add_option('--help', dest='help', action='store_true')
if args is None:
args = sys.argv[1:]
options, arguments = parser.parse_args(args)
if options.help:
print usage
sys.exit(0)
if options.action_from:
scp = self._get_scp()
scp.verbosity = options.verbosity
scp.debug(ssh.scp.core.DEBUG_EXTRA, 'options: %r', args)
scp.read_response()
scp.send(options.preserve,
options.recursive,
arguments
)
sys.exit(int(scp.had_errors))
elif options.action_to:
scp = self._get_scp()
scp.verbosity = options.verbosity
scp.debug(ssh.scp.core.DEBUG_EXTRA, 'options: %r', args)
if len(arguments) != 1:
scp.hard_error('Must specify 1 target.')
scp.receive(options.preserve,
options.recursive,
options.target_should_be_dir,
arguments[0]
)
sys.exit(int(scp.had_errors))
else:
print 'Function unavailable.'
sys.exit(1)
client = self._get_client()
client.main(options.preserve,
options.recursive,
options.verbosity,
arguments
)
def _get_scp(self):
return ssh.scp.core.SCP()
def _get_client(self):
return ssh.scp.client.Client()
if __name__ == '__main__':
cli = CLI()
cli.main()
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
# Copyright (c) 2002-2012 IronPort Systems and Cisco Systems
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment