more docu and enable loglevel for nwwebui

This commit is contained in:
Mario Fetka
2026-04-21 09:48:15 +02:00
parent 0d3bbdec8e
commit a4d3ded77e
3 changed files with 335 additions and 83 deletions

View File

@@ -11,8 +11,10 @@
#define LOG_LEVEL_DEBUG 2
#define LOG_LEVEL_DEFAULT LOG_LEVEL_INFO
#define DEFAULT_BIND_IP "0.0.0.0"
#define DEFAULT_TLS_PORT 9443
#define DEFAULT_BIND_IP "0.0.0.0"
#define DEFAULT_SSL_ENABLE 0
#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"
@@ -20,4 +22,4 @@
#define NW_BACKLOG 64
#define NW_BUF_SZ 16384
#endif
#endif

315
nwwebui.c
View File

@@ -15,7 +15,6 @@
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
@@ -25,7 +24,10 @@
typedef struct {
char bind_ip[64];
int tls_port;
int ssl_enable;
int http_port;
int https_port;
char cert_file[512];
char key_file[512];
@@ -200,14 +202,15 @@ static void init_defaults(nw_config_t *cfg, const char *smart_conf_path) {
memset(cfg, 0, sizeof(*cfg));
snprintf(cfg->bind_ip, sizeof(cfg->bind_ip), "%s", DEFAULT_BIND_IP);
cfg->tls_port = DEFAULT_TLS_PORT;
cfg->ssl_enable = DEFAULT_SSL_ENABLE;
cfg->http_port = DEFAULT_HTTP_PORT;
cfg->https_port = DEFAULT_HTTPS_PORT;
snprintf(cfg->cert_file, sizeof(cfg->cert_file), "%s", DEFAULT_CERT_FILE);
snprintf(cfg->key_file, sizeof(cfg->key_file), "%s", DEFAULT_KEY_FILE);
/* Use the built-in default path unless smart.conf overrides it */
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);
}
@@ -229,27 +232,42 @@ 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_tls_port") == 0) {
cfg->tls_port = atoi(val);
} else if (strcmp(key, "nw_log_level") == 0) {
g_log_level = atoi(val);
} else if (strcmp(key, "nw_ssl_enable") == 0) {
cfg->ssl_enable = atoi(val);
} else if (strcmp(key, "nw_http_port") == 0) {
cfg->http_port = atoi(val);
} else if (strcmp(key, "nw_https_port") == 0) {
cfg->https_port = atoi(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) {
snprintf(cfg->key_file, sizeof(cfg->key_file), "%s", val);
} else if (strcmp(key, "smart_perl_path") == 0) {
/* Override the default path only if smart.conf defines it */
snprintf(cfg->smart_perl_path, sizeof(cfg->smart_perl_path), "%s", val);
}
}
fclose(fp);
if (cfg->tls_port <= 0) {
cfg->tls_port = DEFAULT_TLS_PORT;
if (cfg->http_port < 0) {
cfg->http_port = DEFAULT_HTTP_PORT;
}
if (cfg->https_port < 0) {
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;
}
}
/* ------------------------------------------------------------ */
/* Listener / TLS */
/* Listener / socket helpers */
/* ------------------------------------------------------------ */
static int create_listener(const char *bind_ip, int port) {
@@ -400,10 +418,100 @@ static pid_t spawn_smart_perl(const nw_config_t *cfg, int *child_stdin_fd, int *
}
/* ------------------------------------------------------------ */
/* Connection handler */
/* Shared child cleanup */
/* ------------------------------------------------------------ */
static void handle_connection(SSL_CTX *ctx, int client_fd, const nw_config_t *cfg) {
static void cleanup_child_process(int *child_stdin_fd, int *child_stdout_fd, pid_t *child_pid) {
if (*child_stdin_fd >= 0) {
close(*child_stdin_fd);
*child_stdin_fd = -1;
}
if (*child_stdout_fd >= 0) {
close(*child_stdout_fd);
*child_stdout_fd = -1;
}
if (*child_pid > 0) {
int status = 0;
pid_t w = waitpid(*child_pid, &status, WNOHANG);
if (w == 0) {
kill(*child_pid, SIGTERM);
waitpid(*child_pid, &status, 0);
}
*child_pid = -1;
}
}
/* ------------------------------------------------------------ */
/* Plain HTTP handler */
/* ------------------------------------------------------------ */
static void handle_connection_plain(int client_fd, const nw_config_t *cfg) {
int child_stdin_fd = -1;
int child_stdout_fd = -1;
pid_t child_pid = -1;
unsigned char buf[NW_BUF_SZ];
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");
goto cleanup;
}
for (;;) {
struct pollfd pfd;
pfd.fd = child_stdout_fd;
pfd.events = POLLIN | POLLHUP | POLLERR;
pfd.revents = 0;
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");
break;
}
} else if (n == 0) {
break;
} else if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
break;
}
int pr = poll(&pfd, 1, 10);
if (pr > 0 && (pfd.revents & (POLLIN | POLLHUP | POLLERR))) {
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");
break;
}
} else if (rn == 0) {
break;
} else if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
break;
}
}
if (child_pid > 0) {
int status = 0;
pid_t w = waitpid(child_pid, &status, WNOHANG);
if (w == child_pid) {
break;
}
}
}
cleanup:
cleanup_child_process(&child_stdin_fd, &child_stdout_fd, &child_pid);
close(client_fd);
}
/* ------------------------------------------------------------ */
/* TLS handler */
/* ------------------------------------------------------------ */
static void handle_connection_tls(SSL_CTX *ctx, int client_fd, const nw_config_t *cfg) {
SSL *ssl = NULL;
int child_stdin_fd = -1;
int child_stdout_fd = -1;
@@ -483,21 +591,7 @@ static void handle_connection(SSL_CTX *ctx, int client_fd, const nw_config_t *cf
}
cleanup:
if (child_stdin_fd >= 0) {
close(child_stdin_fd);
}
if (child_stdout_fd >= 0) {
close(child_stdout_fd);
}
if (child_pid > 0) {
int status = 0;
pid_t w = waitpid(child_pid, &status, WNOHANG);
if (w == 0) {
kill(child_pid, SIGTERM);
waitpid(child_pid, &status, 0);
}
}
cleanup_child_process(&child_stdin_fd, &child_stdout_fd, &child_pid);
if (ssl) {
SSL_shutdown(ssl);
@@ -519,6 +613,9 @@ static void reap_children(int sig) {
int main(int argc, char **argv) {
const char *smart_conf_path = DEFAULT_SMART_CONF;
int http_fd = -1;
int https_fd = -1;
SSL_CTX *ctx = NULL;
if (argc > 1) {
smart_conf_path = argv[1];
@@ -542,77 +639,149 @@ int main(int argc, char **argv) {
}
log_msg(LOG_LEVEL_INFO, "Using SMArT Perl path: %s", cfg.smart_perl_path);
log_msg(LOG_LEVEL_INFO,
"Config loaded: bind=%s:%d cert=%s key=%s smart.conf=%s",
"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,
cfg.tls_port,
g_log_level,
cfg.ssl_enable,
cfg.http_port,
cfg.https_port,
cfg.cert_file,
cfg.key_file,
cfg.smart_conf);
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
if (cfg.ssl_enable) {
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
if (!ctx) {
ssl_die("SSL_CTX_new failed");
ctx = SSL_CTX_new(TLS_server_method());
if (!ctx) {
ssl_die("SSL_CTX_new failed");
}
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");
}
}
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 (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 (SSL_CTX_use_PrivateKey_file(ctx, cfg.key_file, SSL_FILETYPE_PEM) <= 0) {
ssl_die("Could not load private key file");
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 (!SSL_CTX_check_private_key(ctx)) {
ssl_die("Private key does not match certificate");
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;
}
int listen_fd = create_listener(cfg.bind_ip, cfg.tls_port);
log_msg(LOG_LEVEL_INFO,
"TLS wrapper listening on %s:%d and launching %s",
cfg.bind_ip,
cfg.tls_port,
cfg.smart_perl_path);
for (;;) {
struct sockaddr_in peer;
socklen_t peerlen = sizeof(peer);
struct pollfd pfds[2];
int nfds = 0;
int client_fd = accept(listen_fd, (struct sockaddr *)&peer, &peerlen);
if (client_fd < 0) {
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++;
}
int pr = poll(pfds, nfds, -1);
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));
for (int i = 0; i < nfds; i++) {
if (!(pfds[i].revents & POLLIN)) {
continue;
}
struct sockaddr_in peer;
socklen_t peerlen = sizeof(peer);
int 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_t 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) {
close(listen_fd);
handle_connection(ctx, client_fd, &cfg);
SSL_CTX_free(ctx);
_exit(0);
}
close(client_fd);
}
close(listen_fd);
SSL_CTX_free(ctx);
if (http_fd >= 0) {
close(http_fd);
}
if (https_fd >= 0) {
close(https_fd);
}
if (ctx) {
SSL_CTX_free(ctx);
}
return 0;
}

View File

@@ -1,24 +1,105 @@
# SMArT / nwwebui configuration file
# ------------------------------------------------------------
# UI colors
# ------------------------------------------------------------
# Background color used for the main page body.
$COLOR_BACK = "#F0F0FF";
# Background color used for section headers.
$COLOR_HEAD_BACK = "#C0C0FF";
# Text color used for section headers.
$COLOR_HEAD_FORE = "#000000";
# Background color used for subsection headers.
$COLOR_SUBH_BACK = "#D0D0FF";
# Text color used for subsection headers.
$COLOR_SUBH_FORE = "#000000";
# Background color used for normal content rows.
$COLOR_TEXT_BACK = "#E0E0FF";
# Text color used for normal content rows.
$COLOR_TEXT_FORE = "#000000";
# ------------------------------------------------------------
# Main MARS NWE configuration
# ------------------------------------------------------------
# Path to the main mars_nwe server configuration file.
# This file is read and modified by the SMArT configuration pages.
$mars_config = '@MARS_NWE_INSTALL_FULL_CONFDIR@/nwserv.conf';
# User name used when SMArT drops privileges for non-root operations.
# Keep this set to an unprivileged local account.
$nonroot_user = 'nobody';
$smart_conf_path = '@MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf';
# ------------------------------------------------------------
# SMArT internal file layout
# ------------------------------------------------------------
# Absolute path to the SMArT configuration file itself.
# Used when SMArT needs to append updated settings.
$smart_conf_path = '@MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf';
# File used to store bindery login information for SMArT helper tools.
# This file should remain readable only by the service user or root.
$smart_nwclient_path = '@MARS_NWE_INSTALL_FULL_CONFDIR@/.nwclient';
$smart_static_dir = '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/static';
$smart_log_path = '@MARS_NWE_LOG_DIR@/smart.log';
$smart_check_login = '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/check_login';
# Directory containing static HTML and image files served by SMArT.
$smart_static_dir = '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/static';
$nw_bind_ip = '0.0.0.0';
$nw_tls_port = 9443;
# Log file used by the Perl SMArT frontend.
# Keep this separate from the nwwebui log file.
$smart_log_path = '@MARS_NWE_LOG_DIR@/smart.log';
# Path to the PAM-based login helper used for root authentication.
$smart_check_login = '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/check_login';
# Optional explicit path to the main SMArT Perl program.
# This is normally not required, because nwwebui already has a built-in default.
# Uncomment and adjust only if a non-standard location must be used.
# $smart_perl_path = '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/smart';
# ------------------------------------------------------------
# nwwebui listener settings
# ------------------------------------------------------------
# IP address used for the HTTP and HTTPS listeners.
# Use 0.0.0.0 to listen on all IPv4 interfaces.
# Use 127.0.0.1 for local-only testing.
$nw_bind_ip = '0.0.0.0';
# Log level used by nwwebui.
# 0 = errors only
# 1 = informational messages
# 2 = debug messages
$nw_log_level = 1;
# Enable or disable TLS/SSL support.
# 1 = enable HTTPS listener
# 0 = disable HTTPS listener
#
# When disabled, nwwebui can still serve plain HTTP if nw_http_port > 0.
$nw_ssl_enable = 1;
# Plain HTTP listener port.
# Set to 0 to disable plain HTTP completely.
# This is useful for local or isolated-network testing.
$nw_http_port = 9080;
# HTTPS listener port.
# Used only when $nw_ssl_enable is set to 1.
# Set to 0 to disable HTTPS listening.
$nw_https_port = 9443;
# TLS certificate file in PEM format.
# Required only when HTTPS is enabled.
$nw_cert_file = '@MARS_NWE_INSTALL_FULL_CONFDIR@/server.crt';
$nw_key_file = '@MARS_NWE_INSTALL_FULL_CONFDIR@/server.key';
# TLS private key file in PEM format.
# Required only when HTTPS is enabled.
$nw_key_file = '@MARS_NWE_INSTALL_FULL_CONFDIR@/server.key';