v21i031: Safe way to run setuid shell scripts

Rich Salz rsalz at uunet.uu.net
Sat Mar 24 06:14:15 AEST 1990


Submitted-by: Maarten Litmaath <maart at cs.vu.nl>
Posting-number: Volume 21, Issue 31
Archive-name: indir

Suppose you want everyone to be able to remove some lockfile, but you don't
want its directory to be world-writable.  Isn't it ridiculous you'd have to
write a setuid C program to do the equivalent of the following shell script?

	#!/bin/sh
	/bin/rm /some/directory/lockfile

The problem: making this shell script setuid creates a security hole (see
the file `setuid.txt').  The solution: indir(1).  Using this program the
script would be setuid and look like this:

	#!/bin/indir -u
	#?/bin/sh /safe/path/to/this/script
	/bin/rm /some/directory/lockfile

To make indir, check if the Makefile is suited for your environment.
The `exec_test' script will find out if your operating system has `#!'
magic numbers enabled.  To do some tests, issue the command `make test'.

: This is a shar archive.  Extract with sh, not csh.
: This archive ends with exit, so do not worry about trailing junk.
: --------------------------- cut here --------------------------
PATH=/bin:/usr/bin:/usr/ucb
echo Extracting 'README'
sed 's/^X//' > 'README' << '+ END-OF-FILE ''README'
X`Intro' describes why one would use indir(1), `setuid.txt' is a general
Xtext about setuid shell scripts.
XTo make indir, check if the Makefile is suited for your environment.
XThe `exec_test' script will find out if your operating system has `#!'
Xmagic numbers enabled.  To do some tests, issue the command `make test'.
+ END-OF-FILE README
chmod 'u=rw,g=r,o=r' 'README'
set `wc -c 'README'`
count=$1
case $count in
318)	:;;
*)	echo 'Bad character count in ''README' >&2
		echo 'Count should be 318' >&2
esac
echo Extracting 'Intro'
sed 's/^X//' > 'Intro' << '+ END-OF-FILE ''Intro'
X			Why use indir(1)?
X			-----------------
X
XSuppose you want everyone to be able to remove some lockfile, but you don't
Xwant its directory to be world-writable.  Isn't it ridiculous you'd have to
Xwrite a setuid C program to do the equivalent of the following shell script?
X
X	#!/bin/sh
X	/bin/rm /some/directory/lockfile
X
XThe problem: making this shell script setuid creates a security hole (see
Xthe file `setuid.txt').  The solution: indir(1).  Using this program the
Xscript would be setuid and look like this:
X
X	#!/bin/indir -u
X	#?/bin/sh /safe/path/to/this/script
X	/bin/rm /some/directory/lockfile
X
XAs the only command in the script is `rm', you might think the script could
Xlook like this too:
X
X	#!/bin/indir -u
X	#?/bin/rm /some/directory/lockfile
X
XHowever, this is WRONG, because if the script is invoked with arguments,
Xthose will also be arguments to `rm':
X
X	remove_lockfile foo bar
X
Xbecomes
X
X	/bin/rm /some/directory/lockfile foo bar
X
Xclearly not what was intended.
XIndir(1) can also be used for non-setuid scripts, to overcome the constraints
Xon a `#!' line (currently max. 32 characters, max. 1 (option) argument and the
X`interpreter' must be a real binary):
X
X	#!/bin/indir -n
X	#?sed -n -f %
X	/uu/p
X
XIf invoked with the `-n' option, indir(1) will substitute the name of the
Xscript for every `%' argument on the `#?' line.  Furthermore the user's
XPATH will be searched (if necessary) to find the program to be executed.
XEven in setuid scripts every `#?' argument is subject to the `~' convention:
Xa leading string `~user' will be expanded to the home directory of `user'.
X
XSome points of interest and advantages over other schemes:
X- one is not bound to the 32-characters-1-option limit
X- the `interpreter' needn't be a real binary (another `#!' constraint)
X- `%' substitution for normal scripts, `~' convention for all scripts
X- only 1, public program needed, instead of each user having his own setuid
X  `server'
X- no database of valid scripts (`services') needed
X- one can make a private link to the script, with a prefered name, and things
X  still work right:
X
X	$ ln -s /etc/setuid_script my_name
X	$ my_name
+ END-OF-FILE Intro
chmod 'u=rw,g=r,o=r' 'Intro'
set `wc -c 'Intro'`
count=$1
case $count in
2118)	:;;
*)	echo 'Bad character count in ''Intro' >&2
		echo 'Count should be 2118' >&2
esac
echo Extracting 'setuid.txt'
sed 's/^X//' > 'setuid.txt' << '+ END-OF-FILE ''setuid.txt'
X			Setuid Shell Scripts
X			--------------------
X			how to get them safe
X
X			  Maarten Litmaath
X			  (maart at cs.vu.nl)
X
X
XConsider a setuid root shell script written in Bourne shell command language
Xand called `/bin/powerful'.
XThe first line of the script will be (without indentation!):
X
X	#!/bin/sh
X
XIf it doesn't begin with such a line, it's no use to chmod it to 6755 or
Xwhatever, because in that case it's just a shell script of the `old' kind:
Xthe Bourne shell receives an exec format error when trying to execute it, and
Xdecides it must be a shell script, so it forks a subshell with the script name
Xas argument, to indicate from which file the commands are to be read.
XShell scripts of the `new' kind are treated as follows: the kernel discovers
Xthe magic number `#!' and tries to execute the command interpreter pointed out,
Xwhich may be followed in the script by 1 argument.
XBefore the exec of the interpreter the uid and gid fields somewhere in the user
Xstructure of the process are filled in.
XSetuid script scheme (kernel manipulations faked by C routines):
X
X	execl("/bin/powerful", "powerful", (char *) 0);
X
X	  |
X	  V
X
X	setuid(0);
X	setgid(0);      /* possibly */
X	execl("/bin/sh", "sh", "/bin/powerful", (char *) 0);
X
XNow, what if the name of the very shell script were e.g. "-i"? Wouldn't that
Xgive a nice exec?
X
X	execl("/bin/sh", "sh", "-i", (char *) 0);
X
XSo link the script to a file named "-i", and voila!
XYes, one needs write permission somewhere on the same device, if one's
Xoperating system doesn't support symbolic links.
X
XWhat about the csh command interpreter? Well, 4.2BSD provides us with a csh
Xwhich has a NEW option: "-b"! Its goal is to avoid just the thing described
Xabove: the mnemonic for `b' is `break'; this option prevents following
Xarguments of an exec of /bin/csh from being interpreted as options...
XThe csh refuses to run a setuid shell script unless the option is present...
XScheme:
X	#!/bin/csh -b
X	...
X
X	execl("-i", "unimportant", (char *) 0);
X
X	  |
X	  V
X
X	setuid(0);
X	setgid(0);
X	execl("/bin/csh", "csh", "-b", "-i", (char *) 0);
X
XAnd indeed the contents of the file "-i" are executed!
XHowever, there's still another bug hidden, albeit not for long!
XWhat if I could `get between' the setuid()/setgid() and the open() of the
Xcommand file by the command interpreter?
XIn that case I could unlink() my link to the setuid shell script, and quickly
Xlink() some other shell script into its place, couldn't I?
XRight.
XYet another source of trouble for /bin/sh setuid scripts is the reputed IFS
Xshell variable. Of course there's also the PATH variable, which might cause
Xproblems. However, one can circumvent these 2 jokers easily.
XA solution to the link()/unlink() problems would be the specification of the
Xfull path of the script in the script itself:
X
X	#!/bin/sh /etc/setuid_script
X	shift		# remove the `extra' argument
X	...
X
XSome objections:
X1)
X	currently the total length of shell + argument mustn't exceed 32 chars
X	(easily fixed);
X2)
X	4.[23]BSD csh is expecting a `-b' flag as the first argument, instead
X	of the full path (easily fixed);
X3)
X	the interpreter gets an extra argument;
X4)
X	the difficulty of maintaining setuid shell scripts increases - if one
X	moves a script, one shouldn't forget to edit it... - editing in turn
X	could turn off the setuid bits, so one shouldn't forget to chmod(1)
X	the file `back'... - conceptually the solution above isn't `elegant'.
X
XHow does indir(1) tackle the problems? The script to be executed will look
Xlike:
X	#!/bin/indir -u
X	#?/bin/sh /etc/setuid_script
X	...
X
XIndir(1) will try to open the script and read the `#?' line indicating the
Xreal interpreter and a safe (absolute) pathname of the script. But remember:
Xthe link to the script might have been quickly replaced with a link to another
Xscript, i.e. how can we trust this `#?' line?
XAnswer: if and only if the file we're reading from is SETUID (setgid) to the
XEFFECTIVE uid (gid) of the process, we know we're executing the original
Xscript (to be 100% correct: the original link might have been replaced with a
Xlink to ANOTHER setuid script of the same owner -> merely a waste of time).
XTo reliably check the condition stated above, we use fstat(2) on the file
Xdescriptor we're reading from. Can you figure out why stat(2) would be
Xinsecure?
XTo deal with IFS, PATH and other environment problems, indir(1) resets the
Xenvironment to a simple default:
X
X	PATH=/bin:/usr/bin:/usr/ucb
X
XWhen you need e.g. $HOME, you should get it from /etc/passwd instead of
Xtrusting what the environment says. Of course with indir(1) problem 4 remains.
X
X				--------
+ END-OF-FILE setuid.txt
chmod 'u=rw,g=r,o=r' 'setuid.txt'
set `wc -c 'setuid.txt'`
count=$1
case $count in
4577)	:;;
*)	echo 'Bad character count in ''setuid.txt' >&2
		echo 'Count should be 4577' >&2
esac
echo Extracting 'indir.1'
sed 's/^X//' > 'indir.1' << '+ END-OF-FILE ''indir.1'
X.\" maart at cs.vu.nl - Maarten Litmaath Wed Nov  1 07:16:05 MET 1989
X.\" uses -man
X.TH INDIR 1 "Nov 01, 1989"
X.UC 4
X.SH NAME
X.B indir
X\- run (setuid/setgid) (shell) scripts indirectly
X.SH SYNOPSIS
X.B "#!/bin/indir \-ugbn"
X.br
X.B #?...
X.br
X.SH DESCRIPTION
X.I Indir
Xis not executed from a shell normally. Instead it can be used
Xas the interpreter for shell scripts that:
X.PP
X.RS
X1) need to be run \fIsetuid\fR (\fIsetgid\fR) to someone else, or
X.PP
X2) fail to meet the constraints for a `\fB#!\fR' line (see \fIexecve\fR(2)).
X.RE
X.PP
X.I Indir
Xis invoked by making the first line of the script
X.PP
X.RS
X.B "#!/bin/indir \-ugbn"
X.RE
X.PP
Xrather than the usual
X.PP
X.RS
X.B #!/bin/sh
X.RE
X.PP
XExactly 1 of the options must be specified (see below).
X.I Indir
Xtries to open the script for reading. If successful it discards the first
Xline (containing `\fB#!/bin/indir \-ugbn\fR') and tries to read a line
Xformatted as follows:
X.PP
X.RS
X.ft B
X#?absolute\-path\-of\-interpreter arguments
X.RE
X.PP
XWhitespace around the `\fB#?\fR' \fImagic number\fR is discarded.
XThe interpreter as well as the arguments are subject to
Xthe `\fItilde convention\fR': a leading string `\fI~user\fR' is expanded to
Xthe home directory of `\fIuser\fR', where `\fIuser\fR' is the longest
Xstring of \fIlogin-name characters\fR immediately following the `~'.
XIf this string equals the null string, the login name of the REAL uid
Xis used.  Currently \fIlogin-name characters\fR are defined to include every
Xcharacter except the following: `/', space, horizontal tab, newline and NUL.
X.PP
XFurthermore an argument consisting of a single `%' is expanded to the name
Xof the script, if and only if the `\-\fIn\fR' option has been specified, else
Xthe expansion is inhibited (see below).
X.PP
XThe `\-\fIn\fR' option turns off security checking: it must be used only for
Xscripts that are not setuid or setgid. For scripts that are only setuid the
X`\-\fIu\fR' option must be used. The `\-\fIg\fR' option is for scripts that
Xare only setgid. Finally the `\-\fIb\fR' option is for scripts that are both
Xsetuid and setgid.  Examples:
X.PP
X.RS
X.ft B
X#!/bin/indir \-u
X.br
X#?/bin/csh \-bf /usr/etc/setuid_script \-v
X.sp
X#!/bin/indir \-g
X.br
X#?~my_name/bin/prog \-f ~my_name/lib/setgid_script
X.sp
X#!/bin/indir \-n
X.br
X#?sed \-n \-f %
X.RE
X.PP
XA `\fB#?\fR' line is limited to 256 characters. However,
Xif the line ends in a backslash (`\fB\\\fR'), the next line is assumed to
Xcontain further arguments after a mandatory leading `\fB#?\fR', and so on.
XThere is a system-imposed limit on the total number of characters present
Xin the argument list.
X.PP
XTo avoid `linking tricks' through which uncontrolled privileges of the
Xowner of the file could be acquired, 3 measures have been taken for setuid
X(setgid) scripts:
X.RS
X.PP
X1) the script must contain its own safe invocation, that is the
X`\fB#?\fR' line; `%' arguments will cause \fIindir\fR to abort;
X.PP
X2) the \fIenvironment\fR is reset to a simple default:
X.PP
X.RS
X.B PATH=/bin:/usr/bin:/usr/ucb
X.RE
X.PP
X3) before the final \fIexecve\fR(2)
X.I indir
Xchecks if the owner and mode of the script are still what they are supposed
Xto be (using \fIfstat\fR(2)); if there is a discrepancy,
X.I indir
Xwill abort with an error message.
X.RE
X.PP
X.I Indir
Xwill only exec a pathname beginning with a `\fB/\fR', unless the `\-\fIn\fR'
Xoption was specified. In the latter case the user's PATH will be searched
X(if necessary) to locate the interpreter (see \fIexecvp\fR(3)). Of course
X.I indir
Xcan be `fooled' by supplying dubious arguments to the interpreter,
Xlike \fIrelative pathnames\fR. Furthermore it is a mistake to let any of
Xthe directory components of an ultimate path be writable by others.
XIn our first example `/', `/\fIbin\fR', `/\fIusr\fR' and `/\fIusr/etc\fR'
Xshould not be writable for ordinary users.
X.SH AUTHOR
XMaarten Litmaath @ VU Informatika Amsterdam (maart at cs.vu.nl)
X.SH "SEE ALSO"
X.B "sh(1), csh(1)"
X.SH BUGS
XThe maintenance of setuid (setgid) scripts is a bit annoying: if a script is
Xmoved, one must not forget to change the path mentioned in the script.
XPossibly the editing causes a setuid bit to get turned off.
+ END-OF-FILE indir.1
chmod 'u=rw,g=r,o=r' 'indir.1'
set `wc -c 'indir.1'`
count=$1
case $count in
4112)	:;;
*)	echo 'Bad character count in ''indir.1' >&2
		echo 'Count should be 4112' >&2
esac
echo Extracting 'Makefile'
sed 's/^X//' > 'Makefile' << '+ END-OF-FILE ''Makefile'
XSRCS		= indir.c error.c
XOBJS		= indir.o error.o
XMAN		= indir.1
X
X# a few defines for testing purposes; if you test in the current
X# directory the paths needn't be absolute
X# the path of indir will be used in #! lines -> max. 32 characters for
X# #! + name + mandatory option
XPATH_OF_INDIR	= ./indir
XTEST_DIRECTORY	= .
X
XCC		= cc
X# if your C library doesn't have strrchr()
X# STRRCHR	= -Dstrrchr=rindex
X
XCFLAGS		= -O $(STRRCHR)
X
Xindir:		exec_ok $(OBJS)
X		$(CC) -O -o indir $(OBJS)
X
Xexec_ok:
X		exec_test
X
Xtest:		exec_ok
X		test -f tests_made || PATH_OF_INDIR=$(PATH_OF_INDIR) \
X			TEST_DIRECTORY=$(TEST_DIRECTORY) make_tests
X		do_tests
X
Xlint:
X		lint $(SRCS)
X
Xman:
X		nroff -man $(MAN) > indir.man
X
Xshar:
X		shar README Intro setuid.txt $(MAN) Makefile $(SRCS) indir.h \
X			error.h my_varargs.h exec_test make_tests do_tests \
X			file_argument > indir.sh
X
Xclean:
X		/bin/rm -f *.o indir.man wrong.* ok.* core exec_ok tests_made
X
Xindir.o:	indir.c indir.h error.h my_varargs.h
Xerror.o:	error.c error.h my_varargs.h
+ END-OF-FILE Makefile
chmod 'u=rw,g=r,o=r' 'Makefile'
set `wc -c 'Makefile'`
count=$1
case $count in
1002)	:;;
*)	echo 'Bad character count in ''Makefile' >&2
		echo 'Count should be 1002' >&2
esac
echo Extracting 'indir.c'
sed 's/^X//' > 'indir.c' << '+ END-OF-FILE ''indir.c'
Xstatic	char	sccsid[] = "@(#)indir.c 2.0 89/11/01 Maarten Litmaath";
X
X/*
X * indir.c
X * execute (setuid/setgid) (shell) scripts indirectly
X * see indir.1
X */
X
X#include	"indir.h"
X#include	"error.h"
X
X
Xstatic	char	*Env[] = {
X	"PATH=/bin:/usr/bin:/usr/ucb",
X	0
X};
Xstatic	char	*Prog, *File, *Interpreter, *Newargv[MAXARGV];
Xstatic	int	Uid_check = 1, Gid_check = 1;
Xstatic	uid_t	Uid;
X
X
Xmain(argc, argv)
Xint	argc;
Xchar	**argv;
X{
X	extern	char	*strrchr();
X	extern	int	execve(), execvp();
X	FILE	*fp;
X	int	c;
X	struct	stat	st;
X
X
X	if (!(Prog = strrchr(argv[0], '/')))
X		Prog = argv[0];
X	else
X		++Prog;
X
X	if (!argv[1] || *argv[1] != '-' || !argv[1][1] || argv[1][2])
X		error(E_opt, Prog);
X
X	switch (argv[1][1]) {
X	case 'n':
X		Uid_check = Gid_check = 0;
X		break;
X	case 'g':
X		Uid_check = 0;
X		break;
X	case 'u':
X		Gid_check = 0;
X		break;
X	case 'b':
X		break;
X	default:
X		error(E_opt, Prog);
X		break;
X	}
X
X	if (argc < 3)
X		error(E_file, Prog);
X
X	File = argv[2];
X
X	if (!(fp = fopen(File, "r")))
X		error(E_open, Prog, File, geterr());
X
X	Uid = getuid();
X
X	while ((c = getc(fp)) != '\n' && c != EOF)	/* skip #! line */
X		;
X
X	if (!(c = getnewargv(fp))) {
X		if (ferror(fp))
X			error(E_read, Prog, File, geterr());
X		error(E_fmt, Prog, File);
X	}
X
X	argv += 3;			/* skip Prog, option and File */
X
X	while (*argv && c < MAXARGV - 1)
X		Newargv[c++] = *argv++;
X
X	if (*argv)
X		error(E_args, Prog, File);
X
X	Newargv[c] = 0;
X
X#ifdef	DEBUG
X	fprintf(stderr, "Interpreter=`%s'\n", Interpreter);
X	for (c = 0; Newargv[c]; c++)
X		fprintf(stderr, "Newargv[%d]=`%s'\n", c, Newargv[c]);
X#endif	/* DEBUG */
X
X	if (fstat(fileno(fp), &st) != 0)
X		error(E_fstat, Prog, File, geterr());
X	/*
X	 * List of possible Uid_check/setuid combinations:
X	 *
X	 * !Uid_check !setuid -> OK: ordinary script
X	 * !Uid_check  setuid -> security hole: checking should be enabled
X	 *  Uid_check !setuid -> fake
X	 *  Uid_check  setuid -> check if st_uid == euid
X	 */
X
X	if (!Uid_check) {
X		/*
X		 * If the file is setuid, consistency should be checked;
X		 * however, checking has been disabled for this file (leading
X		 * to security holes again!), so warn the user and exit.
X		 */
X		if (st.st_mode & S_ISUID)
X			error(E_mode, Prog, File);
X	} else {
X		/*
X		 * Check if the file we're reading from is setuid and owned
X		 * by geteuid().  If this test fails, the file is a fake,
X		 * else it MUST be ok!
X		 */
X		if (!(st.st_mode & S_ISUID) || st.st_uid != geteuid())
X			error(E_alarm, Prog, File);
X	}
X
X	/* setgid checks */
X
X	if (!Gid_check) {
X		if (st.st_mode & S_ISGID)
X			error(E_mode, Prog, File);
X	} else {
X		if (!(st.st_mode & S_ISGID) || st.st_gid != getegid())
X			error(E_alarm, Prog, File);
X	}
X
X	/*
X	 * If we're executing a set[ug]id file, replace the complete
X	 * environment by a save default, else permit the PATH to be
X	 * searched too.
X	 */
X	if (st.st_mode & (S_ISUID | S_ISGID))
X		execve(Interpreter, Newargv, Env);
X	else
X		execvp(Interpreter, Newargv);
X
X	error(E_exec, Prog, Interpreter, File, geterr());
X}
X
X
Xstatic	int	getnewargv(fp)
XFILE	*fp;
X{
X	static	char	buf[MAXLEN];
X	char	*skipblanks(), *skiptoblank(), *strrchr(), *malloc(), *tilde();
X	register char	*p;
X	register int	i = 0;
X
X
X	for (;;) {
X		if (!fgets(buf, sizeof buf, fp) || !*buf
X			|| buf[strlen(buf) - 1] != '\n')
X			return 0;
X
X		p = skipblanks(buf);
X
X		switch (*p++) {
X		case '\0':			/* skip empty lines */
X			continue;
X		case COMMENT:
X			break;
X		default:
X			return 0;
X		}
X
X		if (*p++ == MAGIC)		/* skip ordinary comments */
X			break;
X	}
X
X	p = skipblanks(p);
X
X	if (*p == TILDE)
X		p = tilde(p, &Interpreter);
X	else {
X		if (*p != '/') {
X			if (!*p)
X				return 0;
X			if (Uid_check || Gid_check)
X				error(E_name, Prog, File);
X		}
X		Interpreter = p;
X		p = skiptoblank(p);
X		*p++ = '\0';
X	}
X
X	if (!(Newargv[0] = strrchr(Interpreter, '/')))
X		Newargv[0] = Interpreter;
X	else
X		++Newargv[0];
X
X	while (i < MAXARGV - 2) {
X		p = skipblanks(p);
X
X		if (!*p)
X			break;
X
X		if (*p == '\\' && p[1] == '\n') {
X			if (!(p = malloc(MAXLEN)))
X				error(E_mem, Prog, File, geterr());
X			if (!fgets(p, MAXLEN, fp) || !*p
X				|| p[strlen(p) - 1] != '\n')
X				return 0;
X			p = skipblanks(p);
X			if (*p++ != COMMENT || *p++ != MAGIC)
X				return 0;
X			continue;
X		}
X
X		if (*p == TILDE)
X			p = tilde(p, &Newargv[++i]);
X		else if (*p++ == SUBST
X			&& (*p == '\n' || *p == ' ' || *p == '\t')) {
X			if (Uid_check || Gid_check)
X				error(E_subst, Prog, SUBST, File);
X			Newargv[++i] = File;
X		} else {
X			Newargv[++i] = --p;
X			p = skiptoblank(p);
X			*p++ = '\0';
X		}
X	}
X
X	if (*p)
X		error(E_args, Prog, File);
X
X	Newargv[++i] = 0;
X	return i;
X}
X
X
Xstatic	char	*skipblanks(p)
Xregister char	*p;
X{
X	register int	c;
X
X	while ((c = *p++) == ' ' || c == '\t' || c == '\n')
X		;
X	return --p;
X}
X
X
Xstatic	char	*skiptoblank(p)
Xregister char	*p;
X{
X	register int	c;
X
X	while ((c = *p++) != ' ' && c != '\t' && c != '\n')
X		;
X	return --p;
X}
X
X
X#include	<pwd.h>
X
X
Xstatic	char	*tilde(p, pp)
Xregister char	*p;
Xchar	**pp;
X{
X	register char	*q, c;
X	struct	passwd	*pw, *getpwuid(), *getpwnam();
X	char	*orig, save, *buf, *strcpy(), *strncpy(), *malloc();
X	int	n;
X
X
X	orig = p++;
X	for (q = p; (c = *q++) != '/' && c != '\n' && c != ' ' && c != '\t'; )
X		;
X	if (--q == p)
X		pw = getpwuid(Uid);
X	else {
X		save = *q;
X		*q = '\0';
X		pw = getpwnam(p);
X		*(p = q) = save;
X	}
X	q = skiptoblank(q);
X	save = *q;
X	*q = '\0';
X	if (!pw)
X		error(E_tilde, Prog, orig, File);
X	if (!(buf = malloc((n = strlen(pw->pw_dir)) + q - p + 1)))
X		error(E_mem, Prog, File, geterr());
X	strcpy(buf, pw->pw_dir);
X	strcpy(buf + n, p);
X	*pp = buf;
X	*q = save;
X	return q;
X}
+ END-OF-FILE indir.c
chmod 'u=rw,g=r,o=r' 'indir.c'
set `wc -c 'indir.c'`
count=$1
case $count in
5483)	:;;
*)	echo 'Bad character count in ''indir.c' >&2
		echo 'Count should be 5483' >&2
esac
echo Extracting 'error.c'
sed 's/^X//' > 'error.c' << '+ END-OF-FILE ''error.c'
X/*
X * error.c
X */
X#define EXTERN
X#include	"error.h"
X#include	"my_varargs.h"
X#include	<stdio.h>
X
X/* VARARGS1 */
XVARARGS(void, error, (error_p_type error_p, ...))
X{
X#ifndef __STDC__
X	error_p_type	error_p;
X#endif	/* !__STDC__ */
X
X	va_list ap;
X
X	VA_START(ap, error_p_type, error_p);
X	vfprintf(stderr, error_p->message, ap);
X	va_end(ap);
X	exit(error_p->status);
X}
X
X
Xchar	*geterr()
X{
X	extern	int	errno, sys_nerr;
X	extern	char	*sys_errlist[];
X	static	char	s[64];
X
X	if ((unsigned) errno < sys_nerr)
X		return sys_errlist[errno];
X	(void) sprintf(s, "Unknown error %d", errno);
X	return s;
X}
+ END-OF-FILE error.c
chmod 'u=rw,g=r,o=r' 'error.c'
set `wc -c 'error.c'`
count=$1
case $count in
580)	:;;
*)	echo 'Bad character count in ''error.c' >&2
		echo 'Count should be 580' >&2
esac
echo Extracting 'indir.h'
sed 's/^X//' > 'indir.h' << '+ END-OF-FILE ''indir.h'
X#include	<sys/param.h>
X#include	<sys/stat.h>
X#include	<stdio.h>
X
X#define		COMMENT		'#'
X#define		MAGIC		'?'
X#define		TILDE		'~'
X#define		SUBST		'%'
X#define		MAXLEN		256
X#define		MAXARGV		1024
X
X#ifdef	NCARGS
X#if NCARGS < MAXARGV
X#undef	MAXARGV
X#define		MAXARGV		NCARGS
X#endif	/* NCARGS < MAXARGV */
X#endif	/* NCARGS */
+ END-OF-FILE indir.h
chmod 'u=rw,g=r,o=r' 'indir.h'
set `wc -c 'indir.h'`
count=$1
case $count in
317)	:;;
*)	echo 'Bad character count in ''indir.h' >&2
		echo 'Count should be 317' >&2
esac
echo Extracting 'error.h'
sed 's/^X//' > 'error.h' << '+ END-OF-FILE ''error.h'
X/*
X * error.h
X */
X
X#ifndef ERROR_H
X#define ERROR_H
X
Xstruct	error_s {
X	int	status;
X	char	*message;
X};
Xtypedef struct	error_s *error_p_type;
X
X#include	"my_varargs.h"
XEXTERN_VARARGS(void, error, (error_p_type error, ...));
Xextern	char	*geterr();
X
X#ifndef EXTERN
X#define EXTERN	extern
X#define INIT_ERROR_S(value, message)
X#else	/* !EXTERN */
X#define INIT_ERROR_S(value, message)	= { { value, message } }
X#endif	/* !EXTERN */
X
X#define ERROR(error, value, message)	\
X	EXTERN	struct	error_s error[] INIT_ERROR_S(value, message);
X
XERROR(E_opt,    1, "%s: -[ugbn] option expected\n")
XERROR(E_file,   2, "%s: file argument expected\n")
XERROR(E_open,   3, "%s: cannot open `%s': %s\n")
XERROR(E_name,   4, "%s: pathname of interpreter in `%s' is not absolute\n")
XERROR(E_mem,    5, "%s: malloc error for `%s': %s\n")
XERROR(E_subst,  6,
X	"%s: `%c' substitution attempt under -[ugb] option in file `%s'\n")
XERROR(E_args,   7, "%s: too many arguments for `%s'\n")
XERROR(E_read,   8, "%s: read error in `%s': %s\n")
XERROR(E_fmt,    9, "%s: format error in `%s'\n")
XERROR(E_tilde, 10, "%s: cannot resolve `%s' in `%s'\n")
XERROR(E_fstat, 11, "%s: cannot fstat `%s': %s\n")
XERROR(E_mode,  12, "%s: `%s' is set[ug]id, yet has checking disabled!\n")
XERROR(E_alarm, 13, "%s: `%s' is a fake!\n")
XERROR(E_exec,  14, "%s: cannot execute `%s' in `%s': %s\n")
X
X#endif	/* !ERROR_H */
+ END-OF-FILE error.h
chmod 'u=rw,g=r,o=r' 'error.h'
set `wc -c 'error.h'`
count=$1
case $count in
1356)	:;;
*)	echo 'Bad character count in ''error.h' >&2
		echo 'Count should be 1356' >&2
esac
echo Extracting 'my_varargs.h'
sed 's/^X//' > 'my_varargs.h' << '+ END-OF-FILE ''my_varargs.h'
X/*
X * my_varargs.h
X */
X#ifndef MY_VARARGS_H
X#define MY_VARARGS_H
X
X#ifdef	__STDC__
X
X#define		EXTERN_VARARGS(type, f, args)	extern	type	f args
X#define		VARARGS(type, f, args)		type	f args
X#define		VA_START(ap, type, start)	va_start(ap, start)
X#include	<stdarg.h>
X
X#else	/* __STDC__ */
X
X#define		EXTERN_VARARGS(type, f, args)	extern	type	f()
X#define		VARARGS(type, f, args)		type	f(va_alist) \
X						va_dcl
X#define		VA_START(ap, type, start)	va_start(ap); \
X						start = va_arg(ap, type)
X#include	<varargs.h>
X
X#endif	/* __STDC__ */
X
X#endif	/* !MY_VARARGS_H */
+ END-OF-FILE my_varargs.h
chmod 'u=rw,g=r,o=r' 'my_varargs.h'
set `wc -c 'my_varargs.h'`
count=$1
case $count in
558)	:;;
*)	echo 'Bad character count in ''my_varargs.h' >&2
		echo 'Count should be 558' >&2
esac
echo Extracting 'exec_test'
sed 's/^X//' > 'exec_test' << '+ END-OF-FILE ''exec_test'
X#!/bin/sh
X
Xcat > try << EOF
X#!/bin/test -f
XEOF
X
Xchmod 100 try
X
X./try 2> /dev/null
Xstatus=$?
X/bin/rm -f try
X
Xcase $status in
X0)
X	> exec_ok
X	exit 0
Xesac
X
Xecho "Sorry, your system doesn't seem to"
Xecho "recognize the #! magic number.  Abort."
Xexit 1
+ END-OF-FILE exec_test
chmod 'u=rwx,g=rx,o=rx' 'exec_test'
set `wc -c 'exec_test'`
count=$1
case $count in
247)	:;;
*)	echo 'Bad character count in ''exec_test' >&2
		echo 'Count should be 247' >&2
esac
echo Extracting 'make_tests'
sed 's/^X//' > 'make_tests' << '+ END-OF-FILE ''make_tests'
X#!/bin/sh
X: ${PATH_OF_INDIR?} ${TEST_DIRECTORY?}
Xmyname=ok.01
Xcat > $myname << EOF
X#!$PATH_OF_INDIR -u
X#?/bin/csh -bf $TEST_DIRECTORY/$myname -v
Xwhoami
Xprintenv
Xecho args:
Xforeach i (\$*)
X	echo '	'\$i
Xend
XEOF
Xchmod 4755 $myname
X####################
Xmyname=ok.02
Xcat > $myname << EOF
X#!$PATH_OF_INDIR -n
X
X#comment
X#? /bin/sh \\
X#? $TEST_DIRECTORY/$myname x y \\
X#? z
X
Xwhoami
Xprintenv
Xecho args:
Xfor i
Xdo
X	echo '	'\$i
Xdone
XEOF
Xchmod 755 $myname
X####################
Xmyname=ok.03
Xcat > $myname << EOF
X#!$PATH_OF_INDIR -g
X   #? ~root/bin/echo ~ ~/foo ~bin/bar ~/bin/baz
X
Xthis_will_not_get_executed
XEOF
Xchmod 2755 $myname
X####################
Xmyname=ok.04
Xcat > $myname << EOF
X#!$PATH_OF_INDIR -n
X#?sed -n -f %
X/uu/p
XEOF
Xchmod 755 $myname
X####################
Xmyname=ok.05
Xcat > $myname << EOF
X#!$PATH_OF_INDIR -n
X#?sed -n -f \\
X#? \\
X#? % \\
X#?
X/uu/p
XEOF
Xchmod 755 $myname
X####################
Xmyname=wrong.01
Xcat > $myname << EOF
X#!$PATH_OF_INDIR
X#?/bin/echo you_should_not_see_this
XEOF
Xchmod 755 $myname
X####################
Xmyname=wrong.02
Xcat > $myname << EOF
X#!$PATH_OF_INDIR -n
X#?/bin/echo you_should_not_see_this \\
XEOF
Xchmod 755 $myname
X####################
Xmyname=wrong.03
Xcat > $myname << EOF
X#!$PATH_OF_INDIR -n
Xhello
X#?/bin/echo you_should_not_see_this
XEOF
Xchmod 755 $myname
X####################
Xmyname=wrong.04
Xcat > $myname << EOF
X#!$PATH_OF_INDIR -n
X#?/bin/echo you_should_not_see_this \\
X#comment
X#? %
XEOF
Xchmod 755 $myname
X####################
Xmyname=wrong.05
Xcat > $myname << EOF
X#!$PATH_OF_INDIR -n
X#hello
X#?/bin/echo you_should_not_see_this ~nonexistent/bin/bletch
XEOF
Xchmod 755 $myname
X####################
Xmyname=wrong.06
Xcat > $myname << EOF
X#!$PATH_OF_INDIR -u
X#?/ you_should_not_see_this
XEOF
Xchmod 4755 $myname
X####################
Xmyname=wrong.07
Xcat > $myname << EOF
X#!$PATH_OF_INDIR -u
X#?/bin/echo you_should_not_see_this %
XEOF
Xchmod 755 $myname
X####################
Xmyname=wrong.08
Xcat > $myname << EOF
X#!$PATH_OF_INDIR -g
X#?../bin/echo you_should_not_see_this
XEOF
Xchmod 755 $myname
X####################
Xmyname=wrong.09
Xcat > $myname << EOF
X#!$PATH_OF_INDIR -g
X#?/bin/echo you_should_not_see_this
XEOF
Xchmod 4755 $myname
X####################
Xmyname=wrong.10
Xcat > $myname << EOF
X#!$PATH_OF_INDIR -u
X#?/bin/echo you_should_not_see_this
XEOF
Xchmod 2755 $myname
X####################
Xmyname=wrong.11
Xcat > $myname << EOF
X#!$PATH_OF_INDIR -n
X#?/bin/echo you_should_not_see_this
XEOF
Xchmod 2755 $myname
X####################
X> tests_made
+ END-OF-FILE make_tests
chmod 'u=rwx,g=rx,o=rx' 'make_tests'
set `wc -c 'make_tests'`
count=$1
case $count in
2453)	:;;
*)	echo 'Bad character count in ''make_tests' >&2
		echo 'Count should be 2453' >&2
esac
echo Extracting 'do_tests'
sed 's/^X//' > 'do_tests' << '+ END-OF-FILE ''do_tests'
X#!/bin/sh
Xfor i in wrong.* ok.*
Xdo
X	echo '' >&2
X	echo "$ ls -l $i" >&2
X	ls -l $i >&2
X	echo "$ cat $i" >&2
X	cat $i >&2
X	echo $
X	echo -n "-- hit return to run $i: " >&2
X	read x
X	$i file_argument
X	echo -n "-- hit return to continue: " >&2
X	read x
Xdone
+ END-OF-FILE do_tests
chmod 'u=rwx,g=rx,o=rx' 'do_tests'
set `wc -c 'do_tests'`
count=$1
case $count in
249)	:;;
*)	echo 'Bad character count in ''do_tests' >&2
		echo 'Count should be 249' >&2
esac
echo Extracting 'file_argument'
sed 's/^X//' > 'file_argument' << '+ END-OF-FILE ''file_argument'
Xroot:0sW84d7bh.QOc:0:1:Operator:/:/bin/csh
Xnobody:*:-2:-2::/:
Xdaemon:*:1:1::/:
Xsys:*:2:2::/:/bin/csh
Xbin:*:3:3::/bin:
Xuucp:*:4:4::/var/spool/uucppublic:
Xnews:*:6:6::/var/spool/news:/bin/csh
Xingres:*:7:7::/usr/ingres:/bin/csh
Xaudit:*:9:9::/etc/security/audit:/bin/csh
Xsync::1:1::/:/bin/sync
Xsysdiag:LTmKcPBITIe/M:0:1:System Diagnostic:/usr/diag/sysdiag:/usr/diag/sysdiag/sysdiag
X+:
+ END-OF-FILE file_argument
chmod 'u=rw,g=r,o=r' 'file_argument'
set `wc -c 'file_argument'`
count=$1
case $count in
381)	:;;
*)	echo 'Bad character count in ''file_argument' >&2
		echo 'Count should be 381' >&2
esac
exit 0

-- 
Please send comp.sources.unix-related mail to rsalz at uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.



More information about the Comp.sources.unix mailing list