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