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 #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
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 typedef enum {
124 BASEDIR = 0,
125 SKELDIR,
126 SHELL
127 } path_opt_t;
128
129
130 static void valid_input(path_opt_t, const char *);
131
132 int
133 main(argc, argv)
134 int argc;
135 char *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 case LONGNAME:
400 errmsg(M_TOO_LONG, logname);
401 exit(EX_BADARG);
402 /*NOTREACHED*/
403 }
404
405 if (warning)
406 warningmsg(warning, logname);
407 if (uidstr != NULL) {
408 /* convert uidstr to integer */
409 errno = 0;
410 uid = (uid_t)strtol(uidstr, &ptr, (int)10);
411 if (*ptr || errno == ERANGE) {
412 errmsg(M_INVALID, uidstr, "user id");
413 exit(EX_BADARG);
414 }
415
416 switch (valid_uid(uid, NULL)) {
417 case NOTUNIQUE:
418 if (!oflag) {
419 /* override not specified */
420 errmsg(M_UID_USED, uid);
421 exit(EX_ID_EXISTS);
422 }
423 break;
424 case RESERVED:
425 errmsg(M_RESERVED, uid);
426 break;
427 case TOOBIG:
428 errmsg(M_TOOBIG, "uid", uid);
429 exit(EX_BADARG);
430 break;
431 }
432
433 } else {
434
435 if ((uid = findnextuid()) < 0) {
436 errmsg(M_INVALID, "default id", "user id");
437 exit(EX_ID_EXISTS);
438 }
439 }
440
441 if (group != NULL) {
442 switch (valid_group(group, &g_ptr, &warning)) {
443 case INVALID:
444 errmsg(M_INVALID, group, "group id");
445 exit(EX_BADARG);
446 /*NOTREACHED*/
447 case TOOBIG:
448 errmsg(M_TOOBIG, "gid", group);
449 exit(EX_BADARG);
450 /*NOTREACHED*/
451 case RESERVED:
452 case UNIQUE:
453 errmsg(M_GRP_NOTUSED, group);
454 exit(EX_NAME_NOT_EXIST);
455 /*NOTREACHED*/
456 }
457
458 if (warning)
459 warningmsg(warning, group);
460 gid = g_ptr->gr_gid;
461
462 } else gid = usrdefs->defgroup;
463
464 if (grps != NULL) {
465 if (!*grps)
466 /* ignore -G "" */
467 grps = (char *)0;
468 else if (!(gidlist = valid_lgroup(grps, gid)))
469 exit(EX_BADARG);
470 }
471
472 if (projects != NULL) {
473 if (! *projects)
474 projects = (char *)0;
475 else if (! (projlist = valid_lproject(projects)))
476 exit(EX_BADARG);
477 }
478
479 /* if base_dir is provided, check its validity; otherwise default */
480 if (base_dir != NULL)
481 valid_input(BASEDIR, base_dir);
482 else
483 base_dir = usrdefs->defparent;
484
485 if (dir == NULL) {
486 /* set homedir to home directory made from base_dir */
487 (void) sprintf(homedir, "%s/%s", base_dir, logname);
488
489 } else if (REL_PATH(dir)) {
490 errmsg(M_RELPATH, dir);
491 exit(EX_BADARG);
492
493 } else
494 (void) strcpy(homedir, dir);
495
496 if (mflag) {
497 /* Does home dir. already exist? */
498 if (stat(homedir, &statbuf) == 0) {
499 /* directory exists - don't try to create */
500 mflag = 0;
501
502 if (check_perm(statbuf, uid, gid, S_IXOTH) != 0)
503 errmsg(M_NO_PERM, logname, homedir);
504 }
505 }
506 /*
507 * if shell, skel_dir are provided, check their validity.
508 * Otherwise default.
509 */
510 if (shell != NULL)
511 valid_input(SHELL, shell);
512 else
513 shell = usrdefs->defshell;
514
515 if (skel_dir != NULL)
516 valid_input(SKELDIR, skel_dir);
517 else
518 skel_dir = usrdefs->defskel;
519
520 if (inactstr != NULL) {
521 /* convert inactstr to integer */
522 inact = strtol(inactstr, &ptr, 10);
523 if (*ptr || inact < 0) {
524 errmsg(M_INVALID, inactstr, "inactivity period");
525 exit(EX_BADARG);
526 }
527 } else inact = usrdefs->definact;
528
529 /* expiration string is a date, newer than today */
530 if (expirestr != NULL) {
531 if (*expirestr) {
532 if (valid_expire(expirestr, (time_t *)0) == INVALID) {
533 errmsg(M_INVALID, expirestr, "expiration date");
534 exit(EX_BADARG);
535 }
536 usrdefs->defexpire = expirestr;
537 } else
538 /* Unset the expiration date */
539 expirestr = (char *)0;
540
541 } else expirestr = usrdefs->defexpire;
542
543 import_def(usrdefs);
544
545 /* must now call passmgmt */
546
547 /* set up arguments to passmgmt in nargv array */
548 nargv = malloc((30 + nkeys * 2) * sizeof (char *));
549 argindex = 0;
550 nargv[argindex++] = PASSMGMT;
551 nargv[argindex++] = "-a"; /* add */
552
553 if (comment != NULL) {
554 /* comment */
555 nargv[argindex++] = "-c";
556 nargv[argindex++] = comment;
557 }
558
559 /* flags for home directory */
560 nargv[argindex++] = "-h";
561 nargv[argindex++] = homedir;
562
563 /* set gid flag */
564 nargv[argindex++] = "-g";
565 (void) sprintf(gidstring, "%u", gid);
566 nargv[argindex++] = gidstring;
567
568 /* shell */
569 nargv[argindex++] = "-s";
570 nargv[argindex++] = shell;
571
572 /* set inactive */
573 nargv[argindex++] = "-f";
574 (void) sprintf(inactstring, "%ld", inact);
575 nargv[argindex++] = inactstring;
576
577 /* set expiration date */
578 if (expirestr != NULL) {
579 nargv[argindex++] = "-e";
580 nargv[argindex++] = expirestr;
581 }
582
583 /* set uid flag */
584 nargv[argindex++] = "-u";
585 (void) sprintf(uidstring, "%u", uid);
586 nargv[argindex++] = uidstring;
587
588 if (oflag) nargv[argindex++] = "-o";
589
590 if (nkeys > 1)
591 addkey_args(nargv, &argindex);
592
593 /* finally - login name */
594 nargv[argindex++] = logname;
595
596 /* set the last to null */
597 nargv[argindex++] = NULL;
598
599 /* now call passmgmt */
600 ret = PEX_FAILED;
601 /*
602 * If call_passmgmt fails for any reason other than PEX_BADUID, exit
603 * is invoked with an appropriate error message. If PEX_BADUID is
604 * returned, then if the user specified the ID, exit is invoked
605 * with an appropriate error message. Otherwise we try to pick a
606 * different ID and try again. If we run out of IDs, i.e. no more
607 * users can be created, then -1 is returned and we terminate via exit.
608 * If PEX_BUSY is returned we increment a count, since we will stop
609 * trying if PEX_BUSY reaches 3. For PEX_SUCCESS we immediately
610 * terminate the loop.
611 */
612 while (busy < 3 && ret != PEX_SUCCESS) {
613 switch (ret = call_passmgmt(nargv)) {
614 case PEX_SUCCESS:
615 break;
616 case PEX_BUSY:
617 busy++;
618 break;
619 case PEX_HOSED_FILES:
620 errmsg(M_HOSED_FILES);
621 exit(EX_INCONSISTENT);
622 break;
623
624 case PEX_SYNTAX:
625 case PEX_BADARG:
626 /* should NEVER occur that passmgmt usage is wrong */
627 if (is_role(usertype))
628 errmsg(M_ARUSAGE);
629 else
630 errmsg(M_AUSAGE);
631 exit(EX_SYNTAX);
632 break;
633
634 case PEX_BADUID:
635 /*
636 * The uid has been taken. If it was specified by a
637 * user, then we must fail. Otherwise, keep trying
638 * to get a good uid until we run out of IDs.
639 */
640 if (uidstr != NULL) {
641 errmsg(M_UID_USED, uid);
642 exit(EX_ID_EXISTS);
643 } else {
644 if ((uid = findnextuid()) < 0) {
645 errmsg(M_INVALID, "default id",
646 "user id");
647 exit(EX_ID_EXISTS);
648 }
649 (void) sprintf(uidstring, "%u", uid);
650 }
651 break;
652
653 case PEX_BADNAME:
654 /* invalid loname */
655 errmsg(M_USED, logname);
656 exit(EX_NAME_EXISTS);
657 break;
658
659 default:
660 errmsg(M_UPDATE, "created");
661 exit(ret);
662 break;
663 }
664 }
665 if (busy == 3) {
666 errmsg(M_UPDATE, "created");
667 exit(ret);
668 }
669
670 /* add group entry */
671 if ((grps != NULL) && edit_group(logname, (char *)0, gidlist, 0)) {
672 errmsg(M_UPDATE, "created");
673 cleanup(logname);
674 exit(EX_UPDATE);
675 }
676
677 /* update project database */
678 if ((projects != NULL) &&
679 edit_project(logname, (char *)NULL, projlist, 0)) {
680 errmsg(M_UPDATE, "created");
681 cleanup(logname);
682 exit(EX_UPDATE);
683 }
684
685 /* create home directory */
686 if (mflag &&
687 (create_home(homedir, skel_dir, uid, gid) != EX_SUCCESS)) {
688 (void) edit_group(logname, (char *)0, (int **)0, 1);
689 cleanup(logname);
690 exit(EX_HOMEDIR);
691 }
692
693 return (ret);
694 }
695
696 static void
697 cleanup(logname)
698 char *logname;
699 {
700 char *nargv[4];
701
702 nargv[0] = PASSMGMT;
703 nargv[1] = "-d";
704 nargv[2] = logname;
705 nargv[3] = NULL;
706
707 switch (call_passmgmt(nargv)) {
708 case PEX_SUCCESS:
709 break;
710
711 case PEX_SYNTAX:
712 /* should NEVER occur that passmgmt usage is wrong */
713 if (is_role(usertype))
714 errmsg(M_ARUSAGE);
715 else
716 errmsg(M_AUSAGE);
717 break;
718
719 case PEX_BADUID:
720 /* uid is used - shouldn't happen but print message anyway */
721 errmsg(M_UID_USED, uid);
722 break;
723
724 case PEX_BADNAME:
725 /* invalid loname */
726 errmsg(M_USED, logname);
727 break;
728
729 default:
730 errmsg(M_UPDATE, "created");
731 break;
732 }
733 }
734
735 /* Check the validity for shell, base_dir and skel_dir */
736
737 void
738 valid_input(path_opt_t opt, const char *input)
739 {
740 struct stat statbuf;
741
742 if (REL_PATH(input)) {
743 errmsg(M_RELPATH, input);
744 exit(EX_BADARG);
745 }
746 if (stat(input, &statbuf) == -1) {
747 errmsg(M_INVALID, input, "path");
748 exit(EX_BADARG);
749 }
750 if (opt == SHELL) {
751 if (!S_ISREG(statbuf.st_mode) ||
752 (statbuf.st_mode & 0555) != 0555) {
753 errmsg(M_INVALID, input, "shell");
754 exit(EX_BADARG);
755 }
756 } else {
757 if (!S_ISDIR(statbuf.st_mode)) {
758 errmsg(M_INVALID, input, "directory");
759 exit(EX_BADARG);
760 }
761 }
762 }