Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
799 lines (663 sloc) 21.6 KB
/* This file is part of libebb.
*
* Copyright (c) 2008 Ryan Dahl (ry@ndahl.us)
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <assert.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/tcp.h> /* TCP_NODELAY */
#include <netinet/in.h> /* inet_ntoa */
#include <arpa/inet.h> /* inet_ntoa */
#include <unistd.h>
#include <stdio.h> /* perror */
#include <errno.h> /* perror */
#include <stdlib.h> /* for the default methods */
#include <ev.h>
#include "ebb.h"
#include "ebb_request_parser.h"
#ifdef HAVE_GNUTLS
# include <gnutls/gnutls.h>
# include "rbtree.h" /* for session_cache */
#endif
#ifndef TRUE
# define TRUE 1
#endif
#ifndef FALSE
# define FALSE 0
#endif
#ifndef MIN
# define MIN(a,b) (a < b ? a : b)
#endif
#define error(FORMAT, ...) fprintf(stderr, "error: " FORMAT "\n", ##__VA_ARGS__)
#define CONNECTION_HAS_SOMETHING_TO_WRITE (connection->to_write != NULL)
static void
set_nonblock (int fd)
{
int flags = fcntl(fd, F_GETFL, 0);
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
assert(0 <= r && "Setting socket non-block failed!");
}
static ssize_t
nosigpipe_push(void *data, const void *buf, size_t len)
{
int fd = (int)data;
int flags = 0;
#ifdef MSG_NOSIGNAL
flags = MSG_NOSIGNAL;
#endif
return send(fd, buf, len, flags);
}
static void
close_connection(ebb_connection *connection)
{
#ifdef HAVE_GNUTLS
if(connection->server->secure)
ev_io_stop(connection->server->loop, &connection->handshake_watcher);
#endif
ev_io_stop(connection->server->loop, &connection->read_watcher);
ev_io_stop(connection->server->loop, &connection->write_watcher);
ev_timer_stop(connection->server->loop, &connection->timeout_watcher);
if(0 > close(connection->fd))
error("problem closing connection fd");
connection->open = FALSE;
if(connection->on_close)
connection->on_close(connection);
/* No access to the connection past this point!
* The user is allowed to free in the callback
*/
}
#ifdef HAVE_GNUTLS
#define GNUTLS_NEED_WRITE (gnutls_record_get_direction(connection->session) == 1)
#define GNUTLS_NEED_READ (gnutls_record_get_direction(connection->session) == 0)
#define EBB_MAX_SESSION_KEY 32
#define EBB_MAX_SESSION_VALUE 512
struct session_cache {
struct rbtree_node_t node;
gnutls_datum_t key;
gnutls_datum_t value;
char key_storage[EBB_MAX_SESSION_KEY];
char value_storage[EBB_MAX_SESSION_VALUE];
};
static int
session_cache_compare (void *left, void *right)
{
gnutls_datum_t *left_key = left;
gnutls_datum_t *right_key = right;
if(left_key->size < right_key->size)
return -1;
else if(left_key->size > right_key->size)
return 1;
else
return memcmp( left_key->data
, right_key->data
, MIN(left_key->size, right_key->size)
);
}
static int
session_cache_store(void *data, gnutls_datum_t key, gnutls_datum_t value)
{
rbtree tree = data;
if( tree == NULL
|| key.size > EBB_MAX_SESSION_KEY
|| value.size > EBB_MAX_SESSION_VALUE
) return -1;
struct session_cache *cache = gnutls_malloc(sizeof(struct session_cache));
memcpy (cache->key_storage, key.data, key.size);
cache->key.size = key.size;
cache->key.data = (void*)cache->key_storage;
memcpy (cache->value_storage, value.data, value.size);
cache->value.size = value.size;
cache->value.data = (void*)cache->value_storage;
cache->node.key = &cache->key;
cache->node.value = &cache;
rbtree_insert(tree, (rbtree_node)cache);
//printf("session_cache_store\n");
return 0;
}
static gnutls_datum_t
session_cache_retrieve (void *data, gnutls_datum_t key)
{
rbtree tree = data;
gnutls_datum_t res = { NULL, 0 };
struct session_cache *cache = rbtree_lookup(tree, &key);
if(cache == NULL)
return res;
res.size = cache->value.size;
res.data = gnutls_malloc (res.size);
if(res.data == NULL)
return res;
memcpy(res.data, cache->value.data, res.size);
//printf("session_cache_retrieve\n");
return res;
}
static int
session_cache_remove (void *data, gnutls_datum_t key)
{
rbtree tree = data;
if(tree == NULL)
return -1;
struct session_cache *cache = (struct session_cache *)rbtree_delete(tree, &key);
if(cache == NULL)
return -1;
gnutls_free(cache);
//printf("session_cache_remove\n");
return 0;
}
static void
on_handshake(struct ev_loop *loop ,ev_io *watcher, int revents)
{
ebb_connection *connection = watcher->data;
//printf("on_handshake\n");
assert(ev_is_active(&connection->timeout_watcher));
assert(!ev_is_active(&connection->read_watcher));
assert(!ev_is_active(&connection->write_watcher));
if(EV_ERROR & revents) {
error("on_handshake() got error event, closing connection.n");
goto error;
}
int r = gnutls_handshake(connection->session);
if(r < 0) {
if(gnutls_error_is_fatal(r)) goto error;
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN)
ev_io_set( watcher
, connection->fd
, (GNUTLS_NEED_WRITE ? EV_WRITE : EV_READ)
);
return;
}
ebb_connection_reset_timeout(connection);
ev_io_stop(loop, watcher);
ev_io_start(loop, &connection->read_watcher);
if(CONNECTION_HAS_SOMETHING_TO_WRITE)
ev_io_start(loop, &connection->write_watcher);
return;
error:
close_connection(connection);
}
#endif /* HAVE_GNUTLS */
/* Internal callback
* called by connection->timeout_watcher
*/
static void
on_timeout(struct ev_loop *loop, ev_timer *watcher, int revents)
{
ebb_connection *connection = watcher->data;
assert(watcher == &connection->timeout_watcher);
//printf("on_timeout\n");
/* if on_timeout returns true, we don't time out */
if(connection->on_timeout) {
int r = connection->on_timeout(connection);
if(r == EBB_AGAIN) {
ebb_connection_reset_timeout(connection);
return;
}
}
ebb_connection_schedule_close(connection);
}
/* Internal callback
* called by connection->read_watcher
*/
static void
on_readable(struct ev_loop *loop, ev_io *watcher, int revents)
{
ebb_connection *connection = watcher->data;
char recv_buffer[TCP_MAXWIN];
ssize_t recved;
//printf("on_readable\n");
// TODO -- why is this broken?
//assert(ev_is_active(&connection->timeout_watcher));
assert(watcher == &connection->read_watcher);
if(EV_ERROR & revents) {
error("on_readable() got error event, closing connection.");
goto error;
}
#ifdef HAVE_GNUTLS
assert(!ev_is_active(&connection->handshake_watcher));
if(connection->server->secure) {
recved = gnutls_record_recv( connection->session
, recv_buffer
, TCP_MAXWIN
);
if(recved <= 0) {
if(gnutls_error_is_fatal(recved)) goto error;
if( (recved == GNUTLS_E_INTERRUPTED || recved == GNUTLS_E_AGAIN)
&& GNUTLS_NEED_WRITE
) ev_io_start(loop, &connection->write_watcher);
return;
}
} else {
#endif /* HAVE_GNUTLS */
recved = recv(connection->fd, recv_buffer, TCP_MAXWIN, 0);
if(recved <= 0) goto error;
#ifdef HAVE_GNUTLS
}
#endif /* HAVE_GNUTLS */
ebb_connection_reset_timeout(connection);
ebb_request_parser_execute(&connection->parser, recv_buffer, recved);
/* parse error? just drop the client. screw the 400 response */
if(ebb_request_parser_has_error(&connection->parser)) goto error;
return;
error:
ebb_connection_schedule_close(connection);
}
/* Internal callback
* called by connection->write_watcher
*/
static void
on_writable(struct ev_loop *loop, ev_io *watcher, int revents)
{
ebb_connection *connection = watcher->data;
ssize_t sent;
//printf("on_writable\n");
assert(CONNECTION_HAS_SOMETHING_TO_WRITE);
assert(connection->written <= connection->to_write_len);
// TODO -- why is this broken?
//assert(ev_is_active(&connection->timeout_watcher));
assert(watcher == &connection->write_watcher);
if(connection->to_write == 0)
goto stop_writing;
#ifdef HAVE_GNUTLS
assert(!ev_is_active(&connection->handshake_watcher));
if(connection->server->secure) {
sent = gnutls_record_send( connection->session
, connection->to_write + connection->written
, connection->to_write_len - connection->written
);
if(sent < 0) {
if(gnutls_error_is_fatal(sent)) goto error;
if( (sent == GNUTLS_E_INTERRUPTED || sent == GNUTLS_E_AGAIN)
&& GNUTLS_NEED_READ
) ev_io_stop(loop, watcher);
return;
}
} else {
#endif /* HAVE_GNUTLS */
sent = nosigpipe_push( (void*)connection->fd
, connection->to_write + connection->written
, connection->to_write_len - connection->written
);
if(sent < 0) goto error;
if(sent == 0) return;
#ifdef HAVE_GNUTLS
}
#endif /* HAVE_GNUTLS */
ebb_connection_reset_timeout(connection);
connection->written += sent;
if(connection->written == connection->to_write_len) {
goto stop_writing;
}
return;
stop_writing:
ev_io_stop(loop, watcher);
connection->to_write = NULL;
if(connection->after_write_cb)
connection->after_write_cb(connection);
return;
error:
error("close connection on write.");
ebb_connection_schedule_close(connection);
}
#ifdef HAVE_GNUTLS
static void
on_goodbye_tls(struct ev_loop *loop, ev_io *watcher, int revents)
{
ebb_connection *connection = watcher->data;
assert(watcher == &connection->goodbye_tls_watcher);
if(EV_ERROR & revents) {
error("on_goodbye() got error event, closing connection.");
goto die;
}
int r = gnutls_bye(connection->session, GNUTLS_SHUT_RDWR);
if(r < 0) {
if(gnutls_error_is_fatal(r)) goto die;
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN)
ev_io_set( watcher
, connection->fd
, (GNUTLS_NEED_WRITE ? EV_WRITE : EV_READ)
);
return;
}
die:
ev_io_stop(loop, watcher);
if(connection->session)
gnutls_deinit(connection->session);
close_connection(connection);
}
#endif /* HAVE_GNUTLS*/
static void
on_goodbye(struct ev_loop *loop, ev_timer *watcher, int revents)
{
ebb_connection *connection = watcher->data;
assert(watcher == &connection->goodbye_watcher);
close_connection(connection);
}
static ebb_request*
new_request_wrapper(void *data)
{
ebb_connection *connection = data;
if(connection->new_request)
return connection->new_request(connection);
return NULL;
}
/* Internal callback
* Called by server->connection_watcher.
*/
static void
on_connection(struct ev_loop *loop, ev_io *watcher, int revents)
{
ebb_server *server = watcher->data;
//printf("on connection!\n");
assert(server->listening);
assert(server->loop == loop);
assert(&server->connection_watcher == watcher);
if(EV_ERROR & revents) {
error("on_connection() got error event, closing server.");
ebb_server_unlisten(server);
return;
}
struct sockaddr_in addr; // connector's address information
socklen_t addr_len = sizeof(addr);
int fd = accept( server->fd
, (struct sockaddr*) & addr
, & addr_len
);
if(fd < 0) {
perror("accept()");
return;
}
ebb_connection *connection = NULL;
if(server->new_connection)
connection = server->new_connection(server, &addr);
if(connection == NULL) {
close(fd);
return;
}
set_nonblock(fd);
connection->fd = fd;
connection->open = TRUE;
connection->server = server;
memcpy(&connection->sockaddr, &addr, addr_len);
if(server->port[0] != '\0')
connection->ip = inet_ntoa(connection->sockaddr.sin_addr);
#ifdef SO_NOSIGPIPE
int arg = 1;
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &arg, sizeof(int));
#endif
#ifdef HAVE_GNUTLS
if(server->secure) {
gnutls_init(&connection->session, GNUTLS_SERVER);
gnutls_transport_set_lowat(connection->session, 0);
gnutls_set_default_priority(connection->session);
gnutls_credentials_set(connection->session, GNUTLS_CRD_CERTIFICATE, connection->server->credentials);
gnutls_transport_set_ptr(connection->session, (gnutls_transport_ptr) fd);
gnutls_transport_set_push_function(connection->session, nosigpipe_push);
gnutls_db_set_ptr (connection->session, &server->session_cache);
gnutls_db_set_store_function (connection->session, session_cache_store);
gnutls_db_set_retrieve_function (connection->session, session_cache_retrieve);
gnutls_db_set_remove_function (connection->session, session_cache_remove);
}
ev_io_set(&connection->handshake_watcher, connection->fd, EV_READ | EV_WRITE);
#endif /* HAVE_GNUTLS */
/* Note: not starting the write watcher until there is data to be written */
ev_io_set(&connection->write_watcher, connection->fd, EV_WRITE);
ev_io_set(&connection->read_watcher, connection->fd, EV_READ);
/* XXX: seperate error watcher? */
ev_timer_again(loop, &connection->timeout_watcher);
#ifdef HAVE_GNUTLS
if(server->secure) {
ev_io_start(loop, &connection->handshake_watcher);
return;
}
#endif
ev_io_start(loop, &connection->read_watcher);
}
/**
* Begin the server listening on a file descriptor. This DOES NOT start the
* event loop. Start the event loop after making this call.
*/
int
ebb_server_listen_on_fd(ebb_server *server, const int fd)
{
assert(server->listening == FALSE);
if (listen(fd, EBB_MAX_CONNECTIONS) < 0) {
perror("listen()");
return -1;
}
set_nonblock(fd); /* XXX superfluous? */
server->fd = fd;
server->listening = TRUE;
ev_io_set (&server->connection_watcher, server->fd, EV_READ);
ev_io_start (server->loop, &server->connection_watcher);
return server->fd;
}
/**
* Begin the server listening on a file descriptor This DOES NOT start the
* event loop. Start the event loop after making this call.
*/
int
ebb_server_listen_on_port(ebb_server *server, const int port)
{
int fd = -1;
struct linger ling = {0, 0};
struct sockaddr_in addr;
int flags = 1;
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket()");
goto error;
}
flags = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags));
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags));
setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(ling));
/* XXX: Sending single byte chunks in a response body? Perhaps there is a
* need to enable the Nagel algorithm dynamically. For now disabling.
*/
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags));
/* the memset call clears nonstandard fields in some impementations that
* otherwise mess things up.
*/
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind()");
goto error;
}
int ret = ebb_server_listen_on_fd(server, fd);
if (ret >= 0) {
sprintf(server->port, "%d", port);
}
return ret;
error:
if(fd > 0) close(fd);
return -1;
}
/**
* Stops the server. Will not accept new connections. Does not drop
* existing connections.
*/
void
ebb_server_unlisten(ebb_server *server)
{
if(server->listening) {
ev_io_stop(server->loop, &server->connection_watcher);
close(server->fd);
server->port[0] = '\0';
server->listening = FALSE;
}
}
/**
* Initialize an ebb_server structure. After calling ebb_server_init set
* the callback server->new_connection and, optionally, callback data
* server->data. The new connection MUST be initialized with
* ebb_connection_init before returning it to the server.
*
* @param server the server to initialize
* @param loop a libev loop
*/
void
ebb_server_init(ebb_server *server, struct ev_loop *loop)
{
server->loop = loop;
server->listening = FALSE;
server->port[0] = '\0';
server->fd = -1;
server->connection_watcher.data = server;
ev_init (&server->connection_watcher, on_connection);
server->secure = FALSE;
#ifdef HAVE_GNUTLS
rbtree_init(&server->session_cache, session_cache_compare);
server->credentials = NULL;
#endif
server->new_connection = NULL;
server->data = NULL;
}
#ifdef HAVE_GNUTLS
/* similar to server_init.
*
* the user of secure server might want to set additional callbacks from
* GNUTLS. In particular
* gnutls_global_set_mem_functions()
* gnutls_global_set_log_function()
* Also see the note above ebb_connection_init() about setting gnutls cache
* access functions
*
* cert_file: the filename of a PEM certificate file
*
* key_file: the filename of a private key. Currently only PKCS-1 encoded
* RSA and DSA private keys are accepted.
*/
int
ebb_server_set_secure (ebb_server *server, const char *cert_file, const char *key_file)
{
server->secure = TRUE;
gnutls_global_init();
gnutls_certificate_allocate_credentials(&server->credentials);
/* todo gnutls_certificate_free_credentials */
int r = gnutls_certificate_set_x509_key_file( server->credentials
, cert_file
, key_file
, GNUTLS_X509_FMT_PEM
);
if(r < 0) {
error("loading certificates");
return -1;
}
return 1;
}
#endif /* HAVE_GNUTLS */
/**
* Initialize an ebb_connection structure. After calling this function you
* must setup callbacks for the different actions the server can take. See
* server.h for which callbacks are availible.
*
* This should be called immediately after allocating space for a new
* ebb_connection structure. Most likely, this will only be called within
* the ebb_server->new_connection callback which you supply.
*
* If using SSL do consider setting
* gnutls_db_set_retrieve_function (connection->session, _);
* gnutls_db_set_remove_function (connection->session, _);
* gnutls_db_set_store_function (connection->session, _);
* gnutls_db_set_ptr (connection->session, _);
* To provide a better means of storing SSL session caches. libebb provides
* only a simple default implementation.
*
* @param connection the connection to initialize
* @param timeout the timeout in seconds
*/
void
ebb_connection_init(ebb_connection *connection)
{
connection->fd = -1;
connection->server = NULL;
connection->ip = NULL;
connection->open = FALSE;
ebb_request_parser_init( &connection->parser );
connection->parser.data = connection;
connection->parser.new_request = new_request_wrapper;
ev_init (&connection->write_watcher, on_writable);
connection->write_watcher.data = connection;
connection->to_write = NULL;
ev_init(&connection->read_watcher, on_readable);
connection->read_watcher.data = connection;
#ifdef HAVE_GNUTLS
connection->handshake_watcher.data = connection;
ev_init(&connection->handshake_watcher, on_handshake);
ev_init(&connection->goodbye_tls_watcher, on_goodbye_tls);
connection->goodbye_tls_watcher.data = connection;
connection->session = NULL;
#endif /* HAVE_GNUTLS */
ev_timer_init(&connection->goodbye_watcher, on_goodbye, 0., 0.);
connection->goodbye_watcher.data = connection;
ev_timer_init(&connection->timeout_watcher, on_timeout, 0., EBB_DEFAULT_TIMEOUT);
connection->timeout_watcher.data = connection;
connection->new_request = NULL;
connection->on_timeout = NULL;
connection->on_close = NULL;
connection->data = NULL;
}
void
ebb_connection_schedule_close (ebb_connection *connection)
{
#ifdef HAVE_GNUTLS
if(connection->server->secure) {
ev_io_set(&connection->goodbye_tls_watcher, connection->fd, EV_READ | EV_WRITE);
ev_io_start(connection->server->loop, &connection->goodbye_tls_watcher);
return;
}
#endif
ev_timer_start(connection->server->loop, &connection->goodbye_watcher);
}
/*
* Resets the timeout to stay alive for another connection->timeout seconds
*/
void
ebb_connection_reset_timeout(ebb_connection *connection)
{
ev_timer_again(connection->server->loop, &connection->timeout_watcher);
}
/**
* Writes a string to the socket. This is actually sets a watcher
* which may take multiple iterations to write the entire string.
*
* This can only be called once at a time. If you call it again
* while the connection is writing another buffer the ebb_connection_write
* will return FALSE and ignore the request.
*/
int
ebb_connection_write (ebb_connection *connection, const char *buf, size_t len, ebb_after_write_cb cb)
{
if(ev_is_active(&connection->write_watcher))
return FALSE;
assert(!CONNECTION_HAS_SOMETHING_TO_WRITE);
connection->to_write = buf;
connection->to_write_len = len;
connection->written = 0;
connection->after_write_cb = cb;
ev_io_start(connection->server->loop, &connection->write_watcher);
return TRUE;
}