2023-04-06 11:50:44 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2020 - 2023 Andri Yngvason
|
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
|
|
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
|
|
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
|
|
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
|
|
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
|
|
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
|
|
* PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <sys/uio.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <aml.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <poll.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
|
|
|
|
#include <gnutls/gnutls.h>
|
|
|
|
|
|
|
|
#include "rcbuf.h"
|
|
|
|
#include "stream.h"
|
|
|
|
#include "stream-common.h"
|
|
|
|
#include "sys/queue.h"
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
struct stream_gnutls {
|
|
|
|
struct stream base;
|
|
|
|
|
|
|
|
gnutls_session_t session;
|
|
|
|
};
|
|
|
|
|
2023-05-30 08:40:56 +00:00
|
|
|
static_assert(sizeof(struct stream_gnutls) <= STREAM_ALLOC_SIZE,
|
|
|
|
"struct stream_gnutls has grown too large, increase STREAM_ALLOC_SIZE");
|
|
|
|
|
2023-04-06 11:50:44 +00:00
|
|
|
static int stream__try_tls_accept(struct stream* self);
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
static int stream_gnutls_close(struct stream* base)
|
2023-04-06 11:50:44 +00:00
|
|
|
{
|
2023-05-28 15:50:36 +00:00
|
|
|
struct stream_gnutls* self = (struct stream_gnutls*)base;
|
|
|
|
|
|
|
|
if (self->base.state == STREAM_STATE_CLOSED)
|
2023-04-06 11:50:44 +00:00
|
|
|
return -1;
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
self->base.state = STREAM_STATE_CLOSED;
|
2023-04-06 11:50:44 +00:00
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
while (!TAILQ_EMPTY(&self->base.send_queue)) {
|
|
|
|
struct stream_req* req = TAILQ_FIRST(&self->base.send_queue);
|
|
|
|
TAILQ_REMOVE(&self->base.send_queue, req, link);
|
2023-04-06 11:50:44 +00:00
|
|
|
stream_req__finish(req, STREAM_REQ_FAILED);
|
|
|
|
}
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
if (self->session)
|
|
|
|
gnutls_deinit(self->session);
|
|
|
|
self->session = NULL;
|
2023-04-06 11:50:44 +00:00
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
aml_stop(aml_get_default(), self->base.handler);
|
|
|
|
close(self->base.fd);
|
|
|
|
self->base.fd = -1;
|
2023-04-06 11:50:44 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void stream_gnutls_destroy(struct stream* self)
|
|
|
|
{
|
|
|
|
stream_close(self);
|
|
|
|
aml_unref(self->handler);
|
2023-04-11 20:32:37 +00:00
|
|
|
free(self);
|
2023-04-06 11:50:44 +00:00
|
|
|
}
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
static int stream_gnutls__flush(struct stream* base)
|
2023-04-06 11:50:44 +00:00
|
|
|
{
|
2023-05-28 15:50:36 +00:00
|
|
|
struct stream_gnutls* self = (struct stream_gnutls*)base;
|
|
|
|
while (!TAILQ_EMPTY(&self->base.send_queue)) {
|
2023-08-13 12:11:56 +00:00
|
|
|
assert(self->base.state != STREAM_STATE_CLOSED);
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
struct stream_req* req = TAILQ_FIRST(&self->base.send_queue);
|
2023-04-06 11:50:44 +00:00
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
ssize_t rc = gnutls_record_send(self->session,
|
|
|
|
req->payload->payload, req->payload->size);
|
2023-04-06 11:50:44 +00:00
|
|
|
if (rc < 0) {
|
2023-04-11 20:33:13 +00:00
|
|
|
if (gnutls_error_is_fatal(rc)) {
|
2023-05-28 15:50:36 +00:00
|
|
|
stream_close(base);
|
2023-04-11 20:33:13 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
stream__poll_rw(base);
|
2023-04-11 20:33:13 +00:00
|
|
|
return 0;
|
2023-04-06 11:50:44 +00:00
|
|
|
}
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
self->base.bytes_sent += rc;
|
2023-04-06 11:50:44 +00:00
|
|
|
|
|
|
|
ssize_t remaining = req->payload->size - rc;
|
|
|
|
|
|
|
|
if (remaining > 0) {
|
|
|
|
char* p = req->payload->payload;
|
|
|
|
size_t s = req->payload->size;
|
|
|
|
memmove(p, p + s - remaining, remaining);
|
|
|
|
req->payload->size = remaining;
|
2023-05-28 15:50:36 +00:00
|
|
|
stream__poll_rw(base);
|
2023-04-06 11:50:44 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(remaining == 0);
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
TAILQ_REMOVE(&self->base.send_queue, req, link);
|
2023-04-06 11:50:44 +00:00
|
|
|
stream_req__finish(req, STREAM_REQ_DONE);
|
|
|
|
}
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
if (TAILQ_EMPTY(&base->send_queue) && base->state != STREAM_STATE_CLOSED)
|
|
|
|
stream__poll_r(base);
|
2023-04-06 11:50:44 +00:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void stream_gnutls__on_readable(struct stream* self)
|
|
|
|
{
|
|
|
|
switch (self->state) {
|
|
|
|
case STREAM_STATE_NORMAL:
|
|
|
|
/* fallthrough */
|
|
|
|
case STREAM_STATE_TLS_READY:
|
|
|
|
if (self->on_event)
|
|
|
|
self->on_event(self, STREAM_EVENT_READ);
|
|
|
|
break;
|
|
|
|
case STREAM_STATE_TLS_HANDSHAKE:
|
|
|
|
stream__try_tls_accept(self);
|
|
|
|
break;
|
|
|
|
case STREAM_STATE_CLOSED:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void stream_gnutls__on_writable(struct stream* self)
|
|
|
|
{
|
|
|
|
switch (self->state) {
|
|
|
|
case STREAM_STATE_NORMAL:
|
|
|
|
/* fallthrough */
|
|
|
|
case STREAM_STATE_TLS_READY:
|
|
|
|
stream_gnutls__flush(self);
|
|
|
|
break;
|
|
|
|
case STREAM_STATE_TLS_HANDSHAKE:
|
|
|
|
stream__try_tls_accept(self);
|
|
|
|
break;
|
|
|
|
case STREAM_STATE_CLOSED:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void stream_gnutls__on_event(void* obj)
|
|
|
|
{
|
|
|
|
struct stream* self = aml_get_userdata(obj);
|
|
|
|
uint32_t events = aml_get_revents(obj);
|
|
|
|
|
|
|
|
if (events & AML_EVENT_READ)
|
|
|
|
stream_gnutls__on_readable(self);
|
|
|
|
|
|
|
|
if (events & AML_EVENT_WRITE)
|
|
|
|
stream_gnutls__on_writable(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int stream_gnutls_send(struct stream* self, struct rcbuf* payload,
|
|
|
|
stream_req_fn on_done, void* userdata)
|
|
|
|
{
|
|
|
|
if (self->state == STREAM_STATE_CLOSED)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
struct stream_req* req = calloc(1, sizeof(*req));
|
|
|
|
if (!req)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
req->payload = payload;
|
|
|
|
req->on_done = on_done;
|
|
|
|
req->userdata = userdata;
|
|
|
|
|
|
|
|
TAILQ_INSERT_TAIL(&self->send_queue, req, link);
|
|
|
|
|
|
|
|
return stream_gnutls__flush(self);
|
|
|
|
}
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
static ssize_t stream_gnutls_read(struct stream* base, void* dst, size_t size)
|
2023-04-06 11:50:44 +00:00
|
|
|
{
|
2023-05-28 15:50:36 +00:00
|
|
|
struct stream_gnutls* self = (struct stream_gnutls*)base;
|
|
|
|
|
|
|
|
ssize_t rc = gnutls_record_recv(self->session, dst, size);
|
2023-04-06 11:50:44 +00:00
|
|
|
if (rc == 0) {
|
2023-05-28 15:50:36 +00:00
|
|
|
stream__remote_closed(base);
|
2023-04-06 11:50:44 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
if (rc > 0) {
|
2023-05-28 15:50:36 +00:00
|
|
|
self->base.bytes_received += rc;
|
2023-04-06 11:50:44 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (rc) {
|
|
|
|
case GNUTLS_E_INTERRUPTED:
|
|
|
|
errno = EINTR;
|
|
|
|
break;
|
|
|
|
case GNUTLS_E_AGAIN:
|
|
|
|
errno = EAGAIN;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
errno = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure data wasn't being written.
|
2023-05-28 15:50:36 +00:00
|
|
|
assert(gnutls_record_get_direction(self->session) == 0);
|
2023-04-06 11:50:44 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
static int stream__try_tls_accept(struct stream* base)
|
2023-04-06 11:50:44 +00:00
|
|
|
{
|
2023-05-28 15:50:36 +00:00
|
|
|
struct stream_gnutls* self = (struct stream_gnutls*)base;
|
2023-04-06 11:50:44 +00:00
|
|
|
int rc;
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
rc = gnutls_handshake(self->session);
|
2023-04-06 11:50:44 +00:00
|
|
|
if (rc == GNUTLS_E_SUCCESS) {
|
2023-05-28 15:50:36 +00:00
|
|
|
self->base.state = STREAM_STATE_TLS_READY;
|
|
|
|
stream__poll_r(base);
|
2023-04-06 11:50:44 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gnutls_error_is_fatal(rc)) {
|
2023-05-28 15:50:36 +00:00
|
|
|
aml_stop(aml_get_default(), self->base.handler);
|
2023-04-06 11:50:44 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
int was_writing = gnutls_record_get_direction(self->session);
|
2023-04-06 11:50:44 +00:00
|
|
|
if (was_writing)
|
2023-05-28 15:50:36 +00:00
|
|
|
stream__poll_w(base);
|
2023-04-06 11:50:44 +00:00
|
|
|
else
|
2023-05-28 15:50:36 +00:00
|
|
|
stream__poll_r(base);
|
2023-04-06 11:50:44 +00:00
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
self->base.state = STREAM_STATE_TLS_HANDSHAKE;
|
2023-04-06 11:50:44 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct stream_impl impl = {
|
|
|
|
.close = stream_gnutls_close,
|
|
|
|
.destroy = stream_gnutls_destroy,
|
|
|
|
.read = stream_gnutls_read,
|
|
|
|
.send = stream_gnutls_send,
|
|
|
|
};
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
int stream_upgrade_to_tls(struct stream* base, void* context)
|
2023-04-06 11:50:44 +00:00
|
|
|
{
|
2023-05-28 15:50:36 +00:00
|
|
|
struct stream_gnutls* self = (struct stream_gnutls*)base;
|
2023-04-06 11:50:44 +00:00
|
|
|
int rc;
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
rc = gnutls_init(&self->session, GNUTLS_SERVER | GNUTLS_NONBLOCK);
|
2023-04-06 11:50:44 +00:00
|
|
|
if (rc != GNUTLS_E_SUCCESS)
|
|
|
|
return -1;
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
rc = gnutls_set_default_priority(self->session);
|
2023-04-06 11:50:44 +00:00
|
|
|
if (rc != GNUTLS_E_SUCCESS)
|
|
|
|
goto failure;
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
rc = gnutls_credentials_set(self->session, GNUTLS_CRD_CERTIFICATE,
|
2023-04-06 11:50:44 +00:00
|
|
|
context);
|
|
|
|
if (rc != GNUTLS_E_SUCCESS)
|
|
|
|
goto failure;
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
aml_stop(aml_get_default(), self->base.handler);
|
|
|
|
aml_unref(self->base.handler);
|
2023-04-06 11:50:44 +00:00
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
self->base.handler = aml_handler_new(self->base.fd,
|
|
|
|
stream_gnutls__on_event, self, NULL);
|
|
|
|
assert(self->base.handler);
|
2023-04-06 11:50:44 +00:00
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
rc = aml_start(aml_get_default(), self->base.handler);
|
2023-04-06 11:50:44 +00:00
|
|
|
assert(rc >= 0);
|
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
gnutls_transport_set_int(self->session, self->base.fd);
|
2023-04-06 11:50:44 +00:00
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
self->base.impl = &impl;
|
2023-04-06 11:50:44 +00:00
|
|
|
|
2023-05-28 15:50:36 +00:00
|
|
|
return stream__try_tls_accept(base);
|
2023-04-06 11:50:44 +00:00
|
|
|
|
|
|
|
failure:
|
2023-05-28 15:50:36 +00:00
|
|
|
gnutls_deinit(self->session);
|
2023-04-06 11:50:44 +00:00
|
|
|
return -1;
|
|
|
|
}
|