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