From 39244216e4b8b1e0ef684473b9387dca7256bc37 Mon Sep 17 00:00:00 2001 From: Alex Orange Date: Mon, 25 Apr 2016 13:53:06 -0600 Subject: Add support for ECDSA key sizes 384 and 521 alongside the existing 256. Previously only 256-bit was handled and in certain cases (private key reading) 384- and 521-bit keys were treated as 256-bit keys causing silent errors. Tests have been added to specifically test the 384 and 521 keysizes. As RFC 5656 defines 256, 384, and 521 as the required keysizes this seems a good set to test. Also, this will cover the branches at ecdsakey.py:55. Test keys were renamed and test_client.py was modified as a result. This also fixes two bugs in ecdsakey.py. First, when calculating bytes needed to store a key, the assumption was made that the key size (in bits) was divisible by 8 (see line 137). This has been fixed by rounding up (wasn't an issue as only 256-bit keys were used before). Another bug was that the key padding in asbytes was being done backwards (was padding on current_length - needed_length bytes). --- tests/test_client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'tests/test_client.py') diff --git a/tests/test_client.py b/tests/test_client.py index f42d79d9..63ff9297 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -182,7 +182,7 @@ class SSHClientTest (unittest.TestCase): """ verify that SSHClient works with an ECDSA key. """ - self._test_connection(key_filename=test_path('test_ecdsa.key')) + self._test_connection(key_filename=test_path('test_ecdsa_256.key')) def test_3_multiple_key_files(self): """ @@ -199,8 +199,8 @@ class SSHClientTest (unittest.TestCase): for attempt, accept in ( (['rsa', 'dss'], ['dss']), # Original test #3 (['dss', 'rsa'], ['dss']), # Ordering matters sometimes, sadly - (['dss', 'rsa', 'ecdsa'], ['dss']), # Try ECDSA but fail - (['rsa', 'ecdsa'], ['ecdsa']), # ECDSA success + (['dss', 'rsa', 'ecdsa_256'], ['dss']), # Try ECDSA but fail + (['rsa', 'ecdsa_256'], ['ecdsa']), # ECDSA success ): try: self._test_connection( -- cgit v1.2.3 From 28c8be18c25e9d10f8e4759e489949f1e22d6346 Mon Sep 17 00:00:00 2001 From: Philip Lorenz Date: Sun, 21 Sep 2014 12:31:40 +0200 Subject: Support transmission of environment variables The SSH protocol allows the client to transmit environment variables to the server. This is particularly useful if the user wants to modify the environment of an executed command without having to reexecute the actual command from a shell. This patch extends the Client and Channel interface to allow the transmission of environment variables to the server side. In order to use this feature the SSH server must accept environment variables from the client (e.g. the AcceptEnv configuration directive of OpenSSH). FROM BITPROPHET: backport cherry-pick to 1.x line --- paramiko/channel.py | 41 +++++++++++++++++++++++++++++++++++++++++ paramiko/client.py | 9 +++++++-- tests/test_client.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) (limited to 'tests/test_client.py') diff --git a/paramiko/channel.py b/paramiko/channel.py index 3a05bdc4..7735e1f1 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -283,6 +283,47 @@ class Channel (ClosingContextManager): m.add_int(height_pixels) self.transport._send_user_message(m) + @open_only + def update_environment_variables(self, environment): + """ + Updates this channel's environment. This operation is additive - i.e. + the current environment is not reset before the given environment + variables are set. + + :param dict environment: a dictionary containing the name and respective + values to set + :raises SSHException: + if any of the environment variables was rejected by the server or + the channel was closed + """ + for name, value in environment.items(): + try: + self.set_environment_variable(name, value) + except SSHException as e: + raise SSHException("Failed to set environment variable \"%s\"." % name, e) + + @open_only + def set_environment_variable(self, name, value): + """ + Set the value of an environment variable. + + :param str name: name of the environment variable + :param str value: value of the environment variable + + :raises SSHException: + if the request was rejected or the channel was closed + """ + m = Message() + m.add_byte(cMSG_CHANNEL_REQUEST) + m.add_int(self.remote_chanid) + m.add_string('env') + m.add_boolean(True) + m.add_string(name) + m.add_string(value) + self._event_pending() + self.transport._send_user_message(m) + self._wait_for_event() + def exit_status_ready(self): """ Return true if the remote process has exited and returned an exit diff --git a/paramiko/client.py b/paramiko/client.py index ebf21b08..681760cf 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -398,7 +398,8 @@ class SSHClient (ClosingContextManager): self._agent.close() self._agent = None - def exec_command(self, command, bufsize=-1, timeout=None, get_pty=False): + def exec_command(self, command, bufsize=-1, timeout=None, get_pty=False, + environment=None): """ Execute a command on the SSH server. A new `.Channel` is opened and the requested command is executed. The command's input and output @@ -411,6 +412,7 @@ class SSHClient (ClosingContextManager): Python :param int timeout: set command's channel timeout. See `Channel.settimeout`.settimeout + :param dict environment: the command's environment :return: the stdin, stdout, and stderr of the executing command, as a 3-tuple @@ -421,6 +423,7 @@ class SSHClient (ClosingContextManager): if get_pty: chan.get_pty() chan.settimeout(timeout) + chan.update_environment_variables(environment or {}) chan.exec_command(command) stdin = chan.makefile('wb', bufsize) stdout = chan.makefile('r', bufsize) @@ -428,7 +431,7 @@ class SSHClient (ClosingContextManager): return stdin, stdout, stderr def invoke_shell(self, term='vt100', width=80, height=24, width_pixels=0, - height_pixels=0): + height_pixels=0, environment=None): """ Start an interactive shell session on the SSH server. A new `.Channel` is opened and connected to a pseudo-terminal using the requested @@ -440,12 +443,14 @@ class SSHClient (ClosingContextManager): :param int height: the height (in characters) of the terminal window :param int width_pixels: the width (in pixels) of the terminal window :param int height_pixels: the height (in pixels) of the terminal window + :param dict environment: the command's environment :return: a new `.Channel` connected to the remote shell :raises SSHException: if the server fails to invoke a shell """ chan = self._transport.open_session() chan.get_pty(term, width, height, width_pixels, height_pixels) + chan.update_environment_variables(environment or {}) chan.invoke_shell() return chan diff --git a/tests/test_client.py b/tests/test_client.py index d39febac..e7ebbc6a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -79,6 +79,16 @@ class NullServer (paramiko.ServerInterface): return False return True + def check_channel_env_request(self, channel, name, value): + if name == 'INVALID_ENV': + return False + + if not hasattr(channel, 'env'): + setattr(channel, 'env', {}) + + channel.env[name] = value + return True + class SSHClientTest (unittest.TestCase): @@ -373,3 +383,38 @@ class SSHClientTest (unittest.TestCase): password='pygmalion', ) self._test_connection(**kwargs) + + def test_update_environment(self): + """ + Verify that environment variables can be set by the client. + """ + threading.Thread(target=self._run).start() + + self.tc = paramiko.SSHClient() + self.tc.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + self.assertEqual(0, len(self.tc.get_host_keys())) + self.tc.connect(self.addr, self.port, username='slowdive', password='pygmalion') + + self.event.wait(1.0) + self.assertTrue(self.event.isSet()) + self.assertTrue(self.ts.is_active()) + + target_env = {b'A': b'B', b'C': b'd'} + + self.tc.exec_command('yes', environment=target_env) + schan = self.ts.accept(1.0) + self.assertEqual(target_env, getattr(schan, 'env', {})) + schan.close() + + # Cannot use assertRaises in context manager mode as it is not supported + # in Python 2.6. + try: + # Verify that a rejection by the server can be detected + self.tc.exec_command('yes', environment={b'INVALID_ENV': b''}) + except SSHException as e: + self.assertTrue('INVALID_ENV' in str(e), + 'Expected variable name in error message') + self.assertTrue(isinstance(e.args[1], SSHException), + 'Expected original SSHException in exception') + else: + self.assertFalse(False, 'SSHException was not thrown.') -- cgit v1.2.3 From d4a5806d23e95cc386d88a361956d0e0c1df9fb6 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 12 Dec 2016 15:33:01 -0800 Subject: Remove code re #398 from 2.0 branch, as it's feature work --- paramiko/channel.py | 52 ------------------------------------------------- paramiko/client.py | 23 ++-------------------- sites/www/changelog.rst | 10 ---------- tests/test_client.py | 45 ------------------------------------------ 4 files changed, 2 insertions(+), 128 deletions(-) (limited to 'tests/test_client.py') diff --git a/paramiko/channel.py b/paramiko/channel.py index 52b5d849..3a05bdc4 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -283,58 +283,6 @@ class Channel (ClosingContextManager): m.add_int(height_pixels) self.transport._send_user_message(m) - @open_only - def update_environment(self, environment): - """ - Updates this channel's remote shell environment. - - .. note:: - This operation is additive - i.e. the current environment is not - reset before the given environment variables are set. - - .. warning:: - Servers may silently reject some environment variables; see the - warning in `set_environment_variable` for details. - - :param dict environment: - a dictionary containing the name and respective values to set - :raises SSHException: - if any of the environment variables was rejected by the server or - the channel was closed - """ - for name, value in environment.items(): - try: - self.set_environment_variable(name, value) - except SSHException as e: - err = "Failed to set environment variable \"{0}\"." - raise SSHException(err.format(name), e) - - @open_only - def set_environment_variable(self, name, value): - """ - Set the value of an environment variable. - - .. warning:: - The server may reject this request depending on its ``AcceptEnv`` - setting; such rejections will fail silently (which is common client - practice for this particular request type). Make sure you - understand your server's configuration before using! - - :param str name: name of the environment variable - :param str value: value of the environment variable - - :raises SSHException: - if the request was rejected or the channel was closed - """ - m = Message() - m.add_byte(cMSG_CHANNEL_REQUEST) - m.add_int(self.remote_chanid) - m.add_string('env') - m.add_boolean(False) - m.add_string(name) - m.add_string(value) - self.transport._send_user_message(m) - def exit_status_ready(self): """ Return true if the remote process has exited and returned an exit diff --git a/paramiko/client.py b/paramiko/client.py index 40cd5cf2..ebf21b08 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -398,14 +398,7 @@ class SSHClient (ClosingContextManager): self._agent.close() self._agent = None - def exec_command( - self, - command, - bufsize=-1, - timeout=None, - get_pty=False, - environment=None, - ): + def exec_command(self, command, bufsize=-1, timeout=None, get_pty=False): """ Execute a command on the SSH server. A new `.Channel` is opened and the requested command is executed. The command's input and output @@ -418,14 +411,6 @@ class SSHClient (ClosingContextManager): Python :param int timeout: set command's channel timeout. See `Channel.settimeout`.settimeout - :param dict environment: - a dict of shell environment variables, to be merged into the - default environment that the remote command executes within. - - .. warning:: - Servers may silently reject some environment variables; see the - warning in `.Channel.set_environment_variable` for details. - :return: the stdin, stdout, and stderr of the executing command, as a 3-tuple @@ -436,8 +421,6 @@ class SSHClient (ClosingContextManager): if get_pty: chan.get_pty() chan.settimeout(timeout) - if environment: - chan.update_environment(environment) chan.exec_command(command) stdin = chan.makefile('wb', bufsize) stdout = chan.makefile('r', bufsize) @@ -445,7 +428,7 @@ class SSHClient (ClosingContextManager): return stdin, stdout, stderr def invoke_shell(self, term='vt100', width=80, height=24, width_pixels=0, - height_pixels=0, environment=None): + height_pixels=0): """ Start an interactive shell session on the SSH server. A new `.Channel` is opened and connected to a pseudo-terminal using the requested @@ -457,14 +440,12 @@ class SSHClient (ClosingContextManager): :param int height: the height (in characters) of the terminal window :param int width_pixels: the width (in pixels) of the terminal window :param int height_pixels: the height (in pixels) of the terminal window - :param dict environment: the command's environment :return: a new `.Channel` connected to the remote shell :raises SSHException: if the server fails to invoke a shell """ chan = self._transport.open_session() chan.get_pty(term, width, height, width_pixels, height_pixels) - chan.update_environment_variables(environment or {}) chan.invoke_shell() return chan diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 36460eff..72ae1548 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -45,16 +45,6 @@ Changelog signature. Caught by ``@Score_Under``. * :bug:`681` Fix a Python3-specific bug re: the handling of read buffers when using ``ProxyCommand``. Thanks to Paul Kapp for catch & patch. -* :feature:`398 (1.18+)` Add an ``environment`` dict argument to - `Client.exec_command ` (plus the - lower level `Channel.update_environment - ` and - `Channel.set_environment_variable - ` methods) which - implements the ``env`` SSH message type. This means the remote shell - environment can be set without the use of ``VARNAME=value`` shell tricks, - provided the server's ``AcceptEnv`` lists the variables you need to set. - Thanks to Philip Lorenz for the pull request. * :support:`819 backported (>=1.15,<2.0)` Document how lacking ``gmp`` headers at install time can cause a significant performance hit if you build PyCrypto from source. (Most system-distributed packages already have this enabled.) diff --git a/tests/test_client.py b/tests/test_client.py index 32d9ac60..63ff9297 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -82,16 +82,6 @@ class NullServer (paramiko.ServerInterface): return False return True - def check_channel_env_request(self, channel, name, value): - if name == 'INVALID_ENV': - return False - - if not hasattr(channel, 'env'): - setattr(channel, 'env', {}) - - channel.env[name] = value - return True - class SSHClientTest (unittest.TestCase): @@ -379,38 +369,3 @@ class SSHClientTest (unittest.TestCase): password='pygmalion', ) self._test_connection(**kwargs) - - def test_update_environment(self): - """ - Verify that environment variables can be set by the client. - """ - threading.Thread(target=self._run).start() - - self.tc = paramiko.SSHClient() - self.tc.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - self.assertEqual(0, len(self.tc.get_host_keys())) - self.tc.connect(self.addr, self.port, username='slowdive', password='pygmalion') - - self.event.wait(1.0) - self.assertTrue(self.event.isSet()) - self.assertTrue(self.ts.is_active()) - - target_env = {b'A': b'B', b'C': b'd'} - - self.tc.exec_command('yes', environment=target_env) - schan = self.ts.accept(1.0) - self.assertEqual(target_env, getattr(schan, 'env', {})) - schan.close() - - # Cannot use assertRaises in context manager mode as it is not supported - # in Python 2.6. - try: - # Verify that a rejection by the server can be detected - self.tc.exec_command('yes', environment={b'INVALID_ENV': b''}) - except SSHException as e: - self.assertTrue('INVALID_ENV' in str(e), - 'Expected variable name in error message') - self.assertTrue(isinstance(e.args[1], SSHException), - 'Expected original SSHException in exception') - else: - self.assertFalse(False, 'SSHException was not thrown.') -- cgit v1.2.3 From 208922af5a2be81d7bae2b9e2e4a3475b535593c Mon Sep 17 00:00:00 2001 From: james mike dupont Date: Thu, 19 Jan 2017 03:59:30 -0500 Subject: untie agian! --- paramiko/agent.py | 2 +- paramiko/kex_gss.py | 4 ++-- paramiko/server.py | 2 +- paramiko/sftp_client.py | 4 ++-- paramiko/sftp_handle.py | 2 +- paramiko/sftp_si.py | 2 +- paramiko/transport.py | 4 ++-- tests/stub_sftp.py | 2 +- tests/test_client.py | 2 +- tests/test_sftp.py | 2 +- 10 files changed, 13 insertions(+), 13 deletions(-) (limited to 'tests/test_client.py') diff --git a/paramiko/agent.py b/paramiko/agent.py index 6a8e7fb4..c13810bb 100644 --- a/paramiko/agent.py +++ b/paramiko/agent.py @@ -331,7 +331,7 @@ class Agent(AgentSSH): """ Client interface for using private keys from an SSH agent running on the local machine. If an SSH agent is running, this class can be used to - connect to it and retreive `.PKey` objects which can be used when + connect to it and retrieve `.PKey` objects which can be used when attempting to authenticate to remote SSH servers. Upon initialization, a session with the local machine's SSH agent is diff --git a/paramiko/kex_gss.py b/paramiko/kex_gss.py index 69969f8a..e21d55b9 100644 --- a/paramiko/kex_gss.py +++ b/paramiko/kex_gss.py @@ -104,7 +104,7 @@ class KexGSSGroup1(object): """ Parse the next packet. - :param char ptype: The type of the incomming packet + :param char ptype: The type of the incoming packet :param `.Message` m: The paket content """ if self.transport.server_mode and (ptype == MSG_KEXGSS_INIT): @@ -335,7 +335,7 @@ class KexGSSGex(object): """ Parse the next packet. - :param char ptype: The type of the incomming packet + :param char ptype: The type of the incoming packet :param `.Message` m: The paket content """ if ptype == MSG_KEXGSS_GROUPREQ: diff --git a/paramiko/server.py b/paramiko/server.py index f79a1748..bc4ac071 100644 --- a/paramiko/server.py +++ b/paramiko/server.py @@ -385,7 +385,7 @@ class ServerInterface (object): :param int pixelheight: height of screen in pixels, if known (may be ``0`` if unknown). :return: - ``True`` if the psuedo-terminal has been allocated; ``False`` + ``True`` if the pseudo-terminal has been allocated; ``False`` otherwise. """ return False diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index 0df94389..12a9506f 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -223,7 +223,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): ``read_aheads``, an integer controlling how many ``SSH_FXP_READDIR`` requests are made to the server. The default of 50 should suffice for most file listings as each request/response cycle - may contain multiple files (dependant on server implementation.) + may contain multiple files (dependent on server implementation.) .. versionadded:: 1.15 """ @@ -828,6 +828,6 @@ class SFTPClient(BaseSFTP, ClosingContextManager): class SFTP(SFTPClient): """ - An alias for `.SFTPClient` for backwards compatability. + An alias for `.SFTPClient` for backwards compatibility. """ pass diff --git a/paramiko/sftp_handle.py b/paramiko/sftp_handle.py index edceb5ad..05b5e904 100644 --- a/paramiko/sftp_handle.py +++ b/paramiko/sftp_handle.py @@ -179,7 +179,7 @@ class SFTPHandle (ClosingContextManager): def _get_next_files(self): """ - Used by the SFTP server code to retreive a cached directory + Used by the SFTP server code to retrieve a cached directory listing. """ fnlist = self.__files[:16] diff --git a/paramiko/sftp_si.py b/paramiko/sftp_si.py index 61db956c..7ab00ad7 100644 --- a/paramiko/sftp_si.py +++ b/paramiko/sftp_si.py @@ -208,7 +208,7 @@ class SFTPServerInterface (object): The ``attr`` object will contain only those fields provided by the client in its request, so you should use ``hasattr`` to check for - the presense of fields before using them. In some cases, the ``attr`` + the presence of fields before using them. In some cases, the ``attr`` object may be completely empty. :param str path: diff --git a/paramiko/transport.py b/paramiko/transport.py index 71d5109e..f1d590ec 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -507,7 +507,7 @@ class Transport (threading.Thread, ClosingContextManager): be triggered. On failure, `is_active` will return ``False``. (Since 1.4) If ``event`` is ``None``, this method will not return until - negotation is done. On success, the method returns normally. + negotiation is done. On success, the method returns normally. Otherwise an SSHException is raised. After a successful negotiation, the client will need to authenticate. @@ -2291,7 +2291,7 @@ class Transport (threading.Thread, ClosingContextManager): finally: self.lock.release() if kind == 'direct-tcpip': - # handle direct-tcpip requests comming from the client + # handle direct-tcpip requests coming from the client dest_addr = m.get_text() dest_port = m.get_int() origin_addr = m.get_text() diff --git a/tests/stub_sftp.py b/tests/stub_sftp.py index 24380ba1..5fcca386 100644 --- a/tests/stub_sftp.py +++ b/tests/stub_sftp.py @@ -55,7 +55,7 @@ class StubSFTPHandle (SFTPHandle): class StubSFTPServer (SFTPServerInterface): # assume current folder is a fine root - # (the tests always create and eventualy delete a subfolder, so there shouldn't be any mess) + # (the tests always create and eventually delete a subfolder, so there shouldn't be any mess) ROOT = os.getcwd() def _realpath(self, path): diff --git a/tests/test_client.py b/tests/test_client.py index 63ff9297..9c5761d6 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -357,7 +357,7 @@ class SSHClientTest (unittest.TestCase): # NOTE: re #387, re #394 # If pkey module used within Client._auth isn't correctly handling auth # errors (e.g. if it allows things like ValueError to bubble up as per - # midway thru #394) client.connect() will fail (at key load step) + # midway through #394) client.connect() will fail (at key load step) # instead of succeeding (at password step) kwargs = dict( # Password-protected key whose passphrase is not 'pygmalion' (it's diff --git a/tests/test_sftp.py b/tests/test_sftp.py index e4c2c3a3..d3064fff 100755 --- a/tests/test_sftp.py +++ b/tests/test_sftp.py @@ -413,7 +413,7 @@ class SFTPTest (unittest.TestCase): def test_A_readline_seek(self): """ create a text file and write a bunch of text into it. then count the lines - in the file, and seek around to retreive particular lines. this should + in the file, and seek around to retrieve particular lines. this should verify that read buffering and 'tell' work well together, and that read buffering is reset on 'seek'. """ -- cgit v1.2.3