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

Generated by: LCOV version 1.13