Use of these macros, apart from the benefits mentioned in the commit that adds the macros, has some other good side effects: - Consistency in getting the size of the object from sizeof(type), instead of a mix of sizeof(type) sometimes and sizeof(*p) other times. - More readable code: no casts, and no sizeof(), so also shorter lines that we don't need to cut. - Consistency in using array allocation calls for allocations of arrays of objects, even when the object size is 1. Cc: Valentin V. Bartenev <vbartenev@gmail.com> Signed-off-by: Alejandro Colomar <alx@kernel.org>
		
			
				
	
	
		
			1106 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1106 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * SPDX-FileCopyrightText: 2012 - Eric Biederman
 | 
						|
 */
 | 
						|
 | 
						|
#include <config.h>
 | 
						|
 | 
						|
#ifdef ENABLE_SUBIDS
 | 
						|
 | 
						|
#include "prototypes.h"
 | 
						|
#include "defines.h"
 | 
						|
#include <stdio.h>
 | 
						|
#include "commonio.h"
 | 
						|
#include "subordinateio.h"
 | 
						|
#include "../libsubid/subid.h"
 | 
						|
#include <sys/types.h>
 | 
						|
#include <pwd.h>
 | 
						|
#include <ctype.h>
 | 
						|
#include <fcntl.h>
 | 
						|
 | 
						|
#include "alloc.h"
 | 
						|
 | 
						|
#define ID_SIZE 31
 | 
						|
 | 
						|
/*
 | 
						|
 * subordinate_dup: create a duplicate range
 | 
						|
 *
 | 
						|
 * @ent: a pointer to a subordinate_range struct
 | 
						|
 *
 | 
						|
 * Returns a pointer to a newly allocated duplicate subordinate_range struct
 | 
						|
 * or NULL on failure
 | 
						|
 */
 | 
						|
static /*@null@*/ /*@only@*/void *subordinate_dup (const void *ent)
 | 
						|
{
 | 
						|
	const struct subordinate_range *rangeent = ent;
 | 
						|
	struct subordinate_range *range;
 | 
						|
 | 
						|
	range = MALLOC (struct subordinate_range);
 | 
						|
	if (NULL == range) {
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
	range->owner = strdup (rangeent->owner);
 | 
						|
	if (NULL == range->owner) {
 | 
						|
		free(range);
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
	range->start = rangeent->start;
 | 
						|
	range->count = rangeent->count;
 | 
						|
 | 
						|
	return range;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * subordinate_free: free a subordinate_range struct
 | 
						|
 *
 | 
						|
 * @ent: pointer to a subordinate_range struct to free.
 | 
						|
 */
 | 
						|
static void subordinate_free (/*@out@*/ /*@only@*/void *ent)
 | 
						|
{
 | 
						|
	struct subordinate_range *rangeent = ent;
 | 
						|
 | 
						|
	free ((void *)(rangeent->owner));
 | 
						|
	free (rangeent);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * subordinate_parse:
 | 
						|
 *
 | 
						|
 * @line: a line to parse
 | 
						|
 *
 | 
						|
 * Returns a pointer to a subordinate_range struct representing the values
 | 
						|
 * in @line, or NULL on failure.  Note that the returned value should not
 | 
						|
 * be freed by the caller.
 | 
						|
 */
 | 
						|
static void *subordinate_parse (const char *line)
 | 
						|
{
 | 
						|
	static struct subordinate_range range;
 | 
						|
	static char rangebuf[1024];
 | 
						|
	int i;
 | 
						|
	char *cp;
 | 
						|
	char *fields[SUBID_NFIELDS];
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Copy the string to a temporary buffer so the substrings can
 | 
						|
	 * be modified to be NULL terminated.
 | 
						|
	 */
 | 
						|
	if (strlen (line) >= sizeof rangebuf)
 | 
						|
		return NULL;	/* fail if too long */
 | 
						|
	strcpy (rangebuf, line);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Save a pointer to the start of each colon separated
 | 
						|
	 * field.  The fields are converted into NUL terminated strings.
 | 
						|
	 */
 | 
						|
 | 
						|
	for (cp = rangebuf, i = 0; (i < SUBID_NFIELDS) && (NULL != cp); i++) {
 | 
						|
		fields[i] = cp;
 | 
						|
		while (('\0' != *cp) && (':' != *cp)) {
 | 
						|
			cp++;
 | 
						|
		}
 | 
						|
 | 
						|
		if ('\0' != *cp) {
 | 
						|
			*cp = '\0';
 | 
						|
			cp++;
 | 
						|
		} else {
 | 
						|
			cp = NULL;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * There must be exactly SUBID_NFIELDS colon separated fields or
 | 
						|
	 * the entry is invalid.  Also, fields must be non-blank.
 | 
						|
	 */
 | 
						|
	if (i != SUBID_NFIELDS || *fields[0] == '\0' || *fields[1] == '\0' || *fields[2] == '\0')
 | 
						|
		return NULL;
 | 
						|
	range.owner = fields[0];
 | 
						|
	if (getulong (fields[1], &range.start) == 0)
 | 
						|
		return NULL;
 | 
						|
	if (getulong (fields[2], &range.count) == 0)
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	return ⦥
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * subordinate_put: print a subordinate_range value to a file
 | 
						|
 *
 | 
						|
 * @ent: a pointer to a subordinate_range struct to print out.
 | 
						|
 * @file: file to which to print.
 | 
						|
 *
 | 
						|
 * Returns 0 on success, -1 on error.
 | 
						|
 */
 | 
						|
static int subordinate_put (const void *ent, FILE * file)
 | 
						|
{
 | 
						|
	const struct subordinate_range *range = ent;
 | 
						|
 | 
						|
	return fprintf(file, "%s:%lu:%lu\n",
 | 
						|
			       range->owner,
 | 
						|
			       range->start,
 | 
						|
			       range->count) < 0 ? -1  : 0;
 | 
						|
}
 | 
						|
 | 
						|
static struct commonio_ops subordinate_ops = {
 | 
						|
	subordinate_dup,	/* dup */
 | 
						|
	subordinate_free,	/* free */
 | 
						|
	NULL,			/* getname */
 | 
						|
	subordinate_parse,	/* parse */
 | 
						|
	subordinate_put,	/* put */
 | 
						|
	fgets,			/* fgets */
 | 
						|
	fputs,			/* fputs */
 | 
						|
	NULL,			/* open_hook */
 | 
						|
	NULL,			/* close_hook */
 | 
						|
};
 | 
						|
 | 
						|
/*
 | 
						|
 * range_exists: Check whether @owner owns any ranges
 | 
						|
 *
 | 
						|
 * @db: database to query
 | 
						|
 * @owner: owner being queried
 | 
						|
 *
 | 
						|
 * Returns true if @owner owns any subuid ranges, false otherwise.
 | 
						|
 */
 | 
						|
static bool range_exists(struct commonio_db *db, const char *owner)
 | 
						|
{
 | 
						|
	const struct subordinate_range *range;
 | 
						|
	commonio_rewind(db);
 | 
						|
	while ((range = commonio_next(db)) != NULL) {
 | 
						|
		if (0 == strcmp(range->owner, owner))
 | 
						|
			return true;
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * find_range: find a range which @owner is authorized to use which includes
 | 
						|
 *             subuid @val.
 | 
						|
 *
 | 
						|
 * @db: database to query
 | 
						|
 * @owner: owning uid being queried
 | 
						|
 * @val: subuid being searched for.
 | 
						|
 *
 | 
						|
 * Returns a range of subuids belonging to @owner and including the subuid
 | 
						|
 * @val, or NULL if no such range exists.
 | 
						|
 */
 | 
						|
static const struct subordinate_range *find_range(struct commonio_db *db,
 | 
						|
						  const char *owner, unsigned long val)
 | 
						|
{
 | 
						|
	const struct subordinate_range *range;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Search for exact username/group specification
 | 
						|
	 *
 | 
						|
	 * This is the original method - go fast through the db, doing only
 | 
						|
	 * exact username/group string comparison. Therefore we leave it as-is
 | 
						|
	 * for the time being, in order to keep it equally fast as it was
 | 
						|
	 * before.
 | 
						|
	 */
 | 
						|
	commonio_rewind(db);
 | 
						|
	while ((range = commonio_next(db)) != NULL) {
 | 
						|
		unsigned long first = range->start;
 | 
						|
		unsigned long last = first + range->count - 1;
 | 
						|
 | 
						|
		if (0 != strcmp(range->owner, owner))
 | 
						|
			continue;
 | 
						|
 | 
						|
		if ((val >= first) && (val <= last))
 | 
						|
			return range;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
        /*
 | 
						|
         * We only do special handling for these two files
 | 
						|
         */
 | 
						|
        if ((0 != strcmp(db->filename, "/etc/subuid")) && (0 != strcmp(db->filename, "/etc/subgid")))
 | 
						|
                return NULL;
 | 
						|
 | 
						|
        /*
 | 
						|
         * Search loop above did not produce any result. Let's rerun it,
 | 
						|
         * but this time try to match actual UIDs. The first entry that
 | 
						|
         * matches is considered a success.
 | 
						|
         * (It may be specified as literal UID or as another username which
 | 
						|
         * has the same UID as the username we are looking for.)
 | 
						|
         */
 | 
						|
        struct passwd *pwd;
 | 
						|
        uid_t          owner_uid;
 | 
						|
        char           owner_uid_string[33] = "";
 | 
						|
 | 
						|
 | 
						|
        /* Get UID of the username we are looking for */
 | 
						|
        pwd = getpwnam(owner);
 | 
						|
        if (NULL == pwd) {
 | 
						|
                /* Username not defined in /etc/passwd, or error occurred during lookup */
 | 
						|
                return NULL;
 | 
						|
        }
 | 
						|
        owner_uid = pwd->pw_uid;
 | 
						|
        sprintf(owner_uid_string, "%lu", (unsigned long int)owner_uid);
 | 
						|
 | 
						|
        commonio_rewind(db);
 | 
						|
        while ((range = commonio_next(db)) != NULL) {
 | 
						|
                unsigned long first = range->start;
 | 
						|
                unsigned long last = first + range->count - 1;
 | 
						|
 | 
						|
                /* For performance reasons check range before using getpwnam() */
 | 
						|
                if ((val < first) || (val > last)) {
 | 
						|
                        continue;
 | 
						|
                }
 | 
						|
 | 
						|
                /*
 | 
						|
                 * Range matches. Check if range owner is specified
 | 
						|
                 * as numeric UID and if it matches.
 | 
						|
                 */
 | 
						|
                if (0 == strcmp(range->owner, owner_uid_string)) {
 | 
						|
                        return range;
 | 
						|
                }
 | 
						|
 | 
						|
                /*
 | 
						|
                 * Ok, this range owner is not specified as numeric UID
 | 
						|
                 * we are looking for. It may be specified as another
 | 
						|
                 * UID or as a literal username.
 | 
						|
                 *
 | 
						|
                 * If specified as another UID, the call to getpwnam()
 | 
						|
                 * will return NULL.
 | 
						|
                 *
 | 
						|
                 * If specified as literal username, we will get its
 | 
						|
                 * UID and compare that to UID we are looking for.
 | 
						|
                 */
 | 
						|
                const struct passwd *range_owner_pwd;
 | 
						|
 | 
						|
                range_owner_pwd = getpwnam(range->owner);
 | 
						|
                if (NULL == range_owner_pwd) {
 | 
						|
                        continue;
 | 
						|
                }
 | 
						|
 | 
						|
                if (owner_uid == range_owner_pwd->pw_uid) {
 | 
						|
                        return range;
 | 
						|
                }
 | 
						|
        }
 | 
						|
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * have_range: check whether @owner is authorized to use the range
 | 
						|
 *             (@start .. @start+@count-1).
 | 
						|
 * @db: database to check
 | 
						|
 * @owner: owning uid being queried
 | 
						|
 * @start: start of range
 | 
						|
 * @count: number of uids in range
 | 
						|
 *
 | 
						|
 * Returns true if @owner is authorized to use the range, false otherwise.
 | 
						|
 */
 | 
						|
static bool have_range(struct commonio_db *db,
 | 
						|
		       const char *owner, unsigned long start, unsigned long count)
 | 
						|
{
 | 
						|
	const struct subordinate_range *range;
 | 
						|
	unsigned long end;
 | 
						|
 | 
						|
	if (count == 0)
 | 
						|
		return false;
 | 
						|
 | 
						|
	end = start + count - 1;
 | 
						|
	range = find_range (db, owner, start);
 | 
						|
	while (range) {
 | 
						|
		unsigned long last;
 | 
						|
 | 
						|
		last = range->start + range->count - 1;
 | 
						|
		if (last >= (start + count - 1))
 | 
						|
			return true;
 | 
						|
 | 
						|
		count = end - last;
 | 
						|
		start = last + 1;
 | 
						|
		range = find_range(db, owner, start);
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
static bool append_range(struct subid_range **ranges, const struct subordinate_range *new, int n)
 | 
						|
{
 | 
						|
	if (!*ranges) {
 | 
						|
		*ranges = MALLOC(struct subid_range);
 | 
						|
		if (!*ranges)
 | 
						|
			return false;
 | 
						|
	} else {
 | 
						|
		struct subid_range *alloced;
 | 
						|
		alloced = REALLOCARRAY(*ranges, n + 1, struct subid_range);
 | 
						|
		if (!alloced)
 | 
						|
			return false;
 | 
						|
		*ranges = alloced;
 | 
						|
	}
 | 
						|
	(*ranges)[n].start = new->start;
 | 
						|
	(*ranges)[n].count = new->count;
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
void free_subordinate_ranges(struct subordinate_range **ranges, int count)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
 | 
						|
	if (!ranges)
 | 
						|
		return;
 | 
						|
	for (i = 0; i < count; i++)
 | 
						|
		subordinate_free(ranges[i]);
 | 
						|
	free(ranges);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * subordinate_range_cmp: compare uid ranges
 | 
						|
 *
 | 
						|
 * @p1: pointer to a commonio_entry struct to compare
 | 
						|
 * @p2: pointer to second commonio_entry struct to compare
 | 
						|
 *
 | 
						|
 * Returns 0 if the entries are the same.  Otherwise return -1
 | 
						|
 * if the range in p1 is lower than that in p2, or (if the ranges are
 | 
						|
 * equal) if the owning uid in p1 is lower than p2's.  Return 1 if p1's
 | 
						|
 * range or owning uid is great than p2's.
 | 
						|
 */
 | 
						|
static int subordinate_range_cmp (const void *p1, const void *p2)
 | 
						|
{
 | 
						|
	struct subordinate_range *range1, *range2;
 | 
						|
 | 
						|
 | 
						|
	range1 = (*(struct commonio_entry **) p1)->eptr;
 | 
						|
	if (range1 == NULL)
 | 
						|
		return 1;
 | 
						|
 | 
						|
	range2 = (*(struct commonio_entry **) p2)->eptr;
 | 
						|
	if (range2 == NULL)
 | 
						|
		return -1;
 | 
						|
 | 
						|
	if (range1->start < range2->start)
 | 
						|
		return -1;
 | 
						|
	else if (range1->start > range2->start)
 | 
						|
		return 1;
 | 
						|
	else if (range1->count < range2->count)
 | 
						|
		return -1;
 | 
						|
	else if (range1->count > range2->count)
 | 
						|
		return 1;
 | 
						|
	else
 | 
						|
		return strcmp(range1->owner, range2->owner);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * find_free_range: find an unused consecutive sequence of ids to allocate
 | 
						|
 *                  to a user.
 | 
						|
 * @db: database to search
 | 
						|
 * @min: the first uid in the range to find
 | 
						|
 * @max: the highest uid to find
 | 
						|
 * @count: the number of uids needed
 | 
						|
 *
 | 
						|
 * Return the lowest new uid, or ULONG_MAX on failure.
 | 
						|
 */
 | 
						|
static unsigned long find_free_range(struct commonio_db *db,
 | 
						|
				     unsigned long min, unsigned long max,
 | 
						|
				     unsigned long count)
 | 
						|
{
 | 
						|
	const struct subordinate_range *range;
 | 
						|
	unsigned long low, high;
 | 
						|
 | 
						|
	/* When given invalid parameters fail */
 | 
						|
	if ((count == 0) || (max < min))
 | 
						|
		goto fail;
 | 
						|
 | 
						|
	/* Sort by range then by owner */
 | 
						|
	commonio_sort (db, subordinate_range_cmp);
 | 
						|
	commonio_rewind(db);
 | 
						|
 | 
						|
	low = min;
 | 
						|
	while ((range = commonio_next(db)) != NULL) {
 | 
						|
		unsigned long first = range->start;
 | 
						|
		unsigned long last = first + range->count - 1;
 | 
						|
 | 
						|
		/* Find the top end of the hole before this range */
 | 
						|
		high = first;
 | 
						|
 | 
						|
		/* Don't allocate IDs after max (included) */
 | 
						|
		if (high > max + 1) {
 | 
						|
			high = max + 1;
 | 
						|
		}
 | 
						|
 | 
						|
		/* Is the hole before this range large enough? */
 | 
						|
		if ((high > low) && ((high - low) >= count))
 | 
						|
			return low;
 | 
						|
 | 
						|
		/* Compute the low end of the next hole */
 | 
						|
		if (low < (last + 1))
 | 
						|
			low = last + 1;
 | 
						|
		if (low > max)
 | 
						|
			goto fail;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Is the remaining unclaimed area large enough? */
 | 
						|
	if (((max - low) + 1) >= count)
 | 
						|
		return low;
 | 
						|
fail:
 | 
						|
	return ULONG_MAX;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * add_range: add a subuid range to an owning uid's list of authorized
 | 
						|
 *            subuids.
 | 
						|
 * @db: database to which to add
 | 
						|
 * @owner: uid which owns the subuid
 | 
						|
 * @start: the first uid in the owned range
 | 
						|
 * @count: the number of uids in the range
 | 
						|
 *
 | 
						|
 * Return 1 if the range is already present or on success.  On error
 | 
						|
 * return 0 and set errno appropriately.
 | 
						|
 */
 | 
						|
static int add_range(struct commonio_db *db,
 | 
						|
	const char *owner, unsigned long start, unsigned long count)
 | 
						|
{
 | 
						|
	struct subordinate_range range;
 | 
						|
	range.owner = owner;
 | 
						|
	range.start = start;
 | 
						|
	range.count = count;
 | 
						|
 | 
						|
	/* See if the range is already present */
 | 
						|
	if (have_range(db, owner, start, count))
 | 
						|
		return 1;
 | 
						|
 | 
						|
	/* Otherwise append the range */
 | 
						|
	return commonio_append(db, &range);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * remove_range:  remove a range of subuids from an owning uid's list
 | 
						|
 *                of authorized subuids.
 | 
						|
 * @db: database to work on
 | 
						|
 * @owner: owning uid whose range is being removed
 | 
						|
 * @start: start of the range to be removed
 | 
						|
 * @count: number of uids in the range.
 | 
						|
 *
 | 
						|
 * Returns 0 on failure, 1 on success.  Failure means that we needed to
 | 
						|
 * create a new range to represent the new limits, and failed doing so.
 | 
						|
 */
 | 
						|
static int remove_range (struct commonio_db *db,
 | 
						|
                         const char *owner,
 | 
						|
                         unsigned long start, unsigned long count)
 | 
						|
{
 | 
						|
	struct commonio_entry *ent;
 | 
						|
	unsigned long end;
 | 
						|
 | 
						|
	if (count == 0) {
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
 | 
						|
	end = start + count - 1;
 | 
						|
	for (ent = db->head; NULL != ent; ent = ent->next) {
 | 
						|
		struct subordinate_range *range = ent->eptr;
 | 
						|
		unsigned long first;
 | 
						|
		unsigned long last;
 | 
						|
 | 
						|
		/* Skip unparsed entries */
 | 
						|
		if (NULL == range) {
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		first = range->start;
 | 
						|
		last = first + range->count - 1;
 | 
						|
 | 
						|
		/* Skip entries with a different owner */
 | 
						|
		if (0 != strcmp (range->owner, owner)) {
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		/* Skip entries outside of the range to remove */
 | 
						|
		if ((end < first) || (start > last)) {
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (start <= first) {
 | 
						|
			if (end >= last) {
 | 
						|
				/* to be removed: [start,      end]
 | 
						|
				 * range:           [first, last] */
 | 
						|
				/* entry completely contained in the
 | 
						|
				 * range to remove */
 | 
						|
				commonio_del_entry (db, ent);
 | 
						|
			} else {
 | 
						|
				/* to be removed: [start,  end]
 | 
						|
				 * range:           [first, last] */
 | 
						|
				/* Remove only the start of the entry */
 | 
						|
				range->start = end + 1;
 | 
						|
				range->count = (last - range->start) + 1;
 | 
						|
 | 
						|
				ent->changed = true;
 | 
						|
				db->changed = true;
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			if (end >= last) {
 | 
						|
				/* to be removed:   [start,  end]
 | 
						|
				 * range:         [first, last] */
 | 
						|
				/* Remove only the end of the entry */
 | 
						|
				range->count = start - range->start;
 | 
						|
 | 
						|
				ent->changed = true;
 | 
						|
				db->changed = true;
 | 
						|
			} else {
 | 
						|
				/* to be removed:   [start, end]
 | 
						|
				 * range:         [first,    last] */
 | 
						|
				/* Remove the middle of the range
 | 
						|
				 * This requires to create a new range */
 | 
						|
				struct subordinate_range tail;
 | 
						|
				tail.owner = range->owner;
 | 
						|
				tail.start = end + 1;
 | 
						|
				tail.count = (last - tail.start) + 1;
 | 
						|
 | 
						|
				if (commonio_append (db, &tail) == 0) {
 | 
						|
					return 0;
 | 
						|
				}
 | 
						|
 | 
						|
				range->count = start - range->start;
 | 
						|
 | 
						|
				ent->changed = true;
 | 
						|
				db->changed = true;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return 1;
 | 
						|
}
 | 
						|
 | 
						|
static struct commonio_db subordinate_uid_db = {
 | 
						|
	"/etc/subuid",		/* filename */
 | 
						|
	&subordinate_ops,	/* ops */
 | 
						|
	NULL,			/* fp */
 | 
						|
#ifdef WITH_SELINUX
 | 
						|
	NULL,			/* scontext */
 | 
						|
#endif
 | 
						|
	0644,                   /* st_mode */
 | 
						|
	0,                      /* st_uid */
 | 
						|
	0,                      /* st_gid */
 | 
						|
	NULL,			/* head */
 | 
						|
	NULL,			/* tail */
 | 
						|
	NULL,			/* cursor */
 | 
						|
	false,			/* changed */
 | 
						|
	false,			/* isopen */
 | 
						|
	false,			/* locked */
 | 
						|
	false,			/* readonly */
 | 
						|
	false			/* setname */
 | 
						|
};
 | 
						|
 | 
						|
int sub_uid_setdbname (const char *filename)
 | 
						|
{
 | 
						|
	return commonio_setname (&subordinate_uid_db, filename);
 | 
						|
}
 | 
						|
 | 
						|
/*@observer@*/const char *sub_uid_dbname (void)
 | 
						|
{
 | 
						|
	return subordinate_uid_db.filename;
 | 
						|
}
 | 
						|
 | 
						|
bool sub_uid_file_present (void)
 | 
						|
{
 | 
						|
	return commonio_present (&subordinate_uid_db);
 | 
						|
}
 | 
						|
 | 
						|
int sub_uid_lock (void)
 | 
						|
{
 | 
						|
	return commonio_lock (&subordinate_uid_db);
 | 
						|
}
 | 
						|
 | 
						|
int sub_uid_open (int mode)
 | 
						|
{
 | 
						|
	return commonio_open (&subordinate_uid_db, mode);
 | 
						|
}
 | 
						|
 | 
						|
bool local_sub_uid_assigned(const char *owner)
 | 
						|
{
 | 
						|
	return range_exists (&subordinate_uid_db, owner);
 | 
						|
}
 | 
						|
 | 
						|
bool have_sub_uids(const char *owner, uid_t start, unsigned long count)
 | 
						|
{
 | 
						|
	struct subid_nss_ops *h;
 | 
						|
	bool found;
 | 
						|
	enum subid_status status;
 | 
						|
	h = get_subid_nss_handle();
 | 
						|
	if (h) {
 | 
						|
		status = h->has_range(owner, start, count, ID_TYPE_UID, &found);
 | 
						|
		if (status == SUBID_STATUS_SUCCESS && found)
 | 
						|
			return true;
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	return have_range (&subordinate_uid_db, owner, start, count);
 | 
						|
}
 | 
						|
 | 
						|
int sub_uid_add (const char *owner, uid_t start, unsigned long count)
 | 
						|
{
 | 
						|
	if (get_subid_nss_handle())
 | 
						|
		return -EOPNOTSUPP;
 | 
						|
	return add_range (&subordinate_uid_db, owner, start, count);
 | 
						|
}
 | 
						|
 | 
						|
int sub_uid_remove (const char *owner, uid_t start, unsigned long count)
 | 
						|
{
 | 
						|
	if (get_subid_nss_handle())
 | 
						|
		return -EOPNOTSUPP;
 | 
						|
	return remove_range (&subordinate_uid_db, owner, start, count);
 | 
						|
}
 | 
						|
 | 
						|
int sub_uid_close (void)
 | 
						|
{
 | 
						|
	return commonio_close (&subordinate_uid_db);
 | 
						|
}
 | 
						|
 | 
						|
int sub_uid_unlock (void)
 | 
						|
{
 | 
						|
	return commonio_unlock (&subordinate_uid_db);
 | 
						|
}
 | 
						|
 | 
						|
uid_t sub_uid_find_free_range(uid_t min, uid_t max, unsigned long count)
 | 
						|
{
 | 
						|
	unsigned long start;
 | 
						|
	start = find_free_range (&subordinate_uid_db, min, max, count);
 | 
						|
	return start == ULONG_MAX ? (uid_t) -1 : start;
 | 
						|
}
 | 
						|
 | 
						|
static struct commonio_db subordinate_gid_db = {
 | 
						|
	"/etc/subgid",		/* filename */
 | 
						|
	&subordinate_ops,	/* ops */
 | 
						|
	NULL,			/* fp */
 | 
						|
#ifdef WITH_SELINUX
 | 
						|
	NULL,			/* scontext */
 | 
						|
#endif
 | 
						|
	0644,                   /* st_mode */
 | 
						|
	0,                      /* st_uid */
 | 
						|
	0,                      /* st_gid */
 | 
						|
	NULL,			/* head */
 | 
						|
	NULL,			/* tail */
 | 
						|
	NULL,			/* cursor */
 | 
						|
	false,			/* changed */
 | 
						|
	false,			/* isopen */
 | 
						|
	false,			/* locked */
 | 
						|
	false,			/* readonly */
 | 
						|
	false			/* setname */
 | 
						|
};
 | 
						|
 | 
						|
int sub_gid_setdbname (const char *filename)
 | 
						|
{
 | 
						|
	return commonio_setname (&subordinate_gid_db, filename);
 | 
						|
}
 | 
						|
 | 
						|
/*@observer@*/const char *sub_gid_dbname (void)
 | 
						|
{
 | 
						|
	return subordinate_gid_db.filename;
 | 
						|
}
 | 
						|
 | 
						|
bool sub_gid_file_present (void)
 | 
						|
{
 | 
						|
	return commonio_present (&subordinate_gid_db);
 | 
						|
}
 | 
						|
 | 
						|
int sub_gid_lock (void)
 | 
						|
{
 | 
						|
	return commonio_lock (&subordinate_gid_db);
 | 
						|
}
 | 
						|
 | 
						|
int sub_gid_open (int mode)
 | 
						|
{
 | 
						|
	return commonio_open (&subordinate_gid_db, mode);
 | 
						|
}
 | 
						|
 | 
						|
bool have_sub_gids(const char *owner, gid_t start, unsigned long count)
 | 
						|
{
 | 
						|
	struct subid_nss_ops *h;
 | 
						|
	bool found;
 | 
						|
	enum subid_status status;
 | 
						|
	h = get_subid_nss_handle();
 | 
						|
	if (h) {
 | 
						|
		status = h->has_range(owner, start, count, ID_TYPE_GID, &found);
 | 
						|
		if (status == SUBID_STATUS_SUCCESS && found)
 | 
						|
			return true;
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	return have_range(&subordinate_gid_db, owner, start, count);
 | 
						|
}
 | 
						|
 | 
						|
bool local_sub_gid_assigned(const char *owner)
 | 
						|
{
 | 
						|
	return range_exists (&subordinate_gid_db, owner);
 | 
						|
}
 | 
						|
 | 
						|
int sub_gid_add (const char *owner, gid_t start, unsigned long count)
 | 
						|
{
 | 
						|
	if (get_subid_nss_handle())
 | 
						|
		return -EOPNOTSUPP;
 | 
						|
	return add_range (&subordinate_gid_db, owner, start, count);
 | 
						|
}
 | 
						|
 | 
						|
int sub_gid_remove (const char *owner, gid_t start, unsigned long count)
 | 
						|
{
 | 
						|
	if (get_subid_nss_handle())
 | 
						|
		return -EOPNOTSUPP;
 | 
						|
	return remove_range (&subordinate_gid_db, owner, start, count);
 | 
						|
}
 | 
						|
 | 
						|
int sub_gid_close (void)
 | 
						|
{
 | 
						|
	return commonio_close (&subordinate_gid_db);
 | 
						|
}
 | 
						|
 | 
						|
int sub_gid_unlock (void)
 | 
						|
{
 | 
						|
	return commonio_unlock (&subordinate_gid_db);
 | 
						|
}
 | 
						|
 | 
						|
gid_t sub_gid_find_free_range(gid_t min, gid_t max, unsigned long count)
 | 
						|
{
 | 
						|
	unsigned long start;
 | 
						|
	start = find_free_range (&subordinate_gid_db, min, max, count);
 | 
						|
	return start == ULONG_MAX ? (gid_t) -1 : start;
 | 
						|
}
 | 
						|
 | 
						|
static bool get_owner_id(const char *owner, enum subid_type id_type, char *id)
 | 
						|
{
 | 
						|
	struct passwd *pw;
 | 
						|
	struct group *gr;
 | 
						|
	int ret = 0;
 | 
						|
 | 
						|
	switch (id_type) {
 | 
						|
	case ID_TYPE_UID:
 | 
						|
		pw = getpwnam(owner);
 | 
						|
		if (pw == NULL) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		ret = snprintf(id, ID_SIZE, "%u", pw->pw_uid);
 | 
						|
		if (ret < 0 || ret >= ID_SIZE) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	case ID_TYPE_GID:
 | 
						|
		gr = getgrnam(owner);
 | 
						|
		if (gr == NULL) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		ret = snprintf(id, ID_SIZE, "%u", gr->gr_gid);
 | 
						|
		if (ret < 0 || ret >= ID_SIZE) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * int list_owner_ranges(const char *owner, enum subid_type id_type, struct subordinate_range ***ranges)
 | 
						|
 *
 | 
						|
 * @owner: username
 | 
						|
 * @id_type: UID or GUID
 | 
						|
 * @ranges: pointer to array of ranges into which results will be placed.
 | 
						|
 *
 | 
						|
 * Fills in the subuid or subgid ranges which are owned by the specified
 | 
						|
 * user.  Username may be a username or a string representation of a
 | 
						|
 * UID number.  If id_type is UID, then subuids are returned, else
 | 
						|
 * subgids are given.
 | 
						|
 | 
						|
 * Returns the number of ranges found, or < 0 on error.
 | 
						|
 *
 | 
						|
 * The caller must free the subordinate range list.
 | 
						|
 */
 | 
						|
int list_owner_ranges(const char *owner, enum subid_type id_type, struct subid_range **in_ranges)
 | 
						|
{
 | 
						|
	// TODO - need to handle owner being either uid or username
 | 
						|
	struct subid_range *ranges = NULL;
 | 
						|
	const struct subordinate_range *range;
 | 
						|
	struct commonio_db *db;
 | 
						|
	enum subid_status status;
 | 
						|
	int count = 0;
 | 
						|
	struct subid_nss_ops *h;
 | 
						|
	char id[ID_SIZE];
 | 
						|
	bool have_owner_id;
 | 
						|
 | 
						|
	*in_ranges = NULL;
 | 
						|
 | 
						|
	h = get_subid_nss_handle();
 | 
						|
	if (h) {
 | 
						|
		status = h->list_owner_ranges(owner, id_type, in_ranges, &count);
 | 
						|
		if (status == SUBID_STATUS_SUCCESS)
 | 
						|
			return count;
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	switch (id_type) {
 | 
						|
	case ID_TYPE_UID:
 | 
						|
		if (!sub_uid_open(O_RDONLY)) {
 | 
						|
			return -1;
 | 
						|
		}
 | 
						|
		db = &subordinate_uid_db;
 | 
						|
		break;
 | 
						|
	case ID_TYPE_GID:
 | 
						|
		if (!sub_gid_open(O_RDONLY)) {
 | 
						|
			return -1;
 | 
						|
		}
 | 
						|
		db = &subordinate_gid_db;
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	have_owner_id = get_owner_id(owner, id_type, id);
 | 
						|
 | 
						|
	commonio_rewind(db);
 | 
						|
	while ((range = commonio_next(db)) != NULL) {
 | 
						|
		if (0 == strcmp(range->owner, owner)) {
 | 
						|
			if (!append_range(&ranges, range, count++)) {
 | 
						|
				free(ranges);
 | 
						|
				ranges = NULL;
 | 
						|
				count = -1;
 | 
						|
				goto out;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Let's also compare with the ID
 | 
						|
		if (have_owner_id == true && 0 == strcmp(range->owner, id)) {
 | 
						|
			if (!append_range(&ranges, range, count++)) {
 | 
						|
				free(ranges);
 | 
						|
				ranges = NULL;
 | 
						|
				count = -1;
 | 
						|
				goto out;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
out:
 | 
						|
	if (id_type == ID_TYPE_UID)
 | 
						|
		sub_uid_close();
 | 
						|
	else
 | 
						|
		sub_gid_close();
 | 
						|
 | 
						|
	*in_ranges = ranges;
 | 
						|
	return count;
 | 
						|
}
 | 
						|
 | 
						|
static bool all_digits(const char *str)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
 | 
						|
	for (i = 0; str[i] != '\0'; i++)
 | 
						|
		if (!isdigit(str[i]))
 | 
						|
			return false;
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
static int append_uids(uid_t **uids, const char *owner, int n)
 | 
						|
{
 | 
						|
	uid_t owner_uid;
 | 
						|
	uid_t *ret;
 | 
						|
	int i;
 | 
						|
 | 
						|
	if (all_digits(owner)) {
 | 
						|
		i = sscanf(owner, "%d", &owner_uid);
 | 
						|
		if (i != 1) {
 | 
						|
			// should not happen
 | 
						|
			free(*uids);
 | 
						|
			*uids = NULL;
 | 
						|
			return -1;
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		struct passwd *pwd = getpwnam(owner);
 | 
						|
		if (NULL == pwd) {
 | 
						|
			/* Username not defined in /etc/passwd, or error occurred during lookup */
 | 
						|
			free(*uids);
 | 
						|
			*uids = NULL;
 | 
						|
			return -1;
 | 
						|
		}
 | 
						|
		owner_uid = pwd->pw_uid;
 | 
						|
	}
 | 
						|
 | 
						|
	for (i = 0; i < n; i++) {
 | 
						|
		if (owner_uid == (*uids)[i])
 | 
						|
			return n;
 | 
						|
	}
 | 
						|
 | 
						|
	ret = REALLOCARRAY(*uids, n + 1, uid_t);
 | 
						|
	if (!ret) {
 | 
						|
		free(*uids);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
	ret[n] = owner_uid;
 | 
						|
	*uids = ret;
 | 
						|
	return n+1;
 | 
						|
}
 | 
						|
 | 
						|
int find_subid_owners(unsigned long id, enum subid_type id_type, uid_t **uids)
 | 
						|
{
 | 
						|
	const struct subordinate_range *range;
 | 
						|
	struct subid_nss_ops *h;
 | 
						|
	enum subid_status status;
 | 
						|
	struct commonio_db *db;
 | 
						|
	int n = 0;
 | 
						|
 | 
						|
	h = get_subid_nss_handle();
 | 
						|
	if (h) {
 | 
						|
		status = h->find_subid_owners(id, id_type, uids, &n);
 | 
						|
		// Several ways we could handle the error cases here.
 | 
						|
		if (status != SUBID_STATUS_SUCCESS)
 | 
						|
			return -1;
 | 
						|
		return n;
 | 
						|
	}
 | 
						|
 | 
						|
	switch (id_type) {
 | 
						|
	case ID_TYPE_UID:
 | 
						|
		if (!sub_uid_open(O_RDONLY)) {
 | 
						|
			return -1;
 | 
						|
		}
 | 
						|
		db = &subordinate_uid_db;
 | 
						|
		break;
 | 
						|
	case ID_TYPE_GID:
 | 
						|
		if (!sub_gid_open(O_RDONLY)) {
 | 
						|
			return -1;
 | 
						|
		}
 | 
						|
		db = &subordinate_gid_db;
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	*uids = NULL;
 | 
						|
 | 
						|
	commonio_rewind(db);
 | 
						|
	while ((range = commonio_next(db)) != NULL) {
 | 
						|
		if (id >= range->start && id < range->start + range-> count) {
 | 
						|
			n = append_uids(uids, range->owner, n);
 | 
						|
			if (n < 0)
 | 
						|
				break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (id_type == ID_TYPE_UID)
 | 
						|
		sub_uid_close();
 | 
						|
	else
 | 
						|
		sub_gid_close();
 | 
						|
 | 
						|
	return n;
 | 
						|
}
 | 
						|
 | 
						|
bool new_subid_range(struct subordinate_range *range, enum subid_type id_type, bool reuse)
 | 
						|
{
 | 
						|
	struct commonio_db *db;
 | 
						|
	const struct subordinate_range *r;
 | 
						|
	bool ret;
 | 
						|
 | 
						|
	if (get_subid_nss_handle())
 | 
						|
		return false;
 | 
						|
 | 
						|
	switch (id_type) {
 | 
						|
	case ID_TYPE_UID:
 | 
						|
		if (!sub_uid_lock()) {
 | 
						|
			printf("Failed locking subuids (errno %d)\n", errno);
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		if (!sub_uid_open(O_CREAT | O_RDWR)) {
 | 
						|
			printf("Failed opening subuids (errno %d)\n", errno);
 | 
						|
			sub_uid_unlock();
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		db = &subordinate_uid_db;
 | 
						|
		break;
 | 
						|
	case ID_TYPE_GID:
 | 
						|
		if (!sub_gid_lock()) {
 | 
						|
			printf("Failed locking subgids (errno %d)\n", errno);
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		if (!sub_gid_open(O_CREAT | O_RDWR)) {
 | 
						|
			printf("Failed opening subgids (errno %d)\n", errno);
 | 
						|
			sub_gid_unlock();
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		db = &subordinate_gid_db;
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	commonio_rewind(db);
 | 
						|
	if (reuse) {
 | 
						|
		while ((r = commonio_next(db)) != NULL) {
 | 
						|
			// TODO account for username vs uid_t
 | 
						|
			if (0 != strcmp(r->owner, range->owner))
 | 
						|
				continue;
 | 
						|
			if (r->count >= range->count) {
 | 
						|
				range->count = r->count;
 | 
						|
				range->start = r->start;
 | 
						|
				return true;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	range->start = find_free_range(db, range->start, ULONG_MAX, range->count);
 | 
						|
 | 
						|
	if (range->start == ULONG_MAX) {
 | 
						|
		ret = false;
 | 
						|
		goto out;
 | 
						|
	}
 | 
						|
 | 
						|
	ret = add_range(db, range->owner, range->start, range->count) == 1;
 | 
						|
 | 
						|
out:
 | 
						|
	if (id_type == ID_TYPE_UID) {
 | 
						|
		sub_uid_close();
 | 
						|
		sub_uid_unlock();
 | 
						|
	} else {
 | 
						|
		sub_gid_close();
 | 
						|
		sub_gid_unlock();
 | 
						|
	}
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
bool release_subid_range(struct subordinate_range *range, enum subid_type id_type)
 | 
						|
{
 | 
						|
	struct commonio_db *db;
 | 
						|
	bool ret;
 | 
						|
 | 
						|
	if (get_subid_nss_handle())
 | 
						|
		return false;
 | 
						|
 | 
						|
	switch (id_type) {
 | 
						|
	case ID_TYPE_UID:
 | 
						|
		if (!sub_uid_lock()) {
 | 
						|
			printf("Failed locking subuids (errno %d)\n", errno);
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		if (!sub_uid_open(O_CREAT | O_RDWR)) {
 | 
						|
			printf("Failed opening subuids (errno %d)\n", errno);
 | 
						|
			sub_uid_unlock();
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		db = &subordinate_uid_db;
 | 
						|
		break;
 | 
						|
	case ID_TYPE_GID:
 | 
						|
		if (!sub_gid_lock()) {
 | 
						|
			printf("Failed locking subgids (errno %d)\n", errno);
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		if (!sub_gid_open(O_CREAT | O_RDWR)) {
 | 
						|
			printf("Failed opening subgids (errno %d)\n", errno);
 | 
						|
			sub_gid_unlock();
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		db = &subordinate_gid_db;
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	ret = remove_range(db, range->owner, range->start, range->count) == 1;
 | 
						|
 | 
						|
	if (id_type == ID_TYPE_UID) {
 | 
						|
		sub_uid_close();
 | 
						|
		sub_uid_unlock();
 | 
						|
	} else {
 | 
						|
		sub_gid_close();
 | 
						|
		sub_gid_unlock();
 | 
						|
	}
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
#else				/* !ENABLE_SUBIDS */
 | 
						|
extern int ISO_C_forbids_an_empty_translation_unit;
 | 
						|
#endif				/* !ENABLE_SUBIDS */
 | 
						|
 |