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