xref: /illumos-gate/usr/src/cmd/oamuser/user/usermod.c (revision c651b32e)
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 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
27 /*	  All Rights Reserved  	*/
28 
29 
30 
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <sys/param.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <ctype.h>
37 #include <limits.h>
38 #include <string.h>
39 #include <userdefs.h>
40 #include <user_attr.h>
41 #include <nss_dbdefs.h>
42 #include <errno.h>
43 #include <project.h>
44 #include "users.h"
45 #include "messages.h"
46 #include "funcs.h"
47 
48 /*
49  *  usermod [-u uid [-o] | -g group | -G group [[,group]...] | -d dir [-m]
50  *		| -s shell | -c comment | -l new_logname]
51  *		| -f inactive | -e expire ]
52  *		[ -A authorization [, authorization ...]]
53  *		[ -P profile [, profile ...]]
54  *		[ -R role [, role ...]]
55  *		[ -K key=value ]
56  *		[ -p project [, project]] login
57  *
58  *	This command adds new user logins to the system.  Arguments are:
59  *
60  *	uid - an integer less than MAXUID
61  *	group - an existing group's integer ID or char string name
62  *	dir - a directory
63  *	shell - a program to be used as a shell
64  *	comment - any text string
65  *	skel_dir - a directory
66  *	base_dir - a directory
67  *	rid - an integer less than 2**16 (USHORT)
68  *	login - a string of printable chars except colon (:)
69  *	inactive - number of days a login maybe inactive before it is locked
70  *	expire - date when a login is no longer valid
71  *	authorization - One or more comma separated authorizations defined
72  *			in auth_attr(4).
73  *	profile - One or more comma separated execution profiles defined
74  *		  in prof_attr(4)
75  *	role - One or more comma-separated role names defined in user_attr(4)
76  *	key=value - One or more -K options each specifying a valid user_attr(4)
77  *		attribute.
78  *
79  */
80 
81 extern int **valid_lgroup(), isbusy();
82 extern int valid_uid(), check_perm(), create_home(), move_dir();
83 extern int valid_expire(), edit_group(), call_passmgmt();
84 extern projid_t **valid_lproject();
85 
86 static uid_t uid;		/* new uid */
87 static gid_t gid;			/* gid of new login */
88 static char *new_logname = NULL;	/* new login name with -l option */
89 static char *uidstr = NULL;		/* uid from command line */
90 static char *group = NULL;		/* group from command line */
91 static char *grps = NULL;		/* multi groups from command line */
92 static char *dir = NULL;		/* home dir from command line */
93 static char *shell = NULL;		/* shell from command line */
94 static char *comment = NULL;		/* comment from command line */
95 static char *logname = NULL;		/* login name to add */
96 static char *inactstr = NULL;		/* inactive from command line */
97 static char *expire = NULL;		/* expiration date from command line */
98 static char *projects = NULL;		/* project ids from command line */
99 static char *usertype;
100 
101 char *cmdname;
102 static char gidstring[32], uidstring[32];
103 char inactstring[10];
104 
105 char *
106 strcpmalloc(str)
107 char *str;
108 {
109 	if (str == NULL)
110 		return (NULL);
111 
112 	return (strdup(str));
113 }
114 struct passwd *
115 passwd_cpmalloc(opw)
116 struct passwd *opw;
117 {
118 	struct passwd *npw;
119 
120 	if (opw == NULL)
121 		return (NULL);
122 
123 
124 	npw = malloc(sizeof (struct passwd));
125 
126 	npw->pw_name = strcpmalloc(opw->pw_name);
127 	npw->pw_passwd = strcpmalloc(opw->pw_passwd);
128 	npw->pw_uid = opw->pw_uid;
129 	npw->pw_gid = opw->pw_gid;
130 	npw->pw_age = strcpmalloc(opw->pw_age);
131 	npw->pw_comment = strcpmalloc(opw->pw_comment);
132 	npw->pw_gecos  = strcpmalloc(opw->pw_gecos);
133 	npw->pw_dir = strcpmalloc(opw->pw_dir);
134 	npw->pw_shell = strcpmalloc(opw->pw_shell);
135 
136 	return (npw);
137 }
138 
139 int
140 main(argc, argv)
141 int argc;
142 char **argv;
143 {
144 	int ch, ret = EX_SUCCESS, call_pass = 0, oflag = 0;
145 	int tries, mflag = 0, inact, **gidlist, flag = 0;
146 	boolean_t fail_if_busy = B_FALSE;
147 	char *ptr;
148 	struct passwd *pstruct;		/* password struct for login */
149 	struct passwd *pw;
150 	struct group *g_ptr;	/* validated group from -g */
151 	struct stat statbuf;		/* status buffer for stat */
152 #ifndef att
153 	FILE *pwf;		/* fille ptr for opened passwd file */
154 #endif
155 	int warning;
156 	projid_t **projlist;
157 	char **nargv;			/* arguments for execvp of passmgmt */
158 	int argindex;			/* argument index into nargv */
159 	userattr_t *ua;
160 	char *val;
161 	int isrole;			/* current account is role */
162 
163 	cmdname = argv[0];
164 
165 	if (geteuid() != 0) {
166 		errmsg(M_PERM_DENIED);
167 		exit(EX_NO_PERM);
168 	}
169 
170 	opterr = 0;			/* no print errors from getopt */
171 	/* get user type based on the program name */
172 	usertype = getusertype(argv[0]);
173 
174 	while ((ch = getopt(argc, argv,
175 				"c:d:e:f:G:g:l:mop:s:u:A:P:R:K:")) != EOF)
176 		switch (ch) {
177 		case 'c':
178 			comment = optarg;
179 			flag++;
180 			break;
181 		case 'd':
182 			dir = optarg;
183 			fail_if_busy = B_TRUE;
184 			flag++;
185 			break;
186 		case 'e':
187 			expire = optarg;
188 			flag++;
189 			break;
190 		case 'f':
191 			inactstr = optarg;
192 			flag++;
193 			break;
194 		case 'G':
195 			grps = optarg;
196 			flag++;
197 			break;
198 		case 'g':
199 			group = optarg;
200 			fail_if_busy = B_TRUE;
201 			flag++;
202 			break;
203 		case 'l':
204 			new_logname = optarg;
205 			fail_if_busy = B_TRUE;
206 			flag++;
207 			break;
208 		case 'm':
209 			mflag++;
210 			flag++;
211 			fail_if_busy = B_TRUE;
212 			break;
213 		case 'o':
214 			oflag++;
215 			flag++;
216 			fail_if_busy = B_TRUE;
217 			break;
218 		case 'p':
219 			projects = optarg;
220 			flag++;
221 			break;
222 		case 's':
223 			shell = optarg;
224 			flag++;
225 			break;
226 		case 'u':
227 			uidstr = optarg;
228 			flag++;
229 			fail_if_busy = B_TRUE;
230 			break;
231 		case 'A':
232 			change_key(USERATTR_AUTHS_KW, optarg);
233 			flag++;
234 			break;
235 		case 'P':
236 			change_key(USERATTR_PROFILES_KW, optarg);
237 			flag++;
238 			break;
239 		case 'R':
240 			change_key(USERATTR_ROLES_KW, optarg);
241 			flag++;
242 			break;
243 		case 'K':
244 			change_key(NULL, optarg);
245 			flag++;
246 			break;
247 		default:
248 		case '?':
249 			if (is_role(usertype))
250 				errmsg(M_MRUSAGE);
251 			else
252 				errmsg(M_MUSAGE);
253 			exit(EX_SYNTAX);
254 		}
255 
256 	if (optind != argc - 1 || flag == 0) {
257 		if (is_role(usertype))
258 			errmsg(M_MRUSAGE);
259 		else
260 			errmsg(M_MUSAGE);
261 		exit(EX_SYNTAX);
262 	}
263 
264 	if ((!uidstr && oflag) || (mflag && !dir)) {
265 		if (is_role(usertype))
266 			errmsg(M_MRUSAGE);
267 		else
268 			errmsg(M_MUSAGE);
269 		exit(EX_SYNTAX);
270 	}
271 
272 	logname = argv[optind];
273 
274 	/* Determine whether the account is a role or not */
275 	if ((ua = getusernam(logname)) == NULL ||
276 	    (val = kva_match(ua->attr, USERATTR_TYPE_KW)) == NULL ||
277 	    strcmp(val, USERATTR_TYPE_NONADMIN_KW) != 0)
278 		isrole = 0;
279 	else
280 		isrole = 1;
281 
282 	/* Verify that rolemod is used for roles and usermod for users */
283 	if (isrole != is_role(usertype)) {
284 		if (isrole)
285 			errmsg(M_ISROLE);
286 		else
287 			errmsg(M_ISUSER);
288 		exit(EX_SYNTAX);
289 	}
290 
291 	/* Set the usertype key; defaults to the commandline  */
292 	usertype = getsetdefval(USERATTR_TYPE_KW, usertype);
293 
294 	if (is_role(usertype)) {
295 		/* Roles can't have roles */
296 		if (getsetdefval(USERATTR_ROLES_KW, NULL) != NULL) {
297 			errmsg(M_MRUSAGE);
298 			exit(EX_SYNTAX);
299 		}
300 		/* If it was an ordinary user, delete its roles */
301 		if (!isrole)
302 			change_key(USERATTR_ROLES_KW, "");
303 	}
304 
305 #ifdef att
306 	pw = getpwnam(logname);
307 #else
308 	/*
309 	 * Do this with fgetpwent to make sure we are only looking on local
310 	 * system (since passmgmt only works on local system).
311 	 */
312 	if ((pwf = fopen("/etc/passwd", "r")) == NULL) {
313 		errmsg(M_OOPS, "open", "/etc/passwd");
314 		exit(EX_FAILURE);
315 	}
316 	while ((pw = fgetpwent(pwf)) != NULL)
317 		if (strcmp(pw->pw_name, logname) == 0)
318 			break;
319 
320 	fclose(pwf);
321 #endif
322 
323 	if (pw == NULL) {
324 		char		pwdb[NSS_BUFLEN_PASSWD];
325 		struct passwd	pwd;
326 
327 		if (getpwnam_r(logname, &pwd, pwdb, sizeof (pwdb)) == NULL) {
328 			/* This user does not exist. */
329 			errmsg(M_EXIST, logname);
330 			exit(EX_NAME_NOT_EXIST);
331 		} else {
332 			/* This user exists in non-local name service. */
333 			errmsg(M_NONLOCAL, logname);
334 			exit(EX_NOT_LOCAL);
335 		}
336 	}
337 
338 	pstruct = passwd_cpmalloc(pw);
339 
340 	/*
341 	 * We can't modify a logged in user if any of the following
342 	 * are being changed:
343 	 * uid (-u & -o), group (-g), home dir (-m), loginname (-l).
344 	 * If none of those are specified it is okay to go ahead
345 	 * some types of changes only take effect on next login, some
346 	 * like authorisations and profiles take effect instantly.
347 	 * One might think that -K type=role should require that the
348 	 * user not be logged in, however this would make it very
349 	 * difficult to make the root account a role using this command.
350 	 */
351 	if (isbusy(logname)) {
352 		if (fail_if_busy) {
353 			errmsg(M_BUSY, logname, "change");
354 			exit(EX_BUSY);
355 		}
356 		warningmsg(WARN_LOGGED_IN, logname);
357 	}
358 
359 	if (new_logname && strcmp(new_logname, logname)) {
360 		switch (valid_login(new_logname, (struct passwd **)NULL,
361 			&warning)) {
362 		case INVALID:
363 			errmsg(M_INVALID, new_logname, "login name");
364 			exit(EX_BADARG);
365 			/*NOTREACHED*/
366 
367 		case NOTUNIQUE:
368 			errmsg(M_USED, new_logname);
369 			exit(EX_NAME_EXISTS);
370 			/*NOTREACHED*/
371 		default:
372 			call_pass = 1;
373 			break;
374 		}
375 		if (warning)
376 			warningmsg(warning, logname);
377 	}
378 
379 	if (uidstr) {
380 		/* convert uidstr to integer */
381 		errno = 0;
382 		uid = (uid_t)strtol(uidstr, &ptr, (int)10);
383 		if (*ptr || errno == ERANGE) {
384 			errmsg(M_INVALID, uidstr, "user id");
385 			exit(EX_BADARG);
386 		}
387 
388 		if (uid != pstruct->pw_uid) {
389 			switch (valid_uid(uid, NULL)) {
390 			case NOTUNIQUE:
391 				if (!oflag) {
392 					/* override not specified */
393 					errmsg(M_UID_USED, uid);
394 					exit(EX_ID_EXISTS);
395 				}
396 				break;
397 			case RESERVED:
398 				errmsg(M_RESERVED, uid);
399 				break;
400 			case TOOBIG:
401 				errmsg(M_TOOBIG, "uid", uid);
402 				exit(EX_BADARG);
403 				break;
404 			}
405 
406 			call_pass = 1;
407 
408 		} else {
409 			/* uid's the same, so don't change anything */
410 			uidstr = NULL;
411 			oflag = 0;
412 		}
413 
414 	} else uid = pstruct->pw_uid;
415 
416 	if (group) {
417 		switch (valid_group(group, &g_ptr, &warning)) {
418 		case INVALID:
419 			errmsg(M_INVALID, group, "group id");
420 			exit(EX_BADARG);
421 			/*NOTREACHED*/
422 		case TOOBIG:
423 			errmsg(M_TOOBIG, "gid", group);
424 			exit(EX_BADARG);
425 			/*NOTREACHED*/
426 		case UNIQUE:
427 			errmsg(M_GRP_NOTUSED, group);
428 			exit(EX_NAME_NOT_EXIST);
429 			/*NOTREACHED*/
430 		case RESERVED:
431 			gid = (gid_t)strtol(group, &ptr, (int)10);
432 			errmsg(M_RESERVED_GID, gid);
433 			break;
434 		}
435 		if (warning)
436 			warningmsg(warning, group);
437 
438 		if (g_ptr != NULL)
439 			gid = g_ptr->gr_gid;
440 		else
441 			gid = pstruct->pw_gid;
442 
443 		/* call passmgmt if gid is different, else ignore group */
444 		if (gid != pstruct->pw_gid)
445 			call_pass = 1;
446 		else group = NULL;
447 
448 	} else gid = pstruct->pw_gid;
449 
450 	if (grps && *grps) {
451 		if (!(gidlist = valid_lgroup(grps, gid)))
452 			exit(EX_BADARG);
453 	} else
454 		gidlist = (int **)0;
455 
456 	if (projects && *projects) {
457 		if (! (projlist = valid_lproject(projects)))
458 			exit(EX_BADARG);
459 	} else
460 		projlist = (projid_t **)0;
461 
462 	if (dir) {
463 		if (REL_PATH(dir)) {
464 			errmsg(M_RELPATH, dir);
465 			exit(EX_BADARG);
466 		}
467 		if (strcmp(pstruct->pw_dir, dir) == 0) {
468 			/* home directory is the same so ignore dflag & mflag */
469 			dir = NULL;
470 			mflag = 0;
471 		} else call_pass = 1;
472 	}
473 
474 	if (mflag) {
475 		if (stat(dir, &statbuf) == 0) {
476 			/* Home directory exists */
477 			if (check_perm(statbuf, pstruct->pw_uid,
478 			    pstruct->pw_gid, S_IWOTH|S_IXOTH) != 0) {
479 				errmsg(M_NO_PERM, logname, dir);
480 				exit(EX_NO_PERM);
481 			}
482 
483 		} else ret = create_home(dir, NULL, uid, gid);
484 
485 		if (ret == EX_SUCCESS)
486 			ret = move_dir(pstruct->pw_dir, dir, logname);
487 
488 		if (ret != EX_SUCCESS)
489 			exit(ret);
490 	}
491 
492 	if (shell) {
493 		if (REL_PATH(shell)) {
494 			errmsg(M_RELPATH, shell);
495 			exit(EX_BADARG);
496 		}
497 		if (strcmp(pstruct->pw_shell, shell) == 0) {
498 			/* ignore s option if shell is not different */
499 			shell = NULL;
500 		} else {
501 			if (stat(shell, &statbuf) < 0 ||
502 			    (statbuf.st_mode & S_IFMT) != S_IFREG ||
503 			    (statbuf.st_mode & 0555) != 0555) {
504 
505 				errmsg(M_INVALID, shell, "shell");
506 				exit(EX_BADARG);
507 			}
508 
509 			call_pass = 1;
510 		}
511 	}
512 
513 	if (comment)
514 		/* ignore comment if comment is not changed */
515 		if (strcmp(pstruct->pw_comment, comment))
516 			call_pass = 1;
517 		else
518 			comment = NULL;
519 
520 	/* inactive string is a positive integer */
521 	if (inactstr) {
522 		/* convert inactstr to integer */
523 		inact = (int)strtol(inactstr, &ptr, 10);
524 		if (*ptr || inact < 0) {
525 			errmsg(M_INVALID, inactstr, "inactivity period");
526 			exit(EX_BADARG);
527 		}
528 		call_pass = 1;
529 	}
530 
531 	/* expiration string is a date, newer than today */
532 	if (expire) {
533 		if (*expire &&
534 		    valid_expire(expire, (time_t *)0) == INVALID) {
535 			errmsg(M_INVALID, expire, "expiration date");
536 			exit(EX_BADARG);
537 		}
538 		call_pass = 1;
539 	}
540 
541 	if (nkeys > 0)
542 		call_pass = 1;
543 
544 	/* that's it for validations - now do the work */
545 
546 	if (grps) {
547 		/* redefine login's supplentary group memberships */
548 		ret = edit_group(logname, new_logname, gidlist, 1);
549 		if (ret != EX_SUCCESS) {
550 			errmsg(M_UPDATE, "modified");
551 			exit(ret);
552 		}
553 	}
554 	if (projects) {
555 		ret = edit_project(logname, (char *)NULL, projlist, 0);
556 		if (ret != EX_SUCCESS) {
557 			errmsg(M_UPDATE, "modified");
558 			exit(ret);
559 		}
560 	}
561 
562 
563 	if (!call_pass) exit(ret);
564 
565 	/* only get to here if need to call passmgmt */
566 	/* set up arguments to  passmgmt in nargv array */
567 	nargv = malloc((30 + nkeys * 2) * sizeof (char *));
568 
569 	argindex = 0;
570 	nargv[argindex++] = PASSMGMT;
571 	nargv[argindex++] = "-m";	/* modify */
572 
573 	if (comment) {	/* comment */
574 		nargv[argindex++] = "-c";
575 		nargv[argindex++] = comment;
576 	}
577 
578 	if (dir) {
579 		/* flags for home directory */
580 		nargv[argindex++] = "-h";
581 		nargv[argindex++] = dir;
582 	}
583 
584 	if (group) {
585 		/* set gid flag */
586 		nargv[argindex++] = "-g";
587 		(void) sprintf(gidstring, "%u", gid);
588 		nargv[argindex++] = gidstring;
589 	}
590 
591 	if (shell) { 	/* shell */
592 		nargv[argindex++] = "-s";
593 		nargv[argindex++] = shell;
594 	}
595 
596 	if (inactstr) {
597 		nargv[argindex++] = "-f";
598 		nargv[argindex++] = inactstr;
599 	}
600 
601 	if (expire) {
602 		nargv[argindex++] = "-e";
603 		nargv[argindex++] = expire;
604 	}
605 
606 	if (uidstr) {	/* set uid flag */
607 		nargv[argindex++] = "-u";
608 		(void) sprintf(uidstring, "%u", uid);
609 		nargv[argindex++] = uidstring;
610 	}
611 
612 	if (oflag) nargv[argindex++] = "-o";
613 
614 	if (new_logname) {	/* redefine login name */
615 		nargv[argindex++] = "-l";
616 		nargv[argindex++] = new_logname;
617 	}
618 
619 	if (nkeys > 0)
620 		addkey_args(nargv, &argindex);
621 
622 	/* finally - login name */
623 	nargv[argindex++] = logname;
624 
625 	/* set the last to null */
626 	nargv[argindex++] = NULL;
627 
628 	/* now call passmgmt */
629 	ret = PEX_FAILED;
630 	for (tries = 3; ret != PEX_SUCCESS && tries--; ) {
631 		switch (ret = call_passmgmt(nargv)) {
632 		case PEX_SUCCESS:
633 		case PEX_BUSY:
634 			break;
635 
636 		case PEX_HOSED_FILES:
637 			errmsg(M_HOSED_FILES);
638 			exit(EX_INCONSISTENT);
639 			break;
640 
641 		case PEX_SYNTAX:
642 		case PEX_BADARG:
643 			/* should NEVER occur that passmgmt usage is wrong */
644 			if (is_role(usertype))
645 				errmsg(M_MRUSAGE);
646 			else
647 				errmsg(M_MUSAGE);
648 			exit(EX_SYNTAX);
649 			break;
650 
651 		case PEX_BADUID:
652 			/* uid in use - shouldn't happen print message anyway */
653 			errmsg(M_UID_USED, uid);
654 			exit(EX_ID_EXISTS);
655 			break;
656 
657 		case PEX_BADNAME:
658 			/* invalid loname */
659 			errmsg(M_USED, logname);
660 			exit(EX_NAME_EXISTS);
661 			break;
662 
663 		default:
664 			errmsg(M_UPDATE, "modified");
665 			exit(ret);
666 			break;
667 		}
668 	}
669 	if (tries == 0) {
670 		errmsg(M_UPDATE, "modified");
671 	}
672 
673 	exit(ret);
674 	/*NOTREACHED*/
675 }
676