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