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 :
|