xref: /illumos-gate/usr/src/cmd/svr4pkg/libinst/scriptvfy.l (revision 21ecdf64e1e200cd74cadf771fc7ddc3d0062080)
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 /*
23  * Copyright 1995 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 
28 /*
29  * The purpose of this lex specification is to estimate the
30  * correctness of the various scripts that accompany packages. It
31  * is not flawless, but it is a better review than that of prior
32  * package validators. It looks for indications of interaction,
33  * root calls and attempts to modify locked files.
34  */
35 %e 1500
36 %p 3500
37 %s WHROOT
38 %{
39 #undef	input
40 #undef	unput
41 FILE *scr_fp;
42 #define	input()		(((yytchar=yysptr>yysbuf?U(*--yysptr):getc(scr_fp))==10?(yylineno++,yytchar):yytchar)==EOF?0:yytchar)
43 #define	unput(p)	ungetc(p, scr_fp)
44 
45 #define	INTERACT_D	0x00000001	/* definitely */
46 #define	ROOT_D		0x00000002
47 #define	LOCKED_D	0x00000004
48 #define	INTERACT_M	0x00010000	/* might be true, or we ... */
49 #define	ROOT_M		0x00020000	/* ... might be reading it wrong. */
50 #define	LOCKED_M	0x00040000
51 #define	WPARM1_M	0x00080000	/* attempt to write to $1 */
52 #define	USEPARM1_M	0x00100000	/* other attempt to use $1 */
53 #define	ODDPARM_M	0x00200000	/* use of some other parameter */
54 #define	PKGDB_M		0x00400000	/* read access to DB */
55 #define	INITVAL		0x40000000
56 
57 /* Abbreviations */
58 #define	INTERACT	(INTERACT_D | INTERACT_M)
59 #define	ROOT		(ROOT_D | ROOT_M)
60 #define	LOCKED		(LOCKED_D | LOCKED_M)
61 #define	HASPARM		(WPARM1_M | USEPARM1_M | ODDPARM_M)
62 
63 /* Things the preinstall and preremove scripts can't do. */
64 #define	PRE_MASK	(INTERACT | LOCKED | PKGDB_M | HASPARM)
65 /*
66  * Things the class action script can't do. Don't get the impression that
67  * this means the class action script can be interactive; but, it can
68  * legitimately read stdin (which is what INTERACT tests for).
69  */
70 #define	CAS_MASK	(LOCKED | PKGDB_M | WPARM1_M | ODDPARM_M)
71 /* Things the postinstall and postremove scripts can't do. */
72 #define	POST_MASK	(INTERACT | HASPARM)
73 /* Things the request script can't do. */
74 #define	REQ_MASK	(ROOT | ODDPARM_M)
75 /* Things the checkinstall script can't do. */
76 #define	CHK_MASK	(INTERACT | ROOT | ODDPARM_M)
77 
78 /* Nothing definite - not worth returning an error */
79 #define	MAYBE_ONLY	~(INTERACT_D | ROOT_D | LOCKED_D)
80 
81 #define	WRN_INST_F	"WARNING: script <%s> uses installf but no " \
82 			    "installf -f was detected."
83 #define	WRN_REM_F	"WARNING: script <%s> uses removef but no " \
84 			    "removef -f was detected."
85 #define	WRN_INTERACT	"WARNING: script <%s> may require " \
86 			    "user interaction at line <%d>."
87 #define	WRN_LOCKED	"WARNING: script <%s> may seek access to the " \
88 			    "transitional package database at line <%d>. " \
89 			    "This is safest in the postinstall or " \
90 			    "postremove script."
91 #define	WRN_ROOT	"WARNING: script <%s> may not have permission " \
92 			    "to execute line <%d>."
93 #define	WRN_FORM_ARG	"WARNING: not sure where script <%s> gets the "\
94 			    "parameter at line <%d>."
95 #define	WRN_FORM_USE	"WARNING: script <%s> questionable usage of "\
96 			    "parameter at line <%d>."
97 #define	WRN_TRANSDB	"WARNING: script <%s> questionable read " \
98 			    "of package database at line <%d>. An " \
99 			    "intermediate buffer may be appropriate."
100 #define	WRN_SPACEACC	"WARNING: script <%s> updates the package database " \
101 			    "but provides no space file to account for " \
102 			    "the additional package object."
103 #define	ERR_INTERACT	"ERROR: script <%s> requires user " \
104 			    "interaction at line <%d>."
105 #define	ERR_LOCKED	"ERROR: script <%s> attempts to modify locked " \
106 			    "package database at line <%d>."
107 #define	ERR_ROOT	"ERROR: script <%s> requires root permission at " \
108 			    "line <%d>."
109 #define	ERR_FOPEN	"ERROR: Cannot evaluate script <%s>, errno=%d."
110 #define	ERR_ARGS	"ERROR: scripteval() - no script provided for " \
111 			    "evaluation."
112 extern int errno;
113 
114 static int line_no;	/* current line number */
115 int pipe_release = 0;	/* loop level for release of pipe */
116 int loop_depth = 0;	/* current number of nested loops */
117 int case_depth = 0;	/* same for case ... */
118 int if_depth = 0;	/* ... and if statements */
119 int cur_level = 0;	/* current number of nested anything */
120 int braces = 0;		/* depth into a function */
121 
122 int lock_level = 0;
123 int root_level = 0;
124 
125 struct statstrct {
126 	unsigned int in_function:1;
127 	unsigned int in_pipe:1;
128 	unsigned int in_loop:1;
129 	unsigned int in_case:1;
130 	unsigned int in_if:1;
131 	unsigned int in_awk:1;
132 	unsigned int allow_int:1;	/* Allow an interactive function. */
133 	unsigned int pkg_rtn_done:1;
134 	unsigned int pkgchk_f:1;
135 	unsigned int instf:1;
136 	unsigned int instf_f:1;
137 	unsigned int remf:1;
138 	unsigned int remf_f:1;
139 	unsigned int nospacefile:1;
140 	unsigned int needspacefile:1;
141 } status = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
142 
143 %}
144 %%
145 %{
146 /*
147  * Validate a few OK patterns that look like bad patterns. These include:
148  *	1. comments
149  *	2. quoted strings
150  *	3. writes to $1 (request script)
151  *	4. reads from $1 (CAS)
152  *	5. writes to /dev/null
153  */
154 %}
155 #.*$ 	return INITVAL;
156 
157 \`	unput(' ');	/* No executable matching */
158 
159 %{
160 /* Anybody can write to /dev/null and anybody can write to /tmp. */
161 %}
162 \>[ \t]*"/dev/null"	return INITVAL;
163 \>[ \t]*"/tmp"		return INITVAL;
164 
165 %{
166 /* If it's escaped, the next entry may as well be a space. */
167 %}
168 \\	{
169 	char ch;
170 
171 	if ((ch = input()) == '\n')
172 		line_no++;
173 
174 	unput(' ');
175 }
176 
177 %{
178 /* In the quotes is OK. */
179 %}
180 \"	{
181 	char ch;
182 	while ((ch = input()) != '\"') {
183 		if (ch == '\\') {
184 			input();	/* Read this into the bit bucket. */
185 			continue;
186 		}
187 		if (ch == '\n')
188 			line_no++;
189 		else if (ch == '\0')
190 			return (0);	/* EOF */
191 	}
192 }
193 
194 %{
195 /* In the single quotes is OK if they aren't associated with an awk script. */
196 %}
197 \'	{
198 	char ch;
199 
200 	if (status.in_awk != 0)
201 		REJECT;;
202 
203 	while ((ch = input()) != '\'') {
204 		if (ch == '\\') {
205 			input();	/* Read this into the bit bucket. */
206 			continue;
207 		}
208 		if (ch == '\n')
209 			line_no++;
210 		else if (ch == '\0')
211 			return (0);	/* EOF */
212 	}
213 }
214 
215 %{
216 /*
217  * Check for use of parameters passed to the script.
218  *	1. writes to $1 as though it were a file
219  *	2. use of $1 in any capacity
220  *	3. use of other parameters
221  * Within a function or an awk script, these parameters aren't
222  * the one's of interest.
223  */
224 %}
225 \>[\t ]*\$1/[\t\n ]	{
226 	if (status.in_function == 0 && status.in_awk == 0)
227 		return (WPARM1_M);
228 }
229 
230 ^$1/[\t\n ]	|
231 [\t ]$1/[\t\n ]	{
232 	if (status.in_function == 0 && status.in_awk == 0)
233 		return (USEPARM1_M);
234 }
235 
236 \$[2-9]	|
237 \$[0-9][0-9]+ {
238 	if (status.in_function == 0 && status.in_awk == 0)
239 		return (ODDPARM_M);
240 }
241 
242 %{
243 /*
244  * Detect shell function.
245  */
246 %}
247 "()"[ \t]*\n[ \t]*/\{	{ status.in_function = 1; line_no++; }
248 "()"[ ]*/\{	status.in_function = 1;
249 
250 "{" {
251 	if (status.in_function == 1)
252 		braces++;
253 }
254 
255 "}" {
256 	if (status.in_function == 1) {
257 		braces--;
258 		if (braces == 0)
259 			status.in_function = 0;
260 	}
261 }
262 
263 %{
264 /*
265  * Detect for or while loop.
266  */
267 %}
268 ^for/[\t\n ]		|
269 [\t ]for/[\t\n ]	|
270 ^while/[\t\n ]		|
271 [\t ]while/[\t\n ] {
272 	status.in_loop = 1;
273 	loop_depth++;
274 	cur_level++;
275 	REJECT;		/* What's in the argument is important too. */
276 }
277 
278 ^done/[\t\n ] 	|
279 [\t ]done/[\t\n ]  {
280 	if (status.in_loop == 1)
281 		loop_depth--;
282 		cur_level--;
283 		if (loop_depth == 0)
284 			status.in_loop = 0;
285 }
286 
287 %{
288 /*
289  * Detect case.
290  */
291 %}
292 ^case/[\t\n ]	|
293 [\t ]case/[\t\n ] {
294 	status.in_case = 1;
295 	case_depth++;
296 	cur_level++;
297 	REJECT;		/* What's in the argument is important too. */
298 }
299 
300 ^esac/[\t\n ] 	|
301 [\t ]esac/[\t\n ] {
302 	if (status.in_case == 1)
303 		case_depth--;
304 		cur_level--;
305 		if (case_depth == 0)
306 			status.in_case = 0;
307 }
308 
309 %{
310 /*
311  * Detect if.
312  */
313 %}
314 ^if" "*"["	|
315 [\t ]if" "*"[" {
316 	status.in_if = 1;
317 	if_depth++;
318 	cur_level++;
319 	REJECT;		/* What's in the argument is important too. */
320 }
321 
322 ^fi/[\t\n ]	|
323 [\t ]fi/[\t\n ]  {
324 	if (status.in_if == 1)
325 		if_depth--;
326 		cur_level--;
327 		if (if_depth == 0)
328 			status.in_if = 0;
329 }
330 
331 %{
332 /*
333  * Detect awk or nawk function. If the function is enclosed in "`"s
334  * the entire line will be grabbed., so we check for that possibility.
335  */
336 %}
337 ^n?awk[^\n^']*\' 	|
338 [\t \\\(\/]n?awk[^\n^']*\'	status.in_awk = 1;
339 
340 
341 \' {
342 	if (status.in_awk == 1)
343 		status.in_awk = 0;
344 }
345 
346 %{
347 /* Detect pipe target. */
348 %}
349 [\$A-Za-z]	{
350 	if (status.in_pipe == 1 && pipe_release == cur_level)
351 	{
352 		status.in_pipe = 0;	/* target located */
353 		pipe_release = 0;
354 		status.allow_int = 1;	/* this isn't really interactive. */
355 		REJECT;	/* put it back */
356 	}
357 }
358 
359 %{
360 /* If it's a pipe, note that and continue. */
361 %}
362 "||"		|
363 "|"		{
364 	if (status.in_pipe == 0) {
365 		status.in_pipe = 1;
366 		pipe_release = cur_level;
367 	}
368 }
369 
370 %{
371 /*
372  * Test input for admin-type telltale interactive functions. Definite's
373  * first, maybe's next.
374  */
375 %}
376 ^ckdate/[\t\n ]		|
377 [\t \/]ckdate/[\t\n ]	|
378 ^ckint/[\t\n ]		|
379 [\t \/]ckint/[\t\n ]	|
380 ^ckrange/[\t\n ]	|
381 [\t \/]ckrange/[\t\n ]	|
382 ^cktime/[\t\n ]		|
383 [\t \/]cktime/[\t\n ]	|
384 ^ckyorn/[\t\n ]		|
385 [\t \/]ckyorn/[\t\n ]	|
386 ^ckgid/[\t\n ]		|
387 [\t \/]ckgid/[\t\n ]	|
388 ^ckpath/[\t\n ]		|
389 [\t \/]ckpath/[\t\n ]	|
390 ^ckstr/[\t\n ]		|
391 [\t \/]ckstr/[\t\n ]	|
392 ^ckuid/[\t\n ]		|
393 [\t \/]ckuid/[\t\n ]		{
394 	if (status.in_pipe == 1 || status.allow_int == 1)
395 		return (INITVAL);
396 	else
397 		return (INTERACT_M);	/* maybe should be _D */
398 }
399 
400 ^read/[\t\n ]		|
401 [\t ]read/[\t\n ]	|
402 "=[ ]+&<"[\t ]	{
403 	if (status.in_pipe == 1 || status.allow_int == 1)
404 		return (INITVAL);
405 	else
406 		return (INTERACT_M);
407 }
408 
409 %{
410 /* Scan for root authority commands. Definite's first, maybe's next. */
411 %}
412 ^mkdir/[\t\n ]		|
413 [\t \/]mkdir/[\t\n ]	|
414 ^mv/[\t\n ]		|
415 [\t \/]mv/[\t\n ]	|
416 ^cpio/[\t\n ]		|
417 [\t \/]cpio/[\t\n ]	|
418 ^tar/[\t\n ]		|
419 [\t \/]tar/[\t\n ]	|
420 ^(un)?compress/[\t\n ]	|
421 [\t \/](un)?compress/[\t\n ]	|
422 ^rmdir/[\t\n ]		|
423 [\t \/]rmdir/[\t\n ]	return (ROOT_D);
424 
425 ^r?cp(dir)?/[\t\n ]	|
426 [\t \/]r?cp(dir)?/[\t\n ]	|
427 ^rm/[\t\n ]	|
428 [\t \/]rm/[\t\n ]	|
429 \>[ \t]*[\$\/a-zA-Z0-9]	return (ROOT_M);
430 
431 %{
432 /* These root commands may also be locked. */
433 
434 /* Here we analyze any pkgchk calls. If it's "pkgchk ... -f ..." then that calls for root authority. We then check for a "-R" argument. */
435 %}
436 ^pkgchk[^\n^|^>^;]*"-f"	|
437 [\t \/]pkgchk[^\n^|^>^;]*"-f"	{
438 	status.pkgchk_f = 1;
439 	REJECT;		/* We need the intermediate args. */
440 }
441 
442 %{
443 /* If it's "pkgchk ... -R ..." then the local package database is not being tested and no database warning is necessary. */
444 %}
445 ^pkgchk[^\n^|^>^;]*"-R"[ \t][\/\$]/[^ ^\t^\n]		|
446 [\t \/]pkgchk[^\n^|^>^;]*"-R"[ \t][\/\$]/[^ ^\t^\n]  {
447 	if (status.pkgchk_f)
448 		return (ROOT_D);
449 	else
450 		return (INITVAL);
451 }
452 
453 %{
454 /* If it's just "pkgchk ..." then we need to mention something about access to the package database. With Solaris 2.5, an improved locking mechanism is in place, so this message may be something we can drop later. */
455 %}
456 ^pkgchk/[\t\n ]		|
457 [\t \/]pkgchk/[\t\n ]  {
458 	if (status.pkgchk_f) {
459 		status.pkgchk_f = 0;
460 		return (ROOT_D | PKGDB_M);
461 	} else
462 		return (PKGDB_M);
463 }
464 
465 %{
466 /* The installf and removef utilities require root authority, they modify the package database and they must be invoked at least once with a "-f" argument. */
467 
468 /* First test for a "-f" argument. */
469 %}
470 ^installf[^\n^|^>^;]*"-f"	|
471 [\t \/]installf[^\n^|^>^;]*"-f"	{
472 	status.instf_f = 1;
473 
474 	REJECT;		/* The whole line needs to be re-reviewed. */
475 }
476 
477 ^removef[^\n^|^>^;]*"-f"	|
478 [\t \/]removef[^\n^|^>^;]*"-f"	{
479 	status.remf_f = 1;
480 
481 	REJECT;		/* The whole line needs to be re-reviewed. */
482 }
483 
484 ^installf/[\t\n ]	|
485 [\t \/]installf/[\t\n ]	{
486 	status.instf = 1;
487 	status.needspacefile = 1;
488 
489 	root_level = ROOT_D;
490 	lock_level = LOCKED_M;
491 
492 	BEGIN WHROOT;
493 }
494 
495 ^removef/[\t\n ]	|
496 [\t \/]removef/[\t\n ]	{
497 	status.remf = 1;
498 
499 	root_level = ROOT_D;
500 	lock_level = LOCKED_M;
501 	BEGIN WHROOT;
502 }
503 
504 %{
505 /* There's no question that use of a pkgadd or pkgrm in a script is bound to cause problems unless it is to a different root. */
506 %}
507 ^pkgadd/[\t\n ]	|
508 [\t \/]pkgadd/[\t\n ]	|
509 ^pkgrm/[\t\n ]		|
510 [\t \/]pkgrm/[\t\n ] {
511 	root_level = ROOT_D;
512 	lock_level = LOCKED_D;
513 	BEGIN WHROOT;
514 }
515 
516 %{
517 /* The only way to get here is if we are in the middle of a pkg command. */
518 %}
519 <WHROOT>. {
520 	if (status.pkg_rtn_done) {
521 		status.pkg_rtn_done = 0;
522 		BEGIN 0;
523 	} else
524 		REJECT;
525 }
526 <WHROOT>[ \t]+"-R"[ \t][\/\$]/[^ ^\t^\n] {
527 	status.pkg_rtn_done = 1;
528 	return (root_level);		/* "-R" means locking is unlikely. */
529 }
530 <WHROOT>[\n]		{
531 	if (status.pkg_rtn_done) {
532 		status.pkg_rtn_done = 0;
533 		line_no++;
534 		BEGIN 0;
535 	} else {
536 		status.pkg_rtn_done = 1;
537 		unput('\n');
538 		return (root_level | lock_level); /* No "-R". */
539 	}
540 }
541 <WHROOT>[;|>]		{
542 	status.pkg_rtn_done = 1;
543 	return (root_level | lock_level); /* End of command without a "-R". */
544 }
545 
546 \n	{ line_no++; status.allow_int = 0; }
547 .	status.allow_int = 0;
548 %%
549 #include <stdio.h>
550 #include <limits.h>
551 #include <dirent.h>
552 #include <unistd.h>
553 #include <libintl.h>
554 
555 #ifdef DEBUG
556 /*
557  * Since this is a lex specification twice removed from the binary,
558  * I strongly recommend leaving the DEBUG portions in place. When new
559  * keywords are added, this will be very important. After modifying
560  * the specification, create an executable to test in this way.
561  *
562  *	lex scriptvfy.l
563  *	cc -o scriptvfy -g lex.yy.c $ROOT/usr/lib/libpkg.a \
564  *	    -DDEBUG [-DVERBOSE] -ll -lintl
565  *	scriptvfy test_directory
566  */
567 
568 main(int argc, char *argv[])
569 {
570 	int val;
571 
572 	line_no = 1;
573 
574 	if (argc == 1) {
575 		printf("No directory provided.\n");
576 		exit(1);
577 	}
578 
579 	val = checkscripts(argv[1], 0);
580 
581 	printf("return code is %d\n", val);
582 }
583 #endif
584 
585 /*
586  * This function evaluates the provided script and returns a bit string
587  * describing what patterns were located.
588  */
589 static int
590 scripteval(char *script_name, char *script_path, int mask, int silent)
591 {
592 	int val = 0;
593 	int error = 0;
594 	line_no = 1;
595 
596 	if ((script_path == NULL) || (*script_path == NULL) ||
597 	    (script_name == NULL)) {
598 		logerr(gettext(ERR_ARGS));
599 		return (0);
600 	}
601 
602 #ifdef VERBOSE
603 	printf("Evaluating %s\n", script_path);
604 #endif
605 
606 	if ((scr_fp = fopen(script_path, "r")) == NULL) {
607 		logerr(gettext(ERR_FOPEN), script_path, errno);
608 		return (0);
609 	}
610 
611 #ifdef VERBOSE
612 	printf("Openned script\n");
613 #endif
614 
615 	while (val = yylex()) {
616 #ifdef VERBOSE
617 		printf("  Match is %s, returned 0x%x at line %d\n",
618 		    yytext, val, line_no);
619 		printf("    in_function = %d, in_awk = %d, in_loop = %d, " \
620 		    "in_case = %d, in_if = %d, in_pipe = %d\n",
621 		    status.in_function, status.in_awk, status.in_loop,
622 		    status.in_case, status.in_if, status.in_pipe);
623 		printf("    loop_depth = %d, case_depth = %d, " \
624 		    "if_depth = %d, pipe_release = %d, cur_level = %d\n",
625 		    loop_depth, case_depth, if_depth, pipe_release, cur_level);
626 #endif
627 
628 		val &= mask;
629 		if (val) {
630 			error |= ((val & MAYBE_ONLY) ? 1 : 2);
631 
632 			/*
633 			 * So at this point, val contains all status bits
634 			 * appropriate to this script.
635 			 */
636 			if (!silent) {
637 				char *msg_ptr;
638 				if (val & INTERACT_D)
639 					msg_ptr = gettext(ERR_INTERACT);
640 				else if (val & ROOT_D)
641 					msg_ptr = gettext(ERR_ROOT);
642 				else if (val & LOCKED_D)
643 					msg_ptr = gettext(ERR_LOCKED);
644 				else if (val & INTERACT_M)
645 					msg_ptr = gettext(WRN_INTERACT);
646 				else if (val & ROOT_M)
647 					msg_ptr = gettext(WRN_ROOT);
648 				else if (val & LOCKED_M)
649 					msg_ptr = gettext(WRN_LOCKED);
650 				else if (val & WPARM1_M)
651 					msg_ptr = gettext(WRN_FORM_USE);
652 				else if (val & USEPARM1_M)
653 					msg_ptr = gettext(WRN_FORM_USE);
654 				else if (val &  ODDPARM_M)
655 					msg_ptr = gettext(WRN_FORM_ARG);
656 				else if (val &  PKGDB_M)
657 					msg_ptr = gettext(WRN_TRANSDB);
658 				else
659 					msg_ptr = gettext("unknown error");
660 
661 				logerr(msg_ptr, script_name, line_no);
662 			}
663 		}
664 	}
665 
666 	/* Warn if required about missing "-f" calls. */
667 	if (status.instf && !(status.instf_f))
668 		logerr(gettext(WRN_INST_F), script_name);
669 
670 	if (status.remf && !(status.remf_f))
671 		logerr(gettext(WRN_REM_F), script_name);
672 
673 	status.instf = status.instf_f = status.remf = status.remf_f = 0;
674 
675 	/* Warn if installf was used but no space file is in place. */
676 	if (status.nospacefile && status.needspacefile) {
677 		logerr(gettext(WRN_SPACEACC), script_name);
678 		status.needspacefile = 0;
679 	}
680 
681 	status.in_pipe = 0;	/* Pipes may dangle. */
682 	fclose(scr_fp);
683 
684 	if (error == 3)
685 		error = 2;
686 
687 	return (error);
688 }
689 
690 /* Test a preinstall or preremove script for validity. */
691 int
692 pre_valid(char *script_name, char *script_path, int silent)
693 {
694 	return (scripteval(script_name, script_path, PRE_MASK, silent));
695 }
696 
697 /* Test a class action script for validity. */
698 int
699 cas_valid(char *script_name, char *script_path, int silent)
700 {
701 	return (scripteval(script_name, script_path, CAS_MASK, silent));
702 }
703 
704 /* Test a postinstall or postremove script for validity. */
705 int
706 post_valid(char *script_name, char *script_path, int silent)
707 {
708 	return (scripteval(script_name, script_path, POST_MASK, silent));
709 }
710 
711 /* Test a class action script for validity. */
712 int
713 req_valid(char *script_name, char *script_path, int silent)
714 {
715 	return (scripteval(script_name, script_path, REQ_MASK, silent));
716 }
717 
718 
719 /* Test a class action script for validity. */
720 int
721 chk_valid(char *script_name, char *script_path, int silent)
722 {
723 	return (scripteval(script_name, script_path, CHK_MASK, silent));
724 }
725 
726 /* This tests all of the scripts in the provided directory. */
727 int
728 checkscripts(char *inst_dir, int silent)
729 {
730 	DIR *dirfp;
731 	struct dirent *dp;
732 	char path[PATH_MAX];
733 	int retval = 0;
734 
735 	/* For future reference, determine if a space file is present. */
736 	sprintf(path, "%s/%s", inst_dir, "space");
737 	if (access(path, F_OK) != 0)
738 		status.nospacefile = 1;
739 
740 	if ((dirfp = opendir(inst_dir)) == NULL)
741 		return (0);
742 
743 	while ((dp = readdir(dirfp)) != NULL) {
744 #ifdef VERBOSE
745 		printf("Looking at file %s\n", dp->d_name);
746 #endif
747 		if (dp->d_name[0] == '.')
748 			continue;
749 
750 		if ((strcmp(dp->d_name, "preinstall") == 0) ||
751 		    (strcmp(dp->d_name, "preremove") == 0)) {
752 			sprintf(path, "%s/%s", inst_dir, dp->d_name);
753 			retval |= pre_valid(dp->d_name, path, silent);
754 			continue;
755 		}
756 
757 		if ((strncmp(dp->d_name, "i.", 2) == 0) ||
758 		    (strncmp(dp->d_name, "r.", 2) == 0)) {
759 			sprintf(path, "%s/%s", inst_dir, dp->d_name);
760 			retval |= cas_valid(dp->d_name, path, silent);
761 			continue;
762 		}
763 
764 		if ((strcmp(dp->d_name, "postinstall") == 0) ||
765 		    (strcmp(dp->d_name, "postremove") == 0)) {
766 			sprintf(path, "%s/%s", inst_dir, dp->d_name);
767 			retval |= post_valid(dp->d_name, path, silent);
768 			continue;
769 		}
770 
771 		if (strcmp(dp->d_name, "request") == 0) {
772 			sprintf(path, "%s/%s", inst_dir, dp->d_name);
773 			retval |= req_valid(dp->d_name, path, silent);
774 			continue;
775 		}
776 		if (strcmp(dp->d_name, "checkinstall") == 0) {
777 			sprintf(path, "%s/%s", inst_dir, dp->d_name);
778 			retval |= chk_valid(dp->d_name, path, silent);
779 			continue;
780 		}
781 	}
782 
783 	(void) closedir(dirfp);
784 
785 	return (retval);
786 }
787