Browse Source

Implemented general Daemon startup and privilege dropping, refined the daemon class.

Helmut Pozimski 9 years ago
parent
commit
29ea203ef5
4 changed files with 126 additions and 39 deletions
  1. 0 5
      TODO
  2. 29 5
      jwmud
  3. 38 29
      jwmudlib/daemon.py
  4. 59 0
      jwmudlib/jwmu_exceptions.py

+ 0 - 5
TODO

@@ -1,15 +1,10 @@
 TODO for jwmud
 ==============
 
-* Copy daemon class from stdd and adapt it
-* Implement logging based on logging
-* Implement general startup and daemon running
-* Implement configuration class
 * Implement alarm functionality based on configuration
 * Implement some kind of API (optional: User authentication and transfer encryption)
 * Implement setup.py
 * Create debian packaging infomation
-* Implement argument parsing based on argparser
 * Create a man page
 
 TODO for jwmuc

+ 29 - 5
jwmud

@@ -3,7 +3,7 @@
 #jwmud - a program to create dynamic alarms based on configurable wake up
 # times
 #
-#       written by Helmut Pozimski 2012-2014
+#       written by Helmut Pozimski in 2014
 #
 #       This program is free software; you can redistribute it and/or
 #       modify it under the terms of the GNU General Public License
@@ -36,9 +36,9 @@ parser.add_argument("-d", "--daemon", action="store_true", dest="daemon",
                     help="run jwmud as a daemon")
 parser.add_argument("-u", "--user", dest="user", help="define an unprivileged "
                                                       "user to run the daemon")
-parser.add_argument("-g", "--group", dest="group", help = "define an "
-                                                          "unprivileged group "
-                                                          "to run the daemon")
+parser.add_argument("-g", "--group", dest="group", help="define an "
+                                                        "unprivileged group "
+                                                        "to run the daemon")
 parser.add_argument("-c", "--config", dest="config", help="define an alternate"
                                                           " path to the "
                                                           "configuration file")
@@ -80,4 +80,28 @@ if arguments.daemon is True:
 else:
     console_handler = logging.StreamHandler()
     console_handler.setFormatter(formatter)
-    logger.addHandler(console_handler)
+    logger.addHandler(console_handler)
+
+if arguments.daemon is True:
+    if os.access("/run", os.F_OK & os.W_OK):
+        daemon_obj = daemon.Daemon("/run/jwmud", "jwmud.pid")
+    else:
+        daemon_obj = daemon.Daemon("/var/run/jwmud", "jwmud.pid")
+    daemon_obj.Daemonize()
+    try:
+        daemon_obj.Start()
+    except jwmu_exceptions.DaemonAlreadyRunning as e:
+        logger.error(e)
+    except jwmu_exceptions.WritingPIDFileFailed as e:
+        logger.error(e)
+    else:
+        logger.info("Daemon started.")
+    if arguments.user is not None and arguments.group is not None:
+        try:
+            daemon_obj.DropPrivileges(arguments.user, arguments.group)
+        except jwmu_exceptions.PasswdOrGroupAccessFailed as e:
+            logger.error(e)
+            sys.exit(1)
+        else:
+            logger.debug("Dropped privileges, now running as user %s and "
+                         "group %s." % (arguments.user, arguments.group))

+ 38 - 29
jwmudlib/daemon.py

@@ -17,6 +17,8 @@
 import os
 import sys
 
+from jwmudlib import jwmu_exceptions
+
 
 class Daemon(object):
     """ Tries to implement a well behaving unix daemon in a generic way,
@@ -30,7 +32,6 @@ class Daemon(object):
         self.__pfile_name = pfile_name
         self.__daemon = False
         self.__pid = 0
-        self.__pidfile = ""
 
     def Daemonize(self):
         """ Turns the calling process into a daemon running on it's own """
@@ -71,44 +72,52 @@ class Daemon(object):
 
         self.__daemon = True
 
-    def DropPriv(self, uid, gid):
+    def DropPrivileges(self, user, group):
         """ If the daemon is running as root user, drop privileges and continue
-        running as the defined unprivileged user """
-        if os.getuid() == 0:
-            os.setgid(gid)
-            os.setuid(uid)
-
-    def SetName(self, name, cmdline):
-        """ Sets the name of the process shown visible in ps and top,
-        this allows to make your daemon look more like a standalone
-        program instead of a python script.
+        running as the defined unprivileged user.
 
         """
+        pid_file_path = os.path.join(self.__pfile_path, self.__pfile_name)
         try:
-            # First check if prctl is available, otherwise this
-            # function does nothing
-            # noinspection PyUnresolvedReferences
-            import prctl
-        except ImportError:
-            return False
+            passwd_file = open("/etc/passwd", "r")
+            group_file = open("/etc/group", "r")
+        except PermissionError:
+            raise jwmu_exceptions.PasswdOrGroupAccessFailed()
         else:
-            prctl.set_name(name)
-            prctl.set_proctitle(cmdline)
-            return True
+            uid = ""
+            gid = ""
+            for line in passwd_file:
+                if user in line:
+                    uid = line.split(":")[2]
+                    break
+            for line in group_file:
+                if group in line.split(":")[0]:
+                    gid = line.split(":")[2]
+                    break
+            passwd_file.close()
+            group_file.close()
+            if os.getuid() == 0:
+                os.chown(pid_file_path, int(uid), int(gid))
+                os.setgid(int(gid))
+                os.setuid(int(uid))
 
     def Start(self):
         """ Performs the operations needed to "start" the daemon """
+        pid_file_path = os.path.join(self.__pfile_path, self.__pfile_name)
+        if os.access(pid_file_path, os.F_OK):
+            old_pid_file = open(pid_file_path, "r")
+            old_pid = old_pid_file.read().strip()
+            old_pid_file.close()
+            if os.access(os.path.join("/proc/", old_pid), os.F_OK):
+                raise jwmu_exceptions.DaemonAlreadyRunning()
         if self.__daemon is True:
-            if os.access(self.__pfile_path, os.F_OK & os.W_OK):
-                self.__pidfile = open(os.path.join(self.__pfile_path,
-                                                   self.__pfile_name), "w")
-                self.__pidfile.write(str(os.getpid()) + "\n")
-                self.__pidfile.close()
-                return True
+            try:
+                pid_file = open(pid_file_path, "w")
+            except IOError:
+                raise jwmu_exceptions.WritingPIDFileFailed()
             else:
-                return False
-        else:
-            return False
+                pid_file.write(str(os.getpid()) + "\n")
+                pid_file.close()
 
     def Stop(self):
         """ Performs the operations needed to stop the daemon """

+ 59 - 0
jwmudlib/jwmu_exceptions.py

@@ -16,6 +16,10 @@
 
 
 class DataBaseAccessFailed(Exception):
+    """This exception will be raised when opening or creating the database
+    failed.
+
+    """
     def __init__(self):
         self.__message = "Accessing the database failed!"
 
@@ -24,6 +28,10 @@ class DataBaseAccessFailed(Exception):
 
 
 class DataBaseWriteFailed(Exception):
+    """This exception will be raised when a write access to the database
+    failed.
+
+    """
     def __init__(self):
         self.__message = "Writing to the database failed, requested action " \
                          "aborted !"
@@ -33,6 +41,10 @@ class DataBaseWriteFailed(Exception):
 
 
 class WrongParameters(Exception):
+    """This exception will be raised when the number of parameters or their
+    value is wrong.
+
+    """
     def __init__(self):
         self.__message = "The wrong number or values of parameters where " \
                          "given to the function, the requested action failed."
@@ -42,6 +54,10 @@ class WrongParameters(Exception):
 
 
 class CategoryNotFound(Exception):
+    """This exception will be raised when the user tries to access a category
+    that does not exist in the database.
+
+    """
     def __init__(self):
         self.__message = "The category could not be found in the database."
 
@@ -50,6 +66,10 @@ class CategoryNotFound(Exception):
 
 
 class DayAlreadyInDatabase(Exception):
+    """This exception will be raised when the user tries to add a day to the
+    database which does already have an entry there.
+
+    """
     def __init__(self):
         self.__message = "This day already exists in the database."
 
@@ -57,6 +77,10 @@ class DayAlreadyInDatabase(Exception):
         return self.__message
 
 class ConfigurationFileMissing(Exception):
+    """This exception will be raised when the configuration file is
+    missing.
+
+    """
     def __init__(self):
         self.__message = "The configuration file does not exist."
 
@@ -64,9 +88,44 @@ class ConfigurationFileMissing(Exception):
         return self.__message
 
 class ConfigurationFileAccessDenied(Exception):
+    """This exception will be raised when the access to the configuration
+    file is denied by it's permissions.
+
+    """
     def __init__(self):
         self.__message = "The configuration file could not be opened " \
                          "for reading."
 
     def __str__(self):
         return self.__message
+
+class PasswdOrGroupAccessFailed(Exception):
+    """This exception will be raised when accessing either /etc/passwd or
+    /etc/group failed.
+
+    """
+    def __init__(self):
+        self.__message = "Opening /etc/passwd or /etc/group failed."
+
+    def __str__(self):
+        return self.__message
+
+class DaemonAlreadyRunning(Exception):
+    """This exception will be raised when another process is already
+    running.
+
+    """
+    def __init__(self):
+        self.__message = "Another process is already running, exiting."
+
+    def __str__(self):
+        return self.__message
+
+class WritingPIDFileFailed(Exception):
+    """This exception will be raised when creating the PID file failed."""
+    def __init__(self):
+        self.__message = "The PID file could not be created, please check if " \
+                         "/run or /var/run exist and are writable."
+
+    def __str__(self):
+        return  self.__message