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 * Copyright (c) 2013 RackTop Systems.
31 */
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 <libcmdutils.h>
47 #include "users.h"
48 #include "messages.h"
49 #include "userdisp.h"
50 #include "funcs.h"
51
52 /*
53 * useradd [-u uid [-o] | -g group | -G group [[, group]...] | -d dir [-m]
54 * | -s shell | -c comment | -k skel_dir | -b base_dir] ]
55 * [ -A authorization [, authorization ...]]
56 * [ -P profile [, profile ...]]
57 * [ -K key=value ]
58 * [ -R role [, role ...]] [-p project [, project ...]] login
59 * useradd -D [ -g group ] [ -b base_dir | -f inactive | -e expire |
60 * -s shell | -k skel_dir ]
61 * [ -A authorization [, authorization ...]]
62 * [ -P profile [, profile ...]] [ -K key=value ]
63 * [ -R role [, role ...]] [-p project [, project ...]] login
64 *
65 * This command adds new user logins to the system. Arguments are:
66 *
67 * uid - an integer
68 * group - an existing group's integer ID or char string name
69 * dir - home directory
70 * shell - a program to be used as a shell
71 * comment - any text string
72 * skel_dir - a skeleton directory
73 * base_dir - a directory
74 * login - a string of printable chars except colon(:)
75 * authorization - One or more comma separated authorizations defined
76 * in auth_attr(4).
77 * profile - One or more comma separated execution profiles defined
78 * in prof_attr(4)
79 * role - One or more comma-separated role names defined in user_attr(4)
80 * project - One or more comma-separated project names or numbers
81 *
82 */
83
84 extern struct userdefs *getusrdef();
85 extern void dispusrdef();
86
87 static void cleanup();
88
89 extern int check_perm(), valid_expire();
90 extern int putusrdef(), valid_uid();
91 extern int call_passmgmt(), edit_group(), create_home();
92 extern int edit_project();
93 extern int **valid_lgroup();
94 extern projid_t **valid_lproject();
95 extern void update_def(struct userdefs *);
96 extern void import_def(struct userdefs *);
97
98 static uid_t uid; /* new uid */
99 static char *logname; /* login name to add */
100 static struct userdefs *usrdefs; /* defaults for useradd */
101
102 char *cmdname;
103
104 static char homedir[ PATH_MAX + 1 ]; /* home directory */
105 static char gidstring[32]; /* group id string representation */
106 static gid_t gid; /* gid of new login */
107 static char uidstring[32]; /* user id string representation */
108 static char *uidstr = NULL; /* uid from command line */
109 static char *base_dir = NULL; /* base_dir from command line */
110 static char *group = NULL; /* group from command line */
111 static char *grps = NULL; /* multi groups from command line */
112 static char *dir = NULL; /* home dir from command line */
113 static char *shell = NULL; /* shell from command line */
114 static char *comment = NULL; /* comment from command line */
115 static char *skel_dir = NULL; /* skel dir from command line */
116 static long inact; /* inactive days */
117 static char *inactstr = NULL; /* inactive from command line */
118 static char inactstring[10]; /* inactivity string representation */
119 static char *expirestr = NULL; /* expiration date from command line */
120 static char *projects = NULL; /* project id's from command line */
121
122 static char *usertype = NULL; /* type of user, either role or normal */
123
124 typedef enum {
125 BASEDIR = 0,
126 SKELDIR,
127 SHELL
128 } path_opt_t;
129
130
131 static void valid_input(path_opt_t, const char *);
132
133 int
134 main(argc, argv)
135 int argc;
136 char *argv[];
137 {
138 int ch, ret, mflag = 0, oflag = 0, Dflag = 0, **gidlist = NULL;
139 projid_t **projlist = NULL;
140 char *ptr; /* loc in a str, may be set by strtol */
141 struct group *g_ptr;
142 struct project p_ptr;
143 char mybuf[PROJECT_BUFSZ];
144 struct stat statbuf; /* status buffer for stat */
145 int warning;
146 int busy = 0;
147 char **nargv; /* arguments for execvp of passmgmt */
148 int argindex; /* argument index into nargv */
149
150 cmdname = argv[0];
151
152 if (geteuid() != 0) {
153 errmsg(M_PERM_DENIED);
154 exit(EX_NO_PERM);
155 }
156
157 opterr = 0; /* no print errors from getopt */
158 usertype = getusertype(argv[0]);
159
160 change_key(USERATTR_TYPE_KW, usertype);
161
162 while ((ch = getopt(argc, argv,
163 "b:c:Dd:e:f:G:g:k:mop:s:u:A:P:R:K:")) != EOF)
164 switch (ch) {
165 case 'b':
166 base_dir = optarg;
167 break;
168
169 case 'c':
170 comment = optarg;
171 break;
172
173 case 'D':
174 Dflag++;
175 break;
176
177 case 'd':
178 dir = optarg;
179 break;
180
181 case 'e':
182 expirestr = optarg;
183 break;
184
185 case 'f':
186 inactstr = optarg;
187 break;
188
189 case 'G':
190 grps = optarg;
191 break;
192
193 case 'g':
194 group = optarg;
195 break;
196
197 case 'k':
198 skel_dir = optarg;
199 break;
200
201 case 'm':
202 mflag++;
203 break;
204
205 case 'o':
206 oflag++;
207 break;
208
209 case 'p':
210 projects = optarg;
211 break;
212
213 case 's':
214 shell = optarg;
215 break;
216
217 case 'u':
218 uidstr = optarg;
219 break;
220
221 case 'A':
222 change_key(USERATTR_AUTHS_KW, optarg);
223 break;
224
225 case 'P':
226 change_key(USERATTR_PROFILES_KW, optarg);
227 break;
228
229 case 'R':
230 if (is_role(usertype)) {
231 errmsg(M_ARUSAGE);
232 exit(EX_SYNTAX);
233 }
234 change_key(USERATTR_ROLES_KW, optarg);
235 break;
236
237 case 'K':
238 change_key(NULL, optarg);
239 break;
240
241 default:
242 case '?':
243 if (is_role(usertype))
244 errmsg(M_ARUSAGE);
245 else
246 errmsg(M_AUSAGE);
247 exit(EX_SYNTAX);
248 }
249
250 /* get defaults for adding new users */
251 usrdefs = getusrdef(usertype);
252
253 if (Dflag) {
254 /* DISPLAY mode */
255
256 /* check syntax */
257 if (optind != argc) {
258 if (is_role(usertype))
259 errmsg(M_ARUSAGE);
260 else
261 errmsg(M_AUSAGE);
262 exit(EX_SYNTAX);
263 }
264
265 if (uidstr != NULL || oflag || grps != NULL ||
266 dir != NULL || mflag || comment != NULL) {
267 if (is_role(usertype))
268 errmsg(M_ARUSAGE);
269 else
270 errmsg(M_AUSAGE);
271 exit(EX_SYNTAX);
272 }
273
274 /* Group must be an existing group */
275 if (group != NULL) {
276 switch (valid_group(group, &g_ptr, &warning)) {
277 case INVALID:
278 errmsg(M_INVALID, group, "group id");
279 exit(EX_BADARG);
280 /*NOTREACHED*/
281 case TOOBIG:
282 errmsg(M_TOOBIG, "gid", group);
283 exit(EX_BADARG);
284 /*NOTREACHED*/
285 case RESERVED:
286 case UNIQUE:
287 errmsg(M_GRP_NOTUSED, group);
288 exit(EX_NAME_NOT_EXIST);
289 }
290 if (warning)
291 warningmsg(warning, group);
292
293 usrdefs->defgroup = g_ptr->gr_gid;
294 usrdefs->defgname = g_ptr->gr_name;
295
296 }
297
298 /* project must be an existing project */
299 if (projects != NULL) {
300 switch (valid_project(projects, &p_ptr, mybuf,
301 sizeof (mybuf), &warning)) {
302 case INVALID:
303 errmsg(M_INVALID, projects, "project id");
304 exit(EX_BADARG);
305 /*NOTREACHED*/
306 case TOOBIG:
307 errmsg(M_TOOBIG, "projid", projects);
308 exit(EX_BADARG);
309 /*NOTREACHED*/
310 case UNIQUE:
311 errmsg(M_PROJ_NOTUSED, projects);
312 exit(EX_NAME_NOT_EXIST);
313 }
314 if (warning)
315 warningmsg(warning, projects);
316
317 usrdefs->defproj = p_ptr.pj_projid;
318 usrdefs->defprojname = p_ptr.pj_name;
319 }
320
321 /* base_dir must be an existing directory */
322 if (base_dir != NULL) {
323 valid_input(BASEDIR, base_dir);
324 usrdefs->defparent = base_dir;
325 }
326
327 /* inactivity period is an integer */
328 if (inactstr != NULL) {
329 /* convert inactstr to integer */
330 inact = strtol(inactstr, &ptr, 10);
331 if (*ptr || inact < 0) {
332 errmsg(M_INVALID, inactstr,
333 "inactivity period");
334 exit(EX_BADARG);
335 }
336
337 usrdefs->definact = inact;
338 }
339
340 /* expiration string is a date, newer than today */
341 if (expirestr != NULL) {
342 if (*expirestr) {
343 if (valid_expire(expirestr, (time_t *)0)
344 == INVALID) {
345 errmsg(M_INVALID, expirestr,
346 "expiration date");
347 exit(EX_BADARG);
348 }
349 usrdefs->defexpire = expirestr;
350 } else
351 /* Unset the expiration date */
352 usrdefs->defexpire = "";
353 }
354
355 if (shell != NULL) {
356 valid_input(SHELL, shell);
357 usrdefs->defshell = shell;
358 }
359 if (skel_dir != NULL) {
360 valid_input(SKELDIR, skel_dir);
361 usrdefs->defskel = skel_dir;
362 }
363 update_def(usrdefs);
364
365 /* change defaults for useradd */
366 if (putusrdef(usrdefs, usertype) < 0) {
367 errmsg(M_UPDATE, "created");
368 exit(EX_UPDATE);
369 }
370
371 /* Now, display */
372 dispusrdef(stdout, (D_ALL & ~D_RID), usertype);
373 exit(EX_SUCCESS);
374
375 }
376
377 /* ADD mode */
378
379 /* check syntax */
380 if (optind != argc - 1 || (skel_dir != NULL && !mflag)) {
381 if (is_role(usertype))
382 errmsg(M_ARUSAGE);
383 else
384 errmsg(M_AUSAGE);
385 exit(EX_SYNTAX);
386 }
387
388 logname = argv[optind];
389 switch (valid_login(logname, (struct passwd **)NULL, &warning)) {
390 case INVALID:
391 errmsg(M_INVALID, logname, "login name");
392 exit(EX_BADARG);
393 /*NOTREACHED*/
394
395 case NOTUNIQUE:
396 errmsg(M_USED, logname);
397 exit(EX_NAME_EXISTS);
398 /*NOTREACHED*/
399 }
400
401 if (warning)
402 warningmsg(warning, logname);
403 if (uidstr != NULL) {
404 /* convert uidstr to integer */
405 errno = 0;
406 uid = (uid_t)strtol(uidstr, &ptr, (int)10);
407 if (*ptr || errno == ERANGE) {
408 errmsg(M_INVALID, uidstr, "user id");
409 exit(EX_BADARG);
410 }
411
412 switch (valid_uid(uid, NULL)) {
413 case NOTUNIQUE:
414 if (!oflag) {
415 /* override not specified */
416 errmsg(M_UID_USED, uid);
417 exit(EX_ID_EXISTS);
418 }
419 break;
420 case RESERVED:
421 errmsg(M_RESERVED, uid);
422 break;
423 case TOOBIG:
424 errmsg(M_TOOBIG, "uid", uid);
425 exit(EX_BADARG);
426 break;
427 }
428
429 } else {
430
431 if (findnextuid(DEFRID+1, MAXUID, &uid) != 0) {
432 errmsg(M_INVALID, "default id", "user id");
433 exit(EX_ID_EXISTS);
434 }
435 }
436
437 if (group != NULL) {
438 switch (valid_group(group, &g_ptr, &warning)) {
439 case INVALID:
440 errmsg(M_INVALID, group, "group id");
441 exit(EX_BADARG);
442 /*NOTREACHED*/
443 case TOOBIG:
444 errmsg(M_TOOBIG, "gid", group);
445 exit(EX_BADARG);
446 /*NOTREACHED*/
447 case RESERVED:
448 case UNIQUE:
449 errmsg(M_GRP_NOTUSED, group);
450 exit(EX_NAME_NOT_EXIST);
451 /*NOTREACHED*/
452 }
453
454 if (warning)
455 warningmsg(warning, group);
456 gid = g_ptr->gr_gid;
457
458 } else gid = usrdefs->defgroup;
459
460 if (grps != NULL) {
461 if (!*grps)
462 /* ignore -G "" */
463 grps = (char *)0;
464 else if (!(gidlist = valid_lgroup(grps, gid)))
465 exit(EX_BADARG);
466 }
467
468 if (projects != NULL) {
469 if (! *projects)
470 projects = (char *)0;
471 else if (! (projlist = valid_lproject(projects)))
472 exit(EX_BADARG);
473 }
474
475 /* if base_dir is provided, check its validity; otherwise default */
476 if (base_dir != NULL)
477 valid_input(BASEDIR, base_dir);
478 else
479 base_dir = usrdefs->defparent;
480
481 if (dir == NULL) {
482 /* set homedir to home directory made from base_dir */
483 (void) sprintf(homedir, "%s/%s", base_dir, logname);
484
485 } else if (REL_PATH(dir)) {
486 errmsg(M_RELPATH, dir);
487 exit(EX_BADARG);
488
489 } else
490 (void) strcpy(homedir, dir);
491
492 if (mflag) {
493 /* Does home dir. already exist? */
494 if (stat(homedir, &statbuf) == 0) {
495 /* directory exists - don't try to create */
496 mflag = 0;
497
498 if (check_perm(statbuf, uid, gid, S_IXOTH) != 0)
499 errmsg(M_NO_PERM, logname, homedir);
500 }
501 }
502 /*
503 * if shell, skel_dir are provided, check their validity.
504 * Otherwise default.
505 */
506 if (shell != NULL)
507 valid_input(SHELL, shell);
508 else
509 shell = usrdefs->defshell;
510
511 if (skel_dir != NULL)
512 valid_input(SKELDIR, skel_dir);
513 else
514 skel_dir = usrdefs->defskel;
515
516 if (inactstr != NULL) {
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 != NULL) {
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 != NULL) {
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, "%u", 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 != NULL) {
575 nargv[argindex++] = "-e";
576 nargv[argindex++] = expirestr;
577 }
578
579 /* set uid flag */
580 nargv[argindex++] = "-u";
581 (void) sprintf(uidstring, "%u", 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 (findnextuid(DEFRID+1, MAXUID, &uid) != 0) {
641 errmsg(M_INVALID, "default id",
642 "user id");
643 exit(EX_ID_EXISTS);
644 }
645 (void) sprintf(uidstring, "%u", 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 != NULL) && 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 != NULL) &&
675 edit_project(logname, (char *)NULL, projlist, 0)) {
676 errmsg(M_UPDATE, "created");
677 cleanup(logname);
678 exit(EX_UPDATE);
679 }
680
681 /* create home directory */
682 if (mflag &&
683 (create_home(homedir, skel_dir, uid, gid) != EX_SUCCESS)) {
684 (void) edit_group(logname, (char *)0, (int **)0, 1);
685 cleanup(logname);
686 exit(EX_HOMEDIR);
687 }
688
689 return (ret);
690 }
691
692 static void
693 cleanup(logname)
694 char *logname;
695 {
696 char *nargv[4];
697
698 nargv[0] = PASSMGMT;
699 nargv[1] = "-d";
700 nargv[2] = logname;
701 nargv[3] = NULL;
702
703 switch (call_passmgmt(nargv)) {
704 case PEX_SUCCESS:
705 break;
706
707 case PEX_SYNTAX:
708 /* should NEVER occur that passmgmt usage is wrong */
709 if (is_role(usertype))
710 errmsg(M_ARUSAGE);
711 else
712 errmsg(M_AUSAGE);
713 break;
714
715 case PEX_BADUID:
716 /* uid is used - shouldn't happen but print message anyway */
717 errmsg(M_UID_USED, uid);
718 break;
719
720 case PEX_BADNAME:
721 /* invalid loname */
722 errmsg(M_USED, logname);
723 break;
724
725 default:
726 errmsg(M_UPDATE, "created");
727 break;
728 }
729 }
730
731 /* Check the validity for shell, base_dir and skel_dir */
732
733 void
734 valid_input(path_opt_t opt, const char *input)
735 {
736 struct stat statbuf;
737
738 if (REL_PATH(input)) {
739 errmsg(M_RELPATH, input);
740 exit(EX_BADARG);
741 }
742 if (stat(input, &statbuf) == -1) {
743 errmsg(M_INVALID, input, "path");
744 exit(EX_BADARG);
745 }
746 if (opt == SHELL) {
747 if (!S_ISREG(statbuf.st_mode) ||
748 (statbuf.st_mode & 0555) != 0555) {
749 errmsg(M_INVALID, input, "shell");
750 exit(EX_BADARG);
751 }
752 } else {
753 if (!S_ISDIR(statbuf.st_mode)) {
754 errmsg(M_INVALID, input, "directory");
755 exit(EX_BADARG);
756 }
757 }
758 }