char	sccsid[] = "@(#) setuid.c 1.1 88/12/29 Maarten Litmaath";

/*
 * setuid.c
 * execute setuid shell scripts safely
 * see setuid.8
 */

/* Enhancements added by Stuart Levy:
 * setuid -r option causes setuid(geteuid()) and setgid(getegid()).
 * Sets SETUID_REALUSER environment variable to username of real user --
 * nice for privileged shell scripts we want to give restricted access.
 */

#include	<sys/types.h>
#include	<sys/stat.h>
#include	<stdio.h>
#include	<pwd.h>


#define		COMMENT		'#'
#define		MAGIC		'?'
#define		MAXARGV		1024
#define		MAXLEN		256
#define		SEC_ARGC	1
#define		SEC_OPEN	2
#define		SEC_FMT		3
#define		SEC_READ	4
#define		SEC_EXEC	5
#define		SEC_STAT	6
#define		SEC_ALARM	7


char	E_argc[] = "%s: argument expected\n",
	E_open[] = "%s: cannot open ",
	E_fmt[] = "%s: format error in %s\n",
	E_read[] = "%s: read error in ",
	E_exec[] = "%s: cannot execute ",
	E_stat[] = "%s: cannot fstat ",
	E_alarm[] = "%s: %s is fake!\n",
	Defaultpath[] = "/bin:/usr/bin",
	*interpreter,
	*newargv[MAXARGV];


main(argc, argv)
int	argc;
char	**argv;
{
	FILE	*fp;
	int	c;
	char	*prog, *file, *strrchr();
	struct	stat	st;
	struct	passwd	*pw;
	
	
	if (!(prog = strrchr(argv[0], '/')))
		prog = argv[0];
	else
		++prog;

	if ((pw = getpwuid(getuid())) != NULL)
		setenv("SETUID_REALUSER", pw->pw_name, 1);
	else
		unsetenv("SETUID_REALUSER");

	if (argc > 1 && strcmp(argv[1], "-r") == 0) {
		setgid(getegid());
		setuid(geteuid());
		argc--;
		argv++;
	}

	if (argc < 2) {
		fprintf(stderr, E_argc, prog);
		exit(SEC_ARGC);
	}
	file = argv[1];

	if (!(fp = fopen(file, "r"))) {
		fprintf(stderr, E_open, prog);
		perror(file);
		exit(SEC_OPEN);
	}

	while ((c = getc(fp)) != '\n' && c != EOF)	/* skip #! line */
		;


	if (!(c = getparams(fp))) {
		if (ferror(fp)) {
			fprintf(stderr, E_read, prog);
			perror(file);
			exit(SEC_READ);
		}
		fprintf(stderr, E_fmt, prog, file);
		exit(SEC_FMT);
	}

	argv += 2;			/* skip prog + file */

	while (*argv && c < MAXARGV - 1)
		newargv[c++] = *argv++;

	newargv[c] = 0;

#ifdef	DEBUG
	printf("interpreter='%s'\n", interpreter);
	for (c = 0; newargv[c]; c++)
		printf("newargv[%d]='%s'\n", c, newargv[c]);
#endif	DEBUG

	(void) unsetenv("IFS");
	(void) setenv("PATH", Defaultpath, 1);

	if (fstat(fileno(fp), &st) != 0) {
		fprintf(stderr, E_stat, prog);
		perror(file);
		exit(SEC_STAT);
	}

	if (!(st.st_mode & (S_ISUID | S_ISGID)) ||
		(st.st_mode & S_ISUID) && st.st_uid != geteuid() ||
		(st.st_mode & S_ISGID) && st.st_gid != getegid()) {
		fprintf(stderr, E_alarm, prog, file);
		exit(SEC_ALARM);
	}

	execv(interpreter, newargv);

	fprintf(stderr, E_exec, prog);
	perror(interpreter);
	exit(SEC_EXEC);
}


static	int	getparams(fp)
FILE	*fp;
{
	char	buf[MAXLEN], *skipblanks(), *skiptoblank(), *strrchr();
	register char	*p;
	register int	i = 0;


	for (;;) {
		if (!fgets(buf, sizeof buf, fp) ||
			buf[strlen(buf) - 1] != '\n')
			return 0;

		p = skipblanks(buf);

		switch (*p++) {
			case '\n':
				continue;
			case COMMENT:
				break;
			default:
				return 0;
		}

		if (*p++ == MAGIC)
			break;
	}

	p = skipblanks(p);

	if (*(interpreter = p) != '/')
		return 0;

	p = skiptoblank(p);

	if (*p == '\n')
		return 0;

	*p++ = '\0';

	newargv[0] = strrchr(interpreter, '/') + 1;

	while (i < MAXARGV - 1) {
		p = skipblanks(p);

		if (*p == '\n')
			break;

		newargv[++i] = p;

		p = skiptoblank(p);

		if (*p == '\n')
			break;

		*p++ = '\0';
	}

	newargv[++i] = 0;

	/*
	 * we expect 2 args at least: interpreter + file
	 * furthermore we expect p to point to end of line by now
	 */

	if (i <= 1 || *p != '\n')
		return 0;

	*p = '\0';
	return i;
}


static	char	*skipblanks(p)
register char	*p;
{
	while (*p == ' ' || *p == '\t')
		++p;

	return p;
}


static	char	*skiptoblank(p)
register char	*p;
{
	while (*p != ' ' && *p != '\t' && *p != '\n')
		++p;

	return p;
}
