/** * Sesimos - secure, simple, modern web server * @brief Main executable * @file src/server.c * @author Lorenz Stechauner * @date 2020-12-03 */ #include "defs.h" #include "server.h" #include "logger.h" #include "async.h" #include "cache_handler.h" #include "lib/config.h" #include "lib/proxy.h" #include "lib/geoip.h" #include "workers.h" #include "worker/func.h" #include "lib/list.h" #include "lib/utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include const char *config_file; static int sockets[NUM_SOCKETS]; static SSL_CTX *contexts[CONFIG_MAX_CERT_CONFIG]; static client_ctx_t **clients; static sem_t sem_clients_lock; static void clean(void) { notice("Cleaning sesimos cache and metadata files..."); // remove legacy files // /.../server/, /.../server/cache if (rm_rf("/var/sesimos/server") != 0) { error("Unable to remove /var/sesimos/server/"); } else if (!errno) { notice("Successfully removed /var/sesimos/server/"); } errno = 0; // remove cache and metadata files char buf[512]; for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) { host_config_t *hc = &config.hosts[i]; if (hc->type == CONFIG_TYPE_UNSET) break; if (hc->type != CONFIG_TYPE_LOCAL) continue; snprintf(buf, sizeof(buf), "%s/.sesimos", hc->local.webroot); if (rm_rf(buf) != 0) { error("Unable to remove %s/", buf); } else if (!errno) { notice("Successfully removed %s/", buf); } errno = 0; } notice("Cleaned all sesimos cache and metadata files!"); } static int ssl_servername_cb(SSL *ssl, int *ad, void *arg) { const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); if (servername != NULL) { const host_config_t *conf = get_host_config(servername); if (conf != NULL) SSL_set_SSL_CTX(ssl, contexts[conf->cert]); } return SSL_TLSEXT_ERR_OK; } void server_free_client(client_ctx_t *ctx) { // try to lock clients list retry: if (sem_wait(&sem_clients_lock) != 0) { if (errno == EINTR) { goto retry; } else { critical("Unable to lock clients list"); return; } } // delete from list clients = list_delete(clients, &ctx); if (clients == NULL) { critical("Unable to delete context from list"); return; } // unlock clients list sem_post(&sem_clients_lock); // free data free(ctx); } static void ssl_free() { for (int i = 0; i < CONFIG_MAX_CERT_CONFIG; i++) { const cert_config_t *conf = &config.certs[i]; if (conf->name[0] == 0) break; SSL_CTX_free(contexts[i]); } } static void accept_cb(void *arg) { int i = (int) (((int *) arg) - sockets); int fd = sockets[i]; client_ctx_t *client_ctx = malloc(sizeof(client_ctx_t)); if (client_ctx == NULL) { critical("Unable to allocate memory for client context"); errno = 0; return; } sock *client = &client_ctx->socket; client->ctx = contexts[0]; socklen_t addr_len = sizeof(client->_addr); int client_fd = accept(fd, &client->_addr.sock, &addr_len); if (client_fd < 0) { critical("Unable to accept connection"); free(client_ctx); return; } client->socket = client_fd; client->enc = (i == 1); client->ts_start = clock_micros(); client->ts_last = client->ts_start; client_ctx->cnx_s = client->ts_start; client_ctx->cnx_e = -1, client_ctx->req_s = -1, client_ctx->req_e = -1, client_ctx->res_ts = -1; // try to lock clients list retry: if (sem_wait(&sem_clients_lock) != 0) { if (errno == EINTR) { goto retry; } else { critical("Unable to lock clients list"); return; } } // append to list clients = list_append(clients, &client_ctx); if (clients == NULL) { critical("Unable to add client context to list"); free(client_ctx); errno = 0; return; } // unlock clients list sem_post(&sem_clients_lock); tcp_accept(client_ctx); } static void accept_err_cb(void *arg) { int i = (int) (((int *) arg) - sockets); int fd = sockets[i]; // TODO accept error callback } static void terminate_forcefully(int sig) { fprintf(stderr, "\n"); notice("Terminating forcefully!"); geoip_free(); notice("Goodbye"); exit(2); } static void terminate_gracefully(int sig) { if (sig != 0) fprintf(stderr, "\n"); notice("Terminating gracefully..."); struct sigaction act = {0}; act.sa_handler = terminate_forcefully; sigaction(SIGINT, &act, NULL); sigaction(SIGTERM, &act, NULL); for (int i = 0; i < NUM_SOCKETS; i++) { close(sockets[i]); } cache_stop(); workers_stop(); workers_destroy(); while (list_size(clients) > 0) tcp_close(clients[0]); logger_set_prefix(""); proxy_close_all(); async_stop(); } static void nothing(int sig) {} int main(int argc, char *const argv[]) { const int YES = 1; int ret; int mode = 0; memset(sockets, 0, sizeof(sockets)); const struct sockaddr_in6 addresses[2] = { {.sin6_family = AF_INET6, .sin6_addr = IN6ADDR_ANY_INIT, .sin6_port = htons(80)}, {.sin6_family = AF_INET6, .sin6_addr = IN6ADDR_ANY_INIT, .sin6_port = htons(443)} }; logger_set_name("server"); if (setvbuf(stdout, NULL, _IOLBF, 0) != 0 || setvbuf(stderr, NULL, _IOLBF, 0) != 0) { critical("Unable to set stdout/stderr to line-buffered mode"); return 1; } printf("sesimos web server " SERVER_VERSION "\n"); static const struct option long_opts[] = { {"help", no_argument, 0, 'h'}, {"clean", no_argument, 0, 'C'}, {"config", required_argument, 0, 'c'}, { 0, 0, 0, 0 } }; config_file = NULL; for (int c, opt_idx; (c = getopt_long(argc, argv, "hCc:", long_opts, &opt_idx)) != -1;) { switch (c) { case 'h': fprintf(stderr, "Usage: sesimos [-h] [-c ]\n" "\n" "Options:\n" " -c, --config path to the config file. If not provided, default will be used\n" " -C, --clean clear cached files and other metadata\n" " -h, --help print this dialogue\n"); return 0; case 'c': config_file = optarg; break; case 'C': mode = 1; break; case '?': default: critical("Unable to parse arguments"); return 1; } } if (optind != argc) { critical("No positional arguments expected"); return 1; } if (config_load(config_file == NULL ? DEFAULT_CONFIG_FILE : config_file) != 0) return 1; if (mode == 1) { clean(); return 0; } if ((sockets[0] = socket(AF_INET6, SOCK_STREAM, 0)) == -1 || (sockets[1] = socket(AF_INET6, SOCK_STREAM, 0)) == -1) { critical("Unable to create socket"); return 1; } for (int i = 0; i < NUM_SOCKETS; i++) { if (setsockopt(sockets[i], SOL_SOCKET, SO_REUSEADDR, &YES, sizeof(YES)) < 0) { critical("Unable to set options for socket %i", i); return 1; } } if (bind(sockets[0], (struct sockaddr *) &addresses[0], sizeof(addresses[0])) == -1 || bind(sockets[1], (struct sockaddr *) &addresses[1], sizeof(addresses[1])) == -1) { critical("Unable to bind socket to address"); return 1; } struct sigaction act = {0}; act.sa_handler = terminate_gracefully; sigaction(SIGINT, &act, NULL); sigaction(SIGTERM, &act, NULL); act.sa_handler = nothing; sigaction(SIGUSR1, &act, NULL); sigaction(SIGPIPE, &act, NULL); if ((ret = geoip_init(config.geoip_dir)) != 0) { if (ret == -1) { critical("Unable to initialize geoip"); } return 1; } for (int i = 0; i < CONFIG_MAX_CERT_CONFIG; i++) { const cert_config_t *conf = &config.certs[i]; if (conf->name[0] == 0) break; contexts[i] = SSL_CTX_new(TLS_server_method()); SSL_CTX *ctx = contexts[i]; SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE); SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4"); SSL_CTX_set_ecdh_auto(ctx, 1); SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb); if (SSL_CTX_use_certificate_chain_file(ctx, conf->full_chain) != 1) { critical("Unable to load certificate chain file: %s: %s", ERR_reason_error_string(ERR_get_error()), conf->full_chain); geoip_free(); return 1; } if (SSL_CTX_use_PrivateKey_file(ctx, conf->priv_key, SSL_FILETYPE_PEM) != 1) { critical("Unable to load private key file: %s: %s", ERR_reason_error_string(ERR_get_error()), conf->priv_key); geoip_free(); return 1; } } clients = list_create(sizeof(client_ctx_t *), 64); if (clients == NULL) { critical("Unable to initialize client list"); ssl_free(); return 1; } if (sem_init(&sem_clients_lock, 0, 1) != 0) { critical("Unable to create clients lock semaphore"); ssl_free(); list_free(clients); return 1; } if (async_init() != 0) { critical("Unable to initialize async thread"); ssl_free(); geoip_free(); list_free(clients); sem_destroy(&sem_clients_lock); return 1; } if (proxy_preload() != 0) { critical("Unable to initialize proxy"); ssl_free(); geoip_free(); list_free(clients); sem_destroy(&sem_clients_lock); async_free(); return 1; } for (int i = 0; i < NUM_SOCKETS; i++) { if (listen(sockets[i], LISTEN_BACKLOG) < 0) { critical("Unable to listen on socket %i", i); ssl_free(); geoip_free(); list_free(clients); sem_destroy(&sem_clients_lock); async_free(); proxy_unload(); return 1; } } logger_init(); if ((ret = cache_init()) != 0) { if (ret == -1) critical("Unable to initialize cache"); ssl_free(); geoip_free(); list_free(clients); sem_destroy(&sem_clients_lock); async_free(); proxy_unload(); logger_stop(); logger_join(); return 1; } logger_set_name("main"); workers_init(); for (int i = 0; i < NUM_SOCKETS; i++) { async_fd(sockets[i], ASYNC_WAIT_READ, ASYNC_KEEP, &sockets[i], accept_cb, accept_err_cb, accept_err_cb); } notice("Ready to accept connections"); int error = 0; async_thread(); if (errno != 0) { errno = 0; error = 2; terminate_gracefully(0); } notice("Goodbye!"); // cleanup ssl_free(); list_free(clients); sem_destroy(&sem_clients_lock); geoip_free(); proxy_unload(); cache_join(); async_free(); logger_stop(); logger_join(); return error; }