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