/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "users.h" #include "messages.h" #include "funcs.h" /* * usermod [-u uid [-o] | -g group | -G group [[,group]...] * | -d dir [-m [-z|Z]] * | -s shell | -c comment | -l new_logname] * | -f inactive | -e expire ] * [ -A authorization [, authorization ...]] * [ -P profile [, profile ...]] * [ -R role [, role ...]] * [ -K key=value ] * [ -p project [, project]] login * * This command adds new user logins to the system. Arguments are: * * uid - an integer less than MAXUID * group - an existing group's integer ID or char string name * dir - a directory * shell - a program to be used as a shell * comment - any text string * skel_dir - a directory * base_dir - a directory * rid - an integer less than 2**16 (USHORT) * login - a string of printable chars except colon (:) * inactive - number of days a login maybe inactive before it is locked * expire - date when a login is no longer valid * 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) * key=value - One or more -K options each specifying a valid user_attr(5) * attribute. * */ extern int **valid_lgroup(), isbusy(), get_default_zfs_flags(); extern int valid_uid(), check_perm(), create_home(), move_dir(); extern int valid_expire(), edit_group(), call_passmgmt(); extern projid_t **valid_lproject(); static uid_t uid; /* new uid */ static gid_t gid; /* gid of new login */ static char *new_logname = NULL; /* new login name with -l option */ static char *uidstr = NULL; /* uid 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 *logname = NULL; /* login name to add */ static char *inactstr = NULL; /* inactive from command line */ static char *expire = NULL; /* expiration date from command line */ static char *projects = NULL; /* project ids from command line */ static char *usertype; char *cmdname; static char gidstring[32], uidstring[32]; char inactstring[10]; char * strcpmalloc(str) char *str; { if (str == NULL) return (NULL); return (strdup(str)); } struct passwd * passwd_cpmalloc(opw) struct passwd *opw; { struct passwd *npw; if (opw == NULL) return (NULL); npw = malloc(sizeof (struct passwd)); npw->pw_name = strcpmalloc(opw->pw_name); npw->pw_passwd = strcpmalloc(opw->pw_passwd); npw->pw_uid = opw->pw_uid; npw->pw_gid = opw->pw_gid; npw->pw_age = strcpmalloc(opw->pw_age); npw->pw_comment = strcpmalloc(opw->pw_comment); npw->pw_gecos = strcpmalloc(opw->pw_gecos); npw->pw_dir = strcpmalloc(opw->pw_dir); npw->pw_shell = strcpmalloc(opw->pw_shell); return (npw); } int main(argc, argv) int argc; char **argv; { int ch, ret = EX_SUCCESS, call_pass = 0, oflag = 0, zfs_flags = 0; int tries, mflag = 0, inact, **gidlist, flag = 0, zflag = 0, Zflag = 0; boolean_t fail_if_busy = B_FALSE; char *ptr; struct passwd *pstruct; /* password struct for login */ struct passwd *pw; struct group *g_ptr; /* validated group from -g */ struct stat statbuf; /* status buffer for stat */ #ifndef att FILE *pwf; /* fille ptr for opened passwd file */ #endif int warning; projid_t **projlist; char **nargv; /* arguments for execvp of passmgmt */ int argindex; /* argument index into nargv */ userattr_t *ua; char *val; int isrole; /* current account is role */ cmdname = argv[0]; if (geteuid() != 0) { errmsg(M_PERM_DENIED); exit(EX_NO_PERM); } opterr = 0; /* no print errors from getopt */ /* get user type based on the program name */ usertype = getusertype(argv[0]); while ((ch = getopt(argc, argv, "c:d:e:f:G:g:l:mzZop:s:u:A:P:R:K:")) != EOF) switch (ch) { case 'c': comment = optarg; flag++; break; case 'd': dir = optarg; fail_if_busy = B_TRUE; flag++; break; case 'e': expire = optarg; flag++; break; case 'f': inactstr = optarg; flag++; break; case 'G': grps = optarg; flag++; break; case 'g': group = optarg; fail_if_busy = B_TRUE; flag++; break; case 'l': new_logname = optarg; fail_if_busy = B_TRUE; flag++; break; case 'm': mflag++; flag++; fail_if_busy = B_TRUE; break; case 'o': oflag++; flag++; fail_if_busy = B_TRUE; break; case 'p': projects = optarg; flag++; break; case 's': shell = optarg; flag++; break; case 'u': uidstr = optarg; flag++; fail_if_busy = B_TRUE; break; case 'Z': Zflag++; break; case 'z': zflag++; break; case 'A': change_key(USERATTR_AUTHS_KW, optarg); flag++; break; case 'P': change_key(USERATTR_PROFILES_KW, optarg); flag++; break; case 'R': change_key(USERATTR_ROLES_KW, optarg); flag++; break; case 'K': change_key(NULL, optarg); flag++; break; default: case '?': if (is_role(usertype)) errmsg(M_MRUSAGE); else errmsg(M_MUSAGE); 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); } if (optind != argc - 1 || flag == 0) { if (is_role(usertype)) errmsg(M_MRUSAGE); else errmsg(M_MUSAGE); exit(EX_SYNTAX); } if ((!uidstr && oflag) || (mflag && !dir)) { if (is_role(usertype)) errmsg(M_MRUSAGE); else errmsg(M_MUSAGE); exit(EX_SYNTAX); } logname = argv[optind]; /* Determine whether the account is a role or not */ if ((ua = getusernam(logname)) == NULL || (val = kva_match(ua->attr, USERATTR_TYPE_KW)) == NULL || strcmp(val, USERATTR_TYPE_NONADMIN_KW) != 0) isrole = 0; else isrole = 1; /* Verify that rolemod is used for roles and usermod for users */ if (isrole != is_role(usertype)) { if (isrole) errmsg(M_ISROLE); else errmsg(M_ISUSER); exit(EX_SYNTAX); } /* Set the usertype key; defaults to the commandline */ usertype = getsetdefval(USERATTR_TYPE_KW, usertype); if (is_role(usertype)) { /* Roles can't have roles */ if (getsetdefval(USERATTR_ROLES_KW, NULL) != NULL) { errmsg(M_MRUSAGE); exit(EX_SYNTAX); } /* If it was an ordinary user, delete its roles */ if (!isrole) change_key(USERATTR_ROLES_KW, ""); } #ifdef att pw = getpwnam(logname); #else /* * Do this with fgetpwent to make sure we are only looking on local * system (since passmgmt only works on local system). */ if ((pwf = fopen("/etc/passwd", "r")) == NULL) { errmsg(M_OOPS, "open", "/etc/passwd"); exit(EX_FAILURE); } while ((pw = fgetpwent(pwf)) != NULL) if (strcmp(pw->pw_name, logname) == 0) break; fclose(pwf); #endif if (pw == NULL) { char pwdb[NSS_BUFLEN_PASSWD]; struct passwd pwd; if (getpwnam_r(logname, &pwd, pwdb, sizeof (pwdb)) == NULL) { /* This user does not exist. */ errmsg(M_EXIST, logname); exit(EX_NAME_NOT_EXIST); } else { /* This user exists in non-local name service. */ errmsg(M_NONLOCAL, logname); exit(EX_NOT_LOCAL); } } pstruct = passwd_cpmalloc(pw); /* * We can't modify a logged in user if any of the following * are being changed: * uid (-u & -o), group (-g), home dir (-m), loginname (-l). * If none of those are specified it is okay to go ahead * some types of changes only take effect on next login, some * like authorisations and profiles take effect instantly. * One might think that -K type=role should require that the * user not be logged in, however this would make it very * difficult to make the root account a role using this command. */ if (isbusy(logname)) { if (fail_if_busy) { errmsg(M_BUSY, logname, "change"); exit(EX_BUSY); } warningmsg(WARN_LOGGED_IN, logname); } if (new_logname && strcmp(new_logname, logname)) { switch (valid_login(new_logname, (struct passwd **)NULL, &warning)) { case INVALID: errmsg(M_INVALID, new_logname, "login name"); exit(EX_BADARG); /*NOTREACHED*/ case NOTUNIQUE: errmsg(M_USED, new_logname); exit(EX_NAME_EXISTS); /*NOTREACHED*/ case LONGNAME: errmsg(M_TOO_LONG, new_logname); exit(EX_BADARG); /*NOTREACHED*/ default: call_pass = 1; break; } if (warning) warningmsg(warning, logname); } if (uidstr) { /* 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); } if (uid != pstruct->pw_uid) { 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; } call_pass = 1; } else { /* uid's the same, so don't change anything */ uidstr = NULL; oflag = 0; } } else uid = pstruct->pw_uid; if (group) { 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 UNIQUE: errmsg(M_GRP_NOTUSED, group); exit(EX_NAME_NOT_EXIST); /*NOTREACHED*/ case RESERVED: gid = (gid_t)strtol(group, &ptr, (int)10); errmsg(M_RESERVED_GID, gid); break; } if (warning) warningmsg(warning, group); if (g_ptr != NULL) gid = g_ptr->gr_gid; else gid = pstruct->pw_gid; /* call passmgmt if gid is different, else ignore group */ if (gid != pstruct->pw_gid) call_pass = 1; else group = NULL; } else gid = pstruct->pw_gid; if (grps && *grps) { if (!(gidlist = valid_lgroup(grps, gid))) exit(EX_BADARG); } else gidlist = (int **)0; if (projects && *projects) { if (! (projlist = valid_lproject(projects))) exit(EX_BADARG); } else projlist = (projid_t **)0; if (dir) { if (REL_PATH(dir)) { errmsg(M_RELPATH, dir); exit(EX_BADARG); } if (strcmp(pstruct->pw_dir, dir) == 0) { /* home directory is the same so ignore dflag & mflag */ dir = NULL; mflag = 0; } else call_pass = 1; } if (mflag) { if (stat(dir, &statbuf) == 0) { /* Home directory exists */ if (check_perm(statbuf, pstruct->pw_uid, pstruct->pw_gid, S_IWOTH|S_IXOTH) != 0) { errmsg(M_NO_PERM, logname, dir); exit(EX_NO_PERM); } } else { zfs_flags = get_default_zfs_flags(); if (zflag || mflag > 1) zfs_flags |= MANAGE_ZFS; else if (Zflag) zfs_flags &= ~MANAGE_ZFS; ret = create_home(dir, NULL, uid, gid, zfs_flags); } if (ret == EX_SUCCESS) ret = move_dir(pstruct->pw_dir, dir, logname, zfs_flags); if (ret != EX_SUCCESS) exit(ret); } if (shell) { if (REL_PATH(shell)) { errmsg(M_RELPATH, shell); exit(EX_BADARG); } if (strcmp(pstruct->pw_shell, shell) == 0) { /* ignore s option if shell is not different */ shell = NULL; } else { if (stat(shell, &statbuf) < 0 || (statbuf.st_mode & S_IFMT) != S_IFREG || (statbuf.st_mode & 0555) != 0555) { errmsg(M_INVALID, shell, "shell"); exit(EX_BADARG); } call_pass = 1; } } if (comment) { /* ignore comment if comment is not changed */ if (strcmp(pstruct->pw_comment, comment)) call_pass = 1; else comment = NULL; } /* inactive string is a positive integer */ if (inactstr) { /* convert inactstr to integer */ inact = (int)strtol(inactstr, &ptr, 10); if (*ptr || inact < 0) { errmsg(M_INVALID, inactstr, "inactivity period"); exit(EX_BADARG); } call_pass = 1; } /* expiration string is a date, newer than today */ if (expire) { if (*expire && valid_expire(expire, (time_t *)0) == INVALID) { errmsg(M_INVALID, expire, "expiration date"); exit(EX_BADARG); } call_pass = 1; } if (nkeys > 0) call_pass = 1; /* that's it for validations - now do the work */ if (grps) { /* redefine login's supplentary group memberships */ ret = edit_group(logname, new_logname, gidlist, 1); if (ret != EX_SUCCESS) { errmsg(M_UPDATE, "modified"); exit(ret); } } if (projects) { ret = edit_project(logname, (char *)NULL, projlist, 0); if (ret != EX_SUCCESS) { errmsg(M_UPDATE, "modified"); exit(ret); } } if (!call_pass) exit(ret); /* only get to here if need to call passmgmt */ /* set up arguments to passmgmt in nargv array */ nargv = malloc((30 + nkeys * 2) * sizeof (char *)); argindex = 0; nargv[argindex++] = PASSMGMT; nargv[argindex++] = "-m"; /* modify */ if (comment) { /* comment */ nargv[argindex++] = "-c"; nargv[argindex++] = comment; } if (dir) { /* flags for home directory */ nargv[argindex++] = "-h"; nargv[argindex++] = dir; } if (group) { /* set gid flag */ nargv[argindex++] = "-g"; (void) sprintf(gidstring, "%u", gid); nargv[argindex++] = gidstring; } if (shell) { /* shell */ nargv[argindex++] = "-s"; nargv[argindex++] = shell; } if (inactstr) { nargv[argindex++] = "-f"; nargv[argindex++] = inactstr; } if (expire) { nargv[argindex++] = "-e"; nargv[argindex++] = expire; } if (uidstr) { /* set uid flag */ nargv[argindex++] = "-u"; (void) sprintf(uidstring, "%u", uid); nargv[argindex++] = uidstring; } if (oflag) nargv[argindex++] = "-o"; if (new_logname) { /* redefine login name */ nargv[argindex++] = "-l"; nargv[argindex++] = new_logname; } if (nkeys > 0) addkey_args(nargv, &argindex); /* finally - login name */ nargv[argindex++] = logname; /* set the last to null */ nargv[argindex++] = NULL; /* now call passmgmt */ ret = PEX_FAILED; for (tries = 3; ret != PEX_SUCCESS && tries--; ) { switch (ret = call_passmgmt(nargv)) { case PEX_SUCCESS: case PEX_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_MRUSAGE); else errmsg(M_MUSAGE); exit(EX_SYNTAX); break; case PEX_BADUID: /* uid in use - shouldn't happen print message anyway */ errmsg(M_UID_USED, uid); exit(EX_ID_EXISTS); break; case PEX_BADNAME: /* invalid loname */ errmsg(M_USED, logname); exit(EX_NAME_EXISTS); break; default: errmsg(M_UPDATE, "modified"); exit(ret); break; } } if (tries == 0) { errmsg(M_UPDATE, "modified"); } exit(ret); /*NOTREACHED*/ }