1
0
forked from aniani/gmnisrv
gmnisrv/src/server.c

579 lines
14 KiB
C
Raw Normal View History

2020-09-23 12:40:28 -04:00
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <openssl/err.h>
2020-09-23 14:19:28 -04:00
#include <signal.h>
#include <stdbool.h>
2020-09-23 12:40:28 -04:00
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
2020-09-23 14:19:28 -04:00
#include <unistd.h>
2020-09-23 12:40:28 -04:00
#include "config.h"
#include "gemini.h"
2020-09-23 14:32:52 -04:00
#include "log.h"
2020-09-26 14:36:52 -04:00
#include "mime.h"
#include "server.h"
#include "tls.h"
#include "url.h"
2020-09-23 12:40:28 -04:00
int
server_init(struct gmnisrv_server *server, struct gmnisrv_config *conf)
{
server->conf = conf;
for (struct gmnisrv_bind *b = conf->binds; b; b = b->next) {
++server->nlisten;
}
assert(server->nlisten < 1024);
server->nfds = server->nlisten;
2020-09-23 14:19:28 -04:00
server->fds = calloc(1024, sizeof(struct pollfd));
server->fdsz = 1024;
2020-09-23 12:40:28 -04:00
assert(server->fds);
2020-09-23 14:19:28 -04:00
server->clientsz = 1024;
2020-09-23 12:40:28 -04:00
server->clients = calloc(server->clientsz, sizeof(struct gmnisrv_client));
size_t i = 0;
for (struct gmnisrv_bind *b = conf->binds; b; b = b->next) {
int sockfd = socket(b->family, SOCK_STREAM, 0);
if (sockfd < 0) {
fprintf(stderr,
"Failed to establish socket %s: %s\n",
b->name, strerror(errno));
return 1;
}
struct sockaddr *addr;
size_t addrsz;
if (b->family == AF_INET) {
struct sockaddr_in in = {0};
in.sin_family = AF_INET;
memcpy(&in.sin_addr, b->addr, sizeof(b->addr));
in.sin_port = htons(b->port);
addr = (struct sockaddr *)&in;
addrsz = sizeof(in);
} else if (b->family == AF_INET6) {
struct sockaddr_in6 in = {0};
in.sin6_family = AF_INET6;
memcpy(&in.sin6_addr, b->addr, sizeof(b->addr));
in.sin6_port = htons(b->port);
addr = (struct sockaddr *)&in;
addrsz = sizeof(in);
#ifdef IPV6_V6ONLY
2020-09-23 12:40:28 -04:00
static int t = 1;
setsockopt(sockfd, IPPROTO_IPV6,
IPV6_V6ONLY, &t, sizeof(t));
#endif
} else {
assert(0);
}
int r = bind(sockfd, addr, addrsz);
if (r == -1) {
fprintf(stderr,
"Failed to bind socket %s: %s\n",
b->name, strerror(errno));
return 1;
}
r = listen(sockfd, 16);
2020-09-24 11:15:04 -04:00
server_log("listening on %s", b->name);
2020-09-23 12:40:28 -04:00
server->fds[i].fd = sockfd;
server->fds[i].events = POLLIN;
++i;
}
return 0;
}
2020-09-23 14:19:28 -04:00
static struct pollfd *
alloc_pollfd(struct gmnisrv_server *server)
{
if (server->nfds >= server->fdsz) {
size_t fdsz = server->fdsz * 2;
struct pollfd *new = realloc(server->fds,
fdsz * sizeof(struct pollfd));
if (!new) {
fprintf(stderr, "<serv>\tOut of file descriptors!\n");
return NULL;
}
server->fds = new;
server->fdsz = fdsz;
}
return &server->fds[server->nfds++];
}
static void
accept_client(struct gmnisrv_server *server, int fd)
{
struct sockaddr addr;
socklen_t addrlen = sizeof(addr);
int sockfd = accept(fd, &addr, &addrlen);
if (sockfd == -1) {
server_error("accept error: %s", strerror(errno));
return;
}
int flags = fcntl(fd, F_GETFL);
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
if (r == -1) {
close(sockfd);
server_error("error setting O_NONBLOCK on client fd: %s", strerror(errno));
2020-09-23 14:19:28 -04:00
return;
}
if (server->nclients >= server->clientsz) {
size_t clientsz = server->clientsz * 2;
struct gmnisrv_client *new = realloc(server->clients,
clientsz * sizeof(struct gmnisrv_client));
if (!new) {
2020-09-23 14:32:52 -04:00
client_error(&addr, "disconnecting due to OOM condition");
2020-09-23 14:19:28 -04:00
close(sockfd);
return;
}
server->clients = new;
server->clientsz = clientsz;
}
struct pollfd *pollfd = alloc_pollfd(server);
if (pollfd == NULL) {
2020-09-23 14:32:52 -04:00
client_error(&addr, "disconnecting due to OOM condition");
2020-09-23 14:19:28 -04:00
close(sockfd);
return;
}
struct gmnisrv_client *client = &server->clients[server->nclients++];
memset(client, 0, sizeof(*client));
2020-09-23 14:19:28 -04:00
client->sockfd = sockfd;
client->pollfd = pollfd;
2020-09-23 14:19:28 -04:00
client->addrlen = addrlen;
client->server = server;
2020-09-26 14:41:17 -04:00
client->bodyfd = -1;
clock_gettime(CLOCK_MONOTONIC, &client->ctime);
memcpy(&client->addr, &addr, sizeof(addr));
2020-09-23 14:19:28 -04:00
pollfd->fd = sockfd;
pollfd->events = POLLIN;
}
static void
timespec_diff(struct timespec *start,
struct timespec *stop, struct timespec *out)
{
if ((stop->tv_nsec - start->tv_nsec) < 0) {
out->tv_sec = stop->tv_sec - start->tv_sec - 1;
out->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000;
} else {
out->tv_sec = stop->tv_sec - start->tv_sec;
out->tv_nsec = stop->tv_nsec - start->tv_nsec;
}
}
2020-09-23 14:19:28 -04:00
static void
disconnect_client(struct gmnisrv_server *server, struct gmnisrv_client *client)
{
if (client->status != GEMINI_STATUS_NONE) {
struct timespec now, diff;
clock_gettime(CLOCK_MONOTONIC, &now);
timespec_diff(&client->ctime, &now, &diff);
int ms = diff.tv_sec * 1000 + (int)(diff.tv_nsec / 1.0e6);
2020-09-26 14:41:17 -04:00
client_log(&client->addr, "%3dms %5d %s %s %02d %s",
ms, client->bbytes,
client->host ? client->host->hostname : "(none)",
client->path ? client->path : "(none)",
(int)client->status, client->meta);
}
2020-09-26 13:58:51 -04:00
if (client->bio) {
BIO_free_all(client->bio);
}
2020-09-23 14:19:28 -04:00
close(client->sockfd);
free(client->meta);
2020-09-23 14:19:28 -04:00
size_t index = (client - server->clients) / sizeof(struct gmnisrv_client);
memmove(client, &client[1], &server->clients[server->clientsz] - client);
memmove(&server->fds[server->nlisten + index],
&server->fds[server->nlisten + index + 1],
server->fdsz - (server->nlisten + index + 1) * sizeof(struct pollfd));
--server->nfds;
--server->nclients;
}
static int
client_init_ssl(struct gmnisrv_server *server, struct gmnisrv_client *client)
{
client->ssl = gmnisrv_tls_get_ssl(server->conf, client->sockfd);
if (!client->ssl) {
client_error(&client->addr,
"unable to initialize SSL, disconnecting");
disconnect_client(server, client);
return 1;
}
int r = SSL_accept(client->ssl);
if (r != 1) {
r = SSL_get_error(client->ssl, r);
if (r == SSL_ERROR_WANT_READ || r == SSL_ERROR_WANT_WRITE) {
return 1;
}
client_error(&client->addr, "SSL accept error %s, disconnecting",
ERR_error_string(r, NULL));
disconnect_client(server, client);
return 1;
}
client->sbio = BIO_new(BIO_f_ssl());
BIO_set_ssl(client->sbio, client->ssl, 0);
client->bio = BIO_new(BIO_f_buffer());
BIO_push(client->bio, client->sbio);
return 0;
}
static void
client_submit_response(struct gmnisrv_client *client,
enum gemini_status status, const char *meta, int bodyfd)
{
client->status = status;
client->meta = strdup(meta);
client->bodyfd = bodyfd;
client->pollfd->events = POLLOUT;
}
static void
client_oom(struct gmnisrv_client *client)
{
const char *error = "Out of memory";
client_submit_response(client,
GEMINI_STATUS_TEMPORARY_FAILURE, error, -1);
}
static bool
request_validate(struct gmnisrv_client *client, char **path)
{
struct Curl_URL *url = curl_url();
if (!url) {
client_oom(client);
return false;
}
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);
goto exit;
}
char *part;
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);
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);
goto exit;
}
free(part);
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);
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);
goto exit;
}
free(part);
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);
goto exit;
}
// NOTE: curl_url_set(..., CURLUPART_URL, ..., 0) will consoldate .. and
// . to prevent directory traversal without additional code.
*path = part;
exit:
curl_url_cleanup(url);
return true;
}
2020-09-26 14:36:52 -04:00
static void
serve_request(struct gmnisrv_client *client)
{
struct gmnisrv_host *host = client->host;
assert(host);
assert(host->root); // TODO: reverse proxy support
char path[PATH_MAX + 1];
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);
return;
}
if (path[strlen(path) - 1] == '/') {
// TODO: Let user configure index file name?
strncat(path, "index.gmi", sizeof(path) - 1);
}
int fd = open(path, O_RDONLY);
if (fd == -1) {
if (errno == ENOENT) {
client_submit_response(client, GEMINI_STATUS_NOT_FOUND,
"Not found", -1);
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);
return;
}
}
const char *meta = gmnisrv_mimetype_for_path(path);
client_submit_response(client, GEMINI_STATUS_SUCCESS, meta, fd);
}
2020-09-23 14:19:28 -04:00
static void
client_readable(struct gmnisrv_server *server, struct gmnisrv_client *client)
2020-09-23 12:40:28 -04:00
{
if (!client->ssl && client_init_ssl(server, client) != 0) {
return;
}
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);
return;
}
int r = BIO_gets(client->bio, client->buf, sizeof(client->buf));
if (r <= 0) {
r = SSL_get_error(client->ssl, r);
if (r == SSL_ERROR_WANT_READ) {
return;
}
client_error(&client->addr, "SSL read error %s, disconnecting",
ERR_error_string(r, NULL));
2020-09-23 14:19:28 -04:00
disconnect_client(server, client);
return;
}
client->buf[r] = '\0';
char *newline = strstr(client->buf, "\r\n");
if (!newline) {
const char *error = "Protocol error: malformed request";
client_submit_response(client,
GEMINI_STATUS_BAD_REQUEST, error, -1);
return;
}
*newline = 0;
if (!request_validate(client, &client->path)) {
return;
}
2020-09-26 14:36:52 -04:00
serve_request(client);
2020-09-23 12:40:28 -04:00
}
2020-09-23 14:19:28 -04:00
static void
client_writable(struct gmnisrv_server *server, struct gmnisrv_client *client)
{
int r;
ssize_t n;
switch (client->state) {
case RESPOND_HEADER:
if (client->bufix == 0) {
assert(strlen(client->meta) <= 1024);
n = snprintf(client->buf, sizeof(client->buf),
"%02d %s\r\n", (int)client->status,
client->meta);
assert(n > 0);
client->bufln = n;
}
r = BIO_write(client->sbio, &client->buf[client->bufix],
client->bufln - client->bufix);
if (r <= 0) {
r = SSL_get_error(client->ssl, r);
if (r == SSL_ERROR_WANT_WRITE) {
return;
}
client->status = GEMINI_STATUS_NONE;
2020-09-26 14:36:52 -04:00
client_error(&client->addr,
"header write error %s, disconnecting",
ERR_error_string(r, NULL));
disconnect_client(server, client);
return;
}
client->bufix += r;
if (client->bufix >= client->bufln) {
2020-09-26 14:36:52 -04:00
if (client->bodyfd == -1) {
disconnect_client(server, client);
} else {
client->state = RESPOND_BODY;
client->bufix = client->bufln = 0;
}
return;
}
break;
case RESPOND_BODY:
2020-09-26 14:36:52 -04:00
if (client->bufix >= client->bufln) {
n = read(client->bodyfd,
client->buf, sizeof(client->buf));
if (n == -1) {
client_error(&client->addr,
"Error reading response body: %s",
strerror(errno));
disconnect_client(server, client);
return;
}
if (n == 0) {
// EOF
disconnect_client(server, client);
return;
}
2020-09-26 14:41:17 -04:00
client->bbytes += n;
2020-09-26 14:36:52 -04:00
client->bufln = n;
client->bufix = 0;
}
r = BIO_write(client->sbio, &client->buf[client->bufix],
client->bufln - client->bufix);
if (r <= 0) {
r = SSL_get_error(client->ssl, r);
if (r == SSL_ERROR_WANT_WRITE) {
return;
}
client->status = GEMINI_STATUS_NONE;
client_error(&client->addr, "body write error %s, disconnecting",
ERR_error_string(r, NULL));
disconnect_client(server, client);
return;
}
client->bufix += r;
break;
}
2020-09-23 14:19:28 -04:00
}
static long
sni_callback(SSL *ssl, int *al, void *arg)
{
(void)al;
struct gmnisrv_server *server = (struct gmnisrv_server *)arg;
struct gmnisrv_client *client;
for (size_t i = 0; i < server->nclients; ++i) {
client = &server->clients[i];
if (client->ssl == ssl) {
break;
}
client = NULL;
}
if (!client) {
return SSL_TLSEXT_ERR_NOACK;
}
const char *hostname = SSL_get_servername(client->ssl,
SSL_get_servername_type(client->ssl));
struct gmnisrv_host *host = gmnisrv_config_get_host(
server->conf, hostname);
if (!host) {
return SSL_TLSEXT_ERR_NOACK;
}
client->host = host;
gmnisrv_tls_set_host(client->ssl, client->host);
return SSL_TLSEXT_ERR_OK;
}
2020-09-23 14:19:28 -04:00
bool *run;
static void
handle_sigint(int s, siginfo_t *i, void *c)
{
*run = false;
(void)s; (void)i; (void)c;
}
void
server_run(struct gmnisrv_server *server)
{
struct sigaction act = {
.sa_sigaction = handle_sigint,
.sa_flags = SA_SIGINFO,
};
struct sigaction oint, oterm;
run = &server->run;
int r = sigaction(SIGINT, &act, &oint);
assert(r == 0);
r = sigaction(SIGTERM, &act, &oterm);
assert(r == 0);
SSL_CTX_set_tlsext_servername_arg(server->conf->tls.ssl_ctx, server);
SSL_CTX_set_tlsext_servername_callback(
server->conf->tls.ssl_ctx, sni_callback);
2020-09-24 11:15:04 -04:00
server_log("gmnisrv started");
2020-09-23 14:19:28 -04:00
server->run = true;
do {
r = poll(server->fds, server->nfds, -1);
if (r == -1 && (errno == EAGAIN || errno == EINTR)) {
continue;
} else if (r == -1) {
break;
}
for (size_t i = 0; i < server->nlisten; ++i) {
if ((server->fds[i].revents & POLLIN)) {
accept_client(server, server->fds[i].fd);
}
if ((server->fds[i].revents & POLLERR)) {
2020-09-23 14:32:52 -04:00
server_error("Error on listener poll");
2020-09-23 14:19:28 -04:00
server->run = false;
}
}
for (size_t i = 0; i < server->nclients; ++i) {
if ((server->fds[server->nlisten + i].revents & (POLLHUP | POLLERR))) {
disconnect_client(server, &server->clients[i]);
--i;
continue;
}
if ((server->fds[server->nlisten + i].revents & POLLIN)) {
client_readable(server, &server->clients[i]);
}
if ((server->fds[server->nlisten + i].revents & POLLOUT)) {
client_writable(server, &server->clients[i]);
}
}
} while (server->run);
2020-09-24 11:15:04 -04:00
server_log("gmnisrv terminating");
2020-09-23 14:19:28 -04:00
r = sigaction(SIGINT, &oint, NULL);
assert(r == 0);
r = sigaction(SIGTERM, &oterm, NULL);
assert(r == 0);
}
2020-09-23 12:40:28 -04:00
void
server_finish(struct gmnisrv_server *server)
{
// TODO
(void)server;
}