X-Git-Url: https://git.tokkee.org/?p=sysdb.git;a=blobdiff_plain;f=src%2Ffrontend%2Fsock.c;h=1f3ec5fd74b70ee903a68ce8678bd2fb69c4a87b;hp=2def8da868f9e94df057681e99d2508abced36c6;hb=ed2c9fc3e4ca6840a5a31c735f0cfc02fd21d4fc;hpb=2bdb80165c2e095b169c59ef18607b30f8758ec7 diff --git a/src/frontend/sock.c b/src/frontend/sock.c index 2def8da..1f3ec5f 100644 --- a/src/frontend/sock.c +++ b/src/frontend/sock.c @@ -38,6 +38,7 @@ #include "utils/error.h" #include "utils/llist.h" #include "utils/os.h" +#include "utils/ssl.h" #include "utils/strbuf.h" #include @@ -65,6 +66,8 @@ #include +#include +#include #include #include @@ -76,9 +79,13 @@ typedef struct { char *address; int type; + /* optional SSL settings */ + sdb_ssl_options_t ssl_opts; + sdb_ssl_server_t *ssl; + + /* listener configuration */ int sock_fd; - int (*accept)(sdb_conn_t *); - int (*peer)(sdb_conn_t *); + int (*setup)(sdb_conn_t *, void *); } listener_t; typedef struct { @@ -93,19 +100,50 @@ struct sdb_fe_socket { listener_t *listeners; size_t listeners_num; + /* list of open, idle connections; active connections will be passed on to + * the connection handler threads which place them back after handling + * pending actions; the trigger pipe is used to notify the main thread + * after adding a connection (back) to the list */ sdb_llist_t *open_connections; + int trigger[2]; +#define TRIGGER_READ 0 +#define TRIGGER_WRITE 1 /* channel used for communication between main * and connection handler threads */ sdb_channel_t *chan; }; +/* + * SSL helper functions + */ + +static ssize_t +ssl_read(sdb_conn_t *conn, size_t n) +{ + char buf[n]; + ssize_t ret; + + ret = sdb_ssl_session_read(conn->ssl_session, buf, n); + if (ret <= 0) + return ret; + + sdb_strbuf_memappend(conn->buf, buf, ret); + return ret; +} /* ssl_read */ + +static ssize_t +ssl_write(sdb_conn_t *conn, const void *buf, size_t n) +{ + return sdb_ssl_session_write(conn->ssl_session, buf, n); +} /* ssl_write */ + /* * connection management functions */ static int -unixsock_peer(sdb_conn_t *conn) +setup_unixsock(sdb_conn_t *conn, void __attribute__((unused)) *user_data) { uid_t uid; @@ -142,7 +180,7 @@ unixsock_peer(sdb_conn_t *conn) return -1; } return 0; -} /* unixsock_peer */ +} /* setup_unixsock */ static int open_unixsock(listener_t *listener) @@ -198,7 +236,7 @@ open_unixsock(listener_t *listener) return -1; } - listener->peer = unixsock_peer; + listener->setup = setup_unixsock; return 0; } /* open_unixsock */ @@ -217,6 +255,108 @@ close_unixsock(listener_t *listener) unlink(listener->address); } /* close_unixsock */ +static int +finish_tcp(sdb_conn_t *conn) +{ + if (! conn->ssl_session) + return 0; + + sdb_ssl_session_destroy(conn->ssl_session); + conn->ssl_session = NULL; + return 0; +} /* finish_tcp */ + +static int +setup_tcp(sdb_conn_t *conn, void *user_data) +{ + listener_t *listener = user_data; + + conn->ssl_session = sdb_ssl_server_accept(listener->ssl, conn->fd); + if (! conn->ssl_session) + return -1; + + conn->username = sdb_ssl_session_peer(conn->ssl_session); + + conn->finish = finish_tcp; + conn->read = ssl_read; + conn->write = ssl_write; + return 0; +} /* setup_tcp */ + +static int +open_tcp(listener_t *listener) +{ + struct addrinfo *ai, *ai_list = NULL; + int status; + + assert(listener); + + listener->ssl = sdb_ssl_server_create(&listener->ssl_opts); + if (! listener->ssl) + return -1; + + if ((status = sdb_resolve(SDB_NET_TCP, listener->address, &ai_list))) { + sdb_log(SDB_LOG_ERR, "frontend: Failed to resolve '%s': %s", + listener->address, gai_strerror(status)); + return -1; + } + + for (ai = ai_list; ai != NULL; ai = ai->ai_next) { + char errbuf[1024]; + int reuse = 1; + + listener->sock_fd = socket(ai->ai_family, + ai->ai_socktype, ai->ai_protocol); + if (listener->sock_fd < 0) { + sdb_log(SDB_LOG_ERR, "frontend: Failed to open socket for %s: %s", + listener->address, + sdb_strerror(errno, errbuf, sizeof(errbuf))); + continue; + } + + if (setsockopt(listener->sock_fd, SOL_SOCKET, SO_REUSEADDR, + &reuse, sizeof(reuse)) < 0) { + sdb_log(SDB_LOG_ERR, "frontend: Failed to set socket option: %s", + sdb_strerror(errno, errbuf, sizeof(errbuf))); + close(listener->sock_fd); + listener->sock_fd = -1; + continue; + } + + if (bind(listener->sock_fd, ai->ai_addr, ai->ai_addrlen) < 0) { + char host[1024], port[32]; + getnameinfo(ai->ai_addr, ai->ai_addrlen, host, sizeof(host), + port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); + sdb_log(SDB_LOG_ERR, "frontend: Failed to bind to %s:%s: %s", + host, port, sdb_strerror(errno, errbuf, sizeof(errbuf))); + close(listener->sock_fd); + listener->sock_fd = -1; + continue; + } + break; + } + freeaddrinfo(ai_list); + + if (listener->sock_fd < 0) + return -1; + + listener->setup = setup_tcp; + return 0; +} /* open_tcp */ + +static void +close_tcp(listener_t *listener) +{ + assert(listener); + + sdb_ssl_server_destroy(listener->ssl); + listener->ssl = NULL; + + if (listener->sock_fd >= 0) + close(listener->sock_fd); + listener->sock_fd = -1; +} /* close_tcp */ + /* * private variables */ @@ -224,9 +364,11 @@ close_unixsock(listener_t *listener) /* the enum has to be sorted the same as the implementations array * to ensure that the type may be used as index into the array */ enum { - LISTENER_UNIXSOCK = 0, /* this is the default */ + LISTENER_TCP = 0, /* this is the default */ + LISTENER_UNIXSOCK, }; static fe_listener_impl_t listener_impls[] = { + { LISTENER_TCP, "tcp", open_tcp, close_tcp }, { LISTENER_UNIXSOCK, "unix", open_unixsock, close_unixsock }, }; @@ -251,6 +393,7 @@ listener_listen(listener_t *listener) listener->address, sdb_strerror(errno, buf, sizeof(buf))); return -1; } + sdb_log(SDB_LOG_INFO, "frontend: Listening on %s", listener->address); return 0; } /* listener_listen */ @@ -274,6 +417,8 @@ get_type(const char *address) size_t len; size_t i; + if (*address == '/') + return LISTENER_UNIXSOCK; sep = strchr(address, (int)':'); if (! sep) return listener_impls[0].type; @@ -289,7 +434,8 @@ get_type(const char *address) return impl->type; } } - return -1; + /* don't report an error, this could be an IPv6 address */ + return listener_impls[0].type; } /* get_type */ static void @@ -299,6 +445,7 @@ listener_destroy(listener_t *listener) return; listener_close(listener); + sdb_ssl_free_options(&listener->ssl_opts); if (listener->address) free(listener->address); @@ -335,6 +482,7 @@ listener_create(sdb_fe_socket_t *sock, const char *address) if ((! strncmp(address, listener_impls[type].prefix, len)) && (address[len] == ':')) address += strlen(listener_impls[type].prefix) + 1; + memset(listener, 0, sizeof(*listener)); listener->sock_fd = -1; listener->address = strdup(address); @@ -346,13 +494,8 @@ listener_create(sdb_fe_socket_t *sock, const char *address) return NULL; } listener->type = type; - listener->accept = NULL; - - if (listener_impls[type].open(listener)) { - /* prints error */ - listener_destroy(listener); - return NULL; - } + listener->setup = NULL; + listener->ssl = NULL; ++sock->listeners_num; return listener; @@ -427,6 +570,11 @@ connection_handler(void *data) "connection %s to list of open connections", SDB_OBJ(conn)->name); } + if (write(sock->trigger[TRIGGER_WRITE], "", 1) <= 0) { + /* This shouldn't happen and it's not critical; in the worst cases + * it slows us down. */ + sdb_log(SDB_LOG_WARNING, "frontend: Failed to trigger main loop"); + } /* pass ownership back to list; or destroy in case of an error */ sdb_object_deref(SDB_OBJ(conn)); @@ -440,21 +588,11 @@ connection_accept(sdb_fe_socket_t *sock, listener_t *listener) sdb_object_t *obj; int status; - obj = SDB_OBJ(sdb_connection_accept(listener->sock_fd)); + obj = SDB_OBJ(sdb_connection_accept(listener->sock_fd, + listener->setup, listener)); if (! obj) return -1; - if (listener->accept && listener->accept(CONN(obj))) { - /* accept() is expected to log an error */ - sdb_object_deref(obj); - return -1; - } - if (listener->peer && listener->peer(CONN(obj))) { - /* peer() is expected to log an error */ - sdb_object_deref(obj); - return -1; - } - status = sdb_llist_append(sock->open_connections, obj); if (status) sdb_log(SDB_LOG_ERR, "frontend: Failed to append " @@ -516,16 +654,44 @@ sdb_fe_socket_t * sdb_fe_sock_create(void) { sdb_fe_socket_t *sock; + int flags; sock = calloc(1, sizeof(*sock)); if (! sock) return NULL; + sock->trigger[TRIGGER_READ] = sock->trigger[TRIGGER_WRITE] = -1; sock->open_connections = sdb_llist_create(); if (! sock->open_connections) { sdb_fe_sock_destroy(sock); return NULL; } + + if (pipe(sock->trigger)) { + char errbuf[1024]; + sdb_log(SDB_LOG_ERR, "frontend: Failed to create pipe: %s", + sdb_strerror(errno, errbuf, sizeof(errbuf))); + sdb_fe_sock_destroy(sock); + return NULL; + } + /* TODO: Can we do a zero-byte write as well to trigger select()? + * Linux does not seem to support the I_SWROPT ioctl on a pipe. */ + flags = fcntl(sock->trigger[TRIGGER_WRITE], F_GETFL); + if (fcntl(sock->trigger[TRIGGER_WRITE], F_SETFL, flags | O_NONBLOCK)) { + char errbuf[1024]; + sdb_log(SDB_LOG_ERR, "frontend: Failed to switch pipe to non-blocking " + "mode: %s", sdb_strerror(errno, errbuf, sizeof(errbuf))); + sdb_fe_sock_destroy(sock); + return NULL; + } + flags = fcntl(sock->trigger[TRIGGER_READ], F_GETFL); + if (fcntl(sock->trigger[TRIGGER_READ], F_SETFL, flags | O_NONBLOCK)) { + char errbuf[1024]; + sdb_log(SDB_LOG_ERR, "frontend: Failed to switch pipe to non-blocking " + "mode: %s", sdb_strerror(errno, errbuf, sizeof(errbuf))); + sdb_fe_sock_destroy(sock); + return NULL; + } return sock; } /* sdb_fe_sock_create */ @@ -537,13 +703,20 @@ sdb_fe_sock_destroy(sdb_fe_socket_t *sock) socket_clear(sock); + if (sock->trigger[TRIGGER_WRITE] >= 0) + close(sock->trigger[TRIGGER_WRITE]); + if (sock->trigger[TRIGGER_READ] >= 0) + close(sock->trigger[TRIGGER_READ]); + sock->trigger[TRIGGER_READ] = sock->trigger[TRIGGER_WRITE] = -1; + sdb_llist_destroy(sock->open_connections); sock->open_connections = NULL; free(sock); } /* sdb_fe_sock_destroy */ int -sdb_fe_sock_add_listener(sdb_fe_socket_t *sock, const char *address) +sdb_fe_sock_add_listener(sdb_fe_socket_t *sock, const char *address, + const sdb_ssl_options_t *opts) { listener_t *listener; @@ -553,6 +726,44 @@ sdb_fe_sock_add_listener(sdb_fe_socket_t *sock, const char *address) listener = listener_create(sock, address); if (! listener) return -1; + + if (opts) { + int ret = 0; + + if (opts->ca_file) { + listener->ssl_opts.ca_file = strdup(opts->ca_file); + if (! listener->ssl_opts.ca_file) + ret = -1; + } + if (opts->key_file) { + listener->ssl_opts.key_file = strdup(opts->key_file); + if (! listener->ssl_opts.key_file) + ret = -1; + } + if (opts->cert_file) { + listener->ssl_opts.cert_file = strdup(opts->cert_file); + if (! listener->ssl_opts.cert_file) + ret = -1; + } + if (opts->crl_file) { + listener->ssl_opts.crl_file = strdup(opts->crl_file); + if (! listener->ssl_opts.crl_file) + ret = -1; + } + + if (ret) { + listener_destroy(listener); + --sock->listeners_num; + return ret; + } + } + + if (listener_impls[listener->type].open(listener)) { + /* prints error */ + listener_destroy(listener); + --sock->listeners_num; + return -1; + } return 0; } /* sdb_fe_sock_add_listener */ @@ -635,6 +846,7 @@ sdb_fe_sock_listen_and_serve(sdb_fe_socket_t *sock, sdb_fe_loop_t *loop) FD_ZERO(&exceptions); ready = sockets; + FD_SET(sock->trigger[TRIGGER_READ], &ready); iter = sdb_llist_get_iter(sock->open_connections); if (! iter) { @@ -675,6 +887,12 @@ sdb_fe_sock_listen_and_serve(sdb_fe_socket_t *sock, sdb_fe_loop_t *loop) else if (! n) continue; + if (FD_ISSET(sock->trigger[TRIGGER_READ], &ready)) { + char buf[1024]; + while (read(sock->trigger[TRIGGER_READ], buf, sizeof(buf)) > 0) + /* do nothing */; + } + /* handle new and open connections */ if (socket_handle_incoming(sock, &ready, &exceptions)) break;