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