summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDrew DeVault <sir@cmpwn.com>2020-10-25 23:16:50 -0400
committerDrew DeVault <sir@cmpwn.com>2020-10-25 23:16:50 -0400
commitcc1bd152e30e14827d2a002fef99384f418c22ab (patch)
tree9362291ad4d52508bf605dc51e698e5aa79e1f99
parent8baeb5a51c4dfa03956887ade2ef77295f17c95e (diff)
downloadgmnisrv-cc1bd152e30e14827d2a002fef99384f418c22ab.tar.gz
gmnisrv-cc1bd152e30e14827d2a002fef99384f418c22ab.tar.xz
gmnisrv-cc1bd152e30e14827d2a002fef99384f418c22ab.zip
Initial support for CGI scripts
-rw-r--r--doc/gmnisrvini.scd61
-rw-r--r--include/config.h1
-rw-r--r--src/config.c1
-rw-r--r--src/serve.c83
4 files changed, 146 insertions, 0 deletions
diff --git a/doc/gmnisrvini.scd b/doc/gmnisrvini.scd
index 73049a9..3fbfba1 100644
--- a/doc/gmnisrvini.scd
+++ b/doc/gmnisrvini.scd
@@ -92,3 +92,64 @@ Within each routing section, the following keys are used to configure how
"on" to enable the auto-index feature, which presents clients with a
list of files in the requested directory when an index file cannot be
found. Off by default.
+
+*cgi*
+ "on" to enable CGI support. *root* must also be configured. See "CGI
+ Support" for details.
+
+# CGI Support
+
+*gmnisrv* supports a limited version of CGI, compatible with the Jetforce
+server. It is not a faithful implementation of RFC 3875, but is sufficient for
+most of the needs of Gemini servers.
+
+Set *cgi=on* for a route configuration to enable CGI for that route and set
+*root* to the path where the CGI scripts are found. If a client requests a
+script, it will be executed, and must print a Gemini response (including status
+code and meta) to stdout.
+
+The following environment variables will be set:
+
+[[ *Variable*
+:[ *Example*
+:< *Description*
+| *GATEWAY_INTERFACE*
+: GCI/1.1
+: CGI version
+| *SERVER_PROTOCOL*
+: GEMINI
+: The server protocol
+| *SERVER_SOFTWARE*
+: gmnisrv/0.0.0
+: The gmnisrv server name and version
+| *GEMINI_URL*
+: See [1]
+: The URL requested by the client
+| *SCRIPT_NAME*
+: /cgi-bin/foo.sh
+: The portion of the URL referring to the script name.
+| *PATH_INFO*
+: /bar
+: The remainder of the path following *SCRIPT_NAME*.
+| *QUERY_STRING*
+: hello=world
+: The query string portion of the URL.
+| *SERVER_NAME*, *HOSTNAME*
+: example.org
+: The server host name.
+| *SERVER_PORT*
+: 1965
+: The server port number.
+| *REMOTE_HOST*, *REMOTE_ADDR*
+: 10.10.0.2
+: The clients IP address.
+| *TLS_CIPHER*
+: TLS_AES_256_GCM_SHA384
+: The negotiated TLS cipher.
+| *TLS_VERSION*
+: TLSv1.3
+: The negotiated TLS version.
+
+\[1]: gemini://example.org/cgi-bin/foo.sh/bar?hello=world
+
+The exit status of the script is ignored.
diff --git a/include/config.h b/include/config.h
index a489f2d..3a7d7b4 100644
--- a/include/config.h
+++ b/include/config.h
@@ -27,6 +27,7 @@ struct gmnisrv_route {
char *root;
char *index;
bool autoindex;
+ bool cgi;
struct gmnisrv_route *next;
};
diff --git a/src/config.c b/src/config.c
index 693766e..dc2a07e 100644
--- a/src/config.c
+++ b/src/config.c
@@ -210,6 +210,7 @@ conf_ini_handler(void *user, const char *section,
bool *value;
} route_bvars[] = {
{ "autoindex", &route->autoindex },
+ { "cgi", &route->cgi },
};
for (size_t i = 0; i < sizeof(route_strvars) / sizeof(route_strvars[0]); ++i) {
diff --git a/src/serve.c b/src/serve.c
index 18c2993..de38c9f 100644
--- a/src/serve.c
+++ b/src/serve.c
@@ -1,3 +1,4 @@
+#include <arpa/inet.h>
#include <assert.h>
#include <dirent.h>
#include <errno.h>
@@ -6,6 +7,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/types.h>
#include <unistd.h>
#include "config.h"
#include "gemini.h"
@@ -117,6 +119,82 @@ internal_error:
goto exit;
}
+static void
+serve_cgi(struct gmnisrv_client *client, const char *path)
+{
+ int pfd[2];
+ if (pipe(pfd) == -1) {
+ server_error("pipe: %s", strerror(errno));
+ client_submit_response(client, GEMINI_STATUS_PERMANENT_FAILURE,
+ "Internal server error", NULL);
+ return;
+ }
+
+ pid_t pid = fork();
+ if (pid == -1) {
+ server_error("fork: %s", strerror(errno));
+ client_submit_response(client, GEMINI_STATUS_PERMANENT_FAILURE,
+ "Internal server error", NULL);
+ close(pfd[0]);
+ close(pfd[1]);
+ return;
+ } else if (pid == 0) {
+ close(pfd[0]);
+ dup2(pfd[1], STDOUT_FILENO);
+ close(pfd[1]);
+
+ // I don't feel like freeing this stuff and this process is
+ // going to die soon anyway so let's just be hip and call it an
+ // arena allocator :^)
+ struct Curl_URL *url = curl_url();
+ assert(url);
+ CURLUcode uc = curl_url_set(url, CURLUPART_URL, client->buf, 0);
+ assert(uc == CURLUE_OK);
+
+ char *query;
+ uc = curl_url_get(url, CURLUPART_QUERY, &query, CURLU_URLDECODE);
+ if (uc != CURLUE_OK) {
+ assert(uc == CURLUE_NO_QUERY);
+ } else {
+ setenv("QUERY_STRING", query, 1);
+ }
+
+ char abuf[INET6_ADDRSTRLEN + 1];
+ const char *addrs = inet_ntop(client->addr.sa_family,
+ client->addr.sa_data, abuf, sizeof(abuf));
+ assert(addrs);
+
+ // Compatible with Jetforce
+ setenv("GATEWAY_INTERFACE", "GCI/1.1", 1);
+ setenv("SERVER_PROTOCOL", "GEMINI", 1);
+ setenv("SERVER_SOFTWARE", "gmnisrv/0.0.0", 1);
+ setenv("GEMINI_URL", client->buf, 1);
+ setenv("SCRIPT_NAME", path, 1);
+ //setenv("PATH_INFO", "", 1); // TODO
+ setenv("SERVER_NAME", client->host->hostname, 1);
+ setenv("HOSTNAME", client->host->hostname, 1);
+ //setenv("SERVER_PORT", "", 1); // TODO
+ setenv("REMOTE_HOST", addrs, 1);
+ setenv("REMOTE_ADDR", addrs, 1);
+
+ const SSL_CIPHER *cipher = SSL_get_current_cipher(client->ssl);
+ setenv("TLS_CIPHER", SSL_CIPHER_get_name(cipher), 1);
+ setenv("TLS_VERSION", SSL_CIPHER_get_version(cipher), 1);
+
+ // TODO: Client certificate details
+
+ execlp(path, path, NULL);
+ server_error("execlp: %s", strerror(errno));
+ _exit(1);
+ } else {
+ close(pfd[1]);
+ FILE *f = fdopen(pfd[0], "r");
+ client_submit_response(client, GEMINI_STATUS_SUCCESS, "(cgi)", f);
+ client->state = CLIENT_STATE_BODY; // The CGI script sends meta
+ client->bufix = client->bufln = 0;
+ }
+}
+
static bool
route_match(struct gmnisrv_route *route, const char *path, const char **revised)
{
@@ -219,6 +297,11 @@ serve_request(struct gmnisrv_client *client)
}
}
+ if (route->cgi) {
+ serve_cgi(client, path);
+ return;
+ }
+
FILE *body = fopen(path, "r");
if (!body) {
if (errno == ENOENT) {