home *** CD-ROM | disk | FTP | other *** search
- Path: wuarchive!mit-eddie!bbn!papaya.bbn.com!rsalz
- From: rsalz@uunet.uu.net (Rich Salz)
- Newsgroups: comp.sources.unix
- Subject: v21i031: Safe way to run setuid shell scripts
- Message-ID: <2358@litchi.bbn.com>
- Date: 23 Mar 90 20:14:15 GMT
- Lines: 1149
- Approved: rsalz@uunet.UU.NET
- X-Checksum-Snefru: 4f572b9c 2ce9cfdd 5b62cea8 69ef7415
-
- Submitted-by: Maarten Litmaath <maart@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@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@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@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@uunet.uu.net.
- Use a domain-based address or give alternate paths, or you may lose out.
-