LCOV - code coverage report
Current view: top level - src/plugins/path - path.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 55 115 47.8 %
Date: 2019-09-12 12:28:41 Functions: 7 10 70.0 %

          Line data    Source code
       1             : /**
       2             :  * @file
       3             :  *
       4             :  * @brief
       5             :  *
       6             :  * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
       7             :  */
       8             : 
       9             : #include "path.h"
      10             : 
      11             : #ifndef HAVE_KDBCONFIG
      12             : 
      13             : #include "kdbconfig.h"
      14             : 
      15             : #endif
      16             : 
      17             : static int createModeBits (const char * modes);
      18             : 
      19             : static int handleNoUserCase (Key * parentKey, const char * validPath, const char * modes);
      20             : 
      21             : static int switchUser (Key * key, Key * parentKey, const struct passwd * p);
      22             : 
      23             : static int switchGroup (Key * key, Key * parentKey, const char * name, const struct group * gr);
      24             : 
      25             : static int getAllGroups (Key * parentKey, uid_t currentUID, const struct passwd * p, int ngroups, gid_t ** groups);
      26             : 
      27             : /**
      28             :  * This method tries to find a matching group from a group struct containing more than one group
      29             :  * @param val The group name which is searched
      30             :  * @param groups The struct containing all groups
      31             :  * @param size The size of the groups struct because it is a linked list
      32             :  * @return true if the group is in the struct
      33             :  */
      34             : static bool isUserInGroup (unsigned int val, gid_t * groups, unsigned int size)
      35             : {
      36             :         unsigned int i;
      37             :         for (i = 0; i < size; i++)
      38             :         {
      39             :                 if (groups[i] == val) return true;
      40             :         }
      41             :         return false;
      42             : }
      43             : 
      44          11 : static int validateKey (Key * key, Key * parentKey)
      45             : {
      46             :         struct stat buf;
      47             :         /* TODO: make exceptions configurable using path/allow */
      48          11 :         if (!strcmp (keyString (key), "proc"))
      49             :         {
      50             :                 return 1;
      51             :         }
      52          11 :         else if (!strcmp (keyString (key), "tmpfs"))
      53             :         {
      54             :                 return 1;
      55             :         }
      56          11 :         else if (!strcmp (keyString (key), "none"))
      57             :         {
      58             :                 return 1;
      59             :         }
      60          11 :         else if (keyString (key)[0] != '/')
      61             :         {
      62           0 :                 ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (parentKey, "Given path '%s' is not absolute", keyString (key));
      63           0 :                 return 0;
      64             :         }
      65          11 :         int errnosave = errno;
      66          11 :         const Key * meta = keyGetMeta (key, "check/path");
      67          22 :         if (stat (keyString (key), &buf) == -1)
      68             :         {
      69           0 :                 char * errmsg = elektraMalloc (ERRORMSG_LENGTH + 1 + keyGetNameSize (key) + keyGetValueSize (key) +
      70             :                                                sizeof ("name:  value:  message: "));
      71           0 :                 if (strerror_r (errno, errmsg, ERRORMSG_LENGTH) != 0)
      72             :                 {
      73           0 :                         strcpy (errmsg, "Unknown error");
      74             :                 }
      75           0 :                 strcat (errmsg, " from key: ");
      76           0 :                 strcat (errmsg, keyName (key));
      77           0 :                 strcat (errmsg, " with path: ");
      78           0 :                 strcat (errmsg, keyValue (key));
      79           0 :                 ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Could not stat file, message: %s", errmsg);
      80           0 :                 elektraFree (errmsg);
      81           0 :                 errno = errnosave;
      82           0 :                 return -1;
      83             :         }
      84          11 :         else if (!strcmp (keyString (meta), "device"))
      85             :         {
      86           0 :                 if (!S_ISBLK (buf.st_mode))
      87             :                 {
      88           0 :                         ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Device not found: %s", keyString (key));
      89             :                 }
      90             :         }
      91          11 :         else if (!strcmp (keyString (meta), "directory"))
      92             :         {
      93           0 :                 if (!S_ISDIR (buf.st_mode))
      94             :                 {
      95           0 :                         ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Directory not found: %s", keyString (key));
      96             :                 }
      97             :         }
      98             :         return 1;
      99             : }
     100             : 
     101             : /**
     102             :  * This method validates the file permission for a certain user
     103             :  * @param key The key containing all metadata
     104             :  * @param parentKey The parentKey which is used for error writing
     105             :  * @retval 1 if success
     106             :  * @retval -1 for failure
     107             :  */
     108           7 : static int validatePermission (Key * key, Key * parentKey)
     109             : {
     110             : 
     111           7 :         uid_t currentUID = geteuid ();
     112             : 
     113           7 :         const Key * userMeta = keyGetMeta (key, "check/path/user");
     114           7 :         const Key * userTypes = keyGetMeta (key, "check/path/mode");
     115             : 
     116             :         // ***** central variables *******
     117           7 :         const char * validPath = keyString (key);
     118           7 :         const char * name = keyString (userMeta);
     119           7 :         const char * modes = keyString (userTypes);
     120             :         // ****************************
     121             : 
     122           7 :         int modeMask = createModeBits (modes);
     123             :         struct passwd * p;
     124             : 
     125             :         // Changing to specified user. Can only be done when executing user is root user
     126           7 :         if (userMeta && name[0] != '\0')
     127             :         {
     128           0 :                 p = getpwnam (name);
     129             :                 // Check if user exists
     130           0 :                 if (p == NULL)
     131             :                 {
     132           0 :                         ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (parentKey,
     133             :                                                                 "Could not find user '%s' for key '%s'. "
     134             :                                                                 "Does the user exist?",
     135             :                                                                 name, keyName (key));
     136           0 :                         return -1;
     137             :                 }
     138           0 :                 name = p->pw_name;
     139           0 :                 int result = switchUser (key, parentKey, p);
     140           0 :                 if (result != 0)
     141             :                 {
     142             :                         return result;
     143             :                 }
     144             :         }
     145             : 
     146             :         // If user metadata is available but empty
     147           7 :         else if (userMeta)
     148             :         {
     149           7 :                 return handleNoUserCase (parentKey, validPath, modes);
     150             :         }
     151             : 
     152             :         // If user metadata is not given ... can only check if root can access the file
     153             :         else
     154             :         {
     155           0 :                 uid_t uid = geteuid ();
     156           0 :                 p = getpwuid (uid);
     157           0 :                 name = p->pw_name;
     158           0 :                 if (uid != 0)
     159             :                 {
     160             :                         // TODO: Solution
     161           0 :                         ELEKTRA_SET_RESOURCE_ERRORF (parentKey,
     162             :                                                      "To check permissions for %s I need to be the root user."
     163             :                                                      " Are you running kdb as root?",
     164             :                                                      keyName (key));
     165           0 :                         return -1;
     166             :                 }
     167             :         }
     168           0 :         int ngroups = 0;
     169             :         gid_t * groups;
     170           0 :         int allGroupsReturnCode = getAllGroups (parentKey, currentUID, p, ngroups, &groups);
     171           0 :         if (allGroupsReturnCode != 0)
     172             :         {
     173             :                 return allGroupsReturnCode;
     174             :         }
     175             : 
     176             :         // Get groupID of file being checked
     177             :         struct stat sb;
     178           0 :         stat (validPath, &sb);
     179           0 :         struct group * gr = getgrgid (sb.st_gid);
     180             : 
     181           0 :         bool isUserInGroupBool = isUserInGroup ((int) gr->gr_gid, groups, (unsigned int) ngroups);
     182           0 :         elektraFree (groups);
     183             : 
     184             :         // Save group so we can switch back to the original later again
     185           0 :         gid_t currentGID = getegid ();
     186             : 
     187             :         // Check if fileGroup is in userGroup. If yes change egid to that group
     188             :         if (isUserInGroupBool)
     189             :         {
     190             :                 ELEKTRA_LOG_DEBUG ("User ā€œ%sā€ has group of file ā€œ%sā€œ", name, validPath);
     191             :                 int result = switchGroup (key, parentKey, name, gr);
     192             :                 if (result != 0)
     193             :                 {
     194             :                         return result;
     195             :                 }
     196             :         }
     197             : 
     198             :         // Actual check is done
     199           0 :         int canAccess = euidaccess (validPath, modeMask);
     200             : 
     201             :         // Change back to initial effective IDs
     202           0 :         int euidResult = seteuid (currentUID);
     203           0 :         int egidResult = setegid (currentGID);
     204             : 
     205           0 :         if (euidResult != 0 || egidResult != 0)
     206             :         {
     207           0 :                 ELEKTRA_SET_INTERNAL_ERROR (parentKey,
     208             :                                             "There was a problem in the user switching process."
     209             :                                             "Please report the issue at https://issues.libelektra.org");
     210           0 :                 return -1;
     211             :         }
     212             : 
     213           0 :         if (canAccess != 0)
     214             :         {
     215             :                 // No Resource error per se because related to the specification check!
     216           0 :                 ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (parentKey, "User %s does not have required permission (%s) on %s", name, modes,
     217             :                                                         validPath);
     218           0 :                 return -1;
     219             :         }
     220             : 
     221             :         return 1;
     222             : }
     223             : 
     224             : /**
     225             :  * This method saves all groups into the groups parameter and also saves the number of groups into ngroups
     226             :  * @param parentKey The parentKey to which error messages are logged
     227             :  * @param currentUID The current userID which is used to switch back to in case of an error
     228             :  * @param p passwd struct which has all relevant user information
     229             :  * @param ngroups the number of groups of a user. Should be initialized with 0
     230             :  * @param groups the actual groups which are returned
     231             :  * @retval 0 if success
     232             :  */
     233           0 : static int getAllGroups (Key * parentKey, uid_t currentUID, const struct passwd * p, int ngroups, gid_t ** groups)
     234             : {
     235           0 :         gid_t * tmpGroups = (gid_t *) elektraMalloc (0 * sizeof (gid_t));
     236           0 :         getgrouplist (p->pw_name, (int) p->pw_gid, tmpGroups, &ngroups);
     237           0 :         elektraFree (tmpGroups);
     238           0 :         (*groups) = (gid_t *) elektraMalloc (ngroups * sizeof (gid_t));
     239             :         // call to getgrouplist fails because at least one group (p->pw_gid) is returned
     240             :         // therefore ngroups now contains the actual number of groups for the user
     241           0 :         if (getgrouplist (p->pw_name, (int) p->pw_gid, (*groups), &ngroups) < 0)
     242             :         {
     243           0 :                 ELEKTRA_SET_INTERNAL_ERROR (parentKey,
     244             :                                             "There was a problem in the getting all groups for the user."
     245             :                                             "Please report the issue at https://issues.libelektra.org");
     246           0 :                 if (seteuid (currentUID) < 0)
     247             :                 {
     248           0 :                         ELEKTRA_SET_INTERNAL_ERROR (parentKey,
     249             :                                                     "There was a problem in the user switching process."
     250             :                                                     "Please report the issue at https://issues.libelektra.org");
     251             :                 }
     252             :                 return -1;
     253             :         }
     254             :         return 0;
     255             : }
     256             : 
     257             : /**
     258             :  * Switches the effective groupID of a user
     259             :  * @param key Used for senseful logging of where the error occurred
     260             :  * @param parentKey The parentKey to which error messages are logged
     261             :  * @param name Used for senseful logging of where the error occurred. Represents the actual username
     262             :  * @param gr The group to which it is switched
     263             :  * @retval 0 if success
     264             :  */
     265             : static int switchGroup (Key * key, Key * parentKey, const char * name, const struct group * gr)
     266             : {
     267             :         int gidErr = setegid ((int) gr->gr_gid);
     268             :         if (gidErr < 0)
     269             :         {
     270             :                 // TODO: Solution
     271             :                 ELEKTRA_SET_RESOURCE_ERRORF (parentKey,
     272             :                                              "Could not set egid of user '%s' for key '%s'."
     273             :                                              " Are you running kdb as root?",
     274             :                                              name, keyName (key));
     275             :                 return -1;
     276             :         }
     277             :         return 0;
     278             : }
     279             : 
     280             : /**
     281             :  * Switches the effective userID of a user. This method only works if the executing user has root privileged.
     282             :  * @param key Used for senseful logging of where the error occurred
     283             :  * @param parentKey The parentKey to which error messages are logged
     284             :  * @param p passwd struct which has all relevant user information
     285             :  * @retval 0 if success
     286             :  * @retval -1 if failure happens
     287             :  */
     288           0 : static int switchUser (Key * key, Key * parentKey, const struct passwd * p)
     289             : {
     290             :         // Check if I can change the UID as root
     291           0 :         int err = seteuid ((int) p->pw_uid);
     292           0 :         if (err < 0)
     293             :         {
     294             :                 // TODO: Solution
     295           0 :                 ELEKTRA_SET_RESOURCE_ERRORF (parentKey,
     296             :                                              "Could not set euid of user '%s' for key '%s'."
     297             :                                              " Are you running kdb as root?",
     298             :                                              p->pw_name, keyName (key));
     299             :                 return -1;
     300             :         }
     301             :         return 0;
     302             : }
     303             : 
     304             : /**
     305             :  * This method checks for the current executing user if he can access the file/directory given with the respective modes.
     306             :  * @param parentKey The parentKey to which error messages are logged
     307             :  * @param validPath Used for senseful logging of where the error occurred
     308             :  * @param modes The modes which should be checked for the current user
     309             :  * @retval 1 if success
     310             :  * @retval -1 if failure happens
     311             :  */
     312           7 : static int handleNoUserCase (Key * parentKey, const char * validPath, const char * modes)
     313             : {
     314           7 :         int modeMask = createModeBits (modes);
     315           7 :         struct passwd * p = getpwuid (getuid ());
     316           7 :         int result = access (validPath, modeMask);
     317           7 :         if (result != 0)
     318             :         {
     319           2 :                 ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (parentKey, "User '%s' does not have required permission (%s) on %s", p->pw_name,
     320             :                                                         modes, validPath);
     321           2 :                 return -1;
     322             :         }
     323             :         return 1;
     324             : }
     325             : 
     326             : /**
     327             :  * Takes modes given by the user (e.g. rwx) and converts it to the bitmask required for access and euidaccess
     328             :  * @param modes The modes given by the user in the metakey
     329             :  * @return The modes as bits required for access and euidaccess
     330             :  */
     331          14 : static int createModeBits (const char * modes)
     332             : {
     333          14 :         int modeMask = 0;
     334          14 :         if (strchr (modes, 'r') != NULL)
     335             :         {
     336          14 :                 modeMask |= R_OK;
     337             :         }
     338          14 :         if (strchr (modes, 'w') != NULL)
     339             :         {
     340          14 :                 modeMask |= W_OK;
     341             :         }
     342          14 :         if (strchr (modes, 'x') != NULL)
     343             :         {
     344           8 :                 modeMask |= X_OK;
     345             :         }
     346          14 :         return modeMask;
     347             : }
     348             : 
     349          44 : int elektraPathGet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * parentKey ELEKTRA_UNUSED)
     350             : {
     351             :         /* contract only */
     352             :         KeySet * n;
     353          44 :         ksAppend (returned, n = ksNew (30, keyNew ("system/elektra/modules/path", KEY_VALUE, "path plugin waits for your orders", KEY_END),
     354             :                                        keyNew ("system/elektra/modules/path/exports", KEY_END),
     355             :                                        keyNew ("system/elektra/modules/path/exports/get", KEY_FUNC, elektraPathGet, KEY_END),
     356             :                                        keyNew ("system/elektra/modules/path/exports/set", KEY_FUNC, elektraPathSet, KEY_END),
     357             :                                        keyNew ("system/elektra/modules/path/exports/validateKey", KEY_FUNC, validateKey, KEY_END),
     358             : 
     359             : #include "readme_path.c"
     360             : 
     361             :                                        keyNew ("system/elektra/modules/path/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END));
     362          44 :         ksDel (n);
     363             : 
     364          44 :         return 1; /* success */
     365             : }
     366             : 
     367          13 : int elektraPathSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * parentKey)
     368             : {
     369             :         /* set all keys */
     370             :         Key * cur;
     371          13 :         ksRewind (returned);
     372          13 :         int rc = 1;
     373          42 :         while ((cur = ksNext (returned)) != 0)
     374             :         {
     375          16 :                 const Key * pathMeta = keyGetMeta (cur, "check/path");
     376          16 :                 if (!pathMeta) continue;
     377          11 :                 rc = validateKey (cur, parentKey);
     378          11 :                 if (rc <= 0) return -1;
     379             : 
     380          11 :                 const Key * accessMeta = keyGetMeta (cur, "check/path/mode");
     381          11 :                 if (!accessMeta) continue;
     382           7 :                 rc = validatePermission (cur, parentKey);
     383           7 :                 if (!rc) return -1;
     384             :         }
     385             : 
     386             :         return 1; /* success */
     387             : }
     388             : 
     389         132 : Plugin * ELEKTRA_PLUGIN_EXPORT
     390             : {
     391             :         // clang-format off
     392         132 :         return elektraPluginExport ("path",
     393             :                                     ELEKTRA_PLUGIN_GET, &elektraPathGet,
     394             :                                     ELEKTRA_PLUGIN_SET, &elektraPathSet,
     395             :                                     ELEKTRA_PLUGIN_END);
     396             : }
     397             : 

Generated by: LCOV version 1.13