From 3a94c23aed0f687940a0442d318359699e00015e Mon Sep 17 00:00:00 2001
From: Peter Wu <lekensteyn@gmail.com>
Date: Sat, 13 Apr 2013 11:35:26 +0200
Subject: [PATCH 2/3] Add `-p path` option to restrict scripts

If the purpose of fcgiwrap is to wrap cgit, then I want to be sure that no other
program can be executed under the privileges of the fcgiwrap user.

When the option `-p path` is given, only the programs specified by `path` are
allowed to execute (multiple occurrences of `-p` are merged to form a list of
allowed programs).

Note that this value will be matched literally, no attempt is done to
canonicalize the path. This also implies that glob patterns or directories will
never match.
---
 fcgiwrap.c |   29 +++++++++++++++++++++++++++--
 1 file changed, 27 insertions(+), 2 deletions(-)

diff --git a/fcgiwrap.c b/fcgiwrap.c
index 81c5062..e86ff9d 100644
--- a/fcgiwrap.c
+++ b/fcgiwrap.c
@@ -58,6 +58,8 @@
 
 extern char **environ;
 static char * const * inherited_environ;
+static const char **allowed_programs;
+static size_t allowed_programs_count;
 
 static const char * blacklisted_env_vars[] = {
 	"AUTH_TYPE",
@@ -485,6 +487,19 @@ static void inherit_environment(void)
 	}
 }
 
+static bool is_allowed_program(const char *program) {
+	size_t i;
+	if (!allowed_programs_count)
+		return true;
+
+	for (i = 0; i < allowed_programs_count; i++) {
+		if (!strcmp(allowed_programs[i], program))
+			return true;
+	}
+
+	return false;
+}
+
 static void cgi_error(const char *message, const char *reason, const char *filename)
 {
 	printf("Status: %s\r\nContent-Type: text/plain\r\n\r\n%s\r\n",
@@ -541,6 +556,9 @@ static void handle_fcgi_request(void)
 			if (!filename)
 				cgi_error("403 Forbidden", "Cannot get script name, are DOCUMENT_ROOT and SCRIPT_NAME (or SCRIPT_FILENAME) set and is the script executable?", NULL);
 
+			if (!is_allowed_program(filename))
+				cgi_error("403 Forbidden", "The given script is not allowed to execute", filename);
+
 			last_slash = strrchr(filename, '/');
 			if (!last_slash)
 				cgi_error("403 Forbidden", "Script name must be a fully qualified path", filename);
@@ -760,7 +778,7 @@ int main(int argc, char **argv)
 	char *socket_url = NULL;
 	int c;
 
-	while ((c = getopt(argc, argv, "c:hfs:")) != -1) {
+	while ((c = getopt(argc, argv, "c:hfs:p:")) != -1) {
 		switch (c) {
 			case 'f':
 				stderr_to_fastcgi++;
@@ -773,6 +791,7 @@ int main(int argc, char **argv)
 					"  -c <number>\t\tNumber of processes to prefork\n"
 					"  -s <socket_url>\tSocket to bind to (say -s help for help)\n"
 					"  -h\t\t\tShow this help message and exit\n"
+					"  -p <path>\t\tRestrict execution to this script. (repeated options will be merged)\n"
 					"\nReport bugs to Grzegorz Nosek <"PACKAGE_BUGREPORT">.\n"
 					PACKAGE_NAME" home page: <http://nginx.localdomain.pl/wiki/FcgiWrap>\n",
 					argv[0]
@@ -784,8 +803,14 @@ int main(int argc, char **argv)
 			case 's':
 				socket_url = strdup(optarg);
 				break;
+			case 'p':
+				allowed_programs = realloc(allowed_programs, (allowed_programs_count + 1) * sizeof (char *));
+				if (!allowed_programs)
+					abort();
+				allowed_programs[allowed_programs_count++] = strdup(optarg);
+				break;
 			case '?':
-				if (optopt == 'c' || optopt == 's')
+				if (optopt == 'c' || optopt == 's' || optopt == 'p')
 					fprintf(stderr, "Option -%c requires an argument.\n", optopt);
 				else if (isprint(optopt))
 					fprintf(stderr, "Unknown option `-%c'.\n", optopt);
-- 
1.7.10.4

