xref: /illumos-gate/usr/src/cmd/oamuser/user/useradd.c (revision 7c478bd9)
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 #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	<errno.h>
43 #include	<project.h>
44 #include	<unistd.h>
45 #include	<user_attr.h>
46 #include	"users.h"
47 #include	"messages.h"
48 #include	"userdisp.h"
49 #include	"funcs.h"
50 
51 /*
52  *  useradd [-u uid [-o] | -g group | -G group [[, group]...] | -d dir [-m]
53  *		| -s shell | -c comment | -k skel_dir] ]
54  *		[ -A authorization [, authorization ...]]
55  *		[ -P profile [, profile ...]]
56  *		[ -K key=value ]
57  *		[ -R role [, role ...]] [-p project [, project ...]] login
58  *  useradd -D [ -g group ] [ -b base_dir | -f inactive | -e expire ]
59  *		[ -A authorization [, authorization ...]]
60  *		[ -P profile [, profile ...]] [ -K key=value ]
61  *		[ -R role [, role ...]] [-p project [, project ...]] login
62  *
63  *	This command adds new user logins to the system.  Arguments are:
64  *
65  *	uid - an integer
66  *	group - an existing group's integer ID or char string name
67  *	dir - home directory
68  *	shell - a program to be used as a shell
69  *	comment - any text string
70  *	skel_dir - a skeleton directory
71  *	base_dir - a directory
72  *	login - a string of printable chars except colon(:)
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  *	project - One or more comma-separated project names or numbers
79  *
80  */
81 
82 extern struct userdefs *getusrdef();
83 extern void dispusrdef();
84 
85 static void cleanup();
86 
87 extern uid_t findnextuid(void);
88 extern int check_perm(), valid_expire();
89 extern int putusrdef(), valid_uid();
90 extern int call_passmgmt(), edit_group(), create_home();
91 extern int edit_project();
92 extern int **valid_lgroup();
93 extern projid_t **valid_lproject();
94 extern void update_def(struct userdefs *);
95 extern void import_def(struct userdefs *);
96 
97 static uid_t uid;			/* new uid */
98 static char *logname;			/* login name to add */
99 static struct userdefs *usrdefs;	/* defaults for useradd */
100 
101 char *cmdname;
102 
103 static char homedir[ PATH_MAX + 1 ];	/* home directory */
104 static char gidstring[32];		/* group id string representation */
105 static gid_t gid;			/* gid of new login */
106 static char uidstring[32];		/* user id string representation */
107 static char *uidstr = NULL;		/* uid from command line */
108 static char *base_dir = NULL;		/* base_dir from command line */
109 static char *group = NULL;		/* group from command line */
110 static char *grps = NULL;		/* multi groups from command line */
111 static char *dir = NULL;		/* home dir from command line */
112 static char *shell = NULL;		/* shell from command line */
113 static char *comment = NULL;		/* comment from command line */
114 static char *skel_dir = NULL;		/* skel dir from command line */
115 static long inact;			/* inactive days */
116 static char *inactstr = NULL;		/* inactive from command line */
117 static char inactstring[10];		/* inactivity string representation */
118 static char *expirestr = NULL;		/* expiration date from command line */
119 static char *projects = NULL;		/* project id's from command line */
120 
121 static char *usertype = NULL;	/* type of user, either role or normal */
122 
123 int
124 main(argc, argv)
125 int argc;
126 char *argv[];
127 {
128 	int ch, ret, mflag = 0, oflag = 0, Dflag = 0, **gidlist;
129 	projid_t **projlist;
130 	char *ptr;			/* loc in a str, may be set by strtol */
131 	struct group *g_ptr;
132 	struct project p_ptr;
133 	char mybuf[PROJECT_BUFSZ];
134 	struct stat statbuf;		/* status buffer for stat */
135 	int warning;
136 	int busy = 0;
137 	char **nargv;			/* arguments for execvp of passmgmt */
138 	int argindex;			/* argument index into nargv */
139 
140 	cmdname = argv[0];
141 
142 	if (geteuid() != 0) {
143 		errmsg(M_PERM_DENIED);
144 		exit(EX_NO_PERM);
145 	}
146 
147 	opterr = 0;			/* no print errors from getopt */
148 	usertype = getusertype(argv[0]);
149 
150 	change_key(USERATTR_TYPE_KW, usertype);
151 
152 	while ((ch = getopt(argc, argv,
153 		    "b:c:Dd:e:f:G:g:k:mop:s:u:A:P:R:K:")) != EOF)
154 		switch (ch) {
155 		case 'b':
156 			base_dir = optarg;
157 			break;
158 
159 		case 'c':
160 			comment = optarg;
161 			break;
162 
163 		case 'D':
164 			Dflag++;
165 			break;
166 
167 		case 'd':
168 			dir = optarg;
169 			break;
170 
171 		case 'e':
172 			expirestr = optarg;
173 			break;
174 
175 		case 'f':
176 			inactstr = optarg;
177 			break;
178 
179 		case 'G':
180 			grps = optarg;
181 			break;
182 
183 		case 'g':
184 			group = optarg;
185 			break;
186 
187 		case 'k':
188 			skel_dir = optarg;
189 			break;
190 
191 		case 'm':
192 			mflag++;
193 			break;
194 
195 		case 'o':
196 			oflag++;
197 			break;
198 
199 		case 'p':
200 			projects = optarg;
201 			break;
202 
203 		case 's':
204 			shell = optarg;
205 			break;
206 
207 		case 'u':
208 			uidstr = optarg;
209 			break;
210 
211 		case 'A':
212 			change_key(USERATTR_AUTHS_KW, optarg);
213 			break;
214 
215 		case 'P':
216 			change_key(USERATTR_PROFILES_KW, optarg);
217 			break;
218 
219 		case 'R':
220 			if (is_role(usertype)) {
221 				errmsg(M_ARUSAGE);
222 				exit(EX_SYNTAX);
223 			}
224 			change_key(USERATTR_ROLES_KW, optarg);
225 			break;
226 
227 		case 'K':
228 			change_key(NULL, optarg);
229 			break;
230 
231 		default:
232 		case '?':
233 			if (is_role(usertype))
234 				errmsg(M_ARUSAGE);
235 			else
236 				errmsg(M_AUSAGE);
237 			exit(EX_SYNTAX);
238 		}
239 
240 	/* get defaults for adding new users */
241 	usrdefs = getusrdef(usertype);
242 
243 	if (Dflag) {
244 		/* DISPLAY mode */
245 
246 		/* check syntax */
247 		if (optind != argc) {
248 			if (is_role(usertype))
249 				errmsg(M_ARUSAGE);
250 			else
251 				errmsg(M_AUSAGE);
252 			exit(EX_SYNTAX);
253 		}
254 
255 		if (uidstr || oflag || grps || dir || mflag ||
256 		    shell || comment || skel_dir) {
257 			if (is_role(usertype))
258 				errmsg(M_ARUSAGE);
259 			else
260 				errmsg(M_AUSAGE);
261 			exit(EX_SYNTAX);
262 		}
263 
264 		/* Group must be an existing group */
265 		if (group) {
266 			switch (valid_group(group, &g_ptr, &warning)) {
267 			case INVALID:
268 				errmsg(M_INVALID, group, "group id");
269 				exit(EX_BADARG);
270 				/*NOTREACHED*/
271 			case TOOBIG:
272 				errmsg(M_TOOBIG, "gid", group);
273 				exit(EX_BADARG);
274 				/*NOTREACHED*/
275 			case RESERVED:
276 			case UNIQUE:
277 				errmsg(M_GRP_NOTUSED, group);
278 				exit(EX_NAME_NOT_EXIST);
279 			}
280 			if (warning)
281 				warningmsg(warning, group);
282 
283 			usrdefs->defgroup = g_ptr->gr_gid;
284 			usrdefs->defgname = g_ptr->gr_name;
285 
286 		}
287 
288 		/* project must be an existing project */
289 		if (projects) {
290 			switch (valid_project(projects, &p_ptr, mybuf,
291 			    sizeof (mybuf), &warning)) {
292 			case INVALID:
293 				errmsg(M_INVALID, projects, "project id");
294 				exit(EX_BADARG);
295 				/*NOTREACHED*/
296 			case TOOBIG:
297 				errmsg(M_TOOBIG, "projid", projects);
298 				exit(EX_BADARG);
299 				/*NOTREACHED*/
300 			case UNIQUE:
301 				errmsg(M_PROJ_NOTUSED, projects);
302 				exit(EX_NAME_NOT_EXIST);
303 			}
304 			if (warning)
305 				warningmsg(warning, projects);
306 
307 			usrdefs->defproj = p_ptr.pj_projid;
308 			usrdefs->defprojname = p_ptr.pj_name;
309 		}
310 
311 		/* base_dir must be an existing directory */
312 		if (base_dir) {
313 			if (REL_PATH(base_dir)) {
314 				errmsg(M_RELPATH, base_dir);
315 				exit(EX_BADARG);
316 			}
317 			if (stat(base_dir, &statbuf) < 0 &&
318 			    (statbuf.st_mode & S_IFMT) != S_IFDIR) {
319 				errmsg(M_INVALID, base_dir, "base directory");
320 				exit(EX_BADARG);
321 			}
322 
323 			usrdefs->defparent = base_dir;
324 		}
325 
326 		/* inactivity period is an integer */
327 		if (inactstr) {
328 			/* convert inactstr to integer */
329 			inact = strtol(inactstr, &ptr, 10);
330 			if (*ptr || inact < 0) {
331 				errmsg(M_INVALID, inactstr,
332 				    "inactivity period");
333 				exit(EX_BADARG);
334 			}
335 
336 			usrdefs->definact = inact;
337 		}
338 
339 		/* expiration string is a date, newer than today */
340 		if (expirestr) {
341 			if (*expirestr) {
342 				if (valid_expire(expirestr, (time_t *)0)
343 				    == INVALID) {
344 					errmsg(M_INVALID, expirestr,
345 					    "expiration date");
346 					exit(EX_BADARG);
347 				}
348 				usrdefs->defexpire = expirestr;
349 			} else
350 				/* Unset the expiration date */
351 				usrdefs->defexpire = "";
352 		}
353 
354 		update_def(usrdefs);
355 
356 		/* change defaults for useradd */
357 		if (putusrdef(usrdefs, usertype) < 0) {
358 			errmsg(M_UPDATE, "created");
359 			exit(EX_UPDATE);
360 		}
361 
362 		/* Now, display */
363 		dispusrdef(stdout, (D_ALL & ~D_RID), usertype);
364 		exit(EX_SUCCESS);
365 
366 	}
367 
368 	/* ADD mode */
369 
370 	/* check syntax */
371 	if (optind != argc - 1 || base_dir || (skel_dir && !mflag)) {
372 		if (is_role(usertype))
373 			errmsg(M_ARUSAGE);
374 		else
375 			errmsg(M_AUSAGE);
376 		exit(EX_SYNTAX);
377 	}
378 
379 	logname = argv[optind];
380 	switch (valid_login(logname, (struct passwd **)NULL, &warning)) {
381 	case INVALID:
382 		errmsg(M_INVALID, logname, "login name");
383 		exit(EX_BADARG);
384 		/*NOTREACHED*/
385 
386 	case NOTUNIQUE:
387 		errmsg(M_USED, logname);
388 		exit(EX_NAME_EXISTS);
389 		/*NOTREACHED*/
390 	}
391 
392 	if (warning)
393 		warningmsg(warning, logname);
394 	if (uidstr) {
395 		/* convert uidstr to integer */
396 		errno = 0;
397 		uid = (uid_t)strtol(uidstr, &ptr, (int)10);
398 		if (*ptr || errno == ERANGE) {
399 			errmsg(M_INVALID, uidstr, "user id");
400 			exit(EX_BADARG);
401 		}
402 
403 		switch (valid_uid(uid, NULL)) {
404 		case NOTUNIQUE:
405 			if (!oflag) {
406 				/* override not specified */
407 				errmsg(M_UID_USED, uid);
408 				exit(EX_ID_EXISTS);
409 			}
410 			break;
411 		case RESERVED:
412 			errmsg(M_RESERVED, uid);
413 			break;
414 		case TOOBIG:
415 			errmsg(M_TOOBIG, "uid", uid);
416 			exit(EX_BADARG);
417 			break;
418 		}
419 
420 	} else {
421 
422 		if ((uid = findnextuid()) < 0) {
423 			errmsg(M_INVALID, "default id", "user id");
424 			exit(EX_ID_EXISTS);
425 		}
426 	}
427 
428 	if (group) {
429 		switch (valid_group(group, &g_ptr, &warning)) {
430 		case INVALID:
431 			errmsg(M_INVALID, group, "group id");
432 			exit(EX_BADARG);
433 			/*NOTREACHED*/
434 		case TOOBIG:
435 			errmsg(M_TOOBIG, "gid", group);
436 			exit(EX_BADARG);
437 			/*NOTREACHED*/
438 		case RESERVED:
439 		case UNIQUE:
440 			errmsg(M_GRP_NOTUSED, group);
441 			exit(EX_NAME_NOT_EXIST);
442 			/*NOTREACHED*/
443 		}
444 
445 		if (warning)
446 			warningmsg(warning, group);
447 		gid = g_ptr->gr_gid;
448 
449 	} else gid = usrdefs->defgroup;
450 
451 	if (grps) {
452 		if (!*grps)
453 			/* ignore -G "" */
454 			grps = (char *)0;
455 		else if (!(gidlist = valid_lgroup(grps, gid)))
456 			exit(EX_BADARG);
457 	}
458 
459 	if (projects) {
460 		if (! *projects)
461 			projects = (char *)0;
462 		else if (! (projlist = valid_lproject(projects)))
463 			exit(EX_BADARG);
464 	}
465 
466 	if (!dir) {
467 		/* set homedir to home directory made from base_dir */
468 		(void) sprintf(homedir, "%s/%s", usrdefs->defparent, logname);
469 
470 	} else if (REL_PATH(dir)) {
471 		errmsg(M_RELPATH, dir);
472 		exit(EX_BADARG);
473 
474 	} else
475 		(void) strcpy(homedir, dir);
476 
477 	if (mflag) {
478 		/* Does home dir. already exist? */
479 		if (stat(homedir, &statbuf) == 0) {
480 			/* directory exists - don't try to create */
481 			mflag = 0;
482 
483 			if (check_perm(statbuf, uid, gid, S_IXOTH) != 0)
484 				errmsg(M_NO_PERM, logname, homedir);
485 		}
486 	}
487 
488 	if (shell) {
489 		if (REL_PATH(shell)) {
490 			errmsg(M_RELPATH, shell);
491 			exit(EX_BADARG);
492 		}
493 		/* check that shell is an executable file */
494 		if (stat(shell, &statbuf) < 0 ||
495 		    (statbuf.st_mode & S_IFMT) != S_IFREG ||
496 		    (statbuf.st_mode & 0555) != 0555) {
497 
498 			errmsg(M_INVALID, shell, "shell");
499 			exit(EX_BADARG);
500 		}
501 	} else shell = usrdefs->defshell;
502 
503 	if (skel_dir) {
504 		if (REL_PATH(skel_dir)) {
505 			errmsg(M_RELPATH, skel_dir);
506 			exit(EX_BADARG);
507 		}
508 		if (stat(skel_dir, &statbuf) < 0 &&
509 		    (statbuf.st_mode & S_IFMT) != S_IFDIR) {
510 
511 			errmsg(M_INVALID, skel_dir, "directory");
512 			exit(EX_BADARG);
513 		}
514 	} else skel_dir = usrdefs->defskel;
515 
516 	if (inactstr) {
517 		/* convert inactstr to integer */
518 		inact = strtol(inactstr, &ptr, 10);
519 		if (*ptr || inact < 0) {
520 			errmsg(M_INVALID, inactstr, "inactivity period");
521 			exit(EX_BADARG);
522 		}
523 	} else inact = usrdefs->definact;
524 
525 	/* expiration string is a date, newer than today */
526 	if (expirestr) {
527 		if (*expirestr) {
528 			if (valid_expire(expirestr, (time_t *)0) == INVALID) {
529 				errmsg(M_INVALID, expirestr, "expiration date");
530 				exit(EX_BADARG);
531 			}
532 			usrdefs->defexpire = expirestr;
533 		} else
534 			/* Unset the expiration date */
535 			expirestr = (char *)0;
536 
537 	} else expirestr = usrdefs->defexpire;
538 
539 	import_def(usrdefs);
540 
541 	/* must now call passmgmt */
542 
543 	/* set up arguments to  passmgmt in nargv array */
544 	nargv = malloc((30 + nkeys * 2) * sizeof (char *));
545 	argindex = 0;
546 	nargv[argindex++] = "passmgmt";
547 	nargv[argindex++] = "-a";	/* add */
548 
549 	if (comment) {
550 		/* comment */
551 		nargv[argindex++] = "-c";
552 		nargv[argindex++] = comment;
553 	}
554 
555 	/* flags for home directory */
556 	nargv[argindex++] = "-h";
557 	nargv[argindex++] = homedir;
558 
559 	/* set gid flag */
560 	nargv[argindex++] = "-g";
561 	(void) sprintf(gidstring, "%ld", gid);
562 	nargv[argindex++] = gidstring;
563 
564 	/* shell */
565 	nargv[argindex++] = "-s";
566 	nargv[argindex++] = shell;
567 
568 	/* set inactive */
569 	nargv[argindex++] = "-f";
570 	(void) sprintf(inactstring, "%ld", inact);
571 	nargv[argindex++] = inactstring;
572 
573 	/* set expiration date */
574 	if (expirestr) {
575 		nargv[argindex++] = "-e";
576 		nargv[argindex++] = expirestr;
577 	}
578 
579 	/* set uid flag */
580 	nargv[argindex++] = "-u";
581 	(void) sprintf(uidstring, "%ld", uid);
582 	nargv[argindex++] = uidstring;
583 
584 	if (oflag) nargv[argindex++] = "-o";
585 
586 	if (nkeys > 1)
587 		addkey_args(nargv, &argindex);
588 
589 	/* finally - login name */
590 	nargv[argindex++] = logname;
591 
592 	/* set the last to null */
593 	nargv[argindex++] = NULL;
594 
595 	/* now call passmgmt */
596 	ret = PEX_FAILED;
597 	/*
598 	 * If call_passmgmt fails for any reason other than PEX_BADUID, exit
599 	 * is invoked with an appropriate error message. If PEX_BADUID is
600 	 * returned, then if the user specified the ID, exit is invoked
601 	 * with an appropriate error message. Otherwise we try to pick a
602 	 * different ID and try again. If we run out of IDs, i.e. no more
603 	 * users can be created, then -1 is returned and we terminate via exit.
604 	 * If PEX_BUSY is returned we increment a count, since we will stop
605 	 * trying if PEX_BUSY reaches 3. For PEX_SUCCESS we immediately
606 	 * terminate the loop.
607 	 */
608 	while (busy < 3 && ret != PEX_SUCCESS) {
609 		switch (ret = call_passmgmt(nargv)) {
610 		case PEX_SUCCESS:
611 			break;
612 		case PEX_BUSY:
613 			busy++;
614 			break;
615 		case PEX_HOSED_FILES:
616 			errmsg(M_HOSED_FILES);
617 			exit(EX_INCONSISTENT);
618 			break;
619 
620 		case PEX_SYNTAX:
621 		case PEX_BADARG:
622 			/* should NEVER occur that passmgmt usage is wrong */
623 			if (is_role(usertype))
624 				errmsg(M_ARUSAGE);
625 			else
626 				errmsg(M_AUSAGE);
627 			exit(EX_SYNTAX);
628 			break;
629 
630 		case PEX_BADUID:
631 			/*
632 			 * The uid has been taken. If it was specified by a
633 			 * user, then we must fail. Otherwise, keep trying
634 			 * to get a good uid until we run out of IDs.
635 			 */
636 			if (uidstr != NULL) {
637 				errmsg(M_UID_USED, uid);
638 				exit(EX_ID_EXISTS);
639 			} else {
640 				if ((uid = findnextuid()) < 0) {
641 					errmsg(M_INVALID, "default id",
642 					    "user id");
643 					exit(EX_ID_EXISTS);
644 				}
645 				(void) sprintf(uidstring, "%ld", uid);
646 			}
647 			break;
648 
649 		case PEX_BADNAME:
650 			/* invalid loname */
651 			errmsg(M_USED, logname);
652 			exit(EX_NAME_EXISTS);
653 			break;
654 
655 		default:
656 			errmsg(M_UPDATE, "created");
657 			exit(ret);
658 			break;
659 		}
660 	}
661 	if (busy == 3) {
662 		errmsg(M_UPDATE, "created");
663 		exit(ret);
664 	}
665 
666 	/* add group entry */
667 	if (grps && edit_group(logname, (char *)0, gidlist, 0)) {
668 		errmsg(M_UPDATE, "created");
669 		cleanup(logname);
670 		exit(EX_UPDATE);
671 	}
672 
673 	/* update project database */
674 	if (projects && edit_project(logname, (char *)NULL, projlist, 0)) {
675 		errmsg(M_UPDATE, "created");
676 		cleanup(logname);
677 		exit(EX_UPDATE);
678 	}
679 
680 	/* create home directory */
681 	if (mflag &&
682 	    (create_home(homedir, skel_dir, uid, gid) != EX_SUCCESS)) {
683 		(void) edit_group(logname, (char *)0, (int **)0, 1);
684 		cleanup(logname);
685 		exit(EX_HOMEDIR);
686 	}
687 
688 	return (ret);
689 }
690 
691 static void
692 cleanup(logname)
693 char *logname;
694 {
695 	char *nargv[4];
696 
697 	nargv[0] = "passmgmt";
698 	nargv[1] = "-d";
699 	nargv[2] = logname;
700 	nargv[3] = NULL;
701 
702 	switch (call_passmgmt(nargv)) {
703 	case PEX_SUCCESS:
704 		break;
705 
706 	case PEX_SYNTAX:
707 		/* should NEVER occur that passmgmt usage is wrong */
708 		if (is_role(usertype))
709 			errmsg(M_ARUSAGE);
710 		else
711 			errmsg(M_AUSAGE);
712 		break;
713 
714 	case PEX_BADUID:
715 		/* uid is used - shouldn't happen but print message anyway */
716 		errmsg(M_UID_USED, uid);
717 		break;
718 
719 	case PEX_BADNAME:
720 		/* invalid loname */
721 		errmsg(M_USED, logname);
722 		break;
723 
724 	default:
725 		errmsg(M_UPDATE, "created");
726 		break;
727 	}
728 }
729