855 lines
23 KiB
C
855 lines
23 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include <arpa/inet.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <netinet/in.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/ssl.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "config.h"
|
|
|
|
typedef struct {
|
|
char bind_ip[64];
|
|
|
|
int ssl_enable;
|
|
int http_port;
|
|
int https_port;
|
|
|
|
char cert_file[512];
|
|
char key_file[512];
|
|
|
|
char smart_conf[512];
|
|
char smart_perl_path[512];
|
|
} nw_config_t;
|
|
|
|
static FILE *g_log_fp = NULL;
|
|
static int g_log_level = LOG_LEVEL_DEFAULT;
|
|
|
|
/* ------------------------------------------------------------ */
|
|
/* Logging */
|
|
/* ------------------------------------------------------------ */
|
|
|
|
static void log_open(void) {
|
|
if (!g_log_fp) {
|
|
g_log_fp = fopen(LOG_PATH_DEFAULT, "a");
|
|
if (!g_log_fp) {
|
|
g_log_fp = stderr;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void log_msg(int level, const char *fmt, ...) {
|
|
if (level > g_log_level) {
|
|
return;
|
|
}
|
|
|
|
log_open();
|
|
|
|
time_t now = time(NULL);
|
|
struct tm tm_now;
|
|
localtime_r(&now, &tm_now);
|
|
|
|
char tbuf[64];
|
|
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) {
|
|
lvl = "DEBUG";
|
|
}
|
|
|
|
fprintf(g_log_fp, "[%s] [%s] ", tbuf, lvl);
|
|
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vfprintf(g_log_fp, fmt, ap);
|
|
va_end(ap);
|
|
|
|
fputc('\n', g_log_fp);
|
|
fflush(g_log_fp);
|
|
}
|
|
|
|
static void die(const char *msg) {
|
|
log_msg(LOG_LEVEL_ERROR, "%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);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* ------------------------------------------------------------ */
|
|
/* Helpers */
|
|
/* ------------------------------------------------------------ */
|
|
|
|
static void trim(char *s) {
|
|
char *p = s;
|
|
while (*p && isspace((unsigned char)*p)) {
|
|
p++;
|
|
}
|
|
if (p != s) {
|
|
memmove(s, p, strlen(p) + 1);
|
|
}
|
|
|
|
size_t len = strlen(s);
|
|
while (len > 0 && isspace((unsigned char)s[len - 1])) {
|
|
s[len - 1] = '\0';
|
|
len--;
|
|
}
|
|
}
|
|
|
|
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] == '"')) {
|
|
memmove(s, s + 1, len - 2);
|
|
s[len - 2] = '\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
while (*p && isspace((unsigned char)*p)) {
|
|
p++;
|
|
}
|
|
if (*p != '$') {
|
|
return 0;
|
|
}
|
|
p++;
|
|
|
|
size_t ki = 0;
|
|
while (*p && (isalnum((unsigned char)*p) || *p == '_')) {
|
|
if (ki + 1 < ksz) {
|
|
key[ki++] = *p;
|
|
}
|
|
p++;
|
|
}
|
|
key[ki] = '\0';
|
|
|
|
while (*p && isspace((unsigned char)*p)) {
|
|
p++;
|
|
}
|
|
if (*p != '=') {
|
|
return 0;
|
|
}
|
|
p++;
|
|
|
|
while (*p && isspace((unsigned char)*p)) {
|
|
p++;
|
|
}
|
|
|
|
size_t vi = 0;
|
|
while (*p && *p != ';' && *p != '\n' && *p != '\r') {
|
|
if (vi + 1 < vsz) {
|
|
val[vi++] = *p;
|
|
}
|
|
p++;
|
|
}
|
|
val[vi] = '\0';
|
|
|
|
trim(key);
|
|
trim(val);
|
|
strip_quotes(val);
|
|
|
|
return key[0] != '\0';
|
|
}
|
|
|
|
static int file_exists_and_executable(const char *path) {
|
|
return access(path, X_OK) == 0;
|
|
}
|
|
|
|
static int set_nonblocking(int fd) {
|
|
int flags = fcntl(fd, F_GETFL, 0);
|
|
if (flags < 0) {
|
|
return -1;
|
|
}
|
|
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------ */
|
|
/* smart.conf loading */
|
|
/* ------------------------------------------------------------ */
|
|
|
|
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->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);
|
|
|
|
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);
|
|
}
|
|
|
|
static void load_smart_conf(nw_config_t *cfg) {
|
|
FILE *fp = fopen(cfg->smart_conf, "r");
|
|
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];
|
|
|
|
if (!parse_perl_assignment(line, key, sizeof(key), val, sizeof(val))) {
|
|
continue;
|
|
}
|
|
|
|
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 = 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) {
|
|
snprintf(cfg->smart_perl_path, sizeof(cfg->smart_perl_path), "%s", val);
|
|
}
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
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 / socket helpers */
|
|
/* ------------------------------------------------------------ */
|
|
|
|
static int create_listener(const char *bind_ip, int port) {
|
|
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
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);
|
|
|
|
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) {
|
|
close(fd);
|
|
die("bind");
|
|
}
|
|
|
|
if (listen(fd, NW_BACKLOG) < 0) {
|
|
close(fd);
|
|
die("listen");
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
static ssize_t write_all_fd(int fd, const unsigned char *buf, size_t len) {
|
|
size_t off = 0;
|
|
|
|
while (off < len) {
|
|
ssize_t n = write(fd, buf + off, len - off);
|
|
if (n < 0) {
|
|
if (errno == EINTR) {
|
|
continue;
|
|
}
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
continue;
|
|
}
|
|
return -1;
|
|
}
|
|
if (n == 0) {
|
|
break;
|
|
}
|
|
off += (size_t)n;
|
|
}
|
|
|
|
return (ssize_t)off;
|
|
}
|
|
|
|
static ssize_t send_all_ssl(SSL *ssl, const unsigned char *buf, size_t len) {
|
|
size_t off = 0;
|
|
|
|
while (off < len) {
|
|
int n = SSL_write(ssl, buf + off, (int)(len - off));
|
|
if (n <= 0) {
|
|
int err = SSL_get_error(ssl, n);
|
|
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
|
|
continue;
|
|
}
|
|
return -1;
|
|
}
|
|
off += (size_t)n;
|
|
}
|
|
|
|
return (ssize_t)off;
|
|
}
|
|
|
|
/* ------------------------------------------------------------ */
|
|
/* Perl launcher */
|
|
/* ------------------------------------------------------------ */
|
|
|
|
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 };
|
|
|
|
if (pipe(inpipe) < 0) {
|
|
die("pipe stdin");
|
|
}
|
|
|
|
if (pipe(outpipe) < 0) {
|
|
close(inpipe[0]);
|
|
close(inpipe[1]);
|
|
die("pipe stdout");
|
|
}
|
|
|
|
pid_t pid = fork();
|
|
if (pid < 0) {
|
|
close(inpipe[0]);
|
|
close(inpipe[1]);
|
|
close(outpipe[0]);
|
|
close(outpipe[1]);
|
|
die("fork");
|
|
}
|
|
|
|
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);
|
|
}
|
|
if (dup2(outpipe[1], STDOUT_FILENO) < 0) {
|
|
perror("dup2 stdout");
|
|
_exit(127);
|
|
}
|
|
if (dup2(outpipe[1], STDERR_FILENO) < 0) {
|
|
perror("dup2 stderr");
|
|
_exit(127);
|
|
}
|
|
|
|
close(inpipe[0]);
|
|
close(inpipe[1]);
|
|
close(outpipe[0]);
|
|
close(outpipe[1]);
|
|
|
|
execl(cfg->smart_perl_path, cfg->smart_perl_path, (char *)NULL);
|
|
|
|
perror("execl smart_perl_path");
|
|
_exit(127);
|
|
}
|
|
|
|
close(inpipe[0]);
|
|
close(outpipe[1]);
|
|
|
|
*child_stdin_fd = inpipe[1];
|
|
*child_stdout_fd = outpipe[0];
|
|
|
|
return pid;
|
|
}
|
|
|
|
/* ------------------------------------------------------------ */
|
|
/* Shared child cleanup */
|
|
/* ------------------------------------------------------------ */
|
|
|
|
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);
|
|
} else if (w < 0 && errno != ECHILD) {
|
|
log_msg(LOG_LEVEL_ERROR, "waitpid failed for smart child: %s", strerror(errno));
|
|
}
|
|
|
|
*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(client_fd) < 0) {
|
|
log_msg(LOG_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");
|
|
goto cleanup;
|
|
}
|
|
|
|
for (;;) {
|
|
struct pollfd pfds[2];
|
|
pfds[0].fd = client_fd;
|
|
pfds[0].events = POLLIN | POLLHUP | POLLERR;
|
|
pfds[0].revents = 0;
|
|
|
|
pfds[1].fd = child_stdout_fd;
|
|
pfds[1].events = POLLIN | POLLHUP | POLLERR;
|
|
pfds[1].revents = 0;
|
|
|
|
int pr = poll(pfds, 2, -1);
|
|
if (pr < 0) {
|
|
if (errno == EINTR) {
|
|
continue;
|
|
}
|
|
log_msg(LOG_LEVEL_ERROR, "poll failed in plain handler: %s", strerror(errno));
|
|
break;
|
|
}
|
|
|
|
/* Client -> Perl */
|
|
if (pfds[0].revents & (POLLIN | POLLHUP | POLLERR)) {
|
|
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) {
|
|
log_msg(LOG_LEVEL_ERROR, "Read from HTTP client failed: %s", strerror(errno));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Perl -> Client */
|
|
if (pfds[1].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) {
|
|
log_msg(LOG_LEVEL_ERROR, "Read from Perl stdout failed: %s", strerror(errno));
|
|
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;
|
|
pid_t child_pid = -1;
|
|
unsigned char buf[NW_BUF_SZ];
|
|
|
|
if (set_nonblocking(client_fd) < 0) {
|
|
log_msg(LOG_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");
|
|
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);
|
|
goto cleanup;
|
|
}
|
|
|
|
log_msg(LOG_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");
|
|
goto cleanup;
|
|
}
|
|
|
|
for (;;) {
|
|
struct pollfd pfds[2];
|
|
pfds[0].fd = client_fd;
|
|
pfds[0].events = POLLIN | POLLHUP | POLLERR;
|
|
pfds[0].revents = 0;
|
|
|
|
pfds[1].fd = child_stdout_fd;
|
|
pfds[1].events = POLLIN | POLLHUP | POLLERR;
|
|
pfds[1].revents = 0;
|
|
|
|
int pr = poll(pfds, 2, -1);
|
|
if (pr < 0) {
|
|
if (errno == EINTR) {
|
|
continue;
|
|
}
|
|
log_msg(LOG_LEVEL_ERROR, "poll failed in TLS handler: %s", strerror(errno));
|
|
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) {
|
|
if (write_all_fd(child_stdin_fd, buf, (size_t)n) < 0) {
|
|
log_msg(LOG_LEVEL_ERROR, "Write to Perl stdin failed");
|
|
break;
|
|
}
|
|
} else {
|
|
int err = SSL_get_error(ssl, n);
|
|
if (err == SSL_ERROR_ZERO_RETURN) {
|
|
break;
|
|
}
|
|
if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
|
|
log_msg(LOG_LEVEL_ERROR, "SSL_read failed");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Perl -> TLS client */
|
|
if (pfds[1].revents & (POLLIN | POLLHUP | POLLERR)) {
|
|
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");
|
|
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));
|
|
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);
|
|
|
|
if (ssl) {
|
|
SSL_shutdown(ssl);
|
|
SSL_free(ssl);
|
|
}
|
|
|
|
close(client_fd);
|
|
}
|
|
|
|
/* ------------------------------------------------------------ */
|
|
/* SIGCHLD handler */
|
|
/* ------------------------------------------------------------ */
|
|
|
|
static void reap_children(int sig) {
|
|
(void)sig;
|
|
|
|
for (;;) {
|
|
pid_t pid = waitpid(-1, NULL, WNOHANG);
|
|
if (pid <= 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------ */
|
|
/* Main */
|
|
/* ------------------------------------------------------------ */
|
|
|
|
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];
|
|
}
|
|
|
|
{
|
|
struct sigaction sa;
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sa_handler = reap_children;
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
|
|
|
|
if (sigaction(SIGCHLD, &sa, NULL) < 0) {
|
|
die("sigaction(SIGCHLD)");
|
|
}
|
|
}
|
|
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
log_open();
|
|
log_msg(LOG_LEVEL_INFO, "nwwebui starting");
|
|
|
|
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");
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 (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, "poll failed: %s", strerror(errno));
|
|
continue;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
if (http_fd >= 0) {
|
|
close(http_fd);
|
|
}
|
|
if (https_fd >= 0) {
|
|
close(https_fd);
|
|
}
|
|
if (ctx) {
|
|
SSL_CTX_free(ctx);
|
|
}
|
|
|
|
return 0;
|
|
}
|