summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobey Pointer <robey@lag.net>2004-12-12 09:25:15 +0000
committerRobey Pointer <robey@lag.net>2004-12-12 09:25:15 +0000
commita3971274e8ae587a71b887fec15d8b4508b19b4e (patch)
treecc7e5a4f68fa75600b4132bd67c6983cd24eb9a8
parent83a932a1b39a0e81ffe1e5c885151d2db7ddcf4e (diff)
[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.
-rw-r--r--paramiko/channel.py153
-rw-r--r--paramiko/server.py34
2 files changed, 149 insertions, 38 deletions
diff --git a/paramiko/channel.py b/paramiko/channel.py
index f6cffd64..87a2aea2 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:
- 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:
+ size = self._wait_for_send_window(size)
+ if size == 0:
+ # eof or similar
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 23bd036a..d09c1a69 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