123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750 |
- /*++
- Copyright (c) 2015 Minoca Corp. All Rights Reserved
- Module Name:
- useradd.c
- Abstract:
- This module implements support for the useradd utility, which adds a new
- user to the system.
- Author:
- Evan Green 10-Mar-2015
- Environment:
- POSIX
- --*/
- //
- // ------------------------------------------------------------------- Includes
- //
- #include <minoca/lib/types.h>
- #include <assert.h>
- #include <dirent.h>
- #include <errno.h>
- #include <getopt.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include "../swlib.h"
- #include "lutil.h"
- //
- // ---------------------------------------------------------------- Definitions
- //
- #define USERADD_VERSION_MAJOR 1
- #define USERADD_VERSION_MINOR 0
- #define USERADD_USAGE \
- "usage: useradd [options] username\n" \
- "The useradd utility adds a new user to the system. Options are:\n" \
- " -b, --base-dir=dir -- Sets the base directory for the home directory \n"\
- " of the new account (eg. /home).\n" \
- " -c, --comment=gecos -- Sets the GECOS field.\n" \
- " -d, --home=dir -- Sets the home directory.\n" \
- " -g, --gid=group -- Sets the name or ID of the primary group for the\n" \
- " new account.\n" \
- " -G, --groups=group,group -- Sets the supplementary groups.\n" \
- " -k, --skel=dir -- Sets the alternate skeleton directory location.\n" \
- " -m, --create-home -- Creates the home directory if it does not exist.\n"\
- " -M, --no-create-home -- Do not create the home directory.\n" \
- " -N, --no-user-group -- Do not create a group with the same name as \n" \
- " the user.\n" \
- " -o, --non-unique -- Allow users with duplicate IDs.\n" \
- " -p, --password=pw -- Sets the user's password.\n" \
- " -R, --root=dir -- Chroot into the given directory before operating.\n" \
- " -r, --system -- Sets this as a system account.\n" \
- " -s, --shell=shell -- Sets the user's shell.\n" \
- " -u, --uid=id -- Sets the user ID of the new user.\n" \
- " -U, --user-group -- Create a group with the same name as the user.\n" \
- " --help -- Displays this help text and exits.\n" \
- " --version -- Displays the application version and exits.\n"
- #define USERADD_OPTIONS_STRING "b:c:d:g:G:k:mMNop:R:rs:u:UHV"
- #define USERADD_DEFAULT_GECOS ""
- #define USERADD_DEFAULT_SKELETON "/etc/skel"
- #define USERADD_DEFAULT_SHELL "/bin/sh"
- #define USERADD_DEFAULT_PASSWORD "x"
- #define USERADD_DEFAULT_SHADOW_PASSWORD "*"
- #define USERADD_DEFAULT_GROUP "nogroup"
- #define USERADD_DEFAULT_BASE_DIRECTORY "/home"
- #define USERADD_HOME_PERMISSIONS \
- (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | S_ISGID)
- #define USERADD_MAX_ID 0x7FFFFFFE
- //
- // Define application options.
- //
- //
- // Set this option to create the home directory if it does not exist.
- //
- #define USERADD_OPTION_CREATE_HOME 0x00000001
- //
- // Set this option to create a group with the same name as the user.
- //
- #define USERADD_OPTION_CREATE_GROUP 0x00000002
- //
- // Set this option to create a system account.
- //
- #define USERADD_OPTION_SYSTEM 0x00000004
- //
- // Set this option to allow non-unique user IDs.
- //
- #define USERADD_OPTION_NON_UNIQUE 0x00000008
- //
- // Define the default algorithm to use on new accounts: SHA-512.
- //
- #define USERADD_PASSWORD_ALGORITHM "$6$"
- //
- // ------------------------------------------------------ Data Type Definitions
- //
- //
- // ----------------------------------------------- Internal Function Prototypes
- //
- BOOL
- UseraddIsValidUserName (
- PSTR Name
- );
- INT
- UseraddCreateSelfieGroup (
- PSTR Name,
- gid_t Id
- );
- INT
- UseraddAddUserToSupplementaryGroups (
- PSTR User,
- PSTR SupplementaryGroups
- );
- //
- // -------------------------------------------------------------------- Globals
- //
- struct option UseraddLongOptions[] = {
- {"base-dir", required_argument, 0, 'b'},
- {"comment", required_argument, 0, 'c'},
- {"home", required_argument, 0, 'd'},
- {"gid", required_argument, 0, 'g'},
- {"groups", required_argument, 0, 'G'},
- {"skel", required_argument, 0, 'k'},
- {"create-home", no_argument, 0, 'm'},
- {"no-create-home", no_argument, 0, 'M'},
- {"no-user-group", no_argument, 0, 'N'},
- {"non-unique", no_argument, 0, 'o'},
- {"password", required_argument, 0, 'p'},
- {"root", required_argument, 0, 'R'},
- {"system", no_argument, 0, 'r'},
- {"shell", required_argument, 0, 's'},
- {"uid", required_argument, 0, 'u'},
- {"user-group", no_argument, 0, 'U'},
- {"help", no_argument, 0, 'H'},
- {"version", no_argument, 0, 'V'},
- {NULL, 0, 0, 0},
- };
- //
- // ------------------------------------------------------------------ Functions
- //
- INT
- UseraddMain (
- INT ArgumentCount,
- CHAR **Arguments
- )
- /*++
- Routine Description:
- This routine is the main entry point for the useradd utility.
- Arguments:
- ArgumentCount - Supplies the number of command line arguments the program
- was invoked with.
- Arguments - Supplies a tokenized array of command line arguments.
- Return Value:
- Returns an integer exit code. 0 for success, nonzero otherwise.
- --*/
- {
- PSTR AfterScan;
- ULONG ArgumentIndex;
- PSTR BaseDirectory;
- CHOWN_CONTEXT ChownContext;
- BOOL CreateGroupSpecified;
- uid_t ExistingId;
- PSTR GroupsString;
- PSTR GroupString;
- PSTR Home;
- ULONG HomeSize;
- INT Option;
- ULONG Options;
- mode_t OriginalUmask;
- PSTR Password;
- BOOL Result;
- PSTR RootDirectory;
- struct spwd Shadow;
- PSTR Skeleton;
- struct stat Stat;
- int Status;
- int TotalStatus;
- struct passwd User;
- PSTR UserName;
- memset(&Shadow, 0, sizeof(Shadow));
- memset(&User, 0, sizeof(User));
- User.pw_uid = -1;
- User.pw_gid = -1;
- BaseDirectory = USERADD_DEFAULT_BASE_DIRECTORY;
- CreateGroupSpecified = FALSE;
- GroupString = NULL;
- GroupsString = NULL;
- Home = NULL;
- Options = USERADD_OPTION_CREATE_GROUP | USERADD_OPTION_CREATE_HOME;
- Password = NULL;
- RootDirectory = NULL;
- Skeleton = USERADD_DEFAULT_SKELETON;
- TotalStatus = 0;
- UserName = NULL;
- User.pw_gecos = USERADD_DEFAULT_GECOS;
- User.pw_passwd = USERADD_DEFAULT_PASSWORD;
- User.pw_shell = USERADD_DEFAULT_SHELL;
- memcpy(&Shadow, &SwShadowTemplate, sizeof(struct spwd));
- Shadow.sp_lstchg = time(NULL) / (3600 * 24);
- OriginalUmask = umask(0);
- //
- // Process the control arguments.
- //
- while (TRUE) {
- Option = getopt_long(ArgumentCount,
- Arguments,
- USERADD_OPTIONS_STRING,
- UseraddLongOptions,
- NULL);
- if (Option == -1) {
- break;
- }
- if ((Option == '?') || (Option == ':')) {
- Status = 1;
- goto MainEnd;
- }
- switch (Option) {
- case 'b':
- BaseDirectory = optarg;
- break;
- case 'c':
- User.pw_gecos = optarg;
- break;
- case 'd':
- User.pw_dir = optarg;
- break;
- case 'g':
- GroupString = optarg;
- Options &= ~USERADD_OPTION_CREATE_GROUP;
- break;
- case 'G':
- GroupsString = optarg;
- break;
- case 'k':
- Skeleton = optarg;
- break;
- case 'm':
- Options |= USERADD_OPTION_CREATE_HOME;
- break;
- case 'M':
- Options &= ~USERADD_OPTION_CREATE_HOME;
- break;
- case 'N':
- Options &= ~USERADD_OPTION_CREATE_GROUP;
- break;
- case 'o':
- Options |= USERADD_OPTION_NON_UNIQUE;
- break;
- case 'p':
- Password = optarg;
- break;
- case 'R':
- RootDirectory = optarg;
- break;
- case 'r':
- Options |= USERADD_OPTION_SYSTEM;
- break;
- case 's':
- User.pw_shell = optarg;
- break;
- case 'u':
- User.pw_uid = strtoul(optarg, &AfterScan, 10);
- if (AfterScan == optarg) {
- SwPrintError(0, optarg, "Invalid user ID");
- Status = 1;
- goto MainEnd;
- }
- break;
- case 'U':
- Options |= USERADD_OPTION_CREATE_GROUP;
- CreateGroupSpecified = TRUE;
- break;
- case 'V':
- SwPrintVersion(USERADD_VERSION_MAJOR, USERADD_VERSION_MINOR);
- return 1;
- case 'H':
- printf(USERADD_USAGE);
- return 1;
- default:
- assert(FALSE);
- Status = 1;
- goto MainEnd;
- }
- }
- ArgumentIndex = optind;
- if (ArgumentIndex > ArgumentCount) {
- ArgumentIndex = ArgumentCount;
- }
- if (ArgumentIndex >= ArgumentCount) {
- SwPrintError(0, NULL, "Argument expected. Try --help for usage");
- return 1;
- }
- UserName = Arguments[ArgumentIndex];
- ArgumentIndex += 1;
- if (ArgumentIndex != ArgumentCount) {
- SwPrintError(0, NULL, "Unexpected additional arguments");
- return 1;
- }
- //
- // Fail on conflicting options.
- //
- if ((CreateGroupSpecified != FALSE) && (GroupString != NULL)) {
- SwPrintError(0, NULL, "-g and -U conflict");
- Status = EINVAL;
- goto MainEnd;
- }
- //
- // Enforce a valid user name.
- //
- if (SwIsValidUserName(UserName) == FALSE) {
- SwPrintError(0, UserName, "Invalid username");
- Status = 1;
- goto MainEnd;
- }
- //
- // Hash up the password. This is done so that accesses to things like
- // libcrypt and /dev/urandom can be done before chrooting.
- //
- if (Password != NULL) {
- Shadow.sp_pwdp = SwCreateHashedPassword(USERADD_PASSWORD_ALGORITHM,
- -1,
- 0,
- Password);
- if (Shadow.sp_pwdp == NULL) {
- SwPrintError(0,
- NULL,
- "Failed to create password, disabling account.");
- Shadow.sp_pwdp = USERADD_DEFAULT_SHADOW_PASSWORD;
- }
- }
- //
- // Chroot if requested.
- //
- if (RootDirectory != NULL) {
- Status = chroot(RootDirectory);
- if (Status != 0) {
- Status = errno;
- SwPrintError(Status, RootDirectory, "Failed to chroot");
- goto MainEnd;
- }
- Status = chdir("/");
- if (Status != 0) {
- Status = errno;
- SwPrintError(Status, RootDirectory, "Failed to chdir");
- goto MainEnd;
- }
- }
- User.pw_name = UserName;
- Shadow.sp_namp = UserName;
- //
- // Create the directory from the base if needed.
- //
- if (User.pw_dir == NULL) {
- Result = SwAppendPath(BaseDirectory,
- strlen(BaseDirectory) + 1,
- UserName,
- strlen(UserName) + 1,
- &Home,
- &HomeSize);
- if (Result == FALSE) {
- Status = ENOMEM;
- goto MainEnd;
- }
- User.pw_dir = Home;
- }
- //
- // Parse the primary and supplementary groups.
- //
- if ((Options & USERADD_OPTION_CREATE_GROUP) == 0) {
- if (GroupString == NULL) {
- GroupString = USERADD_DEFAULT_GROUP;
- }
- }
- if (GroupString != NULL) {
- Status = SwGetGroupIdFromName(GroupString, &(User.pw_gid));
- if (Status != 0) {
- SwPrintError(0, GroupString, "Invalid group");
- goto MainEnd;
- }
- }
- //
- // Ensure there are no duplicates in the user name.
- //
- if (SwGetUserIdFromName(UserName, &ExistingId) == 0) {
- SwPrintError(0,
- NULL,
- "User %s already exists (ID %d)",
- UserName,
- ExistingId);
- Status = 1;
- goto MainEnd;
- }
- //
- // Find a user ID.
- //
- if (User.pw_uid == (uid_t)-1) {
- if ((Options & USERADD_OPTION_SYSTEM) != 0) {
- User.pw_uid = BASE_SYSTEM_UID;
- } else {
- User.pw_uid = BASE_NON_SYSTEM_UID;
- }
- while (User.pw_uid < USERADD_MAX_ID) {
- if ((getpwuid(User.pw_uid) == NULL) &&
- (((Options & USERADD_OPTION_CREATE_GROUP) == 0) ||
- (getgrgid(User.pw_uid) == NULL))) {
- break;
- }
- User.pw_uid += 1;
- }
- if (User.pw_uid >= USERADD_MAX_ID) {
- SwPrintError(0, NULL, "User IDs exhausted");
- Status = 1;
- goto MainEnd;
- }
- if (User.pw_gid == (gid_t)-1) {
- User.pw_gid = User.pw_uid;
- if (getgrnam(UserName) != NULL) {
- SwPrintError(0, UserName, "Group already exists");
- Status = 1;
- goto MainEnd;
- }
- }
- //
- // The user wanted a specific ID.
- //
- } else {
- if ((Options & USERADD_OPTION_NON_UNIQUE) == 0) {
- if (getpwuid(User.pw_uid) != NULL) {
- SwPrintError(0, NULL, "User ID %d in use", User.pw_uid);
- Status = 1;
- goto MainEnd;
- }
- }
- }
- //
- // Create a group specifically for the user.
- //
- if ((Options & USERADD_OPTION_CREATE_GROUP) != 0) {
- User.pw_gid = User.pw_uid;
- Status = UseraddCreateSelfieGroup(UserName, User.pw_uid);
- if (Status != 0) {
- SwPrintError(Status, UserName, "Unable to create group");
- goto MainEnd;
- }
- }
- //
- // Add the passwd and shadow entries.
- //
- Status = SwUpdatePasswordLine(&User, &Shadow, UpdatePasswordAddLine);
- if (Status != 0) {
- SwPrintError(Status, UserName, "Failed to add user");
- goto MainEnd;
- }
- //
- // Add the user to all the supplementary groups.
- //
- if (GroupsString != NULL) {
- Result = UseraddAddUserToSupplementaryGroups(UserName, GroupsString);
- if (Result != 0) {
- goto MainEnd;
- }
- }
- //
- // Create the home directory.
- //
- if ((Options & USERADD_OPTION_CREATE_HOME) != 0) {
- Status = mkdir(User.pw_dir,
- USERADD_HOME_PERMISSIONS & (~S_ISGID));
- if ((Status != 0) && (errno != EEXIST)) {
- Status = errno;
- SwPrintError(Status,
- User.pw_dir,
- "Failed to create home directory");
- goto MainEnd;
- }
- //
- // If the directory was created, copy the skeleton contents over.
- //
- if ((Status == 0) && (stat(Skeleton, &Stat) == 0)) {
- SwCopy(COPY_OPTION_RECURSIVE, Skeleton, User.pw_dir);
- }
- //
- // Change the new user to be the owner of everything in there.
- //
- memset(&ChownContext, 0, sizeof(ChownContext));
- ChownContext.Options = CHOWN_OPTION_RECURSIVE;
- ChownContext.User = User.pw_uid;
- ChownContext.Group = User.pw_gid;
- ChownContext.FromUser = -1;
- ChownContext.FromGroup = -1;
- Status = ChownChangeOwnership(&ChownContext, User.pw_dir, 0);
- if (Status != 0) {
- SwPrintError(Status, User.pw_dir, "Failed to change ownership");
- Status = 0;
- }
- Status = chmod(User.pw_dir, USERADD_HOME_PERMISSIONS);
- if (Status != 0) {
- SwPrintError(errno, User.pw_dir, "Failed to change mode");
- Status = 0;
- }
- }
- Status = 0;
- MainEnd:
- umask(OriginalUmask);
- if (Home != NULL) {
- free(Home);
- }
- if ((TotalStatus == 0) && (Status != 0)) {
- TotalStatus = Status;
- }
- return TotalStatus;
- }
- //
- // --------------------------------------------------------- Internal Functions
- //
- INT
- UseraddCreateSelfieGroup (
- PSTR Name,
- gid_t Id
- )
- /*++
- Routine Description:
- This routine creates a group with the same name as the given user and with
- one member: the user.
- Arguments:
- Name - Supplies a pointer to the user/group name.
- Id - Supplies the ID of the user/group.
- Return Value:
- 0 on success.
- Non-zero on failure.
- --*/
- {
- struct group Group;
- memset(&Group, 0, sizeof(Group));
- Group.gr_name = Name;
- Group.gr_passwd = USERADD_DEFAULT_PASSWORD;
- Group.gr_gid = Id;
- return SwUpdateGroupLine(&Group, UpdatePasswordAddLine);
- }
- INT
- UseraddAddUserToSupplementaryGroups (
- PSTR User,
- PSTR SupplementaryGroups
- )
- /*++
- Routine Description:
- This routine adds the user to all the supplementary groups.
- Arguments:
- User - Supplies the user name to add.
- SupplementaryGroups - Supplies the supplementary groups to add the user to.
- Return Value:
- 0 on success.
- Non-zero on failure.
- --*/
- {
- PSTR Copy;
- PSTR Current;
- PSTR NextComma;
- INT Result;
- INT TotalResult;
- Copy = strdup(SupplementaryGroups);
- if (Copy == NULL) {
- return ENOMEM;
- }
- TotalResult = 0;
- Current = Copy;
- while (Current != NULL) {
- NextComma = strchr(Current, ',');
- if (NextComma != NULL) {
- *NextComma = '\0';
- NextComma += 1;
- }
- if (*Current != '\0') {
- Result = SwUpdatePasswordFile(GROUP_FILE_PATH,
- Current,
- NULL,
- User,
- UpdatePasswordAddGroupMember);
- if (Result != 0) {
- SwPrintError(Result, Current, "Failed to add user to group");
- TotalResult = Result;
- }
- }
- Current = NextComma;
- }
- free(Copy);
- return TotalResult;
- }
|