forked from aniani/gmnisrv
Implement autoindex option
This commit is contained in:
parent
165e3c02fc
commit
6bc9c4deb9
@ -2,6 +2,7 @@
|
|||||||
#define GMNISRV_CONFIG
|
#define GMNISRV_CONFIG
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
struct gmnisrv_tls {
|
struct gmnisrv_tls {
|
||||||
char *store;
|
char *store;
|
||||||
@ -13,8 +14,12 @@ struct gmnisrv_tls {
|
|||||||
struct gmnisrv_host {
|
struct gmnisrv_host {
|
||||||
char *hostname;
|
char *hostname;
|
||||||
char *root;
|
char *root;
|
||||||
|
char *index;
|
||||||
|
bool autoindex;
|
||||||
|
|
||||||
X509 *x509;
|
X509 *x509;
|
||||||
EVP_PKEY *pkey;
|
EVP_PKEY *pkey;
|
||||||
|
|
||||||
struct gmnisrv_host *next;
|
struct gmnisrv_host *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ struct gmnisrv_client {
|
|||||||
enum response_state state;
|
enum response_state state;
|
||||||
enum gemini_status status;
|
enum gemini_status status;
|
||||||
char *meta;
|
char *meta;
|
||||||
int bodyfd;
|
FILE *body;
|
||||||
size_t bbytes;
|
size_t bbytes;
|
||||||
|
|
||||||
struct gmnisrv_host *host;
|
struct gmnisrv_host *host;
|
||||||
@ -70,7 +70,7 @@ void disconnect_client(struct gmnisrv_server *server,
|
|||||||
void serve_request(struct gmnisrv_client *client);
|
void serve_request(struct gmnisrv_client *client);
|
||||||
bool request_validate(struct gmnisrv_client *client, char **path);
|
bool request_validate(struct gmnisrv_client *client, char **path);
|
||||||
void client_submit_response(struct gmnisrv_client *client,
|
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);
|
void client_oom(struct gmnisrv_client *client);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
20
src/config.c
20
src/config.c
@ -1,8 +1,10 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "ini.h"
|
#include "ini.h"
|
||||||
|
|
||||||
@ -146,6 +148,13 @@ conf_ini_handler(void *user, const char *section,
|
|||||||
char **value;
|
char **value;
|
||||||
} host_strvars[] = {
|
} host_strvars[] = {
|
||||||
{ "root", &host->root },
|
{ "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) {
|
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;
|
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);
|
fprintf(stderr, "Unknown config option [%s]%s\n", section, name);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
163
src/serve.c
163
src/serve.c
@ -1,9 +1,12 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <dirent.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "gemini.h"
|
#include "gemini.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
@ -13,11 +16,11 @@
|
|||||||
|
|
||||||
void
|
void
|
||||||
client_submit_response(struct gmnisrv_client *client,
|
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->status = status;
|
||||||
client->meta = strdup(meta);
|
client->meta = strdup(meta);
|
||||||
client->bodyfd = bodyfd;
|
client->body = body;
|
||||||
client->pollfd->events = POLLOUT;
|
client->pollfd->events = POLLOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +29,92 @@ client_oom(struct gmnisrv_client *client)
|
|||||||
{
|
{
|
||||||
const char *error = "Out of memory";
|
const char *error = "Out of memory";
|
||||||
client_submit_response(client,
|
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
|
void
|
||||||
@ -40,32 +128,69 @@ serve_request(struct gmnisrv_client *client)
|
|||||||
int n = snprintf(path, sizeof(path), "%s%s", host->root, client->path);
|
int n = snprintf(path, sizeof(path), "%s%s", host->root, client->path);
|
||||||
if ((size_t)n >= sizeof(path)) {
|
if ((size_t)n >= sizeof(path)) {
|
||||||
client_submit_response(client, GEMINI_STATUS_PERMANENT_FAILURE,
|
client_submit_response(client, GEMINI_STATUS_PERMANENT_FAILURE,
|
||||||
"Request path exceeds PATH_MAX", -1);
|
"Request path exceeds PATH_MAX", NULL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path[strlen(path) - 1] == '/') {
|
int nlinks = 0;
|
||||||
// TODO: Let user configure index file name?
|
struct stat st;
|
||||||
strncat(path, "index.gmi", sizeof(path) - 1);
|
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);
|
FILE *body = fopen(path, "r");
|
||||||
if (fd == -1) {
|
if (!body) {
|
||||||
if (errno == ENOENT) {
|
if (errno == ENOENT) {
|
||||||
client_submit_response(client, GEMINI_STATUS_NOT_FOUND,
|
client_submit_response(client,
|
||||||
"Not found", -1);
|
GEMINI_STATUS_NOT_FOUND, "Not found", NULL);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
client_error(&client->addr, "error opening %s: %s",
|
client_error(&client->addr, "error opening %s: %s",
|
||||||
path, strerror(errno));
|
path, strerror(errno));
|
||||||
client_submit_response(client, GEMINI_STATUS_PERMANENT_FAILURE,
|
client_submit_response(client, GEMINI_STATUS_PERMANENT_FAILURE,
|
||||||
"Internal server error", -1);
|
"Internal server error", NULL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *meta = gmnisrv_mimetype_for_path(path);
|
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
|
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) {
|
if (curl_url_set(url, CURLUPART_URL, client->buf, 0) != CURLUE_OK) {
|
||||||
const char *error = "Protocol error: invalid URL";
|
const char *error = "Protocol error: invalid URL";
|
||||||
client_submit_response(client,
|
client_submit_response(client,
|
||||||
GEMINI_STATUS_BAD_REQUEST, error, -1);
|
GEMINI_STATUS_BAD_REQUEST, error, NULL);
|
||||||
goto exit;
|
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) {
|
if (curl_url_get(url, CURLUPART_SCHEME, &part, 0) != CURLUE_OK) {
|
||||||
const char *error = "Protocol error: invalid URL (expected scheme)";
|
const char *error = "Protocol error: invalid URL (expected scheme)";
|
||||||
client_submit_response(client,
|
client_submit_response(client,
|
||||||
GEMINI_STATUS_BAD_REQUEST, error, -1);
|
GEMINI_STATUS_BAD_REQUEST, error, NULL);
|
||||||
goto exit;
|
goto exit;
|
||||||
} else if (strcmp(part, "gemini") != 0) {
|
} else if (strcmp(part, "gemini") != 0) {
|
||||||
free(part);
|
free(part);
|
||||||
const char *error = "Refusing proxy to non-gemini URL";
|
const char *error = "Refusing proxy to non-gemini URL";
|
||||||
client_submit_response(client,
|
client_submit_response(client,
|
||||||
GEMINI_STATUS_PROXY_REQUEST_REFUSED, error, -1);
|
GEMINI_STATUS_PROXY_REQUEST_REFUSED, error, NULL);
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
free(part);
|
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) {
|
if (curl_url_get(url, CURLUPART_HOST, &part, 0) != CURLUE_OK) {
|
||||||
const char *error = "Protocol error: invalid URL (expected host)";
|
const char *error = "Protocol error: invalid URL (expected host)";
|
||||||
client_submit_response(client,
|
client_submit_response(client,
|
||||||
GEMINI_STATUS_BAD_REQUEST, error, -1);
|
GEMINI_STATUS_BAD_REQUEST, error, NULL);
|
||||||
goto exit;
|
goto exit;
|
||||||
} else if (strcmp(part, client->host->hostname) != 0) {
|
} else if (strcmp(part, client->host->hostname) != 0) {
|
||||||
free(part);
|
free(part);
|
||||||
const char *error = "Protocol error: hostname does not match SNI";
|
const char *error = "Protocol error: hostname does not match SNI";
|
||||||
client_submit_response(client,
|
client_submit_response(client,
|
||||||
GEMINI_STATUS_BAD_REQUEST, error, -1);
|
GEMINI_STATUS_BAD_REQUEST, error, NULL);
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
free(part);
|
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) {
|
if (curl_url_get(url, CURLUPART_PATH, &part, 0) != CURLUE_OK) {
|
||||||
const char *error = "Protocol error: invalid URL (expected path)";
|
const char *error = "Protocol error: invalid URL (expected path)";
|
||||||
client_submit_response(client,
|
client_submit_response(client,
|
||||||
GEMINI_STATUS_BAD_REQUEST, error, -1);
|
GEMINI_STATUS_BAD_REQUEST, error, NULL);
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
// NOTE: curl_url_set(..., CURLUPART_URL, ..., 0) will consoldate .. and
|
// NOTE: curl_url_set(..., CURLUPART_URL, ..., 0) will consoldate .. and
|
||||||
|
11
src/server.c
11
src/server.c
@ -150,7 +150,6 @@ accept_client(struct gmnisrv_server *server, int fd)
|
|||||||
client->pollfd = pollfd;
|
client->pollfd = pollfd;
|
||||||
client->addrlen = addrlen;
|
client->addrlen = addrlen;
|
||||||
client->server = server;
|
client->server = server;
|
||||||
client->bodyfd = -1;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &client->ctime);
|
clock_gettime(CLOCK_MONOTONIC, &client->ctime);
|
||||||
memcpy(&client->addr, &addr, sizeof(addr));
|
memcpy(&client->addr, &addr, sizeof(addr));
|
||||||
|
|
||||||
@ -240,7 +239,7 @@ client_readable(struct gmnisrv_server *server, struct gmnisrv_client *client)
|
|||||||
if (!client->host) {
|
if (!client->host) {
|
||||||
const char *error = "This server requires clients to support the TLS SNI (server name identification) extension";
|
const char *error = "This server requires clients to support the TLS SNI (server name identification) extension";
|
||||||
client_submit_response(client,
|
client_submit_response(client,
|
||||||
GEMINI_STATUS_BAD_REQUEST, error, -1);
|
GEMINI_STATUS_BAD_REQUEST, error, NULL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,7 +260,7 @@ client_readable(struct gmnisrv_server *server, struct gmnisrv_client *client)
|
|||||||
if (!newline) {
|
if (!newline) {
|
||||||
const char *error = "Protocol error: malformed request";
|
const char *error = "Protocol error: malformed request";
|
||||||
client_submit_response(client,
|
client_submit_response(client,
|
||||||
GEMINI_STATUS_BAD_REQUEST, error, -1);
|
GEMINI_STATUS_BAD_REQUEST, error, NULL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
*newline = 0;
|
*newline = 0;
|
||||||
@ -304,7 +303,7 @@ client_writable(struct gmnisrv_server *server, struct gmnisrv_client *client)
|
|||||||
}
|
}
|
||||||
client->bufix += r;
|
client->bufix += r;
|
||||||
if (client->bufix >= client->bufln) {
|
if (client->bufix >= client->bufln) {
|
||||||
if (client->bodyfd == -1) {
|
if (!client->body) {
|
||||||
disconnect_client(server, client);
|
disconnect_client(server, client);
|
||||||
} else {
|
} else {
|
||||||
client->state = RESPOND_BODY;
|
client->state = RESPOND_BODY;
|
||||||
@ -315,8 +314,8 @@ client_writable(struct gmnisrv_server *server, struct gmnisrv_client *client)
|
|||||||
break;
|
break;
|
||||||
case RESPOND_BODY:
|
case RESPOND_BODY:
|
||||||
if (client->bufix >= client->bufln) {
|
if (client->bufix >= client->bufln) {
|
||||||
n = read(client->bodyfd,
|
n = fread(client->buf, 1,
|
||||||
client->buf, sizeof(client->buf));
|
sizeof(client->buf), client->body);
|
||||||
if (n == -1) {
|
if (n == -1) {
|
||||||
client_error(&client->addr,
|
client_error(&client->addr,
|
||||||
"Error reading response body: %s",
|
"Error reading response body: %s",
|
||||||
|
Loading…
Reference in New Issue
Block a user