[tor-commits] [stem/master] Methods to query user tor is running as

atagar at torproject.org atagar at torproject.org
Thu May 30 03:48:34 UTC 2013


commit d7d14a82e7d28ff863c71b4b027c9922e17a6661
Author: Damian Johnson <atagar at torproject.org>
Date:   Wed May 29 20:44:23 2013 -0700

    Methods to query user tor is running as
    
    Couple additions to make it easier to determine the user tor is running as:
    
    * stem.util.system.get_user(pid) => determines the user a process is running as
    * Controller.get_user() => provides the user that tor is running as
---
 docs/change_log.rst             |    2 ++
 stem/control.py                 |   36 +++++++++++++++++++++++++++--
 stem/util/system.py             |   36 +++++++++++++++++++++++++++++
 test/integ/util/system.py       |   48 +++++++++++++++++++++++++++++++++++++++
 test/unit/control/controller.py |   29 +++++++++++++++++++++++
 5 files changed, 149 insertions(+), 2 deletions(-)

diff --git a/docs/change_log.rst b/docs/change_log.rst
index 6e969fa..a2c92ea 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -44,6 +44,7 @@ The following are only available within stem's `git repository
   * :func:`~stem.control.Controller.attach_stream` could encounter an undocumented 555 response (:trac:`8701`, :spec:`7286576`)
   * :class:`~stem.descriptor.server_descriptor.RelayDescriptor` digest validation was broken when dealing with non-unicode content with python 3 (:trac:`8755`)
   * The :class:`~stem.control.Controller` use of cached content wasn't thread safe (:trac:`8607`)
+  * Added :func:`~stem.control.Controller.get_user` method to the:class:`~stem.control.Controller`
   * Added :func:`~stem.control.Controller.get_pid` method to the:class:`~stem.control.Controller`
 
  * **Descriptors**
@@ -55,6 +56,7 @@ The following are only available within stem's `git repository
   * :func:`~stem.util.system.set_process_name` inserted spaces between characters (:trac:`8631`)
   * :func:`~stem.util.system.get_pid_by_name` can now pull for all processes with a given name
   * :func:`~stem.util.system.call` ignored the subprocess' exit status
+  * Added :func:`stem.util.system.get_user`
   * Added :func:`stem.util.system.get_start_time`
   * Added :func:`stem.util.system.get_bsd_jail_path`
 
diff --git a/stem/control.py b/stem/control.py
index d92acc9..53d49b9 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -24,6 +24,7 @@ providing its own for interacting at a higher level.
     |- get_exit_policy - provides our exit policy
     |- get_socks_listeners - provides where tor is listening for SOCKS connections
     |- get_protocolinfo - information about the controller interface
+    |- get_user - provides the user tor is running as
     |- get_pid - provides the pid of our tor process
     |
     |- get_microdescriptor - querying the microdescriptor for a relay
@@ -991,6 +992,39 @@ class Controller(BaseController):
       else:
         return default
 
+  def get_user(self, default = UNDEFINED):
+    """
+    Provides the user tor is running as. This often only works if tor is
+    running locally. Also, most of its checks are platform dependent, and hence
+    are not entirely reliable.
+
+    :param object default: response if the query fails
+
+    :returns: str with the username tor is running as
+    """
+
+    user = self._get_cache("user")
+
+    if not user:
+      user = self.get_info("process/user", None)
+
+    if not user and self.get_socket().is_localhost():
+      pid = self.get_pid(None)
+
+      if pid:
+        user = stem.util.system.get_user(pid)
+
+    if user:
+      self._set_cache({"user": user})
+      return user
+    elif default == UNDEFINED:
+      if self.get_socket().is_localhost():
+        raise ValueError("Unable to resolve tor's user")
+      else:
+        raise ValueError("Tor isn't running locally")
+    else:
+      return default
+
   def get_pid(self, default = UNDEFINED):
     """
     Provides the process id of tor. This often only works if tor is running
@@ -1008,8 +1042,6 @@ class Controller(BaseController):
     pid = self._get_cache("pid")
 
     if not pid:
-      # this is the only pid resolution method that works remotely
-
       getinfo_pid = self.get_info("process/pid", None)
 
       if getinfo_pid and getinfo_pid.isdigit():
diff --git a/stem/util/system.py b/stem/util/system.py
index 3e72d83..5887fb7 100644
--- a/stem/util/system.py
+++ b/stem/util/system.py
@@ -20,6 +20,7 @@ best-effort, providing **None** if the lookup fails.
   get_pid_by_port - gets the pid for a process listening to a given port
   get_pid_by_open_file - gets the pid for the process with an open file
   get_cwd - provides the current working directory for a given process
+  get_user - provides the user a process is running under
   get_start_time - provides the unix timestamp when the process started
   get_bsd_jail_id - provides the BSD jail id a given process is running within
   get_bsd_jail_path - provides the path of the given BSD jail
@@ -34,6 +35,7 @@ import ctypes
 import ctypes.util
 import os
 import platform
+import pwd
 import subprocess
 import time
 
@@ -598,6 +600,37 @@ def get_cwd(pid):
   return None  # all queries failed
 
 
+def get_user(pid):
+  """
+  Provides the user a process is running under.
+
+  :param int pid: process id of the process to be queried
+
+  :returns: **str** with the username a process is running under, **None** if
+    it can't be determined
+  """
+
+  if not isinstance(pid, int) or pid < 0:
+    return None
+
+  if stem.util.proc.is_available():
+    try:
+      uid = stem.util.proc.get_uid(pid)
+
+      if uid and uid.isdigit():
+        return pwd.getpwuid(int(uid)).pw_name
+    except:
+      pass
+
+  if is_available("ps"):
+    results = call("ps -o user %s" % pid, [])
+
+    if len(results) >= 2:
+      return results[1].strip()
+
+  return None
+
+
 def get_start_time(pid):
   """
   Provides the unix timestamp when the given process started.
@@ -608,6 +641,9 @@ def get_start_time(pid):
     if it can't be determined
   """
 
+  if not isinstance(pid, int) or pid < 0:
+    return None
+
   if stem.util.proc.is_available():
     try:
       return float(stem.util.proc.get_stats(pid, stem.util.proc.Stat.START_TIME)[0])
diff --git a/test/integ/util/system.py b/test/integ/util/system.py
index 0bf77ac..4bfda1e 100644
--- a/test/integ/util/system.py
+++ b/test/integ/util/system.py
@@ -383,6 +383,54 @@ class TestSystem(unittest.TestCase):
     runner_pid, tor_cwd = runner.get_pid(), runner.get_tor_cwd()
     self.assertEquals(tor_cwd, stem.util.system.get_cwd(runner_pid))
 
+  def test_get_user_none(self):
+    """
+    Tests the get_user function when the process doesn't exist.
+    """
+
+    self.assertEqual(None, stem.util.system.get_user(None))
+    self.assertEqual(None, stem.util.system.get_user(-5))
+    self.assertEqual(None, stem.util.system.get_start_time(98765))
+
+  def test_get_user_proc(self):
+    """
+    Tests the get_user function with a proc response.
+    """
+
+    if not stem.util.proc.is_available():
+      test.runner.skip(self, "(proc unavailable)")
+      return
+
+    mocking.mock(stem.util.system.call, filter_system_call(['ps ']))
+
+    # we started our tor process so it should be running with the same user
+
+    pid = test.runner.get_runner().get_pid()
+    self.assertTrue(getpass.getuser(), stem.util.system.get_user(pid))
+
+  def test_get_user_ps(self):
+    """
+    Tests the get_user function with a ps response.
+    """
+
+    if not stem.util.system.is_available("ps"):
+      test.runner.skip(self, "(ps unavailable)")
+      return
+
+    mocking.mock(stem.util.proc.is_available, mocking.return_false())
+
+    pid = test.runner.get_runner().get_pid()
+    self.assertTrue(getpass.getuser(), stem.util.system.get_user(pid))
+
+  def test_get_start_time_none(self):
+    """
+    Tests the get_start_time function when the process doesn't exist.
+    """
+
+    self.assertEqual(None, stem.util.system.get_start_time(None))
+    self.assertEqual(None, stem.util.system.get_start_time(-5))
+    self.assertEqual(None, stem.util.system.get_start_time(98765))
+
   def test_get_start_time_proc(self):
     """
     Tests the get_start_time function with a proc response.
diff --git a/test/unit/control/controller.py b/test/unit/control/controller.py
index 449bc6d..a0deaf1 100644
--- a/test/unit/control/controller.py
+++ b/test/unit/control/controller.py
@@ -248,6 +248,35 @@ class TestControl(unittest.TestCase):
 
     self.assertRaises(ProtocolError, self.controller.get_protocolinfo)
 
+  def test_get_user_remote(self):
+    """
+    Exercise the get_user() method for a non-local socket.
+    """
+
+    mocking.mock_method(ControlSocket, "is_localhost", mocking.return_false())
+
+    self.assertRaises(ValueError, self.controller.get_user)
+    self.assertEqual(123, self.controller.get_user(123))
+
+  def test_get_user_by_getinfo(self):
+    """
+    Exercise the get_user() resolution via its getinfo option.
+    """
+
+    mocking.mock_method(ControlSocket, "is_localhost", mocking.return_true())
+    mocking.mock_method(Controller, "get_info", mocking.return_value('atagar'))
+    self.assertEqual('atagar', self.controller.get_user())
+
+  def test_get_user_by_system(self):
+    """
+    Exercise the get_user() resolution via the system module.
+    """
+
+    mocking.mock_method(ControlSocket, "is_localhost", mocking.return_true())
+    mocking.mock(stem.util.system.get_pid_by_name, mocking.return_value(432))
+    mocking.mock(stem.util.system.get_user, mocking.return_value('atagar'))
+    self.assertEqual('atagar', self.controller.get_user())
+
   def test_get_pid_remote(self):
     """
     Exercise the get_pid() method for a non-local socket.



More information about the tor-commits mailing list