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