xref: /illumos-gate/usr/src/cmd/oamuser/user/usermod.c (revision 7fc68ddf)
1 /*
2  * CDDL HEADER START
3  *
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.
7  *
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.
12  *
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]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (c) 2013 Gary Mills
23  *
24  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
25  * Use is subject to license terms.
26  */
27 
28 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
29 /*	  All Rights Reserved  	*/
30 
31 /*
32  * Copyright (c) 2013 RackTop Systems.
33  */
34 
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"
51 
52 /*
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
62  *
63  *	This command adds new user logins to the system.  Arguments are:
64  *
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.
83  *
84  */
85 
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();
90 
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;
105 
106 char *cmdname;
107 static char gidstring[32], uidstring[32];
108 char inactstring[10];
109 
110 char *
111 strcpmalloc(str)
112 char *str;
113 {
114 	if (str == NULL)
115 		return (NULL);
116 
117 	return (strdup(str));
118 }
119 struct passwd *
120 passwd_cpmalloc(opw)
121 struct passwd *opw;
122 {
123 	struct passwd *npw;
124 
125 	if (opw == NULL)
126 		return (NULL);
127 
128 
129 	npw = malloc(sizeof (struct passwd));
130 
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);
140 
141 	return (npw);
142 }
143 
144 int
145 main(argc, argv)
146 int argc;
147 char **argv;
148 {
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 */
167 
168 	cmdname = argv[0];
169 
170 	if (geteuid() != 0) {
171 		errmsg(M_PERM_DENIED);
172 		exit(EX_NO_PERM);
173 	}
174 
175 	opterr = 0;			/* no print errors from getopt */
176 	/* get user type based on the program name */
177 	usertype = getusertype(argv[0]);
178 
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);
265 		}
266 
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);
274 	}
275 
276 
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);
283 	}
284 
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);
291 	}
292 
293 	logname = argv[optind];
294 
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;
302 
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);
310 	}
311 
312 	/* Set the usertype key; defaults to the commandline  */
313 	usertype = getsetdefval(USERATTR_TYPE_KW, usertype);
314 
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);
320 		}
321 		/* If it was an ordinary user, delete its roles */
322 		if (!isrole)
323 			change_key(USERATTR_ROLES_KW, "");
324 	}
325 
326 #ifdef att
327 	pw = getpwnam(logname);
328 #else
329 	/*
330 	 * Do this with fgetpwent to make sure we are only looking on local
331 	 * system (since passmgmt only works on local system).
332 	 */
333 	if ((pwf = fopen("/etc/passwd", "r")) == NULL) {
334 		errmsg(M_OOPS, "open", "/etc/passwd");
335 		exit(EX_FAILURE);
336 	}
337 	while ((pw = fgetpwent(pwf)) != NULL)
338 		if (strcmp(pw->pw_name, logname) == 0)
339 			break;
340 
341 	fclose(pwf);
342 #endif
343 
344 	if (pw == NULL) {
345 		char		pwdb[NSS_BUFLEN_PASSWD];
346 		struct passwd	pwd;
347 
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);
356 		}
357 	}
358 
359 	pstruct = passwd_cpmalloc(pw);
360 
361 	/*
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.
371 	 */
372 	if (isbusy(logname)) {
373 		if (fail_if_busy) {
374 			errmsg(M_BUSY, logname, "change");
375 			exit(EX_BUSY);
376 		}
377 		warningmsg(WARN_LOGGED_IN, logname);
378 	}
379 
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*/
387 
388 		case NOTUNIQUE:
389 			errmsg(M_USED, new_logname);
390 			exit(EX_NAME_EXISTS);
391 			/*NOTREACHED*/
392 
393 		case LONGNAME:
394 			errmsg(M_TOO_LONG, new_logname);
395 			exit(EX_BADARG);
396 			/*NOTREACHED*/
397 
398 		default:
399 			call_pass = 1;
400 			break;
401 		}
402 		if (warning)
403 			warningmsg(warning, logname);
404 	}
405 
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);
413 		}
414 
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);
422 				}
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;
431 			}
432 
433 			call_pass = 1;
434 
435 		} else {
436 			/* uid's the same, so don't change anything */
437 			uidstr = NULL;
438 			oflag = 0;
439 		}
440 
441 	} else uid = pstruct->pw_uid;
442 
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;
461 		}
462 		if (warning)
463 			warningmsg(warning, group);
464 
465 		if (g_ptr != NULL)
466 			gid = g_ptr->gr_gid;
467 		else
468 			gid = pstruct->pw_gid;
469 
470 		/* call passmgmt if gid is different, else ignore group */
471 		if (gid != pstruct->pw_gid)
472 			call_pass = 1;
473 		else group = NULL;
474 
475 	} else gid = pstruct->pw_gid;
476 
477 	if (grps && *grps) {
478 		if (!(gidlist = valid_lgroup(grps, gid)))
479 			exit(EX_BADARG);
480 	} else
481 		gidlist = (int **)0;
482 
483 	if (projects && *projects) {
484 		if (! (projlist = valid_lproject(projects)))
485 			exit(EX_BADARG);
486 	} else
487 		projlist = (projid_t **)0;
488 
489 	if (dir) {
490 		if (REL_PATH(dir)) {
491 			errmsg(M_RELPATH, dir);
492 			exit(EX_BADARG);
493 		}
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;
499 	}
500 
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);
508 			}
509 
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);
517 		}
518 
519 		if (ret == EX_SUCCESS)
520 			ret = move_dir(pstruct->pw_dir, dir,
521 			    logname, zfs_flags);
522 
523 		if (ret != EX_SUCCESS)
524 			exit(ret);
525 	}
526 
527 	if (shell) {
528 		if (REL_PATH(shell)) {
529 			errmsg(M_RELPATH, shell);
530 			exit(EX_BADARG);
531 		}
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) {
539 
540 				errmsg(M_INVALID, shell, "shell");
541 				exit(EX_BADARG);
542 			}
543 
544 			call_pass = 1;
545 		}
546 	}
547 
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;
554 	}
555 
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);
563 		}
564 		call_pass = 1;
565 	}
566 
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);
573 		}
574 		call_pass = 1;
575 	}
576 
577 	if (nkeys > 0)
578 		call_pass = 1;
579 
580 	/* that's it for validations - now do the work */
581 
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);
588 		}
589 	}
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);
595 		}
596 	}
597 
598 
599 	if (!call_pass) exit(ret);
600 
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 *));
604 
605 	argindex = 0;
606 	nargv[argindex++] = PASSMGMT;
607 	nargv[argindex++] = "-m";	/* modify */
608 
609 	if (comment) {	/* comment */
610 		nargv[argindex++] = "-c";
611 		nargv[argindex++] = comment;
612 	}
613 
614 	if (dir) {
615 		/* flags for home directory */
616 		nargv[argindex++] = "-h";
617 		nargv[argindex++] = dir;
618 	}
619 
620 	if (group) {
621 		/* set gid flag */
622 		nargv[argindex++] = "-g";
623 		(void) sprintf(gidstring, "%u", gid);
624 		nargv[argindex++] = gidstring;
625 	}
626 
627 	if (shell) { 	/* shell */
628 		nargv[argindex++] = "-s";
629 		nargv[argindex++] = shell;
630 	}
631 
632 	if (inactstr) {
633 		nargv[argindex++] = "-f";
634 		nargv[argindex++] = inactstr;
635 	}
636 
637 	if (expire) {
638 		nargv[argindex++] = "-e";
639 		nargv[argindex++] = expire;
640 	}
641 
642 	if (uidstr) {	/* set uid flag */
643 		nargv[argindex++] = "-u";
644 		(void) sprintf(uidstring, "%u", uid);
645 		nargv[argindex++] = uidstring;
646 	}
647 
648 	if (oflag) nargv[argindex++] = "-o";
649 
650 	if (new_logname) {	/* redefine login name */
651 		nargv[argindex++] = "-l";
652 		nargv[argindex++] = new_logname;
653 	}
654 
655 	if (nkeys > 0)
656 		addkey_args(nargv, &argindex);
657 
658 	/* finally - login name */
659 	nargv[argindex++] = logname;
660 
661 	/* set the last to null */
662 	nargv[argindex++] = NULL;
663 
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;
671 
672 		case PEX_HOSED_FILES:
673 			errmsg(M_HOSED_FILES);
674 			exit(EX_INCONSISTENT);
675 			break;
676 
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;
686 
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;
692 
693 		case PEX_BADNAME:
694 			/* invalid loname */
695 			errmsg(M_USED, logname);
696 			exit(EX_NAME_EXISTS);
697 			break;
698 
699 		default:
700 			errmsg(M_UPDATE, "modified");
701 			exit(ret);
702 			break;
703 		}
704 	}
705 	if (tries == 0) {
706 		errmsg(M_UPDATE, "modified");
707 	}
708 
709 	exit(ret);
710 	/*NOTREACHED*/
711 }
712