Commit Diff


commit - 2c3a40faf850ad608abf87f928a11fb4fba3057b
commit + 72342dc9606a0283490a3a9283d2ed2d31888eeb
blob - fc2381fab34076f690fa3eacbef46081fdf20f66
blob + 2a0fea9ca8dcae76ff8702673f1a09e851c67bc1
--- README.md
+++ README.md
@@ -6,7 +6,7 @@
 # SYNOPSIS
 
 **gmid**
-\[**-h**]
+\[**-hx**]
 \[**-c** *cert.pem*]
 \[**-d** *docs*]
 \[**-k** *key.pem*]
@@ -78,6 +78,19 @@ The options are as follows:
 
 > log to the given file instead of the standard error.
 
+**-x**
+
+> Enable CGI scripts.
+
+# CGI
+
+If CGI scripts are enabled, when a file requested by a client is
+marked as executable it is executed and its output fed to the client.
+
+Note that since this give the chance to anybody to execute possibly
+**any file**
+in the served directory, this option is disabled by default.
+
 # EXAMPLES
 
 To quickly getting started
blob - a70e81714c7d397bfef19b484e31f03436ebbbe4
blob + 8f7a44eff28ba12b41299b7c46b11238458fb932
--- gmid.1
+++ gmid.1
@@ -20,7 +20,7 @@
 .Sh SYNOPSIS
 .Nm
 .Bk -words
-.Op Fl h
+.Op Fl hx
 .Op Fl c Ar cert.pem
 .Op Fl d Ar docs
 .Op Fl k Ar key.pem
@@ -81,7 +81,16 @@ The key for the certificate, by default is
 .Pa key.pem .
 .It Fl l Ar access.log
 log to the given file instead of the standard error.
+.It Fl x
+Enable CGI scripts.
 .El
+.Sh CGI
+If CGI scripts are enabled, when a file requested by a client is
+marked as executable it is executed and its output fed to the client.
+.Pp
+Note that since this give the chance to anybody to execute possibly
+.Sy any file
+in the served directory, this option is disabled by default.
 .Sh EXAMPLES
 To quickly getting started
 .Bd -literal -offset indent
blob - 4e03fc371fbc02c3b7c72efd0482d799cd527656
blob + 0a0ff2452bc0b320e96cb59ac92e3e0487ff5e53
--- gmid.c
+++ gmid.c
@@ -48,6 +48,7 @@
 #define PATHBUF		2048
 
 #define SUCCESS		20
+#define TEMP_FAILURE	40
 #define NOT_FOUND	51
 #define BAD_REQUEST	59
 
@@ -68,6 +69,7 @@ struct client {
 	int		 code;
 	const char	*meta;
 	int		 fd;
+	pid_t		 child;
 	void		*buf, *i;
 	ssize_t		 len, off;
 	int		 af;
@@ -104,7 +106,9 @@ struct etm {			/* file extension to mime */
 		dprintf(logfd, "[%s] " fmt "\n", buf, __VA_ARGS__);	\
 	} while (0)
 
+const char *dir;
 int dirfd, logfd;
+int cgi;
 
 char		*url_after_proto(char*);
 char		*url_start_of_request(char*);
@@ -114,10 +118,11 @@ int		 path_isdir(char*);
 ssize_t		 filesize(int);
 
 int		 start_reply(struct pollfd*, struct client*, int, const char*);
-int		 isdir(int);
 const char	*path_ext(const char*);
 const char	*mime(const char*);
 int		 open_file(char*, struct pollfd*, struct client*);
+void		 start_cgi(const char*, struct pollfd*, struct client*);
+void		 handle_cgi(struct pollfd*, struct client*);
 void		 send_file(char*, struct pollfd*, struct client*);
 void		 send_dir(char*, struct pollfd*, struct client*);
 void		 handle(struct pollfd*, struct client*);
@@ -247,19 +252,6 @@ start_reply(struct pollfd *pfd, struct client *client,
 	return 1;
 }
 
-int
-isdir(int fd)
-{
-	struct stat sb;
-
-	if (fstat(fd, &sb) == -1) {
-		warn("fstat");
-		return 1; 	/* we'll probably fail later on anyway */
-	}
-
-	return S_ISDIR(sb.st_mode);
-}
-
 ssize_t
 filesize(int fd)
 {
@@ -308,6 +300,7 @@ int
 open_file(char *path, struct pollfd *fds, struct client *c)
 {
 	char fpath[PATHBUF];
+	struct stat sb;
 
 	assert(path != NULL);
 
@@ -325,7 +318,15 @@ open_file(char *path, struct pollfd *fds, struct clien
 		return 0;
 	}
 
-	if (isdir(c->fd)) {
+	if (fstat(c->fd, &sb) == -1) {
+		LOG(c, "fstat failed for %s", fpath);
+		if (!start_reply(fds, c, TEMP_FAILURE, "internal server error"))
+			return 0;
+		goodbye(fds, c);
+		return 0;
+	}
+
+	if (S_ISDIR(sb.st_mode)) {
 		LOG(c, "%s is a directory, trying %s/index.gmi", fpath, fpath);
 		close(c->fd);
 		c->fd = -1;
@@ -333,6 +334,11 @@ open_file(char *path, struct pollfd *fds, struct clien
 		return 0;
 	}
 
+	if (cgi && (sb.st_mode & S_IXUSR)) {
+		start_cgi(fpath, fds, c);
+		return 0;
+	}
+
 	if ((c->len = filesize(c->fd)) == -1) {
 		LOG(c, "failed to get file size for %s", fpath);
 		goodbye(fds, c);
@@ -348,6 +354,120 @@ open_file(char *path, struct pollfd *fds, struct clien
 	c->i = c->buf;
 
 	return start_reply(fds, c, SUCCESS, mime(fpath));
+}
+
+void
+start_cgi(const char *path, struct pollfd *fds, struct client *c)
+{
+	pid_t pid;
+	int p[2];
+
+	if (pipe(p) == -1)
+		goto err;
+
+	switch (pid = fork()) {
+	case -1:
+		goto err;
+
+	case 0: {		/* child */
+                char *expath;
+		char addr[INET_ADDRSTRLEN];
+		char *argv[] = { NULL, NULL, NULL };
+		char *envp[] = {
+			/* inherited */
+			"PATH=",
+
+			/* CGI */
+			"SERVER_SOFTWARE=gmid",
+			/* "SERVER_NAME=example.com", */
+			/* "GATEWAY_INTERFACE=CGI/version" */
+			"SERVER_PROTOCOL=gemini",
+			"SERVER_PORT=1965",
+			/* "PATH_INFO=" */
+			/* "PATH_TRANSLATED=" */
+			/* "SCRIPT_NAME=" */
+			/* "QUERY_STRING=" */
+                        "REMOTE_ADDR=",
+			NULL,
+		};
+		size_t i;
+
+		close(p[0]);	/* close the read end */
+		if (dup2(p[1], 1) == -1)
+			goto childerr;
+
+		if (inet_ntop(c->af, &c->addr, addr, sizeof(addr)) == NULL)
+                        goto childerr;
+
+		/* skip the ./ at the start of path*/
+		if (asprintf(&expath, "%s%s", dir, path+2) == -1)
+			goto childerr;
+		argv[0] = argv[1] = expath;
+
+		/* fix the envp */
+		for (i = 0; envp[i] != NULL; ++i) {
+			if (!strcmp(envp[i], "PATH="))
+				envp[i] = getenv("PATH");
+			else if (!strcmp(envp[i], "REMOTE_ADDR="))
+				envp[i] = addr;
+			/* else if (!strcmp(envp[i], "PATH_INFO")) */
+			/* ... */
+		}
+
+		execvpe(expath, argv, envp);
+		goto childerr;
+	}
+
+	default:		/* parent */
+		close(p[1]);	/* close the write end */
+		close(c->fd);
+		c->fd = p[0];
+		c->child = pid;
+		handle_cgi(fds, c);
+		return;
+	}
+
+err:
+	if (!start_reply(fds, c, TEMP_FAILURE, "internal server error"))
+		return;
+	goodbye(fds, c);
+	return;
+
+childerr:
+	dprintf(p[1], "%d internal server error\r\n", TEMP_FAILURE);
+	close(p[1]);
+        _exit(1);
+}
+
+void
+handle_cgi(struct pollfd *fds, struct client *c)
+{
+	char buf[1024];
+	ssize_t r, todo;
+
+	while (1) {
+		r = read(c->fd, buf, sizeof(buf));
+		if (r == -1 || r == 0)
+			break;
+		todo = r;
+		while (todo > 0) {
+			switch (r = tls_write(c->ctx, buf, todo)) {
+			case -1:
+				goto end;
+
+			case TLS_WANT_POLLOUT:
+			case TLS_WANT_POLLIN:
+				/* evil! */
+				continue;
+
+			default:
+				todo -= r;
+			}
+		}
+	}
+
+end:
+	goodbye(fds, c);
 }
 
 void
@@ -581,6 +701,7 @@ do_accept(int sock, struct tls *ctx, struct pollfd *fd
 
 			clients[i].state = S_OPEN;
 			clients[i].fd = -1;
+			clients[i].child = -1;
 			clients[i].buf = MAP_FAILED;
 			clients[i].af = AF_INET;
 			clients[i].addr = addr.sin_addr;
@@ -678,16 +799,19 @@ usage(const char *me)
 int
 main(int argc, char **argv)
 {
-	const char *cert = "cert.pem", *key = "key.pem", *dir = "docs";
+	const char *cert = "cert.pem", *key = "key.pem";
 	struct tls *ctx = NULL;
 	struct tls_config *conf;
 	int sock, ch;
 
 	signal(SIGPIPE, SIG_IGN);
+	signal(SIGCHLD, SIG_IGN);
 
+	dir = "docs/";
 	logfd = 2;		/* stderr */
+	cgi = 0;
 
-	while ((ch = getopt(argc, argv, "c:d:hk:l:")) != -1) {
+	while ((ch = getopt(argc, argv, "c:d:hk:l:x")) != -1) {
 		switch (ch) {
 		case 'c':
 			cert = optarg;
@@ -712,6 +836,10 @@ main(int argc, char **argv)
 				err(1, "%s", optarg);
 			break;
 
+		case 'x':
+			cgi = 1;
+			break;
+
 		default:
 			usage(*argv);
 			return 1;
@@ -742,10 +870,10 @@ main(int argc, char **argv)
 	if ((dirfd = open(dir, O_RDONLY | O_DIRECTORY)) == -1)
 		err(1, "open: %s", dir);
 
-	if (unveil(dir, "r") == -1)
+	if (unveil(dir, cgi ? "rx" : "r") == -1)
 		err(1, "unveil");
 
-	if (pledge("stdio rpath inet", "") == -1)
+	if (pledge(cgi ? "stdio rpath inet proc exec" : "stdio rpath inet", NULL) == -1)
 		err(1, "pledge");
 
 	loop(ctx, sock);