2 * Copyright (c) 2012 - Eric Biederman
9 #include "prototypes.h"
13 #include "subordinateio.h"
14 #include <sys/types.h>
17 struct subordinate_range {
26 * subordinate_dup: create a duplicate range
28 * @ent: a pointer to a subordinate_range struct
30 * Returns a pointer to a newly allocated duplicate subordinate_range struct
33 static /*@null@*/ /*@only@*/void *subordinate_dup (const void *ent)
35 const struct subordinate_range *rangeent = ent;
36 struct subordinate_range *range;
38 range = (struct subordinate_range *) malloc (sizeof *range);
42 range->owner = strdup (rangeent->owner);
43 if (NULL == range->owner) {
47 range->start = rangeent->start;
48 range->count = rangeent->count;
54 * subordinate_free: free a subordinate_range struct
56 * @ent: pointer to a subordinate_range struct to free.
58 static void subordinate_free (/*@out@*/ /*@only@*/void *ent)
60 struct subordinate_range *rangeent = ent;
62 free ((void *)(rangeent->owner));
69 * @line: a line to parse
71 * Returns a pointer to a subordinate_range struct representing the values
72 * in @line, or NULL on failure. Note that the returned value should not
73 * be freed by the caller.
75 static void *subordinate_parse (const char *line)
77 static struct subordinate_range range;
78 static char rangebuf[1024];
81 char *fields[NFIELDS];
84 * Copy the string to a temporary buffer so the substrings can
85 * be modified to be NULL terminated.
87 if (strlen (line) >= sizeof rangebuf)
88 return NULL; /* fail if too long */
89 strcpy (rangebuf, line);
92 * Save a pointer to the start of each colon separated
93 * field. The fields are converted into NUL terminated strings.
96 for (cp = rangebuf, i = 0; (i < NFIELDS) && (NULL != cp); i++) {
98 while (('\0' != *cp) && (':' != *cp)) {
111 * There must be exactly NFIELDS colon separated fields or
112 * the entry is invalid. Also, fields must be non-blank.
114 if (i != NFIELDS || *fields[0] == '\0' || *fields[1] == '\0' || *fields[2] == '\0')
116 range.owner = fields[0];
117 if (getulong (fields[1], &range.start) == 0)
119 if (getulong (fields[2], &range.count) == 0)
126 * subordinate_put: print a subordinate_range value to a file
128 * @ent: a pointer to a subordinate_range struct to print out.
129 * @file: file to which to print.
131 * Returns 0 on success, -1 on error.
133 static int subordinate_put (const void *ent, FILE * file)
135 const struct subordinate_range *range = ent;
137 return fprintf(file, "%s:%lu:%lu\n",
140 range->count) < 0 ? -1 : 0;
143 static struct commonio_ops subordinate_ops = {
144 subordinate_dup, /* dup */
145 subordinate_free, /* free */
147 subordinate_parse, /* parse */
148 subordinate_put, /* put */
151 NULL, /* open_hook */
152 NULL, /* close_hook */
155 static /*@observer@*/ /*@null*/const struct subordinate_range *subordinate_next(struct commonio_db *db)
157 return (const struct subordinate_range *)commonio_next (db);
161 * range_exists: Check whether @owner owns any ranges
163 * @db: database to query
164 * @owner: owner being queried
166 * Returns true if @owner owns any subuid ranges, false otherwise.
168 static const bool range_exists(struct commonio_db *db, const char *owner)
170 const struct subordinate_range *range;
172 while ((range = commonio_next(db)) != NULL) {
173 if (0 == strcmp(range->owner, owner))
180 * find_range: find a range which @owner is authorized to use which includes
183 * @db: database to query
184 * @owner: owning uid being queried
185 * @val: subuid being searched for.
187 * Returns a range of subuids belonging to @owner and including the subuid
188 * @val, or NULL if no such range exists.
190 static const struct subordinate_range *find_range(struct commonio_db *db,
191 const char *owner, unsigned long val)
193 const struct subordinate_range *range;
196 * Search for exact username/group specification
198 * This is the original method - go fast through the db, doing only
199 * exact username/group string comparison. Therefore we leave it as-is
200 * for the time being, in order to keep it equally fast as it was
204 while ((range = commonio_next(db)) != NULL) {
205 unsigned long first = range->start;
206 unsigned long last = first + range->count - 1;
208 if (0 != strcmp(range->owner, owner))
211 if ((val >= first) && (val <= last))
217 * We only do special handling for these two files
219 if ((0 != strcmp(db->filename, "/etc/subuid")) && (0 != strcmp(db->filename, "/etc/subgid")))
223 * Search loop above did not produce any result. Let's rerun it,
224 * but this time try to match actual UIDs. The first entry that
225 * matches is considered a success.
226 * (It may be specified as literal UID or as another username which
227 * has the same UID as the username we are looking for.)
231 char owner_uid_string[33] = "";
234 /* Get UID of the username we are looking for */
235 pwd = getpwnam(owner);
237 /* Username not defined in /etc/passwd, or error occured during lookup */
240 owner_uid = pwd->pw_uid;
241 sprintf(owner_uid_string, "%lu", (unsigned long int)owner_uid);
244 while ((range = commonio_next(db)) != NULL) {
245 unsigned long first = range->start;
246 unsigned long last = first + range->count - 1;
248 /* For performance reasons check range before using getpwnam() */
249 if ((val < first) || (val > last)) {
254 * Range matches. Check if range owner is specified
255 * as numeric UID and if it matches.
257 if (0 == strcmp(range->owner, owner_uid_string)) {
262 * Ok, this range owner is not specified as numeric UID
263 * we are looking for. It may be specified as another
264 * UID or as a literal username.
266 * If specified as another UID, the call to getpwnam()
269 * If specified as literal username, we will get its
270 * UID and compare that to UID we are looking for.
272 const struct passwd *range_owner_pwd;
274 range_owner_pwd = getpwnam(range->owner);
275 if (NULL == range_owner_pwd) {
279 if (owner_uid == range_owner_pwd->pw_uid) {
288 * have_range: check whether @owner is authorized to use the range
289 * (@start .. @start+@count-1).
290 * @db: database to check
291 * @owner: owning uid being queried
292 * @start: start of range
293 * @count: number of uids in range
295 * Returns true if @owner is authorized to use the range, false otherwise.
297 static bool have_range(struct commonio_db *db,
298 const char *owner, unsigned long start, unsigned long count)
300 const struct subordinate_range *range;
306 end = start + count - 1;
307 range = find_range (db, owner, start);
311 last = range->start + range->count - 1;
312 if (last >= (start + count - 1))
317 range = find_range(db, owner, start);
323 * subordinate_range_cmp: compare uid ranges
325 * @p1: pointer to a commonio_entry struct to compare
326 * @p2: pointer to second commonio_entry struct to compare
328 * Returns 0 if the entries are the same. Otherwise return -1
329 * if the range in p1 is lower than that in p2, or (if the ranges are
330 * equal) if the owning uid in p1 is lower than p2's. Return 1 if p1's
331 * range or owning uid is great than p2's.
333 static int subordinate_range_cmp (const void *p1, const void *p2)
335 struct subordinate_range *range1, *range2;
337 if ((*(struct commonio_entry **) p1)->eptr == NULL)
339 if ((*(struct commonio_entry **) p2)->eptr == NULL)
342 range1 = ((struct subordinate_range *) (*(struct commonio_entry **) p1)->eptr);
343 range2 = ((struct subordinate_range *) (*(struct commonio_entry **) p2)->eptr);
345 if (range1->start < range2->start)
347 else if (range1->start > range2->start)
349 else if (range1->count < range2->count)
351 else if (range1->count > range2->count)
354 return strcmp(range1->owner, range2->owner);
358 * find_free_range: find an unused consecutive sequence of ids to allocate
360 * @db: database to search
361 * @min: the first uid in the range to find
362 * @max: the highest uid to find
363 * @count: the number of uids needed
365 * Return the lowest new uid, or ULONG_MAX on failure.
367 static unsigned long find_free_range(struct commonio_db *db,
368 unsigned long min, unsigned long max,
371 const struct subordinate_range *range;
372 unsigned long low, high;
374 /* When given invalid parameters fail */
375 if ((count == 0) || (max < min))
378 /* Sort by range then by owner */
379 commonio_sort (db, subordinate_range_cmp);
383 while ((range = commonio_next(db)) != NULL) {
384 unsigned long first = range->start;
385 unsigned long last = first + range->count - 1;
387 /* Find the top end of the hole before this range */
390 /* Don't allocate IDs after max (included) */
391 if (high > max + 1) {
395 /* Is the hole before this range large enough? */
396 if ((high > low) && ((high - low) >= count))
399 /* Compute the low end of the next hole */
400 if (low < (last + 1))
406 /* Is the remaining unclaimed area large enough? */
407 if (((max - low) + 1) >= count)
414 * add_range: add a subuid range to an owning uid's list of authorized
416 * @db: database to which to add
417 * @owner: uid which owns the subuid
418 * @start: the first uid in the owned range
419 * @count: the number of uids in the range
421 * Return 1 if the range is already present or on success. On error
422 * return 0 and set errno appropriately.
424 static int add_range(struct commonio_db *db,
425 const char *owner, unsigned long start, unsigned long count)
427 struct subordinate_range range;
432 /* See if the range is already present */
433 if (have_range(db, owner, start, count))
436 /* Otherwise append the range */
437 return commonio_append(db, &range);
441 * remove_range: remove a range of subuids from an owning uid's list
442 * of authorized subuids.
443 * @db: database to work on
444 * @owner: owning uid whose range is being removed
445 * @start: start of the range to be removed
446 * @count: number of uids in the range.
448 * Returns 0 on failure, 1 on success. Failure means that we needed to
449 * create a new range to represent the new limits, and failed doing so.
451 static int remove_range (struct commonio_db *db,
453 unsigned long start, unsigned long count)
455 struct commonio_entry *ent;
462 end = start + count - 1;
463 for (ent = db->head; NULL != ent; ent = ent->next) {
464 struct subordinate_range *range = ent->eptr;
468 /* Skip unparsed entries */
473 first = range->start;
474 last = first + range->count - 1;
476 /* Skip entries with a different owner */
477 if (0 != strcmp (range->owner, owner)) {
481 /* Skip entries outside of the range to remove */
482 if ((end < first) || (start > last)) {
486 if (start <= first) {
488 /* to be removed: [start, end]
489 * range: [first, last] */
490 /* entry completely contained in the
492 commonio_del_entry (db, ent);
494 /* to be removed: [start, end]
495 * range: [first, last] */
496 /* Remove only the start of the entry */
497 range->start = end + 1;
498 range->count = (last - range->start) + 1;
505 /* to be removed: [start, end]
506 * range: [first, last] */
507 /* Remove only the end of the entry */
508 range->count = start - range->start;
513 /* to be removed: [start, end]
514 * range: [first, last] */
515 /* Remove the middle of the range
516 * This requires to create a new range */
517 struct subordinate_range tail;
518 tail.owner = range->owner;
519 tail.start = end + 1;
520 tail.count = (last - tail.start) + 1;
522 if (commonio_append (db, &tail) == 0) {
526 range->count = start - range->start;
537 static struct commonio_db subordinate_uid_db = {
538 "/etc/subuid", /* filename */
539 &subordinate_ops, /* ops */
553 false, /* readonly */
557 int sub_uid_setdbname (const char *filename)
559 return commonio_setname (&subordinate_uid_db, filename);
562 /*@observer@*/const char *sub_uid_dbname (void)
564 return subordinate_uid_db.filename;
567 bool sub_uid_file_present (void)
569 return commonio_present (&subordinate_uid_db);
572 int sub_uid_lock (void)
574 return commonio_lock (&subordinate_uid_db);
577 int sub_uid_open (int mode)
579 return commonio_open (&subordinate_uid_db, mode);
582 bool sub_uid_assigned(const char *owner)
584 return range_exists (&subordinate_uid_db, owner);
587 bool have_sub_uids(const char *owner, uid_t start, unsigned long count)
589 return have_range (&subordinate_uid_db, owner, start, count);
592 int sub_uid_add (const char *owner, uid_t start, unsigned long count)
594 return add_range (&subordinate_uid_db, owner, start, count);
597 int sub_uid_remove (const char *owner, uid_t start, unsigned long count)
599 return remove_range (&subordinate_uid_db, owner, start, count);
602 int sub_uid_close (void)
604 return commonio_close (&subordinate_uid_db);
607 int sub_uid_unlock (void)
609 return commonio_unlock (&subordinate_uid_db);
612 uid_t sub_uid_find_free_range(uid_t min, uid_t max, unsigned long count)
615 start = find_free_range (&subordinate_uid_db, min, max, count);
616 return start == ULONG_MAX ? (uid_t) -1 : start;
619 static struct commonio_db subordinate_gid_db = {
620 "/etc/subgid", /* filename */
621 &subordinate_ops, /* ops */
635 false, /* readonly */
639 int sub_gid_setdbname (const char *filename)
641 return commonio_setname (&subordinate_gid_db, filename);
644 /*@observer@*/const char *sub_gid_dbname (void)
646 return subordinate_gid_db.filename;
649 bool sub_gid_file_present (void)
651 return commonio_present (&subordinate_gid_db);
654 int sub_gid_lock (void)
656 return commonio_lock (&subordinate_gid_db);
659 int sub_gid_open (int mode)
661 return commonio_open (&subordinate_gid_db, mode);
664 bool have_sub_gids(const char *owner, gid_t start, unsigned long count)
666 return have_range(&subordinate_gid_db, owner, start, count);
669 bool sub_gid_assigned(const char *owner)
671 return range_exists (&subordinate_gid_db, owner);
674 int sub_gid_add (const char *owner, gid_t start, unsigned long count)
676 return add_range (&subordinate_gid_db, owner, start, count);
679 int sub_gid_remove (const char *owner, gid_t start, unsigned long count)
681 return remove_range (&subordinate_gid_db, owner, start, count);
684 int sub_gid_close (void)
686 return commonio_close (&subordinate_gid_db);
689 int sub_gid_unlock (void)
691 return commonio_unlock (&subordinate_gid_db);
694 gid_t sub_gid_find_free_range(gid_t min, gid_t max, unsigned long count)
697 start = find_free_range (&subordinate_gid_db, min, max, count);
698 return start == ULONG_MAX ? (gid_t) -1 : start;
700 #else /* !ENABLE_SUBIDS */
701 extern int errno; /* warning: ANSI C forbids an empty source file */
702 #endif /* !ENABLE_SUBIDS */