diff --git a/CMakeLists.txt b/CMakeLists.txt index 354fa63..e38fcb4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,9 @@ add_executable(smart_userlist smart_userlist.c) ############## target_link_libraries(nwwebui + mars_nwe::core mars_nwe::ssl + libowfat::libowfat ) target_link_libraries(check_login diff --git a/config.h.cmake b/config.h.cmake index fee3b2d..32cd2c7 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -15,12 +15,7 @@ #define DEFAULT_SMART_LOG_PATH "@MARS_NWE_LOG_DIR@/smart.log" #define DEFAULT_SMART_LOG_LEVEL "info" -#define LOG_LEVEL_ERROR 0 -#define LOG_LEVEL_WARNING 1 -#define LOG_LEVEL_INFO 2 -#define LOG_LEVEL_DEBUG 3 -#define LOG_LEVEL_TRACE 4 -#define LOG_LEVEL_DEFAULT LOG_LEVEL_INFO +#define LOG_LEVEL_MASK_DEFAULT 123 #define DEFAULT_BIND_IP "0.0.0.0" #define DEFAULT_SSL_ENABLE 1 diff --git a/nwwebui.c b/nwwebui.c index 037bef6..9ca277b 100644 --- a/nwwebui.c +++ b/nwwebui.c @@ -1,10 +1,8 @@ #define _POSIX_C_SOURCE 200809L -#include #include #include #include -#include #include #include #include @@ -14,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -23,6 +20,10 @@ #include "config.h" +#include +#include +#include "nwlog.h" + typedef struct { char bind_ip[64]; @@ -40,10 +41,6 @@ typedef struct { char smart_perl_path[512]; } nw_config_t; -static FILE *g_log_fp = NULL; -static int g_log_level = LOG_LEVEL_DEFAULT; -static char g_log_path[512] = LOG_PATH_DEFAULT; - static volatile sig_atomic_t g_terminate = 0; static volatile sig_atomic_t g_got_signal = 0; @@ -70,35 +67,16 @@ static void console_msg(const char *fmt, ...) /* Logging */ /* ------------------------------------------------------------ */ -static void log_open(void) -{ - if (!g_log_fp) { - g_log_fp = fopen(g_log_path, "a"); - if (!g_log_fp) { - g_log_fp = stderr; - } - } -} +static unsigned int g_log_level_mask = LOG_LEVEL_MASK_DEFAULT; +static char g_log_path[512] = LOG_PATH_DEFAULT; -static void log_reopen(const char *path) -{ - if (g_log_fp && g_log_fp != stderr) { - fclose(g_log_fp); - } - g_log_fp = NULL; - - snprintf(g_log_path, sizeof(g_log_path), "%s", path); - log_open(); -} - - -static int parse_log_level(const char *value) +static unsigned int parse_log_level(const char *value) { char buf[64]; size_t i; if (!value || !*value) { - return LOG_LEVEL_DEFAULT; + return LOG_LEVEL_MASK_DEFAULT; } while (*value && isspace((unsigned char)*value)) { @@ -116,95 +94,57 @@ static int parse_log_level(const char *value) i--; } - if (strcmp(buf, "error") == 0 || strcmp(buf, "err") == 0 || strcmp(buf, "0") == 0) { - return LOG_LEVEL_ERROR; - } - if (strcmp(buf, "warning") == 0 || strcmp(buf, "warn") == 0 || strcmp(buf, "1") == 0) { - return LOG_LEVEL_WARNING; - } - if (strcmp(buf, "info") == 0 || strcmp(buf, "2") == 0) { - return LOG_LEVEL_INFO; - } - if (strcmp(buf, "debug") == 0 || strcmp(buf, "3") == 0) { - return LOG_LEVEL_DEBUG; - } - if (strcmp(buf, "trace") == 0 || strcmp(buf, "4") == 0) { - return LOG_LEVEL_TRACE; - } - - /* - Compatibility with the old numeric values: - 0 = error - 1 = info - 2 = debug - - Named levels are preferred for new configurations. - */ - if (strcmp(buf, "old-info") == 0) { - return LOG_LEVEL_INFO; - } - - return LOG_LEVEL_DEFAULT; + return nwlog_parse_level_mask(buf, LOG_LEVEL_MASK_DEFAULT); } -static const char *log_level_name(int level) +static void log_configure(const char *path, unsigned int level_mask) { - if (level <= LOG_LEVEL_ERROR) { - return "ERROR"; - } - if (level == LOG_LEVEL_WARNING) { - return "WARNING"; - } - if (level == LOG_LEVEL_DEBUG) { - return "DEBUG"; - } - if (level >= LOG_LEVEL_TRACE) { - return "TRACE"; + if (path && *path) { + snprintf(g_log_path, sizeof(g_log_path), "%s", path); } - return "INFO"; + g_log_level_mask = level_mask; + nwlog_set_process_name(NWWEBUI_NAME); + nwlog_set_level_mask(g_log_level_mask); + + if (nwlog_set_output_path(g_log_path) < 0) { + nwlog_set_output_path(NULL); + nwlog_error("log", "Could not open log file %s: %s", + g_log_path, strerror(errno)); + } } -static void log_msg(int level, const char *fmt, ...) +static void log_msg(NwLogLevel level, const char *fmt, ...) { - time_t now; - struct tm tm_now; - char tbuf[64]; - const char *lvl; va_list ap; - if (level > g_log_level) { - return; - } - - log_open(); - - now = time(NULL); - localtime_r(&now, &tm_now); - strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", &tm_now); - - lvl = log_level_name(level); - - fprintf(g_log_fp, "[%s] [%s] ", tbuf, lvl); - va_start(ap, fmt); - vfprintf(g_log_fp, fmt, ap); + nwlog_emitv(level, "nwwebui", fmt, ap); va_end(ap); +} - fputc('\n', g_log_fp); - fflush(g_log_fp); +static void log_ssl_errors(const char *context) +{ + unsigned long err; + + while ((err = ERR_get_error()) != 0) { + char errbuf[256]; + + ERR_error_string_n(err, errbuf, sizeof(errbuf)); + nwlog_error("tls", "%s: %s", context, errbuf); + } } static void die(const char *msg) { - log_msg(LOG_LEVEL_ERROR, "%s: %s", msg, strerror(errno)); + nwlog_error("fatal", "%s: %s", msg, strerror(errno)); exit(EXIT_FAILURE); } static void ssl_die(const char *msg) { - log_msg(LOG_LEVEL_ERROR, "%s", msg); - ERR_print_errors_fp(g_log_fp ? g_log_fp : stderr); + nwlog_error("tls", "%s", msg); + log_ssl_errors(msg); exit(EXIT_FAILURE); } @@ -526,7 +466,7 @@ static void load_smart_conf(nw_config_t *cfg) char line[2048]; if (!fp) { - log_msg(LOG_LEVEL_ERROR, "Could not open smart.conf: %s", cfg->smart_conf); + log_msg(NWLOG_LEVEL_ERROR, "Could not open smart.conf: %s", cfg->smart_conf); die("fopen smart.conf"); } @@ -541,7 +481,7 @@ static void load_smart_conf(nw_config_t *cfg) if (strcmp(key, "nw_bind_ip") == 0) { snprintf(cfg->bind_ip, sizeof(cfg->bind_ip), "%s", val); } else if (strcmp(key, "nw_log_level") == 0) { - g_log_level = parse_log_level(val); + g_log_level_mask = parse_log_level(val); } else if (strcmp(key, "nw_log_file") == 0) { snprintf(cfg->log_file, sizeof(cfg->log_file), "%s", val); } else if (strcmp(key, "nw_ssl_enable") == 0) { @@ -572,13 +512,6 @@ static void load_smart_conf(nw_config_t *cfg) cfg->https_port = DEFAULT_HTTPS_PORT; } - if (g_log_level < LOG_LEVEL_ERROR) { - g_log_level = LOG_LEVEL_DEFAULT; - } - if (g_log_level > LOG_LEVEL_DEBUG) { - g_log_level = LOG_LEVEL_DEBUG; - } - snprintf(g_log_path, sizeof(g_log_path), "%s", cfg->log_file); } @@ -586,43 +519,46 @@ static void load_smart_conf(nw_config_t *cfg) /* Listener / socket helpers */ /* ------------------------------------------------------------ */ +static int parse_bind_ip4(const char *bind_ip, char ip[4]) +{ + if (!bind_ip || !*bind_ip || strcmp(bind_ip, "0.0.0.0") == 0 || strcmp(bind_ip, "*") == 0) { + memset(ip, 0, 4); + return 1; + } + + return scan_ip4(bind_ip, ip) != 0; +} + 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; + int fd; + char ip[4]; + if (port <= 0 || port > 65535) { + nwlog_error("socket", "Invalid listener port: %d", port); + exit(EXIT_FAILURE); + } + + if (!parse_bind_ip4(bind_ip, ip)) { + nwlog_error("socket", "Invalid bind IPv4 address: %s", bind_ip ? bind_ip : "(null)"); + exit(EXIT_FAILURE); + } + + fd = socket_tcp4(); if (fd < 0) { - die("socket"); + die("socket_tcp4"); } - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) { + socket_tryreservein(fd, NW_BACKLOG); + + if (socket_bind4_reuse(fd, ip, (uint16)port) < 0) { close(fd); - die("setsockopt(SO_REUSEADDR)"); + die("socket_bind4_reuse"); } - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons((uint16_t)port); - - if (strcmp(bind_ip, "0.0.0.0") == 0) { - addr.sin_addr.s_addr = htonl(INADDR_ANY); - } else { - if (inet_pton(AF_INET, bind_ip, &addr.sin_addr) != 1) { - close(fd); - log_msg(LOG_LEVEL_ERROR, "Invalid bind IP: %s", bind_ip); - exit(EXIT_FAILURE); - } - } - - if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + if (socket_listen(fd, NW_BACKLOG) < 0) { close(fd); - die("bind"); - } - - if (listen(fd, NW_BACKLOG) < 0) { - close(fd); - die("listen"); + die("socket_listen"); } return fd; @@ -758,7 +694,7 @@ static void cleanup_child_process(int *child_stdin_fd, int *child_stdout_fd, pid kill(*child_pid, SIGTERM); waitpid(*child_pid, &status, 0); } else if (w < 0 && errno != ECHILD) { - log_msg(LOG_LEVEL_ERROR, "waitpid failed for smart child: %s", strerror(errno)); + log_msg(NWLOG_LEVEL_ERROR, "waitpid failed for smart child: %s", strerror(errno)); } *child_pid = -1; @@ -779,12 +715,12 @@ static void handle_connection_plain(int client_fd, const nw_config_t *cfg) child_pid = spawn_smart_perl(cfg, &child_stdin_fd, &child_stdout_fd); if (set_nonblocking(client_fd) < 0) { - log_msg(LOG_LEVEL_ERROR, "Could not set client socket non-blocking"); + log_msg(NWLOG_LEVEL_ERROR, "Could not set client socket non-blocking"); goto cleanup; } if (set_nonblocking(child_stdout_fd) < 0) { - log_msg(LOG_LEVEL_ERROR, "Could not set child stdout non-blocking"); + log_msg(NWLOG_LEVEL_ERROR, "Could not set child stdout non-blocking"); goto cleanup; } @@ -805,7 +741,7 @@ static void handle_connection_plain(int client_fd, const nw_config_t *cfg) if (errno == EINTR) { continue; } - log_msg(LOG_LEVEL_ERROR, "poll failed in plain handler: %s", strerror(errno)); + log_msg(NWLOG_LEVEL_ERROR, "poll failed in plain handler: %s", strerror(errno)); break; } @@ -813,13 +749,13 @@ static void handle_connection_plain(int client_fd, const nw_config_t *cfg) ssize_t n = read(client_fd, buf, sizeof(buf)); if (n > 0) { if (write_all_fd(child_stdin_fd, buf, (size_t)n) < 0) { - log_msg(LOG_LEVEL_ERROR, "Write to Perl stdin failed"); + log_msg(NWLOG_LEVEL_ERROR, "Write to Perl stdin failed"); break; } } else if (n == 0) { break; } else if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { - log_msg(LOG_LEVEL_ERROR, "Read from HTTP client failed: %s", strerror(errno)); + log_msg(NWLOG_LEVEL_ERROR, "Read from HTTP client failed: %s", strerror(errno)); break; } } @@ -828,13 +764,13 @@ static void handle_connection_plain(int client_fd, const nw_config_t *cfg) ssize_t rn = read(child_stdout_fd, buf, sizeof(buf)); if (rn > 0) { if (write_all_fd(client_fd, buf, (size_t)rn) < 0) { - log_msg(LOG_LEVEL_ERROR, "Write to HTTP client failed"); + log_msg(NWLOG_LEVEL_ERROR, "Write to HTTP client failed"); break; } } else if (rn == 0) { break; } else if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { - log_msg(LOG_LEVEL_ERROR, "Read from Perl stdout failed: %s", strerror(errno)); + log_msg(NWLOG_LEVEL_ERROR, "Read from Perl stdout failed: %s", strerror(errno)); break; } } @@ -866,30 +802,30 @@ static void handle_connection_tls(SSL_CTX *ctx, int client_fd, const nw_config_t unsigned char buf[NW_BUF_SZ]; if (set_nonblocking(client_fd) < 0) { - log_msg(LOG_LEVEL_ERROR, "Could not set client socket non-blocking"); + log_msg(NWLOG_LEVEL_ERROR, "Could not set client socket non-blocking"); goto cleanup; } ssl = SSL_new(ctx); if (!ssl) { - log_msg(LOG_LEVEL_ERROR, "SSL_new failed"); + log_msg(NWLOG_LEVEL_ERROR, "SSL_new failed"); goto cleanup; } SSL_set_fd(ssl, client_fd); if (SSL_accept(ssl) <= 0) { - log_msg(LOG_LEVEL_ERROR, "SSL_accept failed"); - ERR_print_errors_fp(g_log_fp ? g_log_fp : stderr); + log_msg(NWLOG_LEVEL_ERROR, "SSL_accept failed"); + log_ssl_errors("SSL_accept failed"); goto cleanup; } - log_msg(LOG_LEVEL_INFO, "Accepted new TLS connection"); + log_msg(NWLOG_LEVEL_INFO, "Accepted new TLS connection"); child_pid = spawn_smart_perl(cfg, &child_stdin_fd, &child_stdout_fd); if (set_nonblocking(child_stdout_fd) < 0) { - log_msg(LOG_LEVEL_ERROR, "Could not set child stdout non-blocking"); + log_msg(NWLOG_LEVEL_ERROR, "Could not set child stdout non-blocking"); goto cleanup; } @@ -910,7 +846,7 @@ static void handle_connection_tls(SSL_CTX *ctx, int client_fd, const nw_config_t if (errno == EINTR) { continue; } - log_msg(LOG_LEVEL_ERROR, "poll failed in TLS handler: %s", strerror(errno)); + log_msg(NWLOG_LEVEL_ERROR, "poll failed in TLS handler: %s", strerror(errno)); break; } @@ -918,7 +854,7 @@ static void handle_connection_tls(SSL_CTX *ctx, int client_fd, const nw_config_t int n = SSL_read(ssl, buf, sizeof(buf)); if (n > 0) { if (write_all_fd(child_stdin_fd, buf, (size_t)n) < 0) { - log_msg(LOG_LEVEL_ERROR, "Write to Perl stdin failed"); + log_msg(NWLOG_LEVEL_ERROR, "Write to Perl stdin failed"); break; } } else { @@ -927,7 +863,7 @@ static void handle_connection_tls(SSL_CTX *ctx, int client_fd, const nw_config_t break; } if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) { - log_msg(LOG_LEVEL_ERROR, "SSL_read failed"); + log_msg(NWLOG_LEVEL_ERROR, "SSL_read failed"); break; } } @@ -937,13 +873,13 @@ static void handle_connection_tls(SSL_CTX *ctx, int client_fd, const nw_config_t ssize_t rn = read(child_stdout_fd, buf, sizeof(buf)); if (rn > 0) { if (send_all_ssl(ssl, buf, (size_t)rn) < 0) { - log_msg(LOG_LEVEL_ERROR, "SSL_write failed"); + log_msg(NWLOG_LEVEL_ERROR, "SSL_write failed"); break; } } else if (rn == 0) { break; } else if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { - log_msg(LOG_LEVEL_ERROR, "Read from Perl stdout failed: %s", strerror(errno)); + log_msg(NWLOG_LEVEL_ERROR, "Read from Perl stdout failed: %s", strerror(errno)); break; } } @@ -1078,27 +1014,27 @@ int main(int argc, char **argv) snprintf(cfg.log_file, sizeof(cfg.log_file), "%s", logfile_override); } - log_reopen(cfg.log_file); + log_configure(cfg.log_file, g_log_level_mask); if (stop_mode) { return stop_running_instance(cfg.pid_file); } if (!file_exists_and_executable(cfg.smart_perl_path)) { - log_msg(LOG_LEVEL_ERROR, + log_msg(NWLOG_LEVEL_ERROR, "SMArT Perl program is missing or not executable: %s", cfg.smart_perl_path); return EXIT_FAILURE; } - 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, + log_msg(NWLOG_LEVEL_INFO, "%s version %s starting", NWWEBUI_NAME, NWWEBUI_VERSION); + log_msg(NWLOG_LEVEL_INFO, "Using SMArT Perl path: %s", cfg.smart_perl_path); + log_msg(NWLOG_LEVEL_INFO, "%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 log_file=%s smart.conf=%s", NWWEBUI_NAME, NWWEBUI_VERSION, cfg.bind_ip, - g_log_level, + g_log_level_mask, cfg.ssl_enable, cfg.http_port, cfg.https_port, @@ -1112,12 +1048,12 @@ int main(int argc, char **argv) if (cfg.daemonize) { console_msg("%s %s switching to daemon mode", NWWEBUI_NAME, NWWEBUI_VERSION); daemonize_process(); - log_reopen(cfg.log_file); + log_configure(cfg.log_file, g_log_level_mask); } write_pid_file(cfg.pid_file); atexit(remove_pid_file); - log_msg(LOG_LEVEL_INFO, "Wrote PID file: %s", cfg.pid_file); + log_msg(NWLOG_LEVEL_INFO, "Wrote PID file: %s", cfg.pid_file); if (cfg.ssl_enable) { SSL_library_init(); @@ -1146,7 +1082,7 @@ int main(int argc, char **argv) if (cfg.http_port > 0) { http_fd = create_listener(cfg.bind_ip, cfg.http_port); - log_msg(LOG_LEVEL_INFO, + log_msg(NWLOG_LEVEL_INFO, "HTTP listener active on %s:%d", cfg.bind_ip, cfg.http_port); @@ -1154,14 +1090,14 @@ int main(int argc, char **argv) if (cfg.ssl_enable && cfg.https_port > 0) { https_fd = create_listener(cfg.bind_ip, cfg.https_port); - log_msg(LOG_LEVEL_INFO, + log_msg(NWLOG_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"); + log_msg(NWLOG_LEVEL_ERROR, "No listener is active"); if (ctx) { SSL_CTX_free(ctx); } @@ -1177,7 +1113,7 @@ int main(int argc, char **argv) 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); + log_msg(NWLOG_LEVEL_INFO, "Termination requested by signal %d", g_got_signal); break; } @@ -1200,7 +1136,7 @@ int main(int argc, char **argv) if (errno == EINTR) { continue; } - log_msg(LOG_LEVEL_ERROR, "poll failed: %s", strerror(errno)); + log_msg(NWLOG_LEVEL_ERROR, "poll failed: %s", strerror(errno)); continue; } @@ -1209,8 +1145,8 @@ int main(int argc, char **argv) } for (i = 0; i < nfds; i++) { - struct sockaddr_in peer; - socklen_t peerlen = sizeof(peer); + char peer_ip[4]; + uint16 peer_port = 0; int client_fd; pid_t pid; @@ -1218,18 +1154,26 @@ int main(int argc, char **argv) continue; } - client_fd = accept(pfds[i].fd, (struct sockaddr *)&peer, &peerlen); + client_fd = socket_accept4(pfds[i].fd, peer_ip, &peer_port); if (client_fd < 0) { if (errno == EINTR) { continue; } - log_msg(LOG_LEVEL_ERROR, "accept failed: %s", strerror(errno)); + log_msg(NWLOG_LEVEL_ERROR, "socket_accept4 failed: %s", strerror(errno)); continue; } + if (nwlog_enabled(NWLOG_LEVEL_DEBUG)) { + char ipbuf[IP4_FMT]; + size_t iplen = fmt_ip4(ipbuf, peer_ip); + ipbuf[iplen] = '\0'; + nwlog_debug("socket", "Accepted TCP connection from %s:%u", + ipbuf, (unsigned int)peer_port); + } + pid = fork(); if (pid < 0) { - log_msg(LOG_LEVEL_ERROR, "fork failed: %s", strerror(errno)); + log_msg(NWLOG_LEVEL_ERROR, "fork failed: %s", strerror(errno)); close(client_fd); continue; }