1
0
mirror of https://git.sr.ht/~sircmpwn/gmnisrv synced 2024-11-03 06:07:17 -05:00

Implement autoindex option

This commit is contained in:
Drew DeVault 2020-09-26 15:51:28 -04:00
parent 165e3c02fc
commit 6bc9c4deb9
5 changed files with 176 additions and 27 deletions

View File

@ -2,6 +2,7 @@
#define GMNISRV_CONFIG
#include <arpa/inet.h>
#include <openssl/x509.h>
#include <stdbool.h>
struct gmnisrv_tls {
char *store;
@ -13,8 +14,12 @@ struct gmnisrv_tls {
struct gmnisrv_host {
char *hostname;
char *root;
char *index;
bool autoindex;
X509 *x509;
EVP_PKEY *pkey;
struct gmnisrv_host *next;
};

View File

@ -33,7 +33,7 @@ struct gmnisrv_client {
enum response_state state;
enum gemini_status status;
char *meta;
int bodyfd;
FILE *body;
size_t bbytes;
struct gmnisrv_host *host;
@ -70,7 +70,7 @@ void disconnect_client(struct gmnisrv_server *server,
void serve_request(struct gmnisrv_client *client);
bool request_validate(struct gmnisrv_client *client, char **path);
void client_submit_response(struct gmnisrv_client *client,
enum gemini_status status, const char *meta, int bodyfd);
enum gemini_status status, const char *meta, FILE *body);
void client_oom(struct gmnisrv_client *client);
#endif

View File

@ -1,8 +1,10 @@
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "config.h"
#include "ini.h"
@ -146,6 +148,13 @@ conf_ini_handler(void *user, const char *section,
char **value;
} host_strvars[] = {
{ "root", &host->root },
{ "index", &host->index },
};
struct {
char *name;
bool *value;
} host_bvars[] = {
{ "autoindex", &host->autoindex },
};
for (size_t i = 0; i < sizeof(host_strvars) / sizeof(host_strvars[0]); ++i) {
@ -156,6 +165,17 @@ conf_ini_handler(void *user, const char *section,
return 1;
}
for (size_t i = 0; i < sizeof(host_bvars) / sizeof(host_bvars[0]); ++i) {
if (strcmp(host_bvars[i].name, name) != 0) {
continue;
}
*host_bvars[i].value =
strcasecmp(value, "yes") == 0 ||
strcasecmp(value, "true") == 0 ||
strcasecmp(value, "on") == 0;
return 1;
}
fprintf(stderr, "Unknown config option [%s]%s\n", section, name);
return 0;
}

View File

@ -1,9 +1,12 @@
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "config.h"
#include "gemini.h"
#include "log.h"
@ -13,11 +16,11 @@
void
client_submit_response(struct gmnisrv_client *client,
enum gemini_status status, const char *meta, int bodyfd)
enum gemini_status status, const char *meta, FILE *body)
{
client->status = status;
client->meta = strdup(meta);
client->bodyfd = bodyfd;
client->body = body;
client->pollfd->events = POLLOUT;
}
@ -26,7 +29,92 @@ client_oom(struct gmnisrv_client *client)
{
const char *error = "Out of memory";
client_submit_response(client,
GEMINI_STATUS_TEMPORARY_FAILURE, error, -1);
GEMINI_STATUS_TEMPORARY_FAILURE, error, NULL);
}
static int
namecmp(const void *p1, const void *p2)
{
return strcmp(*(char *const *)p1, *(char *const *)p2);
}
void
serve_autoindex(struct gmnisrv_client *client, const char *path)
{
size_t bufsz = 0;
size_t nameln = 0, namesz = 1024;
char **names = calloc(namesz, sizeof(char *));
DIR *dirp = opendir(path);
if (!dirp) {
goto internal_error;
}
bufsz += snprintf(NULL, 0, "# Index of %s\n\n", client->path);
struct dirent *ent;
errno = 0;
while ((ent = readdir(dirp)) != NULL) {
char fpath[PATH_MAX + 1];
strcpy(fpath, path);
strncat(fpath, ent->d_name, sizeof(fpath));
struct stat st;
if (stat(fpath, &st) != 0) {
goto internal_error;
}
if ((S_ISREG(st.st_mode) || S_ISLNK(st.st_mode) || S_ISDIR(st.st_mode))
&& ent->d_name[0] != '.') {
if (nameln >= namesz) {
char **new = realloc(names, namesz * 2);
if (!new) {
goto internal_error;
}
namesz *= 2;
names = new;
}
names[nameln++] = strdup(ent->d_name);
bufsz += snprintf(NULL, 0, "=> %s\n", ent->d_name);
}
errno = 0;
}
if (errno != 0) {
goto internal_error;
}
qsort(names, nameln, sizeof(names[0]), namecmp);
FILE *buf = fmemopen(NULL, bufsz, "w+");
if (!buf) {
goto internal_error;
}
int r;
r = fprintf(buf, "# Index of %s\n\n", client->path);
assert(r > 0);
for (size_t i = 0; i < nameln; ++i) {
r = fprintf(buf, "=> %s\n", names[i]);
assert(r > 0);
}
r = fseek(buf, 0, SEEK_SET);
assert(r == 0);
client_submit_response(client, GEMINI_STATUS_SUCCESS,
"text/gemini", buf);
exit:
closedir(dirp);
for (size_t i = 0; i < nameln; ++i) {
free(names[i]);
}
free(names);
return;
internal_error:
server_error("Error reading %s: %s", path, strerror(errno));
client_submit_response(client, GEMINI_STATUS_PERMANENT_FAILURE,
"Internal server error", NULL);
goto exit;
}
void
@ -40,32 +128,69 @@ serve_request(struct gmnisrv_client *client)
int n = snprintf(path, sizeof(path), "%s%s", host->root, client->path);
if ((size_t)n >= sizeof(path)) {
client_submit_response(client, GEMINI_STATUS_PERMANENT_FAILURE,
"Request path exceeds PATH_MAX", -1);
"Request path exceeds PATH_MAX", NULL);
return;
}
if (path[strlen(path) - 1] == '/') {
// TODO: Let user configure index file name?
strncat(path, "index.gmi", sizeof(path) - 1);
int nlinks = 0;
struct stat st;
while (true) {
if ((n = stat(path, &st)) != 0) {
client_submit_response(client,
GEMINI_STATUS_NOT_FOUND, "Not found", NULL);
return;
}
if (S_ISDIR(st.st_mode)) {
if (host->autoindex) {
serve_autoindex(client, path);
return;
} else {
strncat(path,
host->index ? host->index : "index.gmi",
sizeof(path) - 1);
}
} else if (S_ISLNK(st.st_mode)) {
++nlinks;
if (nlinks > 3) {
server_error("Maximum redirects exceeded for %s",
client->path);
client_submit_response(client,
GEMINI_STATUS_NOT_FOUND,
"Not found", NULL);
return;
}
char path2[PATH_MAX + 1];
ssize_t s = readlink(path, path2, sizeof(path2));
assert(s != -1);
strcpy(path, path2);
} else if (S_ISREG(st.st_mode)) {
break;
} else {
// Don't serve special files
client_submit_response(client,
GEMINI_STATUS_NOT_FOUND, "Not found", NULL);
return;
}
}
int fd = open(path, O_RDONLY);
if (fd == -1) {
FILE *body = fopen(path, "r");
if (!body) {
if (errno == ENOENT) {
client_submit_response(client, GEMINI_STATUS_NOT_FOUND,
"Not found", -1);
client_submit_response(client,
GEMINI_STATUS_NOT_FOUND, "Not found", NULL);
return;
} else {
client_error(&client->addr, "error opening %s: %s",
path, strerror(errno));
client_submit_response(client, GEMINI_STATUS_PERMANENT_FAILURE,
"Internal server error", -1);
"Internal server error", NULL);
return;
}
}
const char *meta = gmnisrv_mimetype_for_path(path);
client_submit_response(client, GEMINI_STATUS_SUCCESS, meta, fd);
client_submit_response(client, GEMINI_STATUS_SUCCESS, meta, body);
}
bool
@ -79,7 +204,7 @@ request_validate(struct gmnisrv_client *client, char **path)
if (curl_url_set(url, CURLUPART_URL, client->buf, 0) != CURLUE_OK) {
const char *error = "Protocol error: invalid URL";
client_submit_response(client,
GEMINI_STATUS_BAD_REQUEST, error, -1);
GEMINI_STATUS_BAD_REQUEST, error, NULL);
goto exit;
}
@ -87,13 +212,13 @@ request_validate(struct gmnisrv_client *client, char **path)
if (curl_url_get(url, CURLUPART_SCHEME, &part, 0) != CURLUE_OK) {
const char *error = "Protocol error: invalid URL (expected scheme)";
client_submit_response(client,
GEMINI_STATUS_BAD_REQUEST, error, -1);
GEMINI_STATUS_BAD_REQUEST, error, NULL);
goto exit;
} else if (strcmp(part, "gemini") != 0) {
free(part);
const char *error = "Refusing proxy to non-gemini URL";
client_submit_response(client,
GEMINI_STATUS_PROXY_REQUEST_REFUSED, error, -1);
GEMINI_STATUS_PROXY_REQUEST_REFUSED, error, NULL);
goto exit;
}
free(part);
@ -101,13 +226,13 @@ request_validate(struct gmnisrv_client *client, char **path)
if (curl_url_get(url, CURLUPART_HOST, &part, 0) != CURLUE_OK) {
const char *error = "Protocol error: invalid URL (expected host)";
client_submit_response(client,
GEMINI_STATUS_BAD_REQUEST, error, -1);
GEMINI_STATUS_BAD_REQUEST, error, NULL);
goto exit;
} else if (strcmp(part, client->host->hostname) != 0) {
free(part);
const char *error = "Protocol error: hostname does not match SNI";
client_submit_response(client,
GEMINI_STATUS_BAD_REQUEST, error, -1);
GEMINI_STATUS_BAD_REQUEST, error, NULL);
goto exit;
}
free(part);
@ -115,7 +240,7 @@ request_validate(struct gmnisrv_client *client, char **path)
if (curl_url_get(url, CURLUPART_PATH, &part, 0) != CURLUE_OK) {
const char *error = "Protocol error: invalid URL (expected path)";
client_submit_response(client,
GEMINI_STATUS_BAD_REQUEST, error, -1);
GEMINI_STATUS_BAD_REQUEST, error, NULL);
goto exit;
}
// NOTE: curl_url_set(..., CURLUPART_URL, ..., 0) will consoldate .. and

View File

@ -150,7 +150,6 @@ accept_client(struct gmnisrv_server *server, int fd)
client->pollfd = pollfd;
client->addrlen = addrlen;
client->server = server;
client->bodyfd = -1;
clock_gettime(CLOCK_MONOTONIC, &client->ctime);
memcpy(&client->addr, &addr, sizeof(addr));
@ -240,7 +239,7 @@ client_readable(struct gmnisrv_server *server, struct gmnisrv_client *client)
if (!client->host) {
const char *error = "This server requires clients to support the TLS SNI (server name identification) extension";
client_submit_response(client,
GEMINI_STATUS_BAD_REQUEST, error, -1);
GEMINI_STATUS_BAD_REQUEST, error, NULL);
return;
}
@ -261,7 +260,7 @@ client_readable(struct gmnisrv_server *server, struct gmnisrv_client *client)
if (!newline) {
const char *error = "Protocol error: malformed request";
client_submit_response(client,
GEMINI_STATUS_BAD_REQUEST, error, -1);
GEMINI_STATUS_BAD_REQUEST, error, NULL);
return;
}
*newline = 0;
@ -304,7 +303,7 @@ client_writable(struct gmnisrv_server *server, struct gmnisrv_client *client)
}
client->bufix += r;
if (client->bufix >= client->bufln) {
if (client->bodyfd == -1) {
if (!client->body) {
disconnect_client(server, client);
} else {
client->state = RESPOND_BODY;
@ -315,8 +314,8 @@ client_writable(struct gmnisrv_server *server, struct gmnisrv_client *client)
break;
case RESPOND_BODY:
if (client->bufix >= client->bufln) {
n = read(client->bodyfd,
client->buf, sizeof(client->buf));
n = fread(client->buf, 1,
sizeof(client->buf), client->body);
if (n == -1) {
client_error(&client->addr,
"Error reading response body: %s",