From a3971274e8ae587a71b887fec15d8b4508b19b4e Mon Sep 17 00:00:00 2001 From: Robey Pointer Date: Sun, 12 Dec 2004 09:25:15 +0000 Subject: [PATCH] [project @ Arch-1:robey@lag.net--2003-public%secsh--dev--1.0--patch-126] server support for stderr & exec_command for the server side of my stderr blunder, add send_stderr & sendall_stderr, and make the sending side of makefile_stderr work correctly. also, call check_channel_exec_request on a server object for exec requests on a channel. --- paramiko/channel.py | 153 ++++++++++++++++++++++++++++++++++---------- paramiko/server.py | 34 ++++++++-- 2 files changed, 149 insertions(+), 38 deletions(-) diff --git a/paramiko/channel.py b/paramiko/channel.py index f6cffd6..87a2aea 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -566,42 +566,54 @@ class Channel (object): @raise socket.timeout: if no data could be sent before the timeout set by L{settimeout}. """ - size = 0 + size = len(s) + self.lock.acquire() try: - self.lock.acquire() - if self.closed or self.eof_sent: + size = self._wait_for_send_window(size) + if size == 0: + # eof or similar return 0 - if self.out_window_size == 0: - # should we block? - if self.timeout == 0.0: - raise socket.timeout() - # loop here in case we get woken up but a different thread has filled the buffer - timeout = self.timeout - while self.out_window_size == 0: - if self.closed or self.eof_sent: - return 0 - then = time.time() - self.out_buffer_cv.wait(timeout) - if timeout != None: - timeout -= time.time() - then - if timeout <= 0.0: - raise socket.timeout() - # we have some window to squeeze into - if self.closed: - return 0 - size = len(s) - if self.out_window_size < size: - size = self.out_window_size - if self.out_max_packet_size - 64 < size: - size = self.out_max_packet_size - 64 m = Message() m.add_byte(chr(MSG_CHANNEL_DATA)) m.add_int(self.remote_chanid) m.add_string(s[:size]) self.transport._send_user_message(m) - self.out_window_size -= size - if self.ultra_debug: - self._log(DEBUG, 'window down to %d' % self.out_window_size) + finally: + self.lock.release() + return size + + def send_stderr(self, s): + """ + Send data to the channel on the "stderr" stream. This is normally + only used by servers to send output from shell commands -- clients + won't use this. Returns the number of bytes sent, or 0 if the channel + stream is closed. Applications are responsible for checking that all + data has been sent: if only some of the data was transmitted, the + application needs to attempt delivery of the remaining data. + + @param s: data to send. + @type s: str + @return: number of bytes actually sent. + @rtype: int + + @raise socket.timeout: if no data could be sent before the timeout set + by L{settimeout}. + + @since: 1.1 + """ + size = len(s) + self.lock.acquire() + try: + size = self._wait_for_send_window(size) + if size == 0: + # eof or similar + return 0 + m = Message() + m.add_byte(chr(MSG_CHANNEL_EXTENDED_DATA)) + m.add_int(self.remote_chanid) + m.add_int(1) + m.add_string(s[:size]) + self.transport._send_user_message(m) finally: self.lock.release() return size @@ -616,9 +628,9 @@ class Channel (object): @type s: str @raise socket.timeout: if sending stalled for longer than the timeout - set by L{settimeout}. + set by L{settimeout}. @raise socket.error: if an error occured before the entire string was - sent. + sent. @note: If the channel is closed while only part of the data hase been sent, there is no way to determine how much data (if any) was sent. @@ -632,6 +644,30 @@ class Channel (object): s = s[sent:] return None + def sendall_stderr(self, s): + """ + Send data to the channel's "stderr" stream, without allowing partial + results. Unlike L{send_stderr}, this method continues to send data + from the given string until all data has been sent or an error occurs. + Nothing is returned. + + @param s: data to send to the client as "stderr" output. + @type s: str + + @raise socket.timeout: if sending stalled for longer than the timeout + set by L{settimeout}. + @raise socket.error: if an error occured before the entire string was + sent. + + @since: 1.1 + """ + while s: + if self.closed: + raise socket.error('Socket is closed') + sent = self.send_stderr(s) + s = s[sent:] + return None + def makefile(self, *params): """ Return a file-like object associated with this channel, without the @@ -651,9 +687,9 @@ class Channel (object): without a pty will ever have data on the stderr stream. The optional C{mode} and C{bufsize} arguments are interpreted the - same way as by the built-in C{file()} function in python, except that - of course it makes no sense to open this file in any mode other than - for reading. + same way as by the built-in C{file()} function in python. For a + client, it only makes sense to open this file for reading. For a + server, it only makes sense to open this file for writing. @return: object which can be used for python file I/O. @rtype: L{ChannelFile} @@ -746,6 +782,44 @@ class Channel (object): def _request_failed(self, m): self.close() + def _wait_for_send_window(self, size): + """ + (You are already holding the lock.) + Wait for the send window to open up, and allocate up to C{size} bytes + for transmission. If no space opens up before the timeout, a timeout + exception is raised. Returns the number of bytes available to send + (may be less than requested). + """ + # you are already holding the lock + if self.closed or self.eof_sent: + return 0 + if self.out_window_size == 0: + # should we block? + if self.timeout == 0.0: + raise socket.timeout() + # loop here in case we get woken up but a different thread has filled the buffer + timeout = self.timeout + while self.out_window_size == 0: + if self.closed or self.eof_sent: + return 0 + then = time.time() + self.out_buffer_cv.wait(timeout) + if timeout != None: + timeout -= time.time() - then + if timeout <= 0.0: + raise socket.timeout() + # we have some window to squeeze into + if self.closed: + return 0 + if self.out_window_size < size: + size = self.out_window_size + if self.out_max_packet_size - 64 < size: + size = self.out_max_packet_size - 64 + self.out_window_size -= size + if self.ultra_debug: + self._log(DEBUG, 'window down to %d' % self.out_window_size) + return size + def _feed(self, m): if type(m) is str: # passed from _feed_extended @@ -820,6 +894,12 @@ class Channel (object): ok = False else: ok = server.check_channel_shell_request(self) + elif key == 'exec': + cmd = m.get_string() + if server is None: + ok = False + else: + ok = server.check_channel_exec_request(self, cmd) elif key == 'subsystem': name = m.get_string() if server is None: @@ -1043,6 +1123,11 @@ class ChannelStderrFile (ChannelFile): def _read(self, size): return self.channel.recv_stderr(size) + + def _write(self, data): + self.channel.sendall_stderr(data) + return len(data) + # vim: set shiftwidth=4 expandtab : diff --git a/paramiko/server.py b/paramiko/server.py index 23bd036..d09c1a6 100644 --- a/paramiko/server.py +++ b/paramiko/server.py @@ -162,6 +162,10 @@ class ServerInterface (object): case, L{get_allowed_auths} will be called to report to the client what options it has for continuing the authentication.) + Note that you don't have to actually verify any key signtature here. + If you're willing to accept the key, paramiko will do the work of + verifying the client's signature. + The default implementation always returns L{AUTH_FAILED}. @param username: the username of the authenticating client. @@ -204,7 +208,7 @@ class ServerInterface (object): @type pixelheight: int @return: C{True} if the psuedo-terminal has been allocated; C{False} otherwise. - @rtype: boolean + @rtype: bool """ return False @@ -221,10 +225,31 @@ class ServerInterface (object): @type channel: L{Channel} @return: C{True} if this channel is now hooked up to a shell; C{False} if a shell can't or won't be provided. - @rtype: boolean + @rtype: bool """ return False + def check_channel_exec_request(self, channel, command): + """ + Determine if a shell command will be executed for the client. If this + method returns C{True}, the channel should be connected to the stdin, + stdout, and stderr of the shell command. + + The default implementation always returns C{False}. + + @param channel: the L{Channel} the request arrived on. + @type channel: L{Channel} + @param command: the command to execute. + @type command: str + @return: C{True} if this channel is now hooked up to the stdin, + stdout, and stderr of the executing command; C{False} if the + command will not be executed. + @rtype: bool + + @since: 1.1 + """ + return False + def check_channel_subsystem_request(self, channel, name): """ Determine if a requested subsystem will be provided to the client on @@ -247,7 +272,7 @@ class ServerInterface (object): @type name: str @return: C{True} if this channel is now hooked up to the requested subsystem; C{False} if that subsystem can't or won't be provided. - @rtype: boolean + @rtype: bool """ handler_class, larg, kwarg = channel.get_transport()._get_subsystem_handler(name) if handler_class is None: @@ -275,7 +300,8 @@ class ServerInterface (object): @param pixelheight: height of screen in pixels, if known (may be C{0} if unknown). @type pixelheight: int - @return: C{True} if the terminal was resized; C{False} if not. + @return: C{True} if the terminal was resized; C{False} if not. + @rtype: bool """ return False