This duplicates the lib/runtime.py functionality, allowing us to check
for runtime users and groups consistency.
---
 Makefile.am              |    1 +
 htools/Ganeti/Runtime.hs |  154 ++++++++++++++++++++++++++++++++++++++++++++++
 lib/daemon.py            |    1 +
 lib/runtime.py           |    2 +-
 4 files changed, 157 insertions(+), 1 deletions(-)
 create mode 100644 htools/Ganeti/Runtime.hs

diff --git a/Makefile.am b/Makefile.am
index d1715a3..0c56cb5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -399,6 +399,7 @@ HS_LIB_SRCS = \
        htools/Ganeti/Luxi.hs \
        htools/Ganeti/Objects.hs \
        htools/Ganeti/OpCodes.hs \
+       htools/Ganeti/Runtime.hs \
        htools/Ganeti/THH.hs
 
 HS_BUILT_SRCS = htools/Ganeti/HTools/Version.hs htools/Ganeti/Constants.hs
diff --git a/htools/Ganeti/Runtime.hs b/htools/Ganeti/Runtime.hs
new file mode 100644
index 0000000..f9bb2ef
--- /dev/null
+++ b/htools/Ganeti/Runtime.hs
@@ -0,0 +1,154 @@
+{-| Implementation of the runtime configuration details.
+
+-}
+
+{-
+
+Copyright (C) 2011, 2012 Google Inc.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA.
+
+-}
+
+module Ganeti.Runtime
+  ( GanetiDaemon(..)
+  , MiscGroup(..)
+  , GanetiGroup(..)
+  , RuntimeEnts
+  , daemonName
+  , daemonUser
+  , daemonGroup
+  , daemonLogFile
+  , daemonPidFile
+  , getEnts
+  , verifyDaemonUser
+  ) where
+
+import Control.Exception
+import Control.Monad
+import qualified Data.Map as M
+import System.Exit
+import System.FilePath
+import System.IO
+import System.IO.Error
+import System.Posix.Types
+import System.Posix.User
+import Text.Printf
+
+import qualified Ganeti.Constants as C
+import Ganeti.BasicTypes
+
+data GanetiDaemon = GanetiMasterd
+                  | GanetiNoded
+                  | GanetiRapi
+                  | GanetiConfd
+                    deriving (Show, Enum, Bounded, Eq, Ord)
+
+data MiscGroup = DaemonsGroup
+               | AdminGroup
+                 deriving (Show, Enum, Bounded, Eq, Ord)
+
+data GanetiGroup = DaemonGroup GanetiDaemon
+                 | ExtraGroup MiscGroup
+                   deriving (Show, Eq, Ord)
+
+type RuntimeEnts = (M.Map GanetiDaemon UserID, M.Map GanetiGroup GroupID)
+
+-- | Returns the daemon name for a given daemon.
+daemonName :: GanetiDaemon -> String
+daemonName GanetiMasterd = C.masterd
+daemonName GanetiNoded   = C.noded
+daemonName GanetiRapi    = C.rapi
+daemonName GanetiConfd   = C.confd
+
+-- | Returns the configured user name for a daemon.
+daemonUser :: GanetiDaemon -> String
+daemonUser GanetiMasterd = C.masterdUser
+daemonUser GanetiNoded   = C.nodedUser
+daemonUser GanetiRapi    = C.rapiUser
+daemonUser GanetiConfd   = C.confdUser
+
+-- | Returns the configured group for a daemon.
+daemonGroup :: GanetiGroup -> String
+daemonGroup (DaemonGroup GanetiMasterd) = C.masterdGroup
+daemonGroup (DaemonGroup GanetiNoded)   = C.nodedGroup
+daemonGroup (DaemonGroup GanetiRapi)    = C.rapiGroup
+daemonGroup (DaemonGroup GanetiConfd)   = C.confdGroup
+daemonGroup (ExtraGroup  DaemonsGroup)  = C.daemonsGroup
+daemonGroup (ExtraGroup  AdminGroup)    = C.adminGroup
+
+-- | Returns the log file for a daemon.
+daemonLogFile :: GanetiDaemon -> FilePath
+daemonLogFile GanetiConfd = C.daemonsLogfilesGanetiConfd
+daemonLogFile _           = error "Unimplemented"
+
+-- | Returns the pid file name for a daemon.
+daemonPidFile :: GanetiDaemon -> FilePath
+daemonPidFile daemon = C.runGanetiDir </> daemonName daemon <.> "pid"
+
+-- | All groups list. A bit hacking, as we can't enforce it's complete
+-- at compile time.
+allGroups :: [GanetiGroup]
+allGroups = map DaemonGroup [minBound..maxBound] ++
+            map ExtraGroup  [minBound..maxBound]
+
+exceptionToResult :: IO a -> IO (Result a)
+exceptionToResult value = do
+  result <- tryJust (\e -> if isDoesNotExistError e
+                             then Nothing
+                             else Just (show e)) value
+  case result of
+    Left e  -> return $ Bad (show e)
+    Right v -> return $ Ok v
+
+-- | Computes the group/user maps.
+getEnts :: IO (Result RuntimeEnts)
+getEnts = do
+  users <- mapM (\daemon -> do
+                   entry <- exceptionToResult .
+                            getUserEntryForName .
+                            daemonUser $ daemon
+                   return (entry >>= \e -> return (daemon, userID e))
+                ) [minBound..maxBound]
+  groups <- mapM (\group -> do
+                    entry <- exceptionToResult .
+                             getGroupEntryForName .
+                             daemonGroup $ group
+                    return (entry >>= \e -> return (group, groupID e))
+                 ) allGroups
+  return $ do -- 'Result' monad
+    users'  <- sequence users
+    groups' <- sequence groups
+    let usermap = M.fromList users'
+        groupmap = M.fromList groups'
+    return (usermap, groupmap)
+
+
+-- | Checks whether a daemon runs as the right user.
+verifyDaemonUser :: GanetiDaemon -> RuntimeEnts -> IO ()
+verifyDaemonUser daemon ents = do
+  myuid <- getEffectiveUserID
+  case M.lookup daemon (fst ents) of
+    -- shouldn't happen, due to the above map construction
+    Nothing -> hPutStrLn stderr $ "Internal error: user entry for daemon " ++
+                                  daemonName daemon ++ " not found"
+
+    Just uid -> when (uid /= myuid) $ do
+                  hPrintf stderr "%s started using wrong user ID (%d), \
+                                 \expected %d\n" (daemonName daemon)
+                                 (fromIntegral myuid::Int)
+                                 (fromIntegral uid::Int) :: IO ()
+                  exitWith $ ExitFailure C.exitFailure
diff --git a/lib/daemon.py b/lib/daemon.py
index 1e41b80..d5c12c7 100644
--- a/lib/daemon.py
+++ b/lib/daemon.py
@@ -621,6 +621,7 @@ def _VerifyDaemonUser(daemon_name):
     constants.NODED: getents.noded_uid,
     constants.CONFD: getents.confd_uid,
     }
+  assert daemon_name in daemon_uids, "Invalid daemon %s" % daemon_name
 
   return (daemon_uids[daemon_name] == running_uid, running_uid,
           daemon_uids[daemon_name])
diff --git a/lib/runtime.py b/lib/runtime.py
index 5180486..b2db408 100644
--- a/lib/runtime.py
+++ b/lib/runtime.py
@@ -171,7 +171,7 @@ def GetEnts(resolver=GetentResolver):
   """Singleton wrapper around resolver instance.
 
   As this method is accessed by multiple threads at the same time
-  we need to take thread-safty carefully
+  we need to take thread-safety carefully.
 
   """
   # We need to use the global keyword here
-- 
1.7.7.3

Reply via email to