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