Merge lp:~mwhudson/launchpad/ssh-errors-on-stderr-bug-335156 into lp:launchpad

Proposed by Michael Hudson-Doyle
Status: Merged
Approved by: Jonathan Lange
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~mwhudson/launchpad/ssh-errors-on-stderr-bug-335156
Merge into: lp:launchpad
Diff against target: 112 lines (+30/-9)
2 files modified
lib/lp/codehosting/sshserver/session.py (+8/-5)
lib/lp/codehosting/sshserver/tests/test_session.py (+22/-4)
To merge this branch: bzr merge lp:~mwhudson/launchpad/ssh-errors-on-stderr-bug-335156
Reviewer Review Type Date Requested Status
Jonathan Lange (community) Approve
Review via email: mp+21353@code.launchpad.net

Commit message

Send the error messages from the ssh server about starting shells etc to the ssh clients stderr, not stdout.

Description of the change

Hi there,

This branch fixes the linked bug by sending the messages our smart server sends over stderr. I totally cargo culted the way to do this, but it works.

To test, run_codehosting and then run "BZR_REMOTE_PATH=foo bzr info lp://dev/gnome-terminal". In trunk, you'll get an ugly message as in the bug report and an oops on the server side. In this branch it should all be neat.

Cheers,
mwh

To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote :

I haven't tested, but it looks good to me.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/codehosting/sshserver/session.py'
--- lib/lp/codehosting/sshserver/session.py 2009-06-25 04:06:00 +0000
+++ lib/lp/codehosting/sshserver/session.py 2010-03-15 07:00:56 +0000
@@ -16,7 +16,7 @@
16from zope.interface import implements16from zope.interface import implements
1717
18from twisted.conch.interfaces import ISession18from twisted.conch.interfaces import ISession
19from twisted.conch.ssh import channel, session19from twisted.conch.ssh import channel, connection, session
20from twisted.internet.process import ProcessExitedAlready20from twisted.internet.process import ProcessExitedAlready
21from twisted.python import log21from twisted.python import log
2222
@@ -128,6 +128,11 @@
128 if self._transport is not None:128 if self._transport is not None:
129 self._transport.closeStdin()129 self._transport.closeStdin()
130130
131 def errorWithMessage(self, protocol, msg):
132 protocol.session.writeExtended(
133 connection.EXTENDED_DATA_STDERR, msg)
134 protocol.loseConnection()
135
131 def execCommand(self, protocol, command):136 def execCommand(self, protocol, command):
132 """Executes `command` using `protocol` as the ProcessProtocol.137 """Executes `command` using `protocol` as the ProcessProtocol.
133138
@@ -140,8 +145,7 @@
140 try:145 try:
141 executable, arguments = self.getCommandToRun(command)146 executable, arguments = self.getCommandToRun(command)
142 except ForbiddenCommand, e:147 except ForbiddenCommand, e:
143 protocol.write(str(e) + '\r\n')148 self.errorWithMessage(protocol, str(e) + '\r\n')
144 protocol.loseConnection()
145 return149 return
146 log.msg('Running: %r, %r, %r'150 log.msg('Running: %r, %r, %r'
147 % (executable, arguments, self.environment))151 % (executable, arguments, self.environment))
@@ -174,8 +178,7 @@
174178
175 def openShell(self, protocol):179 def openShell(self, protocol):
176 """See ISession."""180 """See ISession."""
177 protocol.write("No shells on this server.\r\n")181 self.errorWithMessage(protocol, "No shells on this server.\r\n")
178 protocol.loseConnection()
179182
180 def windowChanged(self, newWindowSize):183 def windowChanged(self, newWindowSize):
181 """See ISession."""184 """See ISession."""
182185
=== modified file 'lib/lp/codehosting/sshserver/tests/test_session.py'
--- lib/lp/codehosting/sshserver/tests/test_session.py 2010-02-19 01:05:19 +0000
+++ lib/lp/codehosting/sshserver/tests/test_session.py 2010-03-15 07:00:56 +0000
@@ -5,11 +5,10 @@
55
6__metaclass__ = type6__metaclass__ = type
77
8import os
9import sys
10import unittest8import unittest
119
12from twisted.conch.interfaces import ISession10from twisted.conch.interfaces import ISession
11from twisted.conch.ssh import connection
13from twisted.internet.process import ProcessExitedAlready12from twisted.internet.process import ProcessExitedAlready
14from twisted.internet.protocol import ProcessProtocol13from twisted.internet.protocol import ProcessProtocol
1514
@@ -36,6 +35,16 @@
36 return MockProcessTransport(executable)35 return MockProcessTransport(executable)
3736
3837
38class MockSSHSession:
39 """Just enough of SSHSession to allow checking of reporting to stderr."""
40
41 def __init__(self, log):
42 self.log = log
43
44 def writeExtended(self, channel, data):
45 self.log.append(('writeExtended', channel, data))
46
47
39class MockProcessTransport:48class MockProcessTransport:
40 """Mock transport used to fake speaking with child processes that are49 """Mock transport used to fake speaking with child processes that are
41 mocked out in tests.50 mocked out in tests.
@@ -44,6 +53,7 @@
44 def __init__(self, executable):53 def __init__(self, executable):
45 self._executable = executable54 self._executable = executable
46 self.log = []55 self.log = []
56 self.session = MockSSHSession(self.log)
4757
48 def closeStdin(self):58 def closeStdin(self):
49 self.log.append(('closeStdin',))59 self.log.append(('closeStdin',))
@@ -90,7 +100,11 @@
90 # openShell closes the connection.100 # openShell closes the connection.
91 protocol = MockProcessTransport('bash')101 protocol = MockProcessTransport('bash')
92 self.session.openShell(protocol)102 self.session.openShell(protocol)
93 self.assertEqual(protocol.log[-1], ('loseConnection',))103 self.assertEqual(
104 [('writeExtended', connection.EXTENDED_DATA_STDERR,
105 'No shells on this server.\r\n'),
106 ('loseConnection',)],
107 protocol.log)
94108
95 def test_windowChangedNotImplemented(self):109 def test_windowChangedNotImplemented(self):
96 # windowChanged raises a NotImplementedError. It doesn't matter what110 # windowChanged raises a NotImplementedError. It doesn't matter what
@@ -253,7 +267,11 @@
253 protocol = MockProcessTransport('cat')267 protocol = MockProcessTransport('cat')
254 self.assertEqual(268 self.assertEqual(
255 None, self.session.execCommand(protocol, 'cat'))269 None, self.session.execCommand(protocol, 'cat'))
256 self.assertEqual(protocol.log[-1], ('loseConnection',))270 self.assertEqual(
271 [('writeExtended', connection.EXTENDED_DATA_STDERR,
272 "Not allowed to execute 'cat'.\r\n"),
273 ('loseConnection',)],
274 protocol.log)
257275
258 def test_getCommandToRunReturnsTemplateCommand(self):276 def test_getCommandToRunReturnsTemplateCommand(self):
259 # When passed the allowed command, getCommandToRun always returns the277 # When passed the allowed command, getCommandToRun always returns the