From cb38ee8589b5fcc8fc30a90de3d6dec78e567278 Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Sat, 2 Oct 2004 17:35:48 +0000 Subject: [PATCH] Cope better with user cancelling su operation. New interface to replace create_su_proxy (Thomas Leonard). git-svn-id: https://rox.svn.sourceforge.net/svnroot/rox/trunk/ROX-Lib2@3677 66de3db3-b00d-0410-b41b-f4738ad19bea --- Help/Changes | 5 ++ python/rox/master_proxy.py | 14 ++-- python/rox/proxy.py | 1 + python/rox/su.py | 205 +++++++++++++++++++++++---------------------- tests/python/testsu.py | 9 +- 5 files changed, 124 insertions(+), 110 deletions(-) rewrite python/rox/su.py (73%) diff --git a/Help/Changes b/Help/Changes index 19e6917..278fc0e 100644 --- a/Help/Changes +++ b/Help/Changes @@ -3,6 +3,11 @@ by Thomas Leonard http://rox.sourceforge.net +02-Oct-2004 +~~~~~~~~~~~ +Cope better with user cancelling su operation. New interface to replace +create_su_proxy (Thomas Leonard). + 27-Sep-2004 ~~~~~~~~~~~ The choices module is now deprecated. You can use choices.migrate() to move diff --git a/python/rox/master_proxy.py b/python/rox/master_proxy.py index 23bcd2a..983d800 100644 --- a/python/rox/master_proxy.py +++ b/python/rox/master_proxy.py @@ -38,7 +38,7 @@ class RequestBlocker(tasks.Blocker): yield blocker print blocker.result - If the remote method raised an exception, accessing 'result' will raise + If the remote method raised an exception, accessing 'isresult' will raise it rather than returning it. """ @@ -66,7 +66,10 @@ class RequestBlocker(tasks.Blocker): else: self.result = data self.trigger() - + +class LostConnection(Exception): + pass + class MasterProxy(Proxy): """Invoking operations on MasterProxy.root will invoke the same operation on the SlaveProxy's slave_object.""" @@ -74,7 +77,7 @@ class MasterProxy(Proxy): def __init__(self, to_slave, from_slave): Proxy.__init__(self, to_slave, from_slave) self.root = MasterObject(self) - self._queue = {} # Serial -> Queue + self._queue = {} # Serial -> RequestBlocker def _dispatch(self, value): serial, data = value @@ -89,6 +92,7 @@ class MasterProxy(Proxy): def _remove_blocker(self, serial): del self._queue[serial] - def finish(self): - Proxy.finish(self) + def lost_connection(self): + for x in self._queue.values(): + x.add(LostConnection('Lost connection to su proxy')) assert not self._queue diff --git a/python/rox/proxy.py b/python/rox/proxy.py index af2bb57..423137e 100644 --- a/python/rox/proxy.py +++ b/python/rox/proxy.py @@ -65,6 +65,7 @@ class Proxy: if not new: self.finish() self.lost_connection() + return False self.in_buffer += new while ':' in self.in_buffer: l, rest = self.in_buffer.split(':', 1) diff --git a/python/rox/su.py b/python/rox/su.py dissimilarity index 73% index 54bcdb7..588dddf 100644 --- a/python/rox/su.py +++ b/python/rox/su.py @@ -1,102 +1,103 @@ -"""The su (switch user) module allows you to execute a command as some -other user (normally 'root'). It supports a variety of methods to perform -the switch (using su, xsu, sudo, etc) so you don't have to worry about -which ones are available on the current platform.""" - -import os, sys, pwd -import rox -from rox import g, _, master_proxy -import traceback -from select import select -import fcntl -try: - import pty -except ImportError: - pty = None - -_my_dir = os.path.abspath(os.path.dirname(__file__)) -_child_script = os.path.join(_my_dir, 'suchild.sh') - -def create_su_proxy(message, uid = 0, confirm = True): - """Creates a new master_proxy.MasterObject and starts the child - process. If necessary, the user is prompted for a password. If no - password is required, the user is simply asked to confirm, - unless 'confirm' is False. - Raises UserAbort if the user clicks Cancel.""" - method = default_method(message, uid, confirm) - return method.get_master().root - -class Method: - need_interaction = True - - def __init__(self, uid): - self.uid = uid - - def get_master(self): - raise NotImplemented() # Abstract - -class Pipe: - """Contains Python file objects for two pipe ends. - Wrapping the FDs in this way ensures that they will be freed on error.""" - readable = None - writeable = None - - def __init__(self): - r, w = os.pipe() - try: - self.readable = os.fdopen(r, 'r') - except: - os.close(r) - raise - try: - self.writeable = os.fdopen(w, 'w') - except: - os.close(w) - raise - -class XtermMethod(Method): - _master = None - - def __init__(self, message, uid, confirm): - Method.__init__(self, uid) - self.message = message - - def get_master(self): - assert self._master is None - - to_child = Pipe() - from_child = Pipe() - - if os.fork() == 0: - try: - try: - to_child.writeable.close() - from_child.readable.close() - self.exec_child(from_child.writeable, - to_child.readable) - except: - traceback.print_exc() - finally: - os._exit(1) - from_child.writeable.close() - to_child.readable.close() - - assert self._master is None - self._master = master_proxy.MasterProxy(to_child.writeable, - from_child.readable) - return self._master - - def exec_child(self, to_parent, from_parent): - fcntl.fcntl(to_parent, fcntl.F_SETFD, 0) - fcntl.fcntl(from_parent, fcntl.F_SETFD, 0) - os.execlp('xterm', 'xterm', - '-geometry', '40x10', - '-title', 'Enter password', - '-e', - _child_script, - self.message, - sys.executable, - str(to_parent.fileno()), - str(from_parent.fileno())) - -default_method = XtermMethod +"""The su (switch user) module allows you to execute commands as root. +Typical usage: + +def fn(): + proxy_maker = SuProxyMaker('I need root access to change /etc/fstab') + yield proxy_maker.blocker + root = proxy_maker.get_root() + + call = root.open('/etc/fstab') + yield call + fd = call.result + + ... + + root.finish() + +tasks.run(fn()) +""" + +import os, sys +import rox +from rox import g, _, master_proxy, tasks +import traceback +import fcntl + +_my_dir = os.path.abspath(os.path.dirname(__file__)) +_child_script = os.path.join(_my_dir, 'suchild.sh') + +class SuProxyMaker(tasks.Blocker): + blocker = None + + def __init__(self, message): + """Displays a box prompting the user for a password. + Creates a new master_proxy.MasterObject and starts the child + process. The user is prompted for the root + password.""" + to_child = _Pipe() + from_child = _Pipe() + + if os.fork() == 0: + try: + try: + to_child.writeable.close() + from_child.readable.close() + _exec_child(message, + from_child.writeable, + to_child.readable) + except: + traceback.print_exc() + finally: + os._exit(1) + from_child.writeable.close() + to_child.readable.close() + + self._root = master_proxy.MasterProxy(to_child.writeable, + from_child.readable).root + + self.blocker = self._root.getuid() + + def get_root(self): + """Raises UserAbort if the user cancels.""" + try: + uid = self.blocker.result + except master_proxy.LostConnection: + raise rox.UserAbort("Failed to become root (cancelled " + "at user's request?)") + assert uid == 0 + self.blocker = None + return self._root + +def _exec_child(message, to_parent, from_parent): + fcntl.fcntl(to_parent, fcntl.F_SETFD, 0) + fcntl.fcntl(from_parent, fcntl.F_SETFD, 0) + os.execlp('xterm', 'xterm', + '-geometry', '40x10', + '-title', 'Enter password', + '-e', + _child_script, + message, + sys.executable, + str(to_parent.fileno()), + str(from_parent.fileno())) + +class _Pipe: + """Contains Python file objects for two pipe ends. + Wrapping the FDs in this way ensures that they will + be freed on error.""" + readable = None + writeable = None + + def __init__(self): + r, w = os.pipe() + try: + self.readable = os.fdopen(r, 'r') + except: + os.close(r) + raise + try: + self.writeable = os.fdopen(w, 'w') + except: + os.close(w) + raise + diff --git a/tests/python/testsu.py b/tests/python/testsu.py index e457707..3cbecdb 100755 --- a/tests/python/testsu.py +++ b/tests/python/testsu.py @@ -13,9 +13,12 @@ assert os.getuid() != 0, "Can't run tests as root" class TestSU(unittest.TestCase): def testSu(self): - root = su.create_su_proxy('Need to become root to test this module.', - confirm = False) def run(): + maker = su.SuProxyMaker( + 'Need to become root to test this module.') + yield maker.blocker + root = maker.get_root() + response = root.spawnvpe(os.P_NOWAIT, 'false', ['false']) yield response pid = response.result @@ -41,11 +44,11 @@ class TestSU(unittest.TestCase): yield response assert response.result == os.getuid() + root.finish() g.mainquit() tasks.Task(run()) g.mainloop() - root.finish() sys.argv.append('-v') unittest.main() -- 2.11.4.GIT