Browse Source

chatgui: validate config options on startup, refuse to start on errors

chatgui
Evgeny 3 months ago
parent
commit
0c2eeb8820
  1. 6
      tools/chatgui/src/main.cpp
  2. 168
      tools/chatgui/transport/node_config.cpp
  3. 6
      tools/chatgui/transport/node_config.h

6
tools/chatgui/src/main.cpp

@ -2,6 +2,7 @@
#include <QPalette>
#include <QStyleFactory>
#include <QDir>
#include <cstdio>
#include "mainwindow.h"
#include "../db/db_manager.h"
#include "../transport/node_config.h"
@ -45,6 +46,11 @@ int main(int argc, char *argv[]) {
qCritical("Cannot open/read config file: %s", qPrintable(cfgPath));
return 1;
}
if (cfg.hasErrors()) {
for (const auto& err : cfg.errors())
fprintf(stderr, "CONFIG ERROR: %s\n", qPrintable(err));
return 1;
}
if (cfg.privKey().length() != 64 || cfg.pubKey().length() != 64) {
qWarning("Config: keys missing/invalid, regenerating identity");
cfg.generateIdentity();

168
tools/chatgui/transport/node_config.cpp

@ -8,6 +8,109 @@ extern "C" {
#include <QFile>
#include <QTextStream>
#include <QFileInfo>
#include <QSet>
// =====================================================================
// Whitelists — union of chatgui + uTun config_parser keys
// =====================================================================
static const QSet<QString> GLOBAL_KEYS = {
"my_node_name", "my_private_key", "my_public_key", "my_node_id",
"tun_enabled", "tun_ifname", "tun_ip", "mtu",
"keepalive_timeout", "keepalive_interval", "keepalive_adaptive", "bbr_max_cwnd",
"debug_level", "log_file", "db_path", "db_sync_enabled", "db_sync_ttl",
"enable_timestamp", "enable_function_names", "enable_file_lines", "enable_colors",
"tun_test_mode"
};
static const QSet<QString> SERVER_KEYS = {
"addr", "so_mark", "fib", "netif", "type", "mtu", "only_local", "transport"
};
static const QSet<QString> CLIENT_KEYS = {
"peer_public_key", "link", "keepalive"
};
static const QSet<QString> CONTROL_KEYS = {
"ip", "control_ip", "port", "control_port", "allow", "control_allow"
};
static const QSet<QString> GUI_KEYS = {
"db_path", "debug_file", "debug_level", "debug_categories"
};
static const QSet<QString> ALLOWED_KEYS_KEYS = {
"allow_all", "key"
};
static const QSet<QString> NAT_KEYS = {
"enabled", "tun_ifname", "tun_ip", "nat_via", "port_start", "port_end", "forward"
};
static const QSet<QString> FIREWALL_KEYS = {
"allow"
};
static const QSet<QString> ROUTING_KEYS = {
"route_subnet", "my_subnet"
};
static const QSet<QString> TCP_PROXY_CLIENT_KEYS = {
"enabled", "tun_name", "tun_ip", "mtu", "via_node",
"forward", "socks_enabled", "socks_addr", "http_proxy_enabled", "http_proxy_addr"
};
static const QSet<QString> TCP_PROXY_SERVER_KEYS = {
"enabled", "tcp_recv_buf"
};
static const QSet<QString> MSG_TRANSPORT_KEYS = {
"port"
};
static const QSet<QString> NETWORK_KEYS = {
"id", "pubkey", "signing_key"
};
static const QStringList VALID_DEBUG_LEVELS = {
"none", "error", "warn", "info", "debug", "trace"
};
// Sections: "" = global, use prefix match for dynamic sections
static bool isSectionValid(const QString& section) {
if (section.isEmpty() || section == "global") return true;
if (section.startsWith("server:")) return true;
if (section.startsWith("client:")) return true;
if (section.startsWith("network:")) return true;
if (section == "control") return true;
if (section == "gui") return true;
if (section == "allowed_keys") return true;
if (section == "nat") return true;
if (section == "firewall") return true;
if (section == "debug") return true;
if (section == "routing" || section == "route") return true;
if (section == "tcp_proxy_client") return true;
if (section == "tcp_proxy_server") return true;
if (section == "msg_transport") return true;
return false;
}
static const QSet<QString>* keysForSection(const QString& section) {
if (section.isEmpty() || section == "global") return &GLOBAL_KEYS;
if (section.startsWith("server:")) return &SERVER_KEYS;
if (section.startsWith("client:")) return &CLIENT_KEYS;
if (section.startsWith("network:")) return &NETWORK_KEYS;
if (section == "control") return &CONTROL_KEYS;
if (section == "gui") return &GUI_KEYS;
if (section == "allowed_keys") return &ALLOWED_KEYS_KEYS;
if (section == "nat") return &NAT_KEYS;
if (section == "firewall") return &FIREWALL_KEYS;
if (section == "routing" || section == "route") return &ROUTING_KEYS;
if (section == "tcp_proxy_client") return &TCP_PROXY_CLIENT_KEYS;
if (section == "tcp_proxy_server") return &TCP_PROXY_SERVER_KEYS;
if (section == "msg_transport") return &MSG_TRANSPORT_KEYS;
return nullptr; // [debug] — free-form, no key validation
}
static QString formatKeyHex(const uint8_t* bin, size_t len) {
return QByteArray(reinterpret_cast<const char*>(bin), len).toHex();
@ -76,24 +179,86 @@ bool NodeConfig::load() {
m_servers.clear();
m_clients.clear();
m_errors.clear();
QString section, controlIp, controlPort;
QTextStream in(&f);
int lineNum = 0;
bool sectionValid = true; // global/empty is valid
bool debugSection = false;
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
lineNum++;
if (line.isEmpty() || line.startsWith('#')) continue;
if (line.startsWith('[') && line.endsWith(']')) {
section = line.mid(1, line.length() - 2);
sectionValid = isSectionValid(section);
debugSection = (section == "debug");
if (!sectionValid) {
m_errors.append(QString("line %1: unknown section [%2]")
.arg(lineNum).arg(section));
}
continue;
}
if (!sectionValid)
continue; // skip keys in unknown sections (already reported)
int eq = line.indexOf('=');
if (eq < 0) continue;
QString key = line.left(eq).trimmed();
QString val = line.mid(eq + 1).trimmed();
if (section == "global") {
// Validate key unless [debug] section (free-form category=level)
if (!debugSection) {
const QSet<QString>* validKeys = keysForSection(section);
if (validKeys && !validKeys->contains(key)) {
QString knownList;
for (const auto& k : *validKeys) {
if (!knownList.isEmpty()) knownList += ", ";
knownList += k;
}
m_errors.append(QString("line %1: [%2] unknown option '%3'. Valid: %4")
.arg(lineNum).arg(section.isEmpty() ? "global" : section)
.arg(key).arg(knownList));
}
}
// Value validation for known keys
if (!debugSection && key == "debug_level" && !val.isEmpty()) {
if (!VALID_DEBUG_LEVELS.contains(val.toLower())) {
m_errors.append(QString("line %1: [%2] debug_level='%3' is invalid. Valid: %4")
.arg(lineNum).arg(section.isEmpty() ? "global" : section)
.arg(val).arg(VALID_DEBUG_LEVELS.join(", ")));
}
}
if ((key == "port" || key == "control_port") && !val.isEmpty()) {
bool ok;
int p = val.toInt(&ok);
if (!ok || p < 1 || p > 65535) {
m_errors.append(QString("line %1: [%2] %3='%4' is invalid (must be 1-65535)")
.arg(lineNum).arg(section).arg(key).arg(val));
}
}
if ((key == "my_public_key" || key == "my_private_key" || key == "peer_public_key")
&& !val.isEmpty() && val.length() != 64) {
m_errors.append(QString("line %1: [%2] %3 must be 64 hex chars (got %4)")
.arg(lineNum).arg(section.isEmpty() ? "global" : section)
.arg(key).arg(val.length()));
}
if (key == "my_node_id" && !val.isEmpty() && val.length() != 16) {
m_errors.append(QString("line %1: [%2] my_node_id must be 16 hex chars (got %3)")
.arg(lineNum).arg(section.isEmpty() ? "global" : section)
.arg(val.length()));
}
// --- store known values (unchanged) ---
if (section == "global" || section.isEmpty()) {
if (key == "my_node_name") m_nodeName = val;
else if (key == "my_node_id") m_nodeId = val;
else if (key == "my_public_key") m_pubKey = val;
@ -110,7 +275,6 @@ bool NodeConfig::load() {
m_clients.append(nc);
} else if (key == "link") {
if (!m_clients.isEmpty() && m_clients.last().name == cliName) {
// link=server:addr:port
int sep1 = val.indexOf(':');
int sep2 = val.lastIndexOf(':');
if (sep1 > 0 && sep2 > sep1) {

6
tools/chatgui/transport/node_config.h

@ -3,6 +3,7 @@
#define NODE_CONFIG_H
#include <QString>
#include <QStringList>
#include <QList>
#include <QPair>
@ -51,6 +52,10 @@ public:
QString debugLevel() const { return m_debugLevel; }
QString debugCategories() const { return m_debugCategories; }
/* Config validation errors (collected during load()) */
bool hasErrors() const { return !m_errors.isEmpty(); }
const QStringList& errors() const { return m_errors; }
/* Identity generation (public for fixing invalid keys) */
void generateIdentity();
@ -68,5 +73,6 @@ private:
QString m_debugFile;
QString m_debugLevel;
QString m_debugCategories;
QStringList m_errors;
};
#endif // NODE_CONFIG_H

Loading…
Cancel
Save