xref: /illumos-gate/usr/src/cmd/oamuser/user/useradd.c (revision a7fe1d5b)
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 }
759