Signed-off-by: "Eric W. Biederman" <ebiederm@xxxxxxxxxxxx> --- man/usermod.8.xml | 80 +++++++++++++++++ src/usermod.c | 255 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 332 insertions(+), 3 deletions(-) diff --git a/man/usermod.8.xml b/man/usermod.8.xml index 322d181..0a23416 100644 --- a/man/usermod.8.xml +++ b/man/usermod.8.xml @@ -391,6 +391,86 @@ </varlistentry> <varlistentry> <term> + <option>-v</option>, <option>--add-sub-uids</option> + <replaceable>FIRST</replaceable>-<replaceable>LAST</replaceable> + </term> + <listitem> + <para> + Add a range of subordinate uids to the users account. + </para> + <para> + This option may be specified multiple times to add multiple ranges to a users account. + </para> + <para> + No checks will be performed with regard to + <option>SUB_UID_MIN</option>, <option>SUB_UID_MAX</option>, or + <option>SUB_UID_COUNT</option> from /etc/login.defs. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-V</option>, <option>--del-sub-uids</option> + <replaceable>FIRST</replaceable>-<replaceable>LAST</replaceable> + </term> + <listitem> + <para> + Remove a range of subordinate uids from the users account. + </para> + <para> + This option may be specified multiple times to remove multiple ranges to a users account. + When both <option>--del-sub-uids</option> and <option>--add-sub-uids</option> are specified + remove of all subordinate uid ranges happens before any subordinate uid ranges are added. + </para> + <para> + No checks will be performed with regard to + <option>SUB_UID_MIN</option>, <option>SUB_UID_MAX</option>, or + <option>SUB_UID_COUNT</option> from /etc/login.defs. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-w</option>, <option>--add-sub-gids</option> + <replaceable>FIRST</replaceable>-<replaceable>LAST</replaceable> + </term> + <listitem> + <para> + Add a range of subordinate gids to the users account. + </para> + <para> + This option may be specified multiple times to add multiple ranges to a users account. + </para> + <para> + No checks will be performed with regard to + <option>SUB_GID_MIN</option>, <option>SUB_GID_MAX</option>, or + <option>SUB_GID_COUNT</option> from /etc/login.defs. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-W</option>, <option>--del-sub-gids</option> + <replaceable>FIRST</replaceable>-<replaceable>LAST</replaceable> + </term> + <listitem> + <para> + Remove a range of subordinate gids from the users account. + </para> + <para> + This option may be specified multiple times to remove multiple ranges to a users account. + When both <option>--del-sub-gids</option> and <option>--add-sub-gids</option> are specified + remove of all subordinate gid ranges happens before any subordinate gid ranges are added. + </para> + <para> + No checks will be performed with regard to + <option>SUB_GID_MIN</option>, <option>SUB_GID_MAX</option>, or + <option>SUB_GID_COUNT</option> from /etc/login.defs. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> <option>-Z</option>, <option>--selinux-user</option> <replaceable>SEUSER</replaceable> </term> diff --git a/src/usermod.c b/src/usermod.c index e411a32..448eed0 100644 --- a/src/usermod.c +++ b/src/usermod.c @@ -63,6 +63,7 @@ #include "sgroupio.h" #endif #include "shadowio.h" +#include "subordinateio.h" #ifdef WITH_TCB #include "tcbfuncs.h" #endif @@ -86,6 +87,8 @@ /* #define E_NOSPACE 11 insufficient space to move home dir */ #define E_HOMEDIR 12 /* unable to complete home dir move */ #define E_SE_UPDATE 13 /* can't update SELinux user mapping */ +#define E_SUB_UID_UPDATE 16 /* can't update the subordinate uid file */ +#define E_SUB_GID_UPDATE 18 /* can't update the subordinate gid file */ #define VALID(s) (strcspn (s, ":\n") == strlen (s)) /* * Global variables @@ -133,7 +136,11 @@ static bool Zflg = false, /* new selinux user */ #endif uflg = false, /* specify new user ID */ - Uflg = false; /* unlock the password */ + Uflg = false, /* unlock the password */ + vflg = false, /* add subordinate uids */ + Vflg = false, /* delete subordinate uids */ + wflg = false, /* add subordinate gids */ + Wflg = false; /* delete subordinate gids */ static bool is_shadow_pwd; @@ -141,12 +148,17 @@ static bool is_shadow_pwd; static bool is_shadow_grp; #endif +static bool is_sub_uid = false; +static bool is_sub_gid = false; + static bool pw_locked = false; static bool spw_locked = false; static bool gr_locked = false; #ifdef SHADOWGRP static bool sgr_locked = false; #endif +static bool sub_uid_locked = false; +static bool sub_gid_locked = false; /* local function prototypes */ @@ -302,6 +314,69 @@ static int get_groups (char *list) return 0; } +struct ulong_range +{ + unsigned long first; + unsigned long last; +}; + +static struct ulong_range getulong_range(const char *str) +{ + struct ulong_range result = { .first = ULONG_MAX, .last = 0 }; + unsigned long long first, last; + char *pos; + + errno = 0; + first = strtoll(str, &pos, 10); + if (('\0' == *str) || ('-' != *pos ) || (ERANGE == errno) || + (first != (unsigned long int)first)) + goto out; + + errno = 0; + last = strtoul(pos + 1, &pos, 10); + if (('\0' != *pos ) || (ERANGE == errno) || + (last != (unsigned long int)last)) + goto out; + + if (first > last) + goto out; + + result.first = (unsigned long int)first; + result.last = (unsigned long int)last; +out: + return result; + +} + +struct ulong_range_list_entry { + struct ulong_range_list_entry *next; + struct ulong_range range; +}; + +static struct ulong_range_list_entry *add_sub_uids = NULL, *del_sub_uids = NULL; +static struct ulong_range_list_entry *add_sub_gids = NULL, *del_sub_gids = NULL; + +static int prepend_range(const char *str, struct ulong_range_list_entry **head) +{ + struct ulong_range range; + struct ulong_range_list_entry *entry; + range = getulong_range(str); + if (range.first > range.last) + return 0; + + entry = malloc(sizeof(*entry)); + if (!entry) { + fprintf (stderr, + _("%s: failed to allocate memory: %s\n"), + Prog, strerror (errno)); + return 0; + } + entry->next = *head; + entry->range = range; + *head = entry; + return 1; +} + /* * usage - display usage message and exit */ @@ -334,6 +409,10 @@ static /*@noreturn@*/void usage (int status) (void) fputs (_(" -s, --shell SHELL new login shell for the user account\n"), usageout); (void) fputs (_(" -u, --uid UID new UID for the user account\n"), usageout); (void) fputs (_(" -U, --unlock unlock the user account\n"), usageout); + (void) fputs (_(" -v, --add-subuids FIRST-LAST add range of subordinate uids\n"), usageout); + (void) fputs (_(" -V, --del-subuids FIRST-LAST remvoe range of subordinate uids\n"), usageout); + (void) fputs (_(" -w, --add-subgids FIRST-LAST add range of subordinate gids\n"), usageout); + (void) fputs (_(" -W, --del-subgids FIRST-LAST remvoe range of subordinate gids\n"), usageout); #ifdef WITH_SELINUX (void) fputs (_(" -Z, --selinux-user SEUSER new SELinux user mapping for the user account\n"), usageout); #endif /* WITH_SELINUX */ @@ -590,6 +669,20 @@ static /*@noreturn@*/void fail_exit (int code) /* continue */ } } + if (sub_uid_locked) { + if (sub_uid_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_uid_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sub_uid_dbname ())); + /* continue */ + } + } + if (sub_gid_locked) { + if (sub_gid_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_gid_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sub_gid_dbname ())); + /* continue */ + } + } #ifdef WITH_AUDIT audit_logger (AUDIT_USER_CHAUTHTOK, Prog, @@ -889,6 +982,10 @@ static void process_flags (int argc, char **argv) {"shell", required_argument, NULL, 's'}, {"uid", required_argument, NULL, 'u'}, {"unlock", no_argument, NULL, 'U'}, + {"add-subuids", required_argument, NULL, 'v'}, + {"del-subuids", required_argument, NULL, 'V'}, + {"add-subgids", required_argument, NULL, 'w'}, + {"del-subgids", required_argument, NULL, 'W'}, #ifdef WITH_SELINUX {"selinux-user", required_argument, NULL, 'Z'}, #endif /* WITH_SELINUX */ @@ -1018,6 +1115,41 @@ static void process_flags (int argc, char **argv) case 'U': Uflg = true; break; + case 'v': + if (prepend_range (optarg, &add_sub_uids) == 0) { + fprintf (stderr, + _("%s: invalid subordinate uid range '%s'\n"), + Prog, optarg); + exit(E_BAD_ARG); + } + vflg = true; + break; + case 'V': + if (prepend_range (optarg, &del_sub_uids) == 0) { + fprintf (stderr, + _("%s: invalid subordinate uid range '%s'\n"), + Prog, optarg); + exit(E_BAD_ARG); + } + Vflg = true; + break; + case 'w': + if (prepend_range (optarg, &add_sub_gids) == 0) { + fprintf (stderr, + _("%s: invalid subordinate gid range '%s'\n"), + Prog, optarg); + exit(E_BAD_ARG); + } + wflg = true; + case 'W': + if (prepend_range (optarg, &del_sub_gids) == 0) { + fprintf (stderr, + _("%s: invalid subordinate gid range '%s'\n"), + Prog, optarg); + exit(E_BAD_ARG); + } + Wflg = true; + break; #ifdef WITH_SELINUX case 'Z': if (is_selinux_enabled () > 0) { @@ -1170,6 +1302,7 @@ static void process_flags (int argc, char **argv) if (!(Uflg || uflg || sflg || pflg || mflg || Lflg || lflg || Gflg || gflg || fflg || eflg || dflg || cflg + || vflg || Vflg || wflg || Wflg #ifdef WITH_SELINUX || Zflg #endif /* WITH_SELINUX */ @@ -1200,6 +1333,7 @@ static void process_flags (int argc, char **argv) Prog, (unsigned long) user_newid); exit (E_UID_IN_USE); } + } /* @@ -1248,6 +1382,10 @@ static void close_files (void) sgr_dbname ())); fail_exit (E_GRP_UPDATE); } + } +#endif +#ifdef SHADOWGRP + if (is_shadow_grp) { if (sgr_unlock () == 0) { fprintf (stderr, _("%s: failed to unlock %s\n"), @@ -1296,6 +1434,33 @@ static void close_files (void) sgr_locked = false; #endif + if (vflg || Vflg) { + if (!is_sub_uid || (sub_uid_close () == 0)) { + fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, sub_uid_dbname ()); + SYSLOG ((LOG_ERR, "failure while writing changes to %s", sub_uid_dbname ())); + fail_exit (E_SUB_UID_UPDATE); + } + if (!is_sub_uid || (sub_uid_unlock () == 0)) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_uid_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sub_uid_dbname ())); + /* continue */ + } + sub_uid_locked = false; + } + if (wflg || Wflg) { + if (!is_sub_gid || (sub_gid_close () == 0)) { + fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, sub_gid_dbname ()); + SYSLOG ((LOG_ERR, "failure while writing changes to %s", sub_gid_dbname ())); + fail_exit (E_SUB_GID_UPDATE); + } + if (!is_sub_gid || (sub_gid_unlock () == 0)) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_gid_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sub_gid_dbname ())); + /* continue */ + } + sub_gid_locked = false; + } + /* * Close the DBM and/or flat files */ @@ -1375,6 +1540,36 @@ static void open_files (void) } #endif } + if (vflg || Vflg) { + if (!is_sub_uid || (sub_uid_lock () == 0)) { + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, sub_uid_dbname ()); + fail_exit (E_SUB_UID_UPDATE); + } + sub_uid_locked = true; + if (!is_sub_uid || (sub_uid_open (O_RDWR) == 0)) { + fprintf (stderr, + _("%s: cannot open %s\n"), + Prog, sub_uid_dbname ()); + fail_exit (E_SUB_UID_UPDATE); + } + } + if (wflg || Wflg) { + if (!is_sub_gid || (sub_gid_lock () == 0)) { + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, sub_gid_dbname ()); + fail_exit (E_SUB_GID_UPDATE); + } + sub_gid_locked = true; + if (!is_sub_gid || (sub_gid_open (O_RDWR) == 0)) { + fprintf (stderr, + _("%s: cannot open %s\n"), + Prog, sub_gid_dbname ()); + fail_exit (E_SUB_GID_UPDATE); + } + } } /* @@ -1476,6 +1671,58 @@ static void usr_update (void) fail_exit (E_PW_UPDATE); } } + if (Vflg) { + struct ulong_range_list_entry *ptr; + for (ptr = del_sub_uids; ptr != NULL; ptr = ptr->next) { + unsigned long count = ptr->range.last - ptr->range.first + 1; + if (sub_uid_remove(user_name, ptr->range.first, count) == 0) { + fprintf (stderr, + _("%s: failed to remove uid range %lu-%lu from '%s'\n"), + Prog, ptr->range.first, ptr->range.last, + sub_uid_dbname ()); + fail_exit (E_SUB_UID_UPDATE); + } + } + } + if (vflg) { + struct ulong_range_list_entry *ptr; + for (ptr = add_sub_uids; ptr != NULL; ptr = ptr->next) { + unsigned long count = ptr->range.last - ptr->range.first + 1; + if (sub_uid_add(user_name, ptr->range.first, count) == 0) { + fprintf (stderr, + _("%s: failed to add uid range %lu-%lu from '%s'\n"), + Prog, ptr->range.first, ptr->range.last, + sub_uid_dbname ()); + fail_exit (E_SUB_UID_UPDATE); + } + } + } + if (Wflg) { + struct ulong_range_list_entry *ptr; + for (ptr = del_sub_gids; ptr != NULL; ptr = ptr->next) { + unsigned long count = ptr->range.last - ptr->range.first + 1; + if (sub_gid_remove(user_name, ptr->range.first, count) == 0) { + fprintf (stderr, + _("%s: failed to remove gid range %lu-%lu from '%s'\n"), + Prog, ptr->range.first, ptr->range.last, + sub_gid_dbname ()); + fail_exit (E_SUB_GID_UPDATE); + } + } + } + if (wflg) { + struct ulong_range_list_entry *ptr; + for (ptr = add_sub_gids; ptr != NULL; ptr = ptr->next) { + unsigned long count = ptr->range.last - ptr->range.first + 1; + if (sub_gid_add(user_name, ptr->range.first, count) == 0) { + fprintf (stderr, + _("%s: failed to add gid range %lu-%lu from '%s'\n"), + Prog, ptr->range.first, ptr->range.last, + sub_gid_dbname ()); + fail_exit (E_SUB_GID_UPDATE); + } + } + } } /* @@ -1811,6 +2058,8 @@ int main (int argc, char **argv) #ifdef SHADOWGRP is_shadow_grp = sgr_file_present (); #endif + is_sub_uid = sub_uid_file_present (); + is_sub_gid = sub_gid_file_present (); process_flags (argc, argv); @@ -1818,7 +2067,7 @@ int main (int argc, char **argv) * The home directory, the username and the user's UID should not * be changed while the user is logged in. */ - if ( (uflg || lflg || dflg) + if ( (uflg || lflg || dflg || Vflg || Wflg) && (user_busy (user_name, user_id) != 0)) { exit (E_USER_BUSY); } @@ -1871,7 +2120,7 @@ int main (int argc, char **argv) */ open_files (); if ( cflg || dflg || eflg || fflg || gflg || Lflg || lflg || pflg - || sflg || uflg || Uflg) { + || sflg || uflg || Uflg || vflg || Vflg || wflg || Wflg) { usr_update (); } if (Gflg || lflg) { -- 1.7.5.4 _______________________________________________ Containers mailing list Containers@xxxxxxxxxxxxxxxxxxxxxxxxxx https://lists.linuxfoundation.org/mailman/listinfo/containers