xref: /illumos-gate/usr/src/cmd/oamuser/user/usermod.c (revision a7fe1d5b)
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  * Copyright (c) 2013 RackTop Systems.
31  */
32 
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <sys/param.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <ctype.h>
39 #include <limits.h>
40 #include <string.h>
41 #include <userdefs.h>
42 #include <user_attr.h>
43 #include <nss_dbdefs.h>
44 #include <errno.h>
45 #include <project.h>
46 #include "users.h"
47 #include "messages.h"
48 #include "funcs.h"
49 
50 /*
51  *  usermod [-u uid [-o] | -g group | -G group [[,group]...] | -d dir [-m]
52  *		| -s shell | -c comment | -l new_logname]
53  *		| -f inactive | -e expire ]
54  *		[ -A authorization [, authorization ...]]
55  *		[ -P profile [, profile ...]]
56  *		[ -R role [, role ...]]
57  *		[ -K key=value ]
58  *		[ -p project [, project]] login
59  *
60  *	This command adds new user logins to the system.  Arguments are:
61  *
62  *	uid - an integer less than MAXUID
63  *	group - an existing group's integer ID or char string name
64  *	dir - a directory
65  *	shell - a program to be used as a shell
66  *	comment - any text string
67  *	skel_dir - a directory
68  *	base_dir - a directory
69  *	rid - an integer less than 2**16 (USHORT)
70  *	login - a string of printable chars except colon (:)
71  *	inactive - number of days a login maybe inactive before it is locked
72  *	expire - date when a login is no longer valid
73  *	authorization - One or more comma separated authorizations defined
74  *			in auth_attr(4).
75  *	profile - One or more comma separated execution profiles defined
76  *		  in prof_attr(4)
77  *	role - One or more comma-separated role names defined in user_attr(4)
78  *	key=value - One or more -K options each specifying a valid user_attr(4)
79  *		attribute.
80  *
81  */
82 
83 extern int **valid_lgroup(), isbusy();
84 extern int valid_uid(), check_perm(), create_home(), move_dir();
85 extern int valid_expire(), edit_group(), call_passmgmt();
86 extern projid_t **valid_lproject();
87 
88 static uid_t uid;		/* new uid */
89 static gid_t gid;			/* gid of new login */
90 static char *new_logname = NULL;	/* new login name with -l option */
91 static char *uidstr = NULL;		/* uid from command line */
92 static char *group = NULL;		/* group from command line */
93 static char *grps = NULL;		/* multi groups from command line */
94 static char *dir = NULL;		/* home dir from command line */
95 static char *shell = NULL;		/* shell from command line */
96 static char *comment = NULL;		/* comment from command line */
97 static char *logname = NULL;		/* login name to add */
98 static char *inactstr = NULL;		/* inactive from command line */
99 static char *expire = NULL;		/* expiration date from command line */
100 static char *projects = NULL;		/* project ids from command line */
101 static char *usertype;
102 
103 char *cmdname;
104 static char gidstring[32], uidstring[32];
105 char inactstring[10];
106 
107 char *
108 strcpmalloc(str)
109 char *str;
110 {
111 	if (str == NULL)
112 		return (NULL);
113 
114 	return (strdup(str));
115 }
116 struct passwd *
117 passwd_cpmalloc(opw)
118 struct passwd *opw;
119 {
120 	struct passwd *npw;
121 
122 	if (opw == NULL)
123 		return (NULL);
124 
125 
126 	npw = malloc(sizeof (struct passwd));
127 
128 	npw->pw_name = strcpmalloc(opw->pw_name);
129 	npw->pw_passwd = strcpmalloc(opw->pw_passwd);
130 	npw->pw_uid = opw->pw_uid;
131 	npw->pw_gid = opw->pw_gid;
132 	npw->pw_age = strcpmalloc(opw->pw_age);
133 	npw->pw_comment = strcpmalloc(opw->pw_comment);
134 	npw->pw_gecos  = strcpmalloc(opw->pw_gecos);
135 	npw->pw_dir = strcpmalloc(opw->pw_dir);
136 	npw->pw_shell = strcpmalloc(opw->pw_shell);
137 
138 	return (npw);
139 }
140 
141 int
142 main(argc, argv)
143 int argc;
144 char **argv;
145 {
146 	int ch, ret = EX_SUCCESS, call_pass = 0, oflag = 0;
147 	int tries, mflag = 0, inact, **gidlist, flag = 0;
148 	boolean_t fail_if_busy = B_FALSE;
149 	char *ptr;
150 	struct passwd *pstruct;		/* password struct for login */
151 	struct passwd *pw;
152 	struct group *g_ptr;	/* validated group from -g */
153 	struct stat statbuf;		/* status buffer for stat */
154 #ifndef att
155 	FILE *pwf;		/* fille ptr for opened passwd file */
156 #endif
157 	int warning;
158 	projid_t **projlist;
159 	char **nargv;			/* arguments for execvp of passmgmt */
160 	int argindex;			/* argument index into nargv */
161 	userattr_t *ua;
162 	char *val;
163 	int isrole;			/* current account is role */
164 
165 	cmdname = argv[0];
166 
167 	if (geteuid() != 0) {
168 		errmsg(M_PERM_DENIED);
169 		exit(EX_NO_PERM);
170 	}
171 
172 	opterr = 0;			/* no print errors from getopt */
173 	/* get user type based on the program name */
174 	usertype = getusertype(argv[0]);
175 
176 	while ((ch = getopt(argc, argv,
177 				"c:d:e:f:G:g:l:mop:s:u:A:P:R:K:")) != EOF)
178 		switch (ch) {
179 		case 'c':
180 			comment = optarg;
181 			flag++;
182 			break;
183 		case 'd':
184 			dir = optarg;
185 			fail_if_busy = B_TRUE;
186 			flag++;
187 			break;
188 		case 'e':
189 			expire = optarg;
190 			flag++;
191 			break;
192 		case 'f':
193 			inactstr = optarg;
194 			flag++;
195 			break;
196 		case 'G':
197 			grps = optarg;
198 			flag++;
199 			break;
200 		case 'g':
201 			group = optarg;
202 			fail_if_busy = B_TRUE;
203 			flag++;
204 			break;
205 		case 'l':
206 			new_logname = optarg;
207 			fail_if_busy = B_TRUE;
208 			flag++;
209 			break;
210 		case 'm':
211 			mflag++;
212 			flag++;
213 			fail_if_busy = B_TRUE;
214 			break;
215 		case 'o':
216 			oflag++;
217 			flag++;
218 			fail_if_busy = B_TRUE;
219 			break;
220 		case 'p':
221 			projects = optarg;
222 			flag++;
223 			break;
224 		case 's':
225 			shell = optarg;
226 			flag++;
227 			break;
228 		case 'u':
229 			uidstr = optarg;
230 			flag++;
231 			fail_if_busy = B_TRUE;
232 			break;
233 		case 'A':
234 			change_key(USERATTR_AUTHS_KW, optarg);
235 			flag++;
236 			break;
237 		case 'P':
238 			change_key(USERATTR_PROFILES_KW, optarg);
239 			flag++;
240 			break;
241 		case 'R':
242 			change_key(USERATTR_ROLES_KW, optarg);
243 			flag++;
244 			break;
245 		case 'K':
246 			change_key(NULL, optarg);
247 			flag++;
248 			break;
249 		default:
250 		case '?':
251 			if (is_role(usertype))
252 				errmsg(M_MRUSAGE);
253 			else
254 				errmsg(M_MUSAGE);
255 			exit(EX_SYNTAX);
256 		}
257 
258 	if (optind != argc - 1 || flag == 0) {
259 		if (is_role(usertype))
260 			errmsg(M_MRUSAGE);
261 		else
262 			errmsg(M_MUSAGE);
263 		exit(EX_SYNTAX);
264 	}
265 
266 	if ((!uidstr && oflag) || (mflag && !dir)) {
267 		if (is_role(usertype))
268 			errmsg(M_MRUSAGE);
269 		else
270 			errmsg(M_MUSAGE);
271 		exit(EX_SYNTAX);
272 	}
273 
274 	logname = argv[optind];
275 
276 	/* Determine whether the account is a role or not */
277 	if ((ua = getusernam(logname)) == NULL ||
278 	    (val = kva_match(ua->attr, USERATTR_TYPE_KW)) == NULL ||
279 	    strcmp(val, USERATTR_TYPE_NONADMIN_KW) != 0)
280 		isrole = 0;
281 	else
282 		isrole = 1;
283 
284 	/* Verify that rolemod is used for roles and usermod for users */
285 	if (isrole != is_role(usertype)) {
286 		if (isrole)
287 			errmsg(M_ISROLE);
288 		else
289 			errmsg(M_ISUSER);
290 		exit(EX_SYNTAX);
291 	}
292 
293 	/* Set the usertype key; defaults to the commandline  */
294 	usertype = getsetdefval(USERATTR_TYPE_KW, usertype);
295 
296 	if (is_role(usertype)) {
297 		/* Roles can't have roles */
298 		if (getsetdefval(USERATTR_ROLES_KW, NULL) != NULL) {
299 			errmsg(M_MRUSAGE);
300 			exit(EX_SYNTAX);
301 		}
302 		/* If it was an ordinary user, delete its roles */
303 		if (!isrole)
304 			change_key(USERATTR_ROLES_KW, "");
305 	}
306 
307 #ifdef att
308 	pw = getpwnam(logname);
309 #else
310 	/*
311 	 * Do this with fgetpwent to make sure we are only looking on local
312 	 * system (since passmgmt only works on local system).
313 	 */
314 	if ((pwf = fopen("/etc/passwd", "r")) == NULL) {
315 		errmsg(M_OOPS, "open", "/etc/passwd");
316 		exit(EX_FAILURE);
317 	}
318 	while ((pw = fgetpwent(pwf)) != NULL)
319 		if (strcmp(pw->pw_name, logname) == 0)
320 			break;
321 
322 	fclose(pwf);
323 #endif
324 
325 	if (pw == NULL) {
326 		char		pwdb[NSS_BUFLEN_PASSWD];
327 		struct passwd	pwd;
328 
329 		if (getpwnam_r(logname, &pwd, pwdb, sizeof (pwdb)) == NULL) {
330 			/* This user does not exist. */
331 			errmsg(M_EXIST, logname);
332 			exit(EX_NAME_NOT_EXIST);
333 		} else {
334 			/* This user exists in non-local name service. */
335 			errmsg(M_NONLOCAL, logname);
336 			exit(EX_NOT_LOCAL);
337 		}
338 	}
339 
340 	pstruct = passwd_cpmalloc(pw);
341 
342 	/*
343 	 * We can't modify a logged in user if any of the following
344 	 * are being changed:
345 	 * uid (-u & -o), group (-g), home dir (-m), loginname (-l).
346 	 * If none of those are specified it is okay to go ahead
347 	 * some types of changes only take effect on next login, some
348 	 * like authorisations and profiles take effect instantly.
349 	 * One might think that -K type=role should require that the
350 	 * user not be logged in, however this would make it very
351 	 * difficult to make the root account a role using this command.
352 	 */
353 	if (isbusy(logname)) {
354 		if (fail_if_busy) {
355 			errmsg(M_BUSY, logname, "change");
356 			exit(EX_BUSY);
357 		}
358 		warningmsg(WARN_LOGGED_IN, logname);
359 	}
360 
361 	if (new_logname && strcmp(new_logname, logname)) {
362 		switch (valid_login(new_logname, (struct passwd **)NULL,
363 			&warning)) {
364 		case INVALID:
365 			errmsg(M_INVALID, new_logname, "login name");
366 			exit(EX_BADARG);
367 			/*NOTREACHED*/
368 
369 		case NOTUNIQUE:
370 			errmsg(M_USED, new_logname);
371 			exit(EX_NAME_EXISTS);
372 			/*NOTREACHED*/
373 		default:
374 			call_pass = 1;
375 			break;
376 		}
377 		if (warning)
378 			warningmsg(warning, logname);
379 	}
380 
381 	if (uidstr) {
382 		/* convert uidstr to integer */
383 		errno = 0;
384 		uid = (uid_t)strtol(uidstr, &ptr, (int)10);
385 		if (*ptr || errno == ERANGE) {
386 			errmsg(M_INVALID, uidstr, "user id");
387 			exit(EX_BADARG);
388 		}
389 
390 		if (uid != pstruct->pw_uid) {
391 			switch (valid_uid(uid, NULL)) {
392 			case NOTUNIQUE:
393 				if (!oflag) {
394 					/* override not specified */
395 					errmsg(M_UID_USED, uid);
396 					exit(EX_ID_EXISTS);
397 				}
398 				break;
399 			case RESERVED:
400 				errmsg(M_RESERVED, uid);
401 				break;
402 			case TOOBIG:
403 				errmsg(M_TOOBIG, "uid", uid);
404 				exit(EX_BADARG);
405 				break;
406 			}
407 
408 			call_pass = 1;
409 
410 		} else {
411 			/* uid's the same, so don't change anything */
412 			uidstr = NULL;
413 			oflag = 0;
414 		}
415 
416 	} else uid = pstruct->pw_uid;
417 
418 	if (group) {
419 		switch (valid_group(group, &g_ptr, &warning)) {
420 		case INVALID:
421 			errmsg(M_INVALID, group, "group id");
422 			exit(EX_BADARG);
423 			/*NOTREACHED*/
424 		case TOOBIG:
425 			errmsg(M_TOOBIG, "gid", group);
426 			exit(EX_BADARG);
427 			/*NOTREACHED*/
428 		case UNIQUE:
429 			errmsg(M_GRP_NOTUSED, group);
430 			exit(EX_NAME_NOT_EXIST);
431 			/*NOTREACHED*/
432 		case RESERVED:
433 			gid = (gid_t)strtol(group, &ptr, (int)10);
434 			errmsg(M_RESERVED_GID, gid);
435 			break;
436 		}
437 		if (warning)
438 			warningmsg(warning, group);
439 
440 		if (g_ptr != NULL)
441 			gid = g_ptr->gr_gid;
442 		else
443 			gid = pstruct->pw_gid;
444 
445 		/* call passmgmt if gid is different, else ignore group */
446 		if (gid != pstruct->pw_gid)
447 			call_pass = 1;
448 		else group = NULL;
449 
450 	} else gid = pstruct->pw_gid;
451 
452 	if (grps && *grps) {
453 		if (!(gidlist = valid_lgroup(grps, gid)))
454 			exit(EX_BADARG);
455 	} else
456 		gidlist = (int **)0;
457 
458 	if (projects && *projects) {
459 		if (! (projlist = valid_lproject(projects)))
460 			exit(EX_BADARG);
461 	} else
462 		projlist = (projid_t **)0;
463 
464 	if (dir) {
465 		if (REL_PATH(dir)) {
466 			errmsg(M_RELPATH, dir);
467 			exit(EX_BADARG);
468 		}
469 		if (strcmp(pstruct->pw_dir, dir) == 0) {
470 			/* home directory is the same so ignore dflag & mflag */
471 			dir = NULL;
472 			mflag = 0;
473 		} else call_pass = 1;
474 	}
475 
476 	if (mflag) {
477 		if (stat(dir, &statbuf) == 0) {
478 			/* Home directory exists */
479 			if (check_perm(statbuf, pstruct->pw_uid,
480 			    pstruct->pw_gid, S_IWOTH|S_IXOTH) != 0) {
481 				errmsg(M_NO_PERM, logname, dir);
482 				exit(EX_NO_PERM);
483 			}
484 
485 		} else ret = create_home(dir, NULL, uid, gid);
486 
487 		if (ret == EX_SUCCESS)
488 			ret = move_dir(pstruct->pw_dir, dir, logname);
489 
490 		if (ret != EX_SUCCESS)
491 			exit(ret);
492 	}
493 
494 	if (shell) {
495 		if (REL_PATH(shell)) {
496 			errmsg(M_RELPATH, shell);
497 			exit(EX_BADARG);
498 		}
499 		if (strcmp(pstruct->pw_shell, shell) == 0) {
500 			/* ignore s option if shell is not different */
501 			shell = NULL;
502 		} else {
503 			if (stat(shell, &statbuf) < 0 ||
504 			    (statbuf.st_mode & S_IFMT) != S_IFREG ||
505 			    (statbuf.st_mode & 0555) != 0555) {
506 
507 				errmsg(M_INVALID, shell, "shell");
508 				exit(EX_BADARG);
509 			}
510 
511 			call_pass = 1;
512 		}
513 	}
514 
515 	if (comment) {
516 		/* ignore comment if comment is not changed */
517 		if (strcmp(pstruct->pw_comment, comment))
518 			call_pass = 1;
519 		else
520 			comment = NULL;
521 	}
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, "%u", 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, "%u", 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