diff --git a/config.h.cmake b/config.h.cmake index 24e7266..0f0a011 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -1,25 +1,31 @@ #ifndef NWWEBUI_CONFIG_H #define NWWEBUI_CONFIG_H -#define DEFAULT_SMART_CONF "@MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf" -#define DEFAULT_SMART_PERL "@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/smart" +#define NWWEBUI_NAME "nwwebui" +#define NWWEBUI_VERSION "@MARS_NWE_VERSION@" -#define LOG_PATH_DEFAULT "@MARS_NWE_LOG_DIR@/nwwebui.log" +#define DEFAULT_SMART_CONF "@MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf" +#define DEFAULT_SMART_PERL "@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/smart" -#define LOG_LEVEL_ERROR 0 -#define LOG_LEVEL_INFO 1 -#define LOG_LEVEL_DEBUG 2 -#define LOG_LEVEL_DEFAULT LOG_LEVEL_INFO +#define LOG_PATH_DEFAULT "@MARS_NWE_LOG_DIR@/nwwebui.log" + +#define LOG_LEVEL_ERROR 0 +#define LOG_LEVEL_INFO 1 +#define LOG_LEVEL_DEBUG 2 +#define LOG_LEVEL_DEFAULT LOG_LEVEL_INFO #define DEFAULT_BIND_IP "0.0.0.0" -#define DEFAULT_SSL_ENABLE 0 +#define DEFAULT_SSL_ENABLE 1 #define DEFAULT_HTTP_PORT 9080 #define DEFAULT_HTTPS_PORT 9443 -#define DEFAULT_CERT_FILE "@MARS_NWE_INSTALL_FULL_CONFDIR@/server.crt" -#define DEFAULT_KEY_FILE "@MARS_NWE_INSTALL_FULL_CONFDIR@/server.key" +#define DEFAULT_CERT_FILE "@MARS_NWE_INSTALL_FULL_CONFDIR@/server.crt" +#define DEFAULT_KEY_FILE "@MARS_NWE_INSTALL_FULL_CONFDIR@/server.key" + +#define DEFAULT_PID_FILE "@MARS_NWE_PID_DIR@/nwwebui.pid" +#define DEFAULT_DAEMONIZE 0 #define NW_BACKLOG 64 #define NW_BUF_SZ 16384 -#endif \ No newline at end of file +#endif diff --git a/nwwebui.c b/nwwebui.c index c38716f..0fd8921 100644 --- a/nwwebui.c +++ b/nwwebui.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -28,9 +29,11 @@ typedef struct { int ssl_enable; int http_port; int https_port; + int daemonize; char cert_file[512]; char key_file[512]; + char pid_file[512]; char smart_conf[512]; char smart_perl_path[512]; @@ -39,6 +42,27 @@ typedef struct { static FILE *g_log_fp = NULL; static int g_log_level = LOG_LEVEL_DEFAULT; +static volatile sig_atomic_t g_terminate = 0; +static volatile sig_atomic_t g_got_signal = 0; + +static int g_pid_file_written = 0; +static char g_pid_file_path[512] = { 0 }; + +/* ------------------------------------------------------------ */ +/* Console lifecycle output */ +/* ------------------------------------------------------------ */ + +static void console_msg(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fputc('\n', stderr); + fflush(stderr); +} + /* ------------------------------------------------------------ */ /* Logging */ /* ------------------------------------------------------------ */ @@ -61,12 +85,13 @@ static void log_msg(int level, const char *fmt, ...) { time_t now = time(NULL); struct tm tm_now; - localtime_r(&now, &tm_now); - char tbuf[64]; + const char *lvl = "INFO"; + va_list ap; + + localtime_r(&now, &tm_now); strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", &tm_now); - const char *lvl = "INFO"; if (level == LOG_LEVEL_ERROR) { lvl = "ERROR"; } else if (level == LOG_LEVEL_DEBUG) { @@ -75,7 +100,6 @@ static void log_msg(int level, const char *fmt, ...) { fprintf(g_log_fp, "[%s] [%s] ", tbuf, lvl); - va_list ap; va_start(ap, fmt); vfprintf(g_log_fp, fmt, ap); va_end(ap); @@ -101,14 +125,17 @@ static void ssl_die(const char *msg) { static void trim(char *s) { char *p = s; + size_t len; + while (*p && isspace((unsigned char)*p)) { p++; } + if (p != s) { memmove(s, p, strlen(p) + 1); } - size_t len = strlen(s); + len = strlen(s); while (len > 0 && isspace((unsigned char)s[len - 1])) { s[len - 1] = '\0'; len--; @@ -117,6 +144,7 @@ static void trim(char *s) { static void strip_quotes(char *s) { size_t len = strlen(s); + if (len >= 2) { if ((s[0] == '\'' && s[len - 1] == '\'') || (s[0] == '"' && s[len - 1] == '"')) { @@ -127,12 +155,9 @@ static void strip_quotes(char *s) { } static int parse_perl_assignment(const char *line, char *key, size_t ksz, char *val, size_t vsz) { - /* Expected format: - $variable = 'value'; - $variable = 1234; - */ - const char *p = line; + size_t ki = 0; + size_t vi = 0; while (*p && isspace((unsigned char)*p)) { p++; @@ -142,7 +167,6 @@ static int parse_perl_assignment(const char *line, char *key, size_t ksz, char * } p++; - size_t ki = 0; while (*p && (isalnum((unsigned char)*p) || *p == '_')) { if (ki + 1 < ksz) { key[ki++] = *p; @@ -163,7 +187,6 @@ static int parse_perl_assignment(const char *line, char *key, size_t ksz, char * p++; } - size_t vi = 0; while (*p && *p != ';' && *p != '\n' && *p != '\r') { if (vi + 1 < vsz) { val[vi++] = *p; @@ -194,6 +217,180 @@ static int set_nonblocking(int fd) { return 0; } +static void print_usage(const char *progname) { + fprintf(stdout, + "%s %s\n" + "\n" + "Usage: %s [-h] [-d] [-s] [-c ] [-p ]\n" + "\n" + "Options:\n" + " -h, --help Show this help text and exit\n" + " -d, --daemon Run in daemon mode\n" + " -s, --stop Stop the running nwwebui instance\n" + " -c, --config Use an alternate smart.conf path\n" + " -p, --pidfile Override PID file path\n", + NWWEBUI_NAME, NWWEBUI_VERSION, progname); +} + +/* ------------------------------------------------------------ */ +/* PID file handling */ +/* ------------------------------------------------------------ */ + +static void remove_pid_file(void) { + if (g_pid_file_written) { + unlink(g_pid_file_path); + g_pid_file_written = 0; + } +} + +static void write_pid_file(const char *path) { + FILE *fp = fopen(path, "w"); + if (!fp) { + die("fopen pid file"); + } + + fprintf(fp, "%ld\n", (long)getpid()); + fclose(fp); + + snprintf(g_pid_file_path, sizeof(g_pid_file_path), "%s", path); + g_pid_file_written = 1; +} + +static pid_t read_pid_file(const char *path) { + FILE *fp; + long pid = -1; + + fp = fopen(path, "r"); + if (!fp) { + return -1; + } + + if (fscanf(fp, "%ld", &pid) != 1) { + fclose(fp); + return -1; + } + + fclose(fp); + + if (pid <= 0) { + return -1; + } + + return (pid_t)pid; +} + +static int process_exists(pid_t pid) { + if (pid <= 0) { + return 0; + } + + if (kill(pid, 0) == 0) { + return 1; + } + + if (errno == EPERM) { + return 1; + } + + return 0; +} + +static int pid_is_nwwebui(pid_t pid) { + char path[128]; + char name[256]; + FILE *fp; + + snprintf(path, sizeof(path), "/proc/%ld/comm", (long)pid); + + fp = fopen(path, "r"); + if (!fp) { + return 0; + } + + if (!fgets(name, sizeof(name), fp)) { + fclose(fp); + return 0; + } + + fclose(fp); + + name[strcspn(name, "\r\n")] = '\0'; + + return strcmp(name, "nwwebui") == 0; +} + +static int stop_running_instance(const char *pid_file) { + pid_t pid = read_pid_file(pid_file); + + if (pid <= 0) { + fprintf(stderr, "Could not read PID file: %s\n", pid_file); + return EXIT_FAILURE; + } + + if (!process_exists(pid)) { + fprintf(stderr, "No running process found for PID %ld\n", (long)pid); + unlink(pid_file); + return EXIT_FAILURE; + } + + if (!pid_is_nwwebui(pid)) { + fprintf(stderr, "PID %ld is not a nwwebui process\n", (long)pid); + return EXIT_FAILURE; + } + + if (kill(pid, SIGTERM) < 0) { + fprintf(stderr, "Failed to stop nwwebui with PID %ld: %s\n", + (long)pid, strerror(errno)); + return EXIT_FAILURE; + } + + fprintf(stderr, "Sent SIGTERM to %s %s with PID %ld\n", + NWWEBUI_NAME, NWWEBUI_VERSION, (long)pid); + return EXIT_SUCCESS; +} + +/* ------------------------------------------------------------ */ +/* Daemonization */ +/* ------------------------------------------------------------ */ + +static void daemonize_process(void) { + pid_t pid; + + pid = fork(); + if (pid < 0) { + die("fork"); + } + if (pid > 0) { + exit(EXIT_SUCCESS); + } + + if (setsid() < 0) { + die("setsid"); + } + + pid = fork(); + if (pid < 0) { + die("fork"); + } + if (pid > 0) { + exit(EXIT_SUCCESS); + } + + if (chdir("/") < 0) { + die("chdir"); + } + + umask(0); + + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + open("/dev/null", O_RDONLY); + open("/dev/null", O_WRONLY); + open("/dev/null", O_WRONLY); +} + /* ------------------------------------------------------------ */ /* smart.conf loading */ /* ------------------------------------------------------------ */ @@ -206,9 +403,11 @@ static void init_defaults(nw_config_t *cfg, const char *smart_conf_path) { cfg->ssl_enable = DEFAULT_SSL_ENABLE; cfg->http_port = DEFAULT_HTTP_PORT; cfg->https_port = DEFAULT_HTTPS_PORT; + cfg->daemonize = DEFAULT_DAEMONIZE; snprintf(cfg->cert_file, sizeof(cfg->cert_file), "%s", DEFAULT_CERT_FILE); snprintf(cfg->key_file, sizeof(cfg->key_file), "%s", DEFAULT_KEY_FILE); + snprintf(cfg->pid_file, sizeof(cfg->pid_file), "%s", DEFAULT_PID_FILE); snprintf(cfg->smart_perl_path, sizeof(cfg->smart_perl_path), "%s", DEFAULT_SMART_PERL); snprintf(cfg->smart_conf, sizeof(cfg->smart_conf), "%s", smart_conf_path); @@ -216,12 +415,13 @@ static void init_defaults(nw_config_t *cfg, const char *smart_conf_path) { static void load_smart_conf(nw_config_t *cfg) { FILE *fp = fopen(cfg->smart_conf, "r"); + char line[2048]; + if (!fp) { log_msg(LOG_LEVEL_ERROR, "Could not open smart.conf: %s", cfg->smart_conf); die("fopen smart.conf"); } - char line[2048]; while (fgets(line, sizeof(line), fp)) { char key[256]; char val[1024]; @@ -240,6 +440,10 @@ static void load_smart_conf(nw_config_t *cfg) { cfg->http_port = atoi(val); } else if (strcmp(key, "nw_https_port") == 0) { cfg->https_port = atoi(val); + } else if (strcmp(key, "nw_daemonize") == 0) { + cfg->daemonize = atoi(val); + } else if (strcmp(key, "nw_pid_file") == 0) { + snprintf(cfg->pid_file, sizeof(cfg->pid_file), "%s", val); } else if (strcmp(key, "nw_cert_file") == 0) { snprintf(cfg->cert_file, sizeof(cfg->cert_file), "%s", val); } else if (strcmp(key, "nw_key_file") == 0) { @@ -272,17 +476,18 @@ static void load_smart_conf(nw_config_t *cfg) { static int create_listener(const char *bind_ip, int port) { int fd = socket(AF_INET, SOCK_STREAM, 0); + int yes = 1; + struct sockaddr_in addr; + if (fd < 0) { die("socket"); } - int yes = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) { close(fd); die("setsockopt(SO_REUSEADDR)"); } - struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons((uint16_t)port); @@ -358,6 +563,7 @@ static ssize_t send_all_ssl(SSL *ssl, const unsigned char *buf, size_t len) { static pid_t spawn_smart_perl(const nw_config_t *cfg, int *child_stdin_fd, int *child_stdout_fd) { int inpipe[2] = { -1, -1 }; int outpipe[2] = { -1, -1 }; + pid_t pid; if (pipe(inpipe) < 0) { die("pipe stdin"); @@ -369,7 +575,7 @@ static pid_t spawn_smart_perl(const nw_config_t *cfg, int *child_stdin_fd, int * die("pipe stdout"); } - pid_t pid = fork(); + pid = fork(); if (pid < 0) { close(inpipe[0]); close(inpipe[1]); @@ -379,11 +585,6 @@ static pid_t spawn_smart_perl(const nw_config_t *cfg, int *child_stdin_fd, int * } if (pid == 0) { - /* Child process: - stdin <- parent writes client request data here - stdout -> parent reads response data here - stderr -> merged into stdout for logging/debugging - */ if (dup2(inpipe[0], STDIN_FILENO) < 0) { perror("dup2 stdin"); _exit(127); @@ -471,6 +672,8 @@ static void handle_connection_plain(int client_fd, const nw_config_t *cfg) { for (;;) { struct pollfd pfds[2]; + int pr; + pfds[0].fd = client_fd; pfds[0].events = POLLIN | POLLHUP | POLLERR; pfds[0].revents = 0; @@ -479,7 +682,7 @@ static void handle_connection_plain(int client_fd, const nw_config_t *cfg) { pfds[1].events = POLLIN | POLLHUP | POLLERR; pfds[1].revents = 0; - int pr = poll(pfds, 2, -1); + pr = poll(pfds, 2, -1); if (pr < 0) { if (errno == EINTR) { continue; @@ -488,7 +691,6 @@ static void handle_connection_plain(int client_fd, const nw_config_t *cfg) { break; } - /* Client -> Perl */ if (pfds[0].revents & (POLLIN | POLLHUP | POLLERR)) { ssize_t n = read(client_fd, buf, sizeof(buf)); if (n > 0) { @@ -504,7 +706,6 @@ static void handle_connection_plain(int client_fd, const nw_config_t *cfg) { } } - /* Perl -> Client */ if (pfds[1].revents & (POLLIN | POLLHUP | POLLERR)) { ssize_t rn = read(child_stdout_fd, buf, sizeof(buf)); if (rn > 0) { @@ -575,6 +776,8 @@ static void handle_connection_tls(SSL_CTX *ctx, int client_fd, const nw_config_t for (;;) { struct pollfd pfds[2]; + int pr; + pfds[0].fd = client_fd; pfds[0].events = POLLIN | POLLHUP | POLLERR; pfds[0].revents = 0; @@ -583,7 +786,7 @@ static void handle_connection_tls(SSL_CTX *ctx, int client_fd, const nw_config_t pfds[1].events = POLLIN | POLLHUP | POLLERR; pfds[1].revents = 0; - int pr = poll(pfds, 2, -1); + pr = poll(pfds, 2, -1); if (pr < 0) { if (errno == EINTR) { continue; @@ -592,7 +795,6 @@ static void handle_connection_tls(SSL_CTX *ctx, int client_fd, const nw_config_t break; } - /* TLS client -> Perl */ if ((pfds[0].revents & (POLLIN | POLLHUP | POLLERR)) || SSL_pending(ssl) > 0) { int n = SSL_read(ssl, buf, sizeof(buf)); if (n > 0) { @@ -612,7 +814,6 @@ static void handle_connection_tls(SSL_CTX *ctx, int client_fd, const nw_config_t } } - /* Perl -> TLS client */ if (pfds[1].revents & (POLLIN | POLLHUP | POLLERR)) { ssize_t rn = read(child_stdout_fd, buf, sizeof(buf)); if (rn > 0) { @@ -649,7 +850,7 @@ cleanup: } /* ------------------------------------------------------------ */ -/* SIGCHLD handler */ +/* Signal handlers */ /* ------------------------------------------------------------ */ static void reap_children(int sig) { @@ -663,18 +864,42 @@ static void reap_children(int sig) { } } +static void term_handler(int sig) { + g_got_signal = sig; + g_terminate = 1; +} + /* ------------------------------------------------------------ */ /* Main */ /* ------------------------------------------------------------ */ int main(int argc, char **argv) { const char *smart_conf_path = DEFAULT_SMART_CONF; + const char *pidfile_override = NULL; int http_fd = -1; int https_fd = -1; SSL_CTX *ctx = NULL; + int force_daemon = 0; + int stop_mode = 0; + int i; - if (argc > 1) { - smart_conf_path = argv[1]; + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { + print_usage(argv[0]); + return EXIT_SUCCESS; + } else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--daemon") == 0) { + force_daemon = 1; + } else if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--stop") == 0) { + stop_mode = 1; + } else if ((strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--config") == 0) && i + 1 < argc) { + smart_conf_path = argv[++i]; + } else if ((strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--pidfile") == 0) && i + 1 < argc) { + pidfile_override = argv[++i]; + } else { + fprintf(stderr, "Unknown argument: %s\n", argv[i]); + print_usage(argv[0]); + return EXIT_FAILURE; + } } { @@ -689,154 +914,215 @@ int main(int argc, char **argv) { } } + { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = term_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + + if (sigaction(SIGTERM, &sa, NULL) < 0) { + die("sigaction(SIGTERM)"); + } + if (sigaction(SIGINT, &sa, NULL) < 0) { + die("sigaction(SIGINT)"); + } + if (sigaction(SIGHUP, &sa, NULL) < 0) { + die("sigaction(SIGHUP)"); + } + } + signal(SIGPIPE, SIG_IGN); - log_open(); - log_msg(LOG_LEVEL_INFO, "nwwebui starting"); + console_msg("%s %s starting", NWWEBUI_NAME, NWWEBUI_VERSION); - nw_config_t cfg; - init_defaults(&cfg, smart_conf_path); - load_smart_conf(&cfg); + { + nw_config_t cfg; + init_defaults(&cfg, smart_conf_path); + load_smart_conf(&cfg); - if (!file_exists_and_executable(cfg.smart_perl_path)) { - log_msg(LOG_LEVEL_ERROR, - "SMArT Perl program is missing or not executable: %s", - cfg.smart_perl_path); - return EXIT_FAILURE; - } - - log_msg(LOG_LEVEL_INFO, "Using SMArT Perl path: %s", cfg.smart_perl_path); - log_msg(LOG_LEVEL_INFO, - "Config loaded: bind=%s log_level=%d ssl_enable=%d http_port=%d https_port=%d cert=%s key=%s smart.conf=%s", - cfg.bind_ip, - g_log_level, - cfg.ssl_enable, - cfg.http_port, - cfg.https_port, - cfg.cert_file, - cfg.key_file, - cfg.smart_conf); - - if (cfg.ssl_enable) { - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_ssl_algorithms(); - - ctx = SSL_CTX_new(TLS_server_method()); - if (!ctx) { - ssl_die("SSL_CTX_new failed"); + if (force_daemon) { + cfg.daemonize = 1; } - SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); - - if (SSL_CTX_use_certificate_file(ctx, cfg.cert_file, SSL_FILETYPE_PEM) <= 0) { - ssl_die("Could not load certificate file"); + if (pidfile_override) { + snprintf(cfg.pid_file, sizeof(cfg.pid_file), "%s", pidfile_override); } - if (SSL_CTX_use_PrivateKey_file(ctx, cfg.key_file, SSL_FILETYPE_PEM) <= 0) { - ssl_die("Could not load private key file"); + if (stop_mode) { + return stop_running_instance(cfg.pid_file); } - if (!SSL_CTX_check_private_key(ctx)) { - ssl_die("Private key does not match certificate"); + if (!file_exists_and_executable(cfg.smart_perl_path)) { + log_msg(LOG_LEVEL_ERROR, + "SMArT Perl program is missing or not executable: %s", + cfg.smart_perl_path); + return EXIT_FAILURE; } - } - if (cfg.http_port > 0) { - http_fd = create_listener(cfg.bind_ip, cfg.http_port); + log_msg(LOG_LEVEL_INFO, "%s version %s starting", NWWEBUI_NAME, NWWEBUI_VERSION); + log_msg(LOG_LEVEL_INFO, "Using SMArT Perl path: %s", cfg.smart_perl_path); log_msg(LOG_LEVEL_INFO, - "HTTP listener active on %s:%d", + "%s version %s config loaded: bind=%s log_level=%d ssl_enable=%d http_port=%d https_port=%d daemonize=%d cert=%s key=%s pid_file=%s smart.conf=%s", + NWWEBUI_NAME, + NWWEBUI_VERSION, cfg.bind_ip, - cfg.http_port); - } + g_log_level, + cfg.ssl_enable, + cfg.http_port, + cfg.https_port, + cfg.daemonize, + cfg.cert_file, + cfg.key_file, + cfg.pid_file, + cfg.smart_conf); - if (cfg.ssl_enable && cfg.https_port > 0) { - https_fd = create_listener(cfg.bind_ip, cfg.https_port); - log_msg(LOG_LEVEL_INFO, - "HTTPS listener active on %s:%d", - cfg.bind_ip, - cfg.https_port); - } - - if (http_fd < 0 && https_fd < 0) { - log_msg(LOG_LEVEL_ERROR, "No listener is active"); - if (ctx) { - SSL_CTX_free(ctx); - } - return EXIT_FAILURE; - } - - for (;;) { - struct pollfd pfds[2]; - int nfds = 0; - - if (http_fd >= 0) { - pfds[nfds].fd = http_fd; - pfds[nfds].events = POLLIN; - pfds[nfds].revents = 0; - nfds++; + if (cfg.daemonize) { + console_msg("%s %s switching to daemon mode", NWWEBUI_NAME, NWWEBUI_VERSION); + daemonize_process(); } - if (https_fd >= 0) { - pfds[nfds].fd = https_fd; - pfds[nfds].events = POLLIN; - pfds[nfds].revents = 0; - nfds++; - } + write_pid_file(cfg.pid_file); + atexit(remove_pid_file); + log_msg(LOG_LEVEL_INFO, "Wrote PID file: %s", cfg.pid_file); - int pr = poll(pfds, nfds, -1); - if (pr < 0) { - if (errno == EINTR) { - continue; - } - log_msg(LOG_LEVEL_ERROR, "poll failed: %s", strerror(errno)); - continue; - } + if (cfg.ssl_enable) { + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_ssl_algorithms(); - for (int i = 0; i < nfds; i++) { - if (!(pfds[i].revents & POLLIN)) { - continue; + ctx = SSL_CTX_new(TLS_server_method()); + if (!ctx) { + ssl_die("SSL_CTX_new failed"); } - struct sockaddr_in peer; - socklen_t peerlen = sizeof(peer); - int client_fd = accept(pfds[i].fd, (struct sockaddr *)&peer, &peerlen); - if (client_fd < 0) { + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + + if (SSL_CTX_use_certificate_file(ctx, cfg.cert_file, SSL_FILETYPE_PEM) <= 0) { + ssl_die("Could not load certificate file"); + } + + if (SSL_CTX_use_PrivateKey_file(ctx, cfg.key_file, SSL_FILETYPE_PEM) <= 0) { + ssl_die("Could not load private key file"); + } + + if (!SSL_CTX_check_private_key(ctx)) { + ssl_die("Private key does not match certificate"); + } + } + + if (cfg.http_port > 0) { + http_fd = create_listener(cfg.bind_ip, cfg.http_port); + log_msg(LOG_LEVEL_INFO, + "HTTP listener active on %s:%d", + cfg.bind_ip, + cfg.http_port); + } + + if (cfg.ssl_enable && cfg.https_port > 0) { + https_fd = create_listener(cfg.bind_ip, cfg.https_port); + log_msg(LOG_LEVEL_INFO, + "HTTPS listener active on %s:%d", + cfg.bind_ip, + cfg.https_port); + } + + if (http_fd < 0 && https_fd < 0) { + log_msg(LOG_LEVEL_ERROR, "No listener is active"); + if (ctx) { + SSL_CTX_free(ctx); + } + return EXIT_FAILURE; + } + + console_msg("%s %s started", NWWEBUI_NAME, NWWEBUI_VERSION); + + for (;;) { + struct pollfd pfds[2]; + int nfds = 0; + int pr; + + if (g_terminate) { + console_msg("%s received signal %d, shutting down", NWWEBUI_NAME, g_got_signal); + log_msg(LOG_LEVEL_INFO, "Termination requested by signal %d", g_got_signal); + break; + } + + if (http_fd >= 0) { + pfds[nfds].fd = http_fd; + pfds[nfds].events = POLLIN; + pfds[nfds].revents = 0; + nfds++; + } + + if (https_fd >= 0) { + pfds[nfds].fd = https_fd; + pfds[nfds].events = POLLIN; + pfds[nfds].revents = 0; + nfds++; + } + + pr = poll(pfds, nfds, 1000); + if (pr < 0) { if (errno == EINTR) { continue; } - log_msg(LOG_LEVEL_ERROR, "accept failed: %s", strerror(errno)); + log_msg(LOG_LEVEL_ERROR, "poll failed: %s", strerror(errno)); continue; } - pid_t pid = fork(); - if (pid < 0) { - log_msg(LOG_LEVEL_ERROR, "fork failed: %s", strerror(errno)); + if (pr == 0) { + continue; + } + + for (i = 0; i < nfds; i++) { + struct sockaddr_in peer; + socklen_t peerlen = sizeof(peer); + int client_fd; + pid_t pid; + + if (!(pfds[i].revents & POLLIN)) { + continue; + } + + client_fd = accept(pfds[i].fd, (struct sockaddr *)&peer, &peerlen); + if (client_fd < 0) { + if (errno == EINTR) { + continue; + } + log_msg(LOG_LEVEL_ERROR, "accept failed: %s", strerror(errno)); + continue; + } + + pid = fork(); + if (pid < 0) { + log_msg(LOG_LEVEL_ERROR, "fork failed: %s", strerror(errno)); + close(client_fd); + continue; + } + + if (pid == 0) { + if (http_fd >= 0) { + close(http_fd); + } + if (https_fd >= 0) { + close(https_fd); + } + + if (pfds[i].fd == https_fd) { + handle_connection_tls(ctx, client_fd, &cfg); + } else { + handle_connection_plain(client_fd, &cfg); + } + + if (ctx) { + SSL_CTX_free(ctx); + } + _exit(0); + } + close(client_fd); - continue; } - - if (pid == 0) { - if (http_fd >= 0) { - close(http_fd); - } - if (https_fd >= 0) { - close(https_fd); - } - - if (pfds[i].fd == https_fd) { - handle_connection_tls(ctx, client_fd, &cfg); - } else { - handle_connection_plain(client_fd, &cfg); - } - - if (ctx) { - SSL_CTX_free(ctx); - } - _exit(0); - } - - close(client_fd); } } @@ -850,5 +1136,6 @@ int main(int argc, char **argv) { SSL_CTX_free(ctx); } + console_msg("%s stopped", NWWEBUI_NAME); return 0; } diff --git a/smart.conf.cmake b/smart.conf.cmake index f1923d5..1d716c4 100644 --- a/smart.conf.cmake +++ b/smart.conf.cmake @@ -84,6 +84,14 @@ $nw_bind_ip = '0.0.0.0'; # 2 = debug messages $nw_log_level = 1; +# Run nwwebui in daemon mode by default. +# 0 = stay in foreground +# 1 = detach into background +$nw_daemonize = 0; + +# PID file written by nwwebui. +$nw_pid_file = '@MARS_NWE_PID_DIR@/nwwebui.pid'; + # Enable or disable TLS/SSL support. # 1 = enable HTTPS listener # 0 = disable HTTPS listener