|
|
|
|
@ -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) { |
|
|
|
|
|