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