usermod.c revision 4321:a8930ec16e52
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 2007 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#pragma ident	"%Z%%M%	%I%	%E% SMI"
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
83extern int **valid_lgroup(), isbusy();
84extern int valid_uid(), check_perm(), create_home(), move_dir();
85extern int valid_expire(), edit_group(), call_passmgmt();
86extern projid_t **valid_lproject();
87
88static uid_t uid;		/* new uid */
89static gid_t gid;			/* gid of new login */
90static char *new_logname = NULL;	/* new login name with -l option */
91static char *uidstr = NULL;		/* uid from command line */
92static char *group = NULL;		/* group from command line */
93static char *grps = NULL;		/* multi groups from command line */
94static char *dir = NULL;		/* home dir from command line */
95static char *shell = NULL;		/* shell from command line */
96static char *comment = NULL;		/* comment from command line */
97static char *logname = NULL;		/* login name to add */
98static char *inactstr = NULL;		/* inactive from command line */
99static char *expire = NULL;		/* expiration date from command line */
100static char *projects = NULL;		/* project ids from command line */
101static char *usertype;
102
103char *cmdname;
104static char gidstring[32], uidstring[32];
105char inactstring[10];
106
107char *
108strcpmalloc(str)
109char *str;
110{
111	if (str == NULL)
112		return (NULL);
113
114	return (strdup(str));
115}
116struct passwd *
117passwd_cpmalloc(opw)
118struct 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
141int
142main(argc, argv)
143int argc;
144char **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	/* inactive string is a positive integer */
523	if (inactstr) {
524		/* convert inactstr to integer */
525		inact = (int)strtol(inactstr, &ptr, 10);
526		if (*ptr || inact < 0) {
527			errmsg(M_INVALID, inactstr, "inactivity period");
528			exit(EX_BADARG);
529		}
530		call_pass = 1;
531	}
532
533	/* expiration string is a date, newer than today */
534	if (expire) {
535		if (*expire &&
536		    valid_expire(expire, (time_t *)0) == INVALID) {
537			errmsg(M_INVALID, expire, "expiration date");
538			exit(EX_BADARG);
539		}
540		call_pass = 1;
541	}
542
543	if (nkeys > 0)
544		call_pass = 1;
545
546	/* that's it for validations - now do the work */
547
548	if (grps) {
549		/* redefine login's supplentary group memberships */
550		ret = edit_group(logname, new_logname, gidlist, 1);
551		if (ret != EX_SUCCESS) {
552			errmsg(M_UPDATE, "modified");
553			exit(ret);
554		}
555	}
556	if (projects) {
557		ret = edit_project(logname, (char *)NULL, projlist, 0);
558		if (ret != EX_SUCCESS) {
559			errmsg(M_UPDATE, "modified");
560			exit(ret);
561		}
562	}
563
564
565	if (!call_pass) exit(ret);
566
567	/* only get to here if need to call passmgmt */
568	/* set up arguments to  passmgmt in nargv array */
569	nargv = malloc((30 + nkeys * 2) * sizeof (char *));
570
571	argindex = 0;
572	nargv[argindex++] = "passmgmt";
573	nargv[argindex++] = "-m";	/* modify */
574
575	if (comment) {	/* comment */
576		nargv[argindex++] = "-c";
577		nargv[argindex++] = comment;
578	}
579
580	if (dir) {
581		/* flags for home directory */
582		nargv[argindex++] = "-h";
583		nargv[argindex++] = dir;
584	}
585
586	if (group) {
587		/* set gid flag */
588		nargv[argindex++] = "-g";
589		(void) sprintf(gidstring, "%u", gid);
590		nargv[argindex++] = gidstring;
591	}
592
593	if (shell) { 	/* shell */
594		nargv[argindex++] = "-s";
595		nargv[argindex++] = shell;
596	}
597
598	if (inactstr) {
599		nargv[argindex++] = "-f";
600		nargv[argindex++] = inactstr;
601	}
602
603	if (expire) {
604		nargv[argindex++] = "-e";
605		nargv[argindex++] = expire;
606	}
607
608	if (uidstr) {	/* set uid flag */
609		nargv[argindex++] = "-u";
610		(void) sprintf(uidstring, "%u", uid);
611		nargv[argindex++] = uidstring;
612	}
613
614	if (oflag) nargv[argindex++] = "-o";
615
616	if (new_logname) {	/* redefine login name */
617		nargv[argindex++] = "-l";
618		nargv[argindex++] = new_logname;
619	}
620
621	if (nkeys > 0)
622		addkey_args(nargv, &argindex);
623
624	/* finally - login name */
625	nargv[argindex++] = logname;
626
627	/* set the last to null */
628	nargv[argindex++] = NULL;
629
630	/* now call passmgmt */
631	ret = PEX_FAILED;
632	for (tries = 3; ret != PEX_SUCCESS && tries--; ) {
633		switch (ret = call_passmgmt(nargv)) {
634		case PEX_SUCCESS:
635		case PEX_BUSY:
636			break;
637
638		case PEX_HOSED_FILES:
639			errmsg(M_HOSED_FILES);
640			exit(EX_INCONSISTENT);
641			break;
642
643		case PEX_SYNTAX:
644		case PEX_BADARG:
645			/* should NEVER occur that passmgmt usage is wrong */
646			if (is_role(usertype))
647				errmsg(M_MRUSAGE);
648			else
649				errmsg(M_MUSAGE);
650			exit(EX_SYNTAX);
651			break;
652
653		case PEX_BADUID:
654			/* uid in use - shouldn't happen print message anyway */
655			errmsg(M_UID_USED, uid);
656			exit(EX_ID_EXISTS);
657			break;
658
659		case PEX_BADNAME:
660			/* invalid loname */
661			errmsg(M_USED, logname);
662			exit(EX_NAME_EXISTS);
663			break;
664
665		default:
666			errmsg(M_UPDATE, "modified");
667			exit(ret);
668			break;
669		}
670	}
671	if (tries == 0) {
672		errmsg(M_UPDATE, "modified");
673	}
674
675	exit(ret);
676	/*NOTREACHED*/
677}
678