From fa69887e52201eb348f1bfbecf72bdab5c57bd9c Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Thu, 24 Sep 2020 19:58:34 -0400 Subject: [PATCH] Implement TLS exchange with clients This probably leaves a bit to be desired tbh --- include/config.h | 2 +- include/server.h | 12 ++++-- include/tls.h | 2 + src/log.c | 6 +-- src/main.c | 3 ++ src/server.c | 105 ++++++++++++++++++++++++++++++++++++++++++++--- src/tls.c | 32 +++++++++++++-- 7 files changed, 145 insertions(+), 17 deletions(-) diff --git a/include/config.h b/include/config.h index 495db3a..83253f7 100644 --- a/include/config.h +++ b/include/config.h @@ -7,12 +7,12 @@ struct gmnisrv_tls { char *store; char *organization; char *email; + SSL_CTX *ssl_ctx; }; struct gmnisrv_host { char *hostname; char *root; - SSL_CTX *ssl_ctx; X509 *x509; EVP_PKEY *pkey; struct gmnisrv_host *next; diff --git a/include/server.h b/include/server.h index ac3bcac..5624b52 100644 --- a/include/server.h +++ b/include/server.h @@ -1,5 +1,6 @@ #ifndef GMNISRV_SERVER #define GMNISRV_SERVER +#include #include #include @@ -8,11 +9,14 @@ struct gmnisrv_client { struct sockaddr addr; socklen_t addrlen; - - char buf[GEMINI_MAX_URL + 2]; - size_t bufln; - int sockfd; + + SSL *ssl; + BIO *bio; + + char buf[GEMINI_MAX_URL + 3]; + + struct gmnisrv_host *host; }; struct gmisrv_config; diff --git a/include/tls.h b/include/tls.h index bc088ef..06d1123 100644 --- a/include/tls.h +++ b/include/tls.h @@ -4,5 +4,7 @@ struct gmnisrv_config; int gmnisrv_tls_init(struct gmnisrv_config *conf); +SSL *gmnisrv_tls_get_ssl(struct gmnisrv_config *conf, int fd); +void gmnisrv_tls_set_host(SSL *ssl, struct gmnisrv_host *host); #endif diff --git a/src/log.c b/src/log.c index aecaf34..7667cce 100644 --- a/src/log.c +++ b/src/log.c @@ -8,7 +8,7 @@ static void server_logf(FILE *f, const char *fmt, va_list ap) { - fprintf(f, "\t"); + fprintf(f, "[gmnisrv] "); vfprintf(f, fmt, ap); fprintf(f, "\n"); } @@ -16,12 +16,12 @@ server_logf(FILE *f, const char *fmt, va_list ap) static void client_logf(FILE *f, struct sockaddr *addr, const char *fmt, va_list ap) { - char abuf[INET6_ADDRSTRLEN]; + char abuf[INET6_ADDRSTRLEN + 1]; const char *addrs = inet_ntop(addr->sa_family, addr->sa_data, abuf, sizeof(abuf)); assert(addrs); - fprintf(f, "%s\t", addrs); + fprintf(f, "%s ", addrs); vfprintf(f, fmt, ap); fprintf(f, "\n"); } diff --git a/src/main.c b/src/main.c index 78d3f9b..aa7ffcf 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,7 @@ #include #include #include "config.h" +#include "log.h" #include "server.h" #include "tls.h" @@ -38,11 +39,13 @@ main(int argc, char **argv) int r = load_config(&conf, confpath); if (r != 0) { + server_error("Config load failed"); goto exit_conf; } r = gmnisrv_tls_init(&conf); if (r != 0) { + server_error("TLS initialization failed"); goto exit_conf; } diff --git a/src/server.c b/src/server.c index aa835a6..cca2947 100644 --- a/src/server.c +++ b/src/server.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -9,8 +10,9 @@ #include #include #include "config.h" -#include "server.h" #include "log.h" +#include "server.h" +#include "tls.h" int server_init(struct gmnisrv_server *server, struct gmnisrv_config *conf) @@ -135,7 +137,7 @@ accept_client(struct gmnisrv_server *server, int fd) struct gmnisrv_client *client = &server->clients[server->nclients++]; client->sockfd = sockfd; client->addrlen = addrlen; - memcpy(&client->addr, &addr, addrlen); + memcpy(&client->addr, &addr, sizeof(addr)); pollfd->fd = sockfd; pollfd->events = POLLIN; } @@ -153,16 +155,72 @@ disconnect_client(struct gmnisrv_server *server, struct gmnisrv_client *client) --server->nclients; } +static int +client_init_ssl(struct gmnisrv_server *server, struct gmnisrv_client *client) +{ + // TODO: Re-work this to use a non-blocking bio + 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); + client_error(&client->addr, "SSL accept error %s, disconnecting", + ERR_error_string(r, NULL)); + disconnect_client(server, client); + return 1; + } + + BIO *sbio = BIO_new(BIO_f_ssl()); + BIO_set_ssl(sbio, client->ssl, 0); + client->bio = BIO_new(BIO_f_buffer()); + BIO_push(client->bio, sbio); + return 0; +} + static void client_readable(struct gmnisrv_server *server, struct gmnisrv_client *client) { - // TODO - ssize_t n = read(client->sockfd, client->buf, sizeof(client->buf)); - if (n == 0) { + if (!client->ssl && client_init_ssl(server, client) != 0) { + 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)); disconnect_client(server, client); return; } - (void)server; + client->buf[r] = '\0'; + + if (!client->host) { + // TODO: We can do a friendly disconnect at this point + client_error(&client->addr, + "client did not perform SNI, disconnecting"); + disconnect_client(server, client); + return; + } + + char *newline = strstr(client->buf, "\r\n"); + if (!newline) { + // TODO: We can do a friendly disconnect at this point + client_error(&client->addr, "protocol error, disconnecting"); + disconnect_client(server, client); + return; + } + *newline = 0; + + client_log(&client->addr, "%s", client->buf); } static void @@ -173,6 +231,37 @@ client_writable(struct gmnisrv_server *server, struct gmnisrv_client *client) (void)client; } +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; +} + bool *run; static void @@ -196,6 +285,10 @@ server_run(struct gmnisrv_server *server) 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); + server_log("gmnisrv started"); server->run = true; diff --git a/src/tls.c b/src/tls.c index ad5ed4c..7d748d7 100644 --- a/src/tls.c +++ b/src/tls.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -102,9 +103,6 @@ tls_host_init(struct gmnisrv_tls *tlsconf, struct gmnisrv_host *host) "%s/%s.key", tlsconf->store, host->hostname); mkdirs(tlsconf->store, 0755); - host->ssl_ctx = SSL_CTX_new(TLS_method()); - assert(host->ssl_ctx); - FILE *xf = fopen(crtpath, "r"); if (!xf && errno != ENOENT) { server_error("error opening %s for reading: %s", @@ -160,6 +158,14 @@ generate: int gmnisrv_tls_init(struct gmnisrv_config *conf) { + SSL_load_error_strings(); + ERR_load_crypto_strings(); + + conf->tls.ssl_ctx = SSL_CTX_new(TLS_method()); + assert(conf->tls.ssl_ctx); + + SSL_CTX_set_tlsext_servername_callback(conf->tls.ssl_ctx, NULL); + int r; for (struct gmnisrv_host *host = conf->hosts; host; host = host->next) { r = tls_host_init(&conf->tls, host); @@ -167,5 +173,25 @@ gmnisrv_tls_init(struct gmnisrv_config *conf) return r; } } + return 0; } + +SSL * +gmnisrv_tls_get_ssl(struct gmnisrv_config *conf, int fd) +{ + SSL *ssl = SSL_new(conf->tls.ssl_ctx); + if (!ssl) { + return NULL; + } + int r = SSL_set_fd(ssl, fd); + assert(r == 1); + return ssl; +} + +void +gmnisrv_tls_set_host(SSL *ssl, struct gmnisrv_host *host) +{ + SSL_use_certificate(ssl, host->x509); + SSL_use_PrivateKey(ssl, host->pkey); +}