[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.
This commit is contained in:
Robey Pointer 2004-12-12 09:25:15 +00:00
parent 83a932a1b3
commit a3971274e8
2 changed files with 149 additions and 38 deletions

View File

@ -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 :

View File

@ -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