293 useradd/del/mod should be ZFS-aware
[illumos-gate.git] / usr / src / cmd / oamuser / user / usermod.c
blob0264660fe32de743a25c4d500fa8de0383e7e948
1 /*
2 * CDDL HEADER START
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
19 * CDDL HEADER END
22 * Copyright (c) 2013 Gary Mills
24 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
25 * Use is subject to license terms.
28 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
29 /* All Rights Reserved */
32 * Copyright (c) 2013 RackTop Systems.
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <sys/param.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <ctype.h>
41 #include <limits.h>
42 #include <string.h>
43 #include <userdefs.h>
44 #include <user_attr.h>
45 #include <nss_dbdefs.h>
46 #include <errno.h>
47 #include <project.h>
48 #include "users.h"
49 #include "messages.h"
50 #include "funcs.h"
53 * usermod [-u uid [-o] | -g group | -G group [[,group]...]
54 * | -d dir [-m [-z|Z]]
55 * | -s shell | -c comment | -l new_logname]
56 * | -f inactive | -e expire ]
57 * [ -A authorization [, authorization ...]]
58 * [ -P profile [, profile ...]]
59 * [ -R role [, role ...]]
60 * [ -K key=value ]
61 * [ -p project [, project]] login
63 * This command adds new user logins to the system. Arguments are:
65 * uid - an integer less than MAXUID
66 * group - an existing group's integer ID or char string name
67 * dir - a directory
68 * shell - a program to be used as a shell
69 * comment - any text string
70 * skel_dir - a directory
71 * base_dir - a directory
72 * rid - an integer less than 2**16 (USHORT)
73 * login - a string of printable chars except colon (:)
74 * inactive - number of days a login maybe inactive before it is locked
75 * expire - date when a login is no longer valid
76 * authorization - One or more comma separated authorizations defined
77 * in auth_attr(4).
78 * profile - One or more comma separated execution profiles defined
79 * in prof_attr(4)
80 * role - One or more comma-separated role names defined in user_attr(4)
81 * key=value - One or more -K options each specifying a valid user_attr(4)
82 * attribute.
86 extern int **valid_lgroup(), isbusy(), get_default_zfs_flags();
87 extern int valid_uid(), check_perm(), create_home(), move_dir();
88 extern int valid_expire(), edit_group(), call_passmgmt();
89 extern projid_t **valid_lproject();
91 static uid_t uid; /* new uid */
92 static gid_t gid; /* gid of new login */
93 static char *new_logname = NULL; /* new login name with -l option */
94 static char *uidstr = NULL; /* uid from command line */
95 static char *group = NULL; /* group from command line */
96 static char *grps = NULL; /* multi groups from command line */
97 static char *dir = NULL; /* home dir from command line */
98 static char *shell = NULL; /* shell from command line */
99 static char *comment = NULL; /* comment from command line */
100 static char *logname = NULL; /* login name to add */
101 static char *inactstr = NULL; /* inactive from command line */
102 static char *expire = NULL; /* expiration date from command line */
103 static char *projects = NULL; /* project ids from command line */
104 static char *usertype;
106 char *cmdname;
107 static char gidstring[32], uidstring[32];
108 char inactstring[10];
110 char *
111 strcpmalloc(str)
112 char *str;
114 if (str == NULL)
115 return (NULL);
117 return (strdup(str));
119 struct passwd *
120 passwd_cpmalloc(opw)
121 struct passwd *opw;
123 struct passwd *npw;
125 if (opw == NULL)
126 return (NULL);
129 npw = malloc(sizeof (struct passwd));
131 npw->pw_name = strcpmalloc(opw->pw_name);
132 npw->pw_passwd = strcpmalloc(opw->pw_passwd);
133 npw->pw_uid = opw->pw_uid;
134 npw->pw_gid = opw->pw_gid;
135 npw->pw_age = strcpmalloc(opw->pw_age);
136 npw->pw_comment = strcpmalloc(opw->pw_comment);
137 npw->pw_gecos = strcpmalloc(opw->pw_gecos);
138 npw->pw_dir = strcpmalloc(opw->pw_dir);
139 npw->pw_shell = strcpmalloc(opw->pw_shell);
141 return (npw);
145 main(argc, argv)
146 int argc;
147 char **argv;
149 int ch, ret = EX_SUCCESS, call_pass = 0, oflag = 0, zfs_flags = 0;
150 int tries, mflag = 0, inact, **gidlist, flag = 0, zflag = 0, Zflag = 0;
151 boolean_t fail_if_busy = B_FALSE;
152 char *ptr;
153 struct passwd *pstruct; /* password struct for login */
154 struct passwd *pw;
155 struct group *g_ptr; /* validated group from -g */
156 struct stat statbuf; /* status buffer for stat */
157 #ifndef att
158 FILE *pwf; /* fille ptr for opened passwd file */
159 #endif
160 int warning;
161 projid_t **projlist;
162 char **nargv; /* arguments for execvp of passmgmt */
163 int argindex; /* argument index into nargv */
164 userattr_t *ua;
165 char *val;
166 int isrole; /* current account is role */
168 cmdname = argv[0];
170 if (geteuid() != 0) {
171 errmsg(M_PERM_DENIED);
172 exit(EX_NO_PERM);
175 opterr = 0; /* no print errors from getopt */
176 /* get user type based on the program name */
177 usertype = getusertype(argv[0]);
179 while ((ch = getopt(argc, argv,
180 "c:d:e:f:G:g:l:mzZop:s:u:A:P:R:K:")) != EOF)
181 switch (ch) {
182 case 'c':
183 comment = optarg;
184 flag++;
185 break;
186 case 'd':
187 dir = optarg;
188 fail_if_busy = B_TRUE;
189 flag++;
190 break;
191 case 'e':
192 expire = optarg;
193 flag++;
194 break;
195 case 'f':
196 inactstr = optarg;
197 flag++;
198 break;
199 case 'G':
200 grps = optarg;
201 flag++;
202 break;
203 case 'g':
204 group = optarg;
205 fail_if_busy = B_TRUE;
206 flag++;
207 break;
208 case 'l':
209 new_logname = optarg;
210 fail_if_busy = B_TRUE;
211 flag++;
212 break;
213 case 'm':
214 mflag++;
215 flag++;
216 fail_if_busy = B_TRUE;
217 break;
218 case 'o':
219 oflag++;
220 flag++;
221 fail_if_busy = B_TRUE;
222 break;
223 case 'p':
224 projects = optarg;
225 flag++;
226 break;
227 case 's':
228 shell = optarg;
229 flag++;
230 break;
231 case 'u':
232 uidstr = optarg;
233 flag++;
234 fail_if_busy = B_TRUE;
235 break;
236 case 'Z':
237 Zflag++;
238 break;
239 case 'z':
240 zflag++;
241 break;
242 case 'A':
243 change_key(USERATTR_AUTHS_KW, optarg);
244 flag++;
245 break;
246 case 'P':
247 change_key(USERATTR_PROFILES_KW, optarg);
248 flag++;
249 break;
250 case 'R':
251 change_key(USERATTR_ROLES_KW, optarg);
252 flag++;
253 break;
254 case 'K':
255 change_key(NULL, optarg);
256 flag++;
257 break;
258 default:
259 case '?':
260 if (is_role(usertype))
261 errmsg(M_MRUSAGE);
262 else
263 errmsg(M_MUSAGE);
264 exit(EX_SYNTAX);
267 if (((!mflag) && (zflag || Zflag)) || (zflag && Zflag) ||
268 (mflag > 1 && (zflag || Zflag))) {
269 if (is_role(usertype))
270 errmsg(M_ARUSAGE);
271 else
272 errmsg(M_AUSAGE);
273 exit(EX_SYNTAX);
277 if (optind != argc - 1 || flag == 0) {
278 if (is_role(usertype))
279 errmsg(M_MRUSAGE);
280 else
281 errmsg(M_MUSAGE);
282 exit(EX_SYNTAX);
285 if ((!uidstr && oflag) || (mflag && !dir)) {
286 if (is_role(usertype))
287 errmsg(M_MRUSAGE);
288 else
289 errmsg(M_MUSAGE);
290 exit(EX_SYNTAX);
293 logname = argv[optind];
295 /* Determine whether the account is a role or not */
296 if ((ua = getusernam(logname)) == NULL ||
297 (val = kva_match(ua->attr, USERATTR_TYPE_KW)) == NULL ||
298 strcmp(val, USERATTR_TYPE_NONADMIN_KW) != 0)
299 isrole = 0;
300 else
301 isrole = 1;
303 /* Verify that rolemod is used for roles and usermod for users */
304 if (isrole != is_role(usertype)) {
305 if (isrole)
306 errmsg(M_ISROLE);
307 else
308 errmsg(M_ISUSER);
309 exit(EX_SYNTAX);
312 /* Set the usertype key; defaults to the commandline */
313 usertype = getsetdefval(USERATTR_TYPE_KW, usertype);
315 if (is_role(usertype)) {
316 /* Roles can't have roles */
317 if (getsetdefval(USERATTR_ROLES_KW, NULL) != NULL) {
318 errmsg(M_MRUSAGE);
319 exit(EX_SYNTAX);
321 /* If it was an ordinary user, delete its roles */
322 if (!isrole)
323 change_key(USERATTR_ROLES_KW, "");
326 #ifdef att
327 pw = getpwnam(logname);
328 #else
330 * Do this with fgetpwent to make sure we are only looking on local
331 * system (since passmgmt only works on local system).
333 if ((pwf = fopen("/etc/passwd", "r")) == NULL) {
334 errmsg(M_OOPS, "open", "/etc/passwd");
335 exit(EX_FAILURE);
337 while ((pw = fgetpwent(pwf)) != NULL)
338 if (strcmp(pw->pw_name, logname) == 0)
339 break;
341 fclose(pwf);
342 #endif
344 if (pw == NULL) {
345 char pwdb[NSS_BUFLEN_PASSWD];
346 struct passwd pwd;
348 if (getpwnam_r(logname, &pwd, pwdb, sizeof (pwdb)) == NULL) {
349 /* This user does not exist. */
350 errmsg(M_EXIST, logname);
351 exit(EX_NAME_NOT_EXIST);
352 } else {
353 /* This user exists in non-local name service. */
354 errmsg(M_NONLOCAL, logname);
355 exit(EX_NOT_LOCAL);
359 pstruct = passwd_cpmalloc(pw);
362 * We can't modify a logged in user if any of the following
363 * are being changed:
364 * uid (-u & -o), group (-g), home dir (-m), loginname (-l).
365 * If none of those are specified it is okay to go ahead
366 * some types of changes only take effect on next login, some
367 * like authorisations and profiles take effect instantly.
368 * One might think that -K type=role should require that the
369 * user not be logged in, however this would make it very
370 * difficult to make the root account a role using this command.
372 if (isbusy(logname)) {
373 if (fail_if_busy) {
374 errmsg(M_BUSY, logname, "change");
375 exit(EX_BUSY);
377 warningmsg(WARN_LOGGED_IN, logname);
380 if (new_logname && strcmp(new_logname, logname)) {
381 switch (valid_login(new_logname, (struct passwd **)NULL,
382 &warning)) {
383 case INVALID:
384 errmsg(M_INVALID, new_logname, "login name");
385 exit(EX_BADARG);
386 /*NOTREACHED*/
388 case NOTUNIQUE:
389 errmsg(M_USED, new_logname);
390 exit(EX_NAME_EXISTS);
391 /*NOTREACHED*/
393 case LONGNAME:
394 errmsg(M_TOO_LONG, new_logname);
395 exit(EX_BADARG);
396 /*NOTREACHED*/
398 default:
399 call_pass = 1;
400 break;
402 if (warning)
403 warningmsg(warning, logname);
406 if (uidstr) {
407 /* convert uidstr to integer */
408 errno = 0;
409 uid = (uid_t)strtol(uidstr, &ptr, (int)10);
410 if (*ptr || errno == ERANGE) {
411 errmsg(M_INVALID, uidstr, "user id");
412 exit(EX_BADARG);
415 if (uid != pstruct->pw_uid) {
416 switch (valid_uid(uid, NULL)) {
417 case NOTUNIQUE:
418 if (!oflag) {
419 /* override not specified */
420 errmsg(M_UID_USED, uid);
421 exit(EX_ID_EXISTS);
423 break;
424 case RESERVED:
425 errmsg(M_RESERVED, uid);
426 break;
427 case TOOBIG:
428 errmsg(M_TOOBIG, "uid", uid);
429 exit(EX_BADARG);
430 break;
433 call_pass = 1;
435 } else {
436 /* uid's the same, so don't change anything */
437 uidstr = NULL;
438 oflag = 0;
441 } else uid = pstruct->pw_uid;
443 if (group) {
444 switch (valid_group(group, &g_ptr, &warning)) {
445 case INVALID:
446 errmsg(M_INVALID, group, "group id");
447 exit(EX_BADARG);
448 /*NOTREACHED*/
449 case TOOBIG:
450 errmsg(M_TOOBIG, "gid", group);
451 exit(EX_BADARG);
452 /*NOTREACHED*/
453 case UNIQUE:
454 errmsg(M_GRP_NOTUSED, group);
455 exit(EX_NAME_NOT_EXIST);
456 /*NOTREACHED*/
457 case RESERVED:
458 gid = (gid_t)strtol(group, &ptr, (int)10);
459 errmsg(M_RESERVED_GID, gid);
460 break;
462 if (warning)
463 warningmsg(warning, group);
465 if (g_ptr != NULL)
466 gid = g_ptr->gr_gid;
467 else
468 gid = pstruct->pw_gid;
470 /* call passmgmt if gid is different, else ignore group */
471 if (gid != pstruct->pw_gid)
472 call_pass = 1;
473 else group = NULL;
475 } else gid = pstruct->pw_gid;
477 if (grps && *grps) {
478 if (!(gidlist = valid_lgroup(grps, gid)))
479 exit(EX_BADARG);
480 } else
481 gidlist = (int **)0;
483 if (projects && *projects) {
484 if (! (projlist = valid_lproject(projects)))
485 exit(EX_BADARG);
486 } else
487 projlist = (projid_t **)0;
489 if (dir) {
490 if (REL_PATH(dir)) {
491 errmsg(M_RELPATH, dir);
492 exit(EX_BADARG);
494 if (strcmp(pstruct->pw_dir, dir) == 0) {
495 /* home directory is the same so ignore dflag & mflag */
496 dir = NULL;
497 mflag = 0;
498 } else call_pass = 1;
501 if (mflag) {
502 if (stat(dir, &statbuf) == 0) {
503 /* Home directory exists */
504 if (check_perm(statbuf, pstruct->pw_uid,
505 pstruct->pw_gid, S_IWOTH|S_IXOTH) != 0) {
506 errmsg(M_NO_PERM, logname, dir);
507 exit(EX_NO_PERM);
510 } else {
511 zfs_flags = get_default_zfs_flags();
512 if (zflag || mflag > 1)
513 zfs_flags |= MANAGE_ZFS;
514 else if (Zflag)
515 zfs_flags &= ~MANAGE_ZFS;
516 ret = create_home(dir, NULL, uid, gid, zfs_flags);
519 if (ret == EX_SUCCESS)
520 ret = move_dir(pstruct->pw_dir, dir,
521 logname, zfs_flags);
523 if (ret != EX_SUCCESS)
524 exit(ret);
527 if (shell) {
528 if (REL_PATH(shell)) {
529 errmsg(M_RELPATH, shell);
530 exit(EX_BADARG);
532 if (strcmp(pstruct->pw_shell, shell) == 0) {
533 /* ignore s option if shell is not different */
534 shell = NULL;
535 } else {
536 if (stat(shell, &statbuf) < 0 ||
537 (statbuf.st_mode & S_IFMT) != S_IFREG ||
538 (statbuf.st_mode & 0555) != 0555) {
540 errmsg(M_INVALID, shell, "shell");
541 exit(EX_BADARG);
544 call_pass = 1;
548 if (comment) {
549 /* ignore comment if comment is not changed */
550 if (strcmp(pstruct->pw_comment, comment))
551 call_pass = 1;
552 else
553 comment = NULL;
556 /* inactive string is a positive integer */
557 if (inactstr) {
558 /* convert inactstr to integer */
559 inact = (int)strtol(inactstr, &ptr, 10);
560 if (*ptr || inact < 0) {
561 errmsg(M_INVALID, inactstr, "inactivity period");
562 exit(EX_BADARG);
564 call_pass = 1;
567 /* expiration string is a date, newer than today */
568 if (expire) {
569 if (*expire &&
570 valid_expire(expire, (time_t *)0) == INVALID) {
571 errmsg(M_INVALID, expire, "expiration date");
572 exit(EX_BADARG);
574 call_pass = 1;
577 if (nkeys > 0)
578 call_pass = 1;
580 /* that's it for validations - now do the work */
582 if (grps) {
583 /* redefine login's supplentary group memberships */
584 ret = edit_group(logname, new_logname, gidlist, 1);
585 if (ret != EX_SUCCESS) {
586 errmsg(M_UPDATE, "modified");
587 exit(ret);
590 if (projects) {
591 ret = edit_project(logname, (char *)NULL, projlist, 0);
592 if (ret != EX_SUCCESS) {
593 errmsg(M_UPDATE, "modified");
594 exit(ret);
599 if (!call_pass) exit(ret);
601 /* only get to here if need to call passmgmt */
602 /* set up arguments to passmgmt in nargv array */
603 nargv = malloc((30 + nkeys * 2) * sizeof (char *));
605 argindex = 0;
606 nargv[argindex++] = PASSMGMT;
607 nargv[argindex++] = "-m"; /* modify */
609 if (comment) { /* comment */
610 nargv[argindex++] = "-c";
611 nargv[argindex++] = comment;
614 if (dir) {
615 /* flags for home directory */
616 nargv[argindex++] = "-h";
617 nargv[argindex++] = dir;
620 if (group) {
621 /* set gid flag */
622 nargv[argindex++] = "-g";
623 (void) sprintf(gidstring, "%u", gid);
624 nargv[argindex++] = gidstring;
627 if (shell) { /* shell */
628 nargv[argindex++] = "-s";
629 nargv[argindex++] = shell;
632 if (inactstr) {
633 nargv[argindex++] = "-f";
634 nargv[argindex++] = inactstr;
637 if (expire) {
638 nargv[argindex++] = "-e";
639 nargv[argindex++] = expire;
642 if (uidstr) { /* set uid flag */
643 nargv[argindex++] = "-u";
644 (void) sprintf(uidstring, "%u", uid);
645 nargv[argindex++] = uidstring;
648 if (oflag) nargv[argindex++] = "-o";
650 if (new_logname) { /* redefine login name */
651 nargv[argindex++] = "-l";
652 nargv[argindex++] = new_logname;
655 if (nkeys > 0)
656 addkey_args(nargv, &argindex);
658 /* finally - login name */
659 nargv[argindex++] = logname;
661 /* set the last to null */
662 nargv[argindex++] = NULL;
664 /* now call passmgmt */
665 ret = PEX_FAILED;
666 for (tries = 3; ret != PEX_SUCCESS && tries--; ) {
667 switch (ret = call_passmgmt(nargv)) {
668 case PEX_SUCCESS:
669 case PEX_BUSY:
670 break;
672 case PEX_HOSED_FILES:
673 errmsg(M_HOSED_FILES);
674 exit(EX_INCONSISTENT);
675 break;
677 case PEX_SYNTAX:
678 case PEX_BADARG:
679 /* should NEVER occur that passmgmt usage is wrong */
680 if (is_role(usertype))
681 errmsg(M_MRUSAGE);
682 else
683 errmsg(M_MUSAGE);
684 exit(EX_SYNTAX);
685 break;
687 case PEX_BADUID:
688 /* uid in use - shouldn't happen print message anyway */
689 errmsg(M_UID_USED, uid);
690 exit(EX_ID_EXISTS);
691 break;
693 case PEX_BADNAME:
694 /* invalid loname */
695 errmsg(M_USED, logname);
696 exit(EX_NAME_EXISTS);
697 break;
699 default:
700 errmsg(M_UPDATE, "modified");
701 exit(ret);
702 break;
705 if (tries == 0) {
706 errmsg(M_UPDATE, "modified");
709 exit(ret);
710 /*NOTREACHED*/