diff --git a/etc/login.defs b/etc/login.defs index 35a1cfb2..5ac62d63 100644 --- a/etc/login.defs +++ b/etc/login.defs @@ -226,6 +226,10 @@ UID_MAX 60000 # System accounts SYS_UID_MIN 101 SYS_UID_MAX 999 +# Extra per user uids +SUB_UID_MIN 100000 +SUB_UID_MAX 600100000 +SUB_UID_COUNT 10000 # # Min/max values for automatic gid selection in groupadd(8) @@ -235,6 +239,10 @@ GID_MAX 60000 # System accounts SYS_GID_MIN 101 SYS_GID_MAX 999 +# Extra per user group ids +SUB_GID_MIN 100000 +SUB_GID_MAX 600100000 +SUB_GID_COUNT 10000 # # Max number of login(1) retries if password is bad diff --git a/lib/Makefile.am b/lib/Makefile.am index c448dd30..6db86cd6 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -39,6 +39,8 @@ libshadow_la_SOURCES = \ pwio.c \ pwio.h \ pwmem.c \ + subordinateio.h \ + subordinateio.c \ selinux.c \ semanage.c \ sgetgrent.c \ diff --git a/lib/getdef.c b/lib/getdef.c index 19c756bd..b5f780ca 100644 --- a/lib/getdef.c +++ b/lib/getdef.c @@ -81,6 +81,12 @@ static struct itemdef def_table[] = { {"SHA_CRYPT_MAX_ROUNDS", NULL}, {"SHA_CRYPT_MIN_ROUNDS", NULL}, #endif + {"SUB_GID_COUNT", NULL}, + {"SUB_GID_MAX", NULL}, + {"SUB_GID_MIN", NULL}, + {"SUB_UID_COUNT", NULL}, + {"SUB_UID_MAX", NULL}, + {"SUB_UID_MIN", NULL}, {"SULOG_FILE", NULL}, {"SU_NAME", NULL}, {"SYS_GID_MAX", NULL}, diff --git a/lib/subordinateio.c b/lib/subordinateio.c new file mode 100644 index 00000000..851cfa05 --- /dev/null +++ b/lib/subordinateio.c @@ -0,0 +1,512 @@ +/* + * Copyright (c) 2012 - Eric Biederman + */ + +#include +#include "prototypes.h" +#include "defines.h" +#include +#include "commonio.h" +#include "subordinateio.h" + +struct subordinate_range { + const char *owner; + unsigned long start; + unsigned long count; +}; + +#define NFIELDS 3 + +static /*@null@*/ /*@only@*/void *subordinate_dup (const void *ent) +{ + const struct subordinate_range *rangeent = ent; + struct subordinate_range *range; + + range = (struct subordinate_range *) malloc (sizeof *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; +} + +static void subordinate_free (/*@out@*/ /*@only@*/void *ent) +{ + struct subordinate_range *rangeent = ent; + + free ((void *)(rangeent->owner)); + free (rangeent); +} + +static void *subordinate_parse (const char *line) +{ + static struct subordinate_range range; + char rangebuf[1024]; + int i; + char *cp; + char *fields[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 < 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 NFIELDS colon separated fields or + * the entry is invalid. Also, fields must be non-blank. + */ + if (i != 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 ⦥ +} + +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 */ +}; + +static /*@observer@*/ /*@null*/const struct subordinate_range *subordinate_next(struct commonio_db *db) +{ + commonio_next (db); +} + +static bool is_range_free(struct commonio_db *db, unsigned long start, + unsigned long count) +{ + const struct subordinate_range *range; + unsigned long end = start + count - 1; + + commonio_rewind(db); + while ((range = commonio_next(db)) != NULL) { + unsigned long first = range->start; + unsigned long last = first + range->count - 1; + + if ((end >= first) && (start <= last)) + return false; + } + return true; +} + +static const bool range_exists(struct commonio_db *db, const char *owner) +{ + const struct subordinate_range *range; + 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)) + return true; + } + return false; +} + +static const struct subordinate_range *find_range(struct commonio_db *db, + const char *owner, unsigned long val) +{ + const struct subordinate_range *range; + 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; + } + return NULL; +} + +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 int subordinate_range_cmp (const void *p1, const void *p2) +{ + struct subordinate_range *range1, *range2; + + if ((*(struct commonio_entry **) p1)->eptr == NULL) + return 1; + if ((*(struct commonio_entry **) p2)->eptr == NULL) + return -1; + + range1 = ((struct subordinate_range *) (*(struct commonio_entry **) p1)->eptr); + range2 = ((struct subordinate_range *) (*(struct commonio_entry **) p2)->eptr); + + 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); +} + +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 than 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; + if (high > max) + high = max; + + /* Is the hole before this range large enough? */ + if ((high > low) && (((high - low) + 1) >= 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; +} + +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; + + /* Oterwise append the range */ + return commonio_append(db, &range); +} + +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; ent; ent = ent->next) { + struct subordinate_range *range = ent->eptr; + unsigned long first; + unsigned long last; + + /* Skip unparsed entries */ + if (!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; + + /* Is entry completely contained in the range to remove? */ + if ((start <= first) && (end >= last)) { + commonio_del_entry (db, ent); + } + /* Is just the start of the entry removed? */ + else if ((start <= first) && (end < last)) { + range->start = end + 1; + range->count = (last - range->start) + 1; + + ent->changed = true; + } + /* Is just the end of the entry removed? */ + else if ((start > first) && (end >= last)) { + range->count = (start - range->start) + 1; + + ent->changed = true; + } + /* The middle of the range is removed */ + else { + struct subordinate_range tail; + tail.owner = range->owner; + tail.start = end + 1; + tail.count = (last - tail.start) + 1; + + if (!commonio_append(db, &tail)) + return 0; + + range->count = (start - range->start) + 1; + + ent->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 + NULL, /* head */ + NULL, /* tail */ + NULL, /* cursor */ + false, /* changed */ + false, /* isopen */ + false, /* locked */ + false /* readonly */ +}; + +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 is_sub_uid_range_free(uid_t start, unsigned long count) +{ + return is_range_free (&subordinate_uid_db, start, count); +} + +bool 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) +{ + return have_range (&subordinate_uid_db, owner, start, count); +} + +int sub_uid_add (const char *owner, uid_t start, unsigned long count) +{ + return add_range (&subordinate_uid_db, owner, start, count); +} + +int sub_uid_remove (const char *owner, uid_t start, unsigned long count) +{ + 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 + NULL, /* head */ + NULL, /* tail */ + NULL, /* cursor */ + false, /* changed */ + false, /* isopen */ + false, /* locked */ + false /* readonly */ +}; + +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 is_sub_gid_range_free(gid_t start, unsigned long count) +{ + return is_range_free (&subordinate_gid_db, start, count); +} + +bool have_sub_gids(const char *owner, gid_t start, unsigned long count) +{ + return have_range(&subordinate_gid_db, owner, start, count); +} + +bool 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) +{ + return add_range (&subordinate_gid_db, owner, start, count); +} + +int sub_gid_remove (const char *owner, gid_t start, unsigned long count) +{ + 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; +} diff --git a/lib/subordinateio.h b/lib/subordinateio.h new file mode 100644 index 00000000..f27b3c0b --- /dev/null +++ b/lib/subordinateio.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2012- Eric W. Biederman + */ + +#ifndef _SUBORDINATEIO_H +#define _SUBORDINATEIO_H + +#include + +extern int sub_uid_close(void); +extern bool is_sub_uid_range_free(uid_t start, unsigned long count); +extern bool have_sub_uids(const char *owner, uid_t start, unsigned long count); +extern bool sub_uid_file_present (void); +extern bool sub_uid_assigned(const char *owner); +extern int sub_uid_lock (void); +extern int sub_uid_setdbname (const char *filename); +extern /*@observer@*/const char *sub_uid_dbname (void); +extern int sub_uid_open (int mode); +extern int sub_uid_unlock (void); +extern int sub_uid_add (const char *owner, uid_t start, unsigned long count); +extern int sub_uid_remove (const char *owner, uid_t start, unsigned long count); +extern uid_t sub_uid_find_free_range(uid_t min, uid_t max, unsigned long count); + +extern int sub_gid_close(void); +extern bool is_sub_gid_range_free(gid_t start, unsigned long count); +extern bool have_sub_gids(const char *owner, gid_t start, unsigned long count); +extern bool sub_gid_file_present (void); +extern bool sub_gid_assigned(const char *owner); +extern int sub_gid_lock (void); +extern int sub_gid_setdbname (const char *filename); +extern /*@observer@*/const char *sub_gid_dbname (void); +extern int sub_gid_open (int mode); +extern int sub_gid_unlock (void); +extern int sub_gid_add (const char *owner, gid_t start, unsigned long count); +extern int sub_gid_remove (const char *owner, gid_t start, unsigned long count); +extern uid_t sub_gid_find_free_range(gid_t min, gid_t max, unsigned long count); + +#endif