root/usr/src/cmd/oamuser/user/useradd.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 2013 Gary Mills
 *
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */

/*
 * Copyright (c) 2013 RackTop Systems.
 */

#include        <sys/types.h>
#include        <sys/stat.h>
#include        <sys/param.h>
#include        <stdio.h>
#include        <stdlib.h>
#include        <ctype.h>
#include        <limits.h>
#include        <string.h>
#include        <userdefs.h>
#include        <errno.h>
#include        <project.h>
#include        <unistd.h>
#include        <user_attr.h>
#include        <libcmdutils.h>
#include        "users.h"
#include        "messages.h"
#include        "userdisp.h"
#include        "funcs.h"

/*
 *  useradd [-u uid [-o] | -g group | -G group [[, group]...]
 *              | -d dir [-m [-z|Z]]
 *              | -s shell | -c comment | -k skel_dir | -b base_dir] ]
 *              [ -A authorization [, authorization ...]]
 *              [ -P profile [, profile ...]]
 *              [ -K key=value ]
 *              [ -R role [, role ...]] [-p project [, project ...]] login
 *  useradd -D [ -g group ] [ -b base_dir | -f inactive | -e expire |
 *              -s shell | -k skel_dir ]
 *              [ -A authorization [, authorization ...]]
 *              [ -P profile [, profile ...]] [ -K key=value ]
 *              [ -R role [, role ...]] [-p project [, project ...]] login
 *
 *      This command adds new user logins to the system.  Arguments are:
 *
 *      uid - an integer
 *      group - an existing group's integer ID or char string name
 *      dir - home directory
 *      shell - a program to be used as a shell
 *      comment - any text string
 *      skel_dir - a skeleton directory
 *      base_dir - a directory
 *      login - a string of printable chars except colon(:)
 *      authorization - One or more comma separated authorizations defined
 *                      in auth_attr(5).
 *      profile - One or more comma separated execution profiles defined
 *                in prof_attr(5)
 *      role - One or more comma-separated role names defined in user_attr(5)
 *      project - One or more comma-separated project names or numbers
 *
 */

extern struct userdefs *getusrdef();
extern void dispusrdef();

static void cleanup();

extern int check_perm(), valid_expire();
extern int putusrdef(), valid_uid();
extern int call_passmgmt(), edit_group(), create_home();
extern int edit_project();
extern int **valid_lgroup();
extern projid_t **valid_lproject();
extern void update_def(struct userdefs *);
extern void import_def(struct userdefs *);
extern int get_default_zfs_flags();

static uid_t uid;                       /* new uid */
static char *logname;                   /* login name to add */
static struct userdefs *usrdefs;        /* defaults for useradd */

char *cmdname;

static char homedir[ PATH_MAX + 1 ];    /* home directory */
static char gidstring[32];              /* group id string representation */
static gid_t gid;                       /* gid of new login */
static char uidstring[32];              /* user id string representation */
static char *uidstr = NULL;             /* uid from command line */
static char *base_dir = NULL;           /* base_dir from command line */
static char *group = NULL;              /* group from command line */
static char *grps = NULL;               /* multi groups from command line */
static char *dir = NULL;                /* home dir from command line */
static char *shell = NULL;              /* shell from command line */
static char *comment = NULL;            /* comment from command line */
static char *skel_dir = NULL;           /* skel dir from command line */
static long inact;                      /* inactive days */
static char *inactstr = NULL;           /* inactive from command line */
static char inactstring[10];            /* inactivity string representation */
static char *expirestr = NULL;          /* expiration date from command line */
static char *projects = NULL;           /* project id's from command line */

static char *usertype = NULL;   /* type of user, either role or normal */

typedef enum {
        BASEDIR = 0,
        SKELDIR,
        SHELL
} path_opt_t;


static void valid_input(path_opt_t, const char *);

int
main(argc, argv)
int argc;
char *argv[];
{
        int ch, ret, mflag = 0, oflag = 0, Dflag = 0;
        int zflag = 0, Zflag = 0, **gidlist = NULL;
        projid_t **projlist = NULL;
        char *ptr;                      /* loc in a str, may be set by strtol */
        struct group *g_ptr;
        struct project p_ptr;
        char mybuf[PROJECT_BUFSZ];
        struct stat statbuf;            /* status buffer for stat */
        int warning;
        int busy = 0;
        char **nargv;                   /* arguments for execvp of passmgmt */
        int argindex;                   /* argument index into nargv */
        int zfs_flags = 0;                      /* create_home flags */

        cmdname = argv[0];

        if (geteuid() != 0) {
                errmsg(M_PERM_DENIED);
                exit(EX_NO_PERM);
        }

        opterr = 0;                     /* no print errors from getopt */
        usertype = getusertype(argv[0]);

        change_key(USERATTR_TYPE_KW, usertype);

        while ((ch = getopt(argc, argv,
                    "b:c:Dd:e:f:G:g:k:mzZop:s:u:A:P:R:K:")) != EOF)
                switch (ch) {
                case 'b':
                        base_dir = optarg;
                        break;

                case 'c':
                        comment = optarg;
                        break;

                case 'D':
                        Dflag++;
                        break;

                case 'd':
                        dir = optarg;
                        break;

                case 'e':
                        expirestr = optarg;
                        break;

                case 'f':
                        inactstr = optarg;
                        break;

                case 'G':
                        grps = optarg;
                        break;

                case 'g':
                        group = optarg;
                        break;

                case 'k':
                        skel_dir = optarg;
                        break;

                case 'm':
                        mflag++;
                        break;

                case 'o':
                        oflag++;
                        break;

                case 'p':
                        projects = optarg;
                        break;

                case 's':
                        shell = optarg;
                        break;

                case 'u':
                        uidstr = optarg;
                        break;

                case 'Z':
                        Zflag++;
                        break;

                case 'z':
                        zflag++;
                        break;

                case 'A':
                        change_key(USERATTR_AUTHS_KW, optarg);
                        break;

                case 'P':
                        change_key(USERATTR_PROFILES_KW, optarg);
                        break;

                case 'R':
                        if (is_role(usertype)) {
                                errmsg(M_ARUSAGE);
                                exit(EX_SYNTAX);
                        }
                        change_key(USERATTR_ROLES_KW, optarg);
                        break;

                case 'K':
                        change_key(NULL, optarg);
                        break;

                default:
                case '?':
                        if (is_role(usertype))
                                errmsg(M_ARUSAGE);
                        else
                                errmsg(M_AUSAGE);
                        exit(EX_SYNTAX);
                }

        if (((!mflag) && (zflag || Zflag)) || (zflag && Zflag) ||
            (mflag > 1 && (zflag || Zflag))) {
                if (is_role(usertype))
                        errmsg(M_ARUSAGE);
                else
                        errmsg(M_AUSAGE);
                exit(EX_SYNTAX);
        }


        /* get defaults for adding new users */
        usrdefs = getusrdef(usertype);

        if (Dflag) {
                /* DISPLAY mode */

                /* check syntax */
                if (optind != argc) {
                        if (is_role(usertype))
                                errmsg(M_ARUSAGE);
                        else
                                errmsg(M_AUSAGE);
                        exit(EX_SYNTAX);
                }

                if (uidstr != NULL || oflag || grps != NULL ||
                    dir != NULL || mflag || comment != NULL) {
                        if (is_role(usertype))
                                errmsg(M_ARUSAGE);
                        else
                                errmsg(M_AUSAGE);
                        exit(EX_SYNTAX);
                }

                /* Group must be an existing group */
                if (group != NULL) {
                        switch (valid_group(group, &g_ptr, &warning)) {
                        case INVALID:
                                errmsg(M_INVALID, group, "group id");
                                exit(EX_BADARG);
                                /*NOTREACHED*/
                        case TOOBIG:
                                errmsg(M_TOOBIG, "gid", group);
                                exit(EX_BADARG);
                                /*NOTREACHED*/
                        case RESERVED:
                        case UNIQUE:
                                errmsg(M_GRP_NOTUSED, group);
                                exit(EX_NAME_NOT_EXIST);
                        }
                        if (warning)
                                warningmsg(warning, group);

                        usrdefs->defgroup = g_ptr->gr_gid;
                        usrdefs->defgname = g_ptr->gr_name;

                }

                /* project must be an existing project */
                if (projects != NULL) {
                        switch (valid_project(projects, &p_ptr, mybuf,
                            sizeof (mybuf), &warning)) {
                        case INVALID:
                                errmsg(M_INVALID, projects, "project id");
                                exit(EX_BADARG);
                                /*NOTREACHED*/
                        case TOOBIG:
                                errmsg(M_TOOBIG, "projid", projects);
                                exit(EX_BADARG);
                                /*NOTREACHED*/
                        case UNIQUE:
                                errmsg(M_PROJ_NOTUSED, projects);
                                exit(EX_NAME_NOT_EXIST);
                        }
                        if (warning)
                                warningmsg(warning, projects);

                        usrdefs->defproj = p_ptr.pj_projid;
                        usrdefs->defprojname = p_ptr.pj_name;
                }

                /* base_dir must be an existing directory */
                if (base_dir != NULL) {
                        valid_input(BASEDIR, base_dir);
                        usrdefs->defparent = base_dir;
                }

                /* inactivity period is an integer */
                if (inactstr != NULL) {
                        /* convert inactstr to integer */
                        inact = strtol(inactstr, &ptr, 10);
                        if (*ptr || inact < 0) {
                                errmsg(M_INVALID, inactstr,
                                    "inactivity period");
                                exit(EX_BADARG);
                        }

                        usrdefs->definact = inact;
                }

                /* expiration string is a date, newer than today */
                if (expirestr != NULL) {
                        if (*expirestr) {
                                if (valid_expire(expirestr, (time_t *)0)
                                    == INVALID) {
                                        errmsg(M_INVALID, expirestr,
                                            "expiration date");
                                        exit(EX_BADARG);
                                }
                                usrdefs->defexpire = expirestr;
                        } else
                                /* Unset the expiration date */
                                usrdefs->defexpire = "";
                }

                if (shell != NULL) {
                        valid_input(SHELL, shell);
                        usrdefs->defshell = shell;
                }
                if (skel_dir != NULL) {
                        valid_input(SKELDIR, skel_dir);
                        usrdefs->defskel = skel_dir;
                }
                update_def(usrdefs);

                /* change defaults for useradd */
                if (putusrdef(usrdefs, usertype) < 0) {
                        errmsg(M_UPDATE, "created");
                        exit(EX_UPDATE);
                }

                /* Now, display */
                dispusrdef(stdout, (D_ALL & ~D_RID), usertype);
                exit(EX_SUCCESS);

        }

        /* ADD mode */

        /* check syntax */
        if (optind != argc - 1 || (skel_dir != NULL && !mflag)) {
                if (is_role(usertype))
                        errmsg(M_ARUSAGE);
                else
                        errmsg(M_AUSAGE);
                exit(EX_SYNTAX);
        }

        logname = argv[optind];
        switch (valid_login(logname, (struct passwd **)NULL, &warning)) {
        case INVALID:
                errmsg(M_INVALID, logname, "login name");
                exit(EX_BADARG);
                /*NOTREACHED*/

        case NOTUNIQUE:
                errmsg(M_USED, logname);
                exit(EX_NAME_EXISTS);
                /*NOTREACHED*/

        case LONGNAME:
                errmsg(M_TOO_LONG, logname);
                exit(EX_BADARG);
                /*NOTREACHED*/
        }

        if (warning)
                warningmsg(warning, logname);
        if (uidstr != NULL) {
                /* convert uidstr to integer */
                errno = 0;
                uid = (uid_t)strtol(uidstr, &ptr, (int)10);
                if (*ptr || errno == ERANGE) {
                        errmsg(M_INVALID, uidstr, "user id");
                        exit(EX_BADARG);
                }

                switch (valid_uid(uid, NULL)) {
                case NOTUNIQUE:
                        if (!oflag) {
                                /* override not specified */
                                errmsg(M_UID_USED, uid);
                                exit(EX_ID_EXISTS);
                        }
                        break;
                case RESERVED:
                        errmsg(M_RESERVED, uid);
                        break;
                case TOOBIG:
                        errmsg(M_TOOBIG, "uid", uid);
                        exit(EX_BADARG);
                        break;
                }

        } else {

                if (findnextuid(DEFRID+1, MAXUID, &uid) != 0) {
                        errmsg(M_INVALID, "default id", "user id");
                        exit(EX_ID_EXISTS);
                }
        }

        if (group != NULL) {
                switch (valid_group(group, &g_ptr, &warning)) {
                case INVALID:
                        errmsg(M_INVALID, group, "group id");
                        exit(EX_BADARG);
                        /*NOTREACHED*/
                case TOOBIG:
                        errmsg(M_TOOBIG, "gid", group);
                        exit(EX_BADARG);
                        /*NOTREACHED*/
                case RESERVED:
                case UNIQUE:
                        errmsg(M_GRP_NOTUSED, group);
                        exit(EX_NAME_NOT_EXIST);
                        /*NOTREACHED*/
                }

                if (warning)
                        warningmsg(warning, group);
                gid = g_ptr->gr_gid;

        } else gid = usrdefs->defgroup;

        if (grps != NULL) {
                if (!*grps)
                        /* ignore -G "" */
                        grps = (char *)0;
                else if (!(gidlist = valid_lgroup(grps, gid)))
                        exit(EX_BADARG);
        }

        if (projects != NULL) {
                if (! *projects)
                        projects = (char *)0;
                else if (! (projlist = valid_lproject(projects)))
                        exit(EX_BADARG);
        }

        /* if base_dir is provided, check its validity; otherwise default */
        if (base_dir != NULL)
                valid_input(BASEDIR, base_dir);
        else
                base_dir = usrdefs->defparent;

        if (dir == NULL) {
                /* set homedir to home directory made from base_dir */
                (void) sprintf(homedir, "%s/%s", base_dir, logname);

        } else if (REL_PATH(dir)) {
                errmsg(M_RELPATH, dir);
                exit(EX_BADARG);

        } else
                (void) strcpy(homedir, dir);

        if (mflag) {
                /* Does home dir. already exist? */
                if (stat(homedir, &statbuf) == 0) {
                        /* directory exists - don't try to create */
                        mflag = 0;

                        if (check_perm(statbuf, uid, gid, S_IXOTH) != 0)
                                errmsg(M_NO_PERM, logname, homedir);
                }
        }
        /*
         * if shell, skel_dir are provided, check their validity.
         * Otherwise default.
         */
        if (shell != NULL)
                valid_input(SHELL, shell);
        else
                shell = usrdefs->defshell;

        if (skel_dir != NULL)
                valid_input(SKELDIR, skel_dir);
        else
                skel_dir = usrdefs->defskel;

        if (inactstr != NULL) {
                /* convert inactstr to integer */
                inact = strtol(inactstr, &ptr, 10);
                if (*ptr || inact < 0) {
                        errmsg(M_INVALID, inactstr, "inactivity period");
                        exit(EX_BADARG);
                }
        } else inact = usrdefs->definact;

        /* expiration string is a date, newer than today */
        if (expirestr != NULL) {
                if (*expirestr) {
                        if (valid_expire(expirestr, (time_t *)0) == INVALID) {
                                errmsg(M_INVALID, expirestr, "expiration date");
                                exit(EX_BADARG);
                        }
                        usrdefs->defexpire = expirestr;
                } else
                        /* Unset the expiration date */
                        expirestr = (char *)0;

        } else expirestr = usrdefs->defexpire;

        import_def(usrdefs);

        /* must now call passmgmt */

        /* set up arguments to  passmgmt in nargv array */
        nargv = malloc((30 + nkeys * 2) * sizeof (char *));
        argindex = 0;
        nargv[argindex++] = PASSMGMT;
        nargv[argindex++] = "-a";       /* add */

        if (comment != NULL) {
                /* comment */
                nargv[argindex++] = "-c";
                nargv[argindex++] = comment;
        }

        /* flags for home directory */
        nargv[argindex++] = "-h";
        nargv[argindex++] = homedir;

        /* set gid flag */
        nargv[argindex++] = "-g";
        (void) sprintf(gidstring, "%u", gid);
        nargv[argindex++] = gidstring;

        /* shell */
        nargv[argindex++] = "-s";
        nargv[argindex++] = shell;

        /* set inactive */
        nargv[argindex++] = "-f";
        (void) sprintf(inactstring, "%ld", inact);
        nargv[argindex++] = inactstring;

        /* set expiration date */
        if (expirestr != NULL) {
                nargv[argindex++] = "-e";
                nargv[argindex++] = expirestr;
        }

        /* set uid flag */
        nargv[argindex++] = "-u";
        (void) sprintf(uidstring, "%u", uid);
        nargv[argindex++] = uidstring;

        if (oflag) nargv[argindex++] = "-o";

        if (nkeys > 1)
                addkey_args(nargv, &argindex);

        /* finally - login name */
        nargv[argindex++] = logname;

        /* set the last to null */
        nargv[argindex++] = NULL;

        /* now call passmgmt */
        ret = PEX_FAILED;
        /*
         * If call_passmgmt fails for any reason other than PEX_BADUID, exit
         * is invoked with an appropriate error message. If PEX_BADUID is
         * returned, then if the user specified the ID, exit is invoked
         * with an appropriate error message. Otherwise we try to pick a
         * different ID and try again. If we run out of IDs, i.e. no more
         * users can be created, then -1 is returned and we terminate via exit.
         * If PEX_BUSY is returned we increment a count, since we will stop
         * trying if PEX_BUSY reaches 3. For PEX_SUCCESS we immediately
         * terminate the loop.
         */
        while (busy < 3 && ret != PEX_SUCCESS) {
                switch (ret = call_passmgmt(nargv)) {
                case PEX_SUCCESS:
                        break;
                case PEX_BUSY:
                        busy++;
                        break;
                case PEX_HOSED_FILES:
                        errmsg(M_HOSED_FILES);
                        exit(EX_INCONSISTENT);
                        break;

                case PEX_SYNTAX:
                case PEX_BADARG:
                        /* should NEVER occur that passmgmt usage is wrong */
                        if (is_role(usertype))
                                errmsg(M_ARUSAGE);
                        else
                                errmsg(M_AUSAGE);
                        exit(EX_SYNTAX);
                        break;

                case PEX_BADUID:
                        /*
                         * The uid has been taken. If it was specified by a
                         * user, then we must fail. Otherwise, keep trying
                         * to get a good uid until we run out of IDs.
                         */
                        if (uidstr != NULL) {
                                errmsg(M_UID_USED, uid);
                                exit(EX_ID_EXISTS);
                        } else {
                                if (findnextuid(DEFRID+1, MAXUID, &uid) != 0) {
                                        errmsg(M_INVALID, "default id",
                                            "user id");
                                        exit(EX_ID_EXISTS);
                                }
                                (void) sprintf(uidstring, "%u", uid);
                        }
                        break;

                case PEX_BADNAME:
                        /* invalid loname */
                        errmsg(M_USED, logname);
                        exit(EX_NAME_EXISTS);
                        break;

                default:
                        errmsg(M_UPDATE, "created");
                        exit(ret);
                        break;
                }
        }
        if (busy == 3) {
                errmsg(M_UPDATE, "created");
                exit(ret);
        }

        /* add group entry */
        if ((grps != NULL) && edit_group(logname, (char *)0, gidlist, 0)) {
                errmsg(M_UPDATE, "created");
                cleanup(logname);
                exit(EX_UPDATE);
        }

        /* update project database */
        if ((projects != NULL) &&
            edit_project(logname, (char *)NULL, projlist, 0)) {
                errmsg(M_UPDATE, "created");
                cleanup(logname);
                exit(EX_UPDATE);
        }

        /* create home directory */
        if (mflag) {
                zfs_flags = get_default_zfs_flags();

                if (zflag || mflag > 1)
                        zfs_flags |= MANAGE_ZFS;
                else if (Zflag)
                        zfs_flags &= ~MANAGE_ZFS;
                ret = create_home(homedir, skel_dir, uid, gid, zfs_flags);
        }
        if (ret != EX_SUCCESS) {
                (void) edit_project(logname, (char *)NULL, (projid_t **)NULL,
                    0);
                (void) edit_group(logname, (char *)0, (int **)0, 1);
                cleanup(logname);
                exit(EX_HOMEDIR);
        }

        return (ret);
}

static void
cleanup(logname)
char *logname;
{
        char *nargv[4];

        nargv[0] = PASSMGMT;
        nargv[1] = "-d";
        nargv[2] = logname;
        nargv[3] = NULL;

        switch (call_passmgmt(nargv)) {
        case PEX_SUCCESS:
                break;

        case PEX_SYNTAX:
                /* should NEVER occur that passmgmt usage is wrong */
                if (is_role(usertype))
                        errmsg(M_ARUSAGE);
                else
                        errmsg(M_AUSAGE);
                break;

        case PEX_BADUID:
                /* uid is used - shouldn't happen but print message anyway */
                errmsg(M_UID_USED, uid);
                break;

        case PEX_BADNAME:
                /* invalid loname */
                errmsg(M_USED, logname);
                break;

        default:
                errmsg(M_UPDATE, "created");
                break;
        }
}

/* Check the validity for shell, base_dir and skel_dir */

void
valid_input(path_opt_t opt, const char *input)
{
        struct stat     statbuf;

        if (REL_PATH(input)) {
                errmsg(M_RELPATH, input);
                exit(EX_BADARG);
        }
        if (stat(input, &statbuf) == -1) {
                errmsg(M_INVALID, input, "path");
                exit(EX_BADARG);
        }
        if (opt == SHELL) {
                if (!S_ISREG(statbuf.st_mode) ||
                    (statbuf.st_mode & 0555) != 0555) {
                        errmsg(M_INVALID, input, "shell");
                        exit(EX_BADARG);
                }
        } else {
                if (!S_ISDIR(statbuf.st_mode)) {
                        errmsg(M_INVALID, input, "directory");
                        exit(EX_BADARG);
                }
        }
}