commit - 2c3a40faf850ad608abf87f928a11fb4fba3057b
commit + 72342dc9606a0283490a3a9283d2ed2d31888eeb
blob - fc2381fab34076f690fa3eacbef46081fdf20f66
blob + 2a0fea9ca8dcae76ff8702673f1a09e851c67bc1
--- README.md
+++ README.md
# SYNOPSIS
**gmid**
-\[**-h**]
+\[**-hx**]
\[**-c** *cert.pem*]
\[**-d** *docs*]
\[**-k** *key.pem*]
> 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
.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
.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
#define PATHBUF 2048
#define SUCCESS 20
+#define TEMP_FAILURE 40
#define NOT_FOUND 51
#define BAD_REQUEST 59
int code;
const char *meta;
int fd;
+ pid_t child;
void *buf, *i;
ssize_t len, off;
int af;
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*);
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*);
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)
{
open_file(char *path, struct pollfd *fds, struct client *c)
{
char fpath[PATHBUF];
+ struct stat sb;
assert(path != NULL);
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;
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);
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
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;
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;
err(1, "%s", optarg);
break;
+ case 'x':
+ cgi = 1;
+ break;
+
default:
usage(*argv);
return 1;
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);