This commit is contained in:
evgeny committed 2026-08-04 15:29:25 +03:00
1 parent 26f4da1b7c
commit 712d1b53df
29 files changed
+1029 -1138

No files matched your search

+170
View File
@@ -52,6 +52,7 @@ typedef enum {
SECTION_GUI,
SECTION_CHAT,
SECTION_NTP,
SECTION_CHATSERVER,
SECTION_LOG_UDP
} section_type_t;
@@ -718,6 +719,51 @@ static int parse_client(const char *key, const char *value, struct CFG_CLIENT *c
return -1;
}
static int parse_size_with_suffix(const char *value, uint64_t *out_bytes) {
if (!value || !out_bytes) return -1;
char *end = NULL;
uint64_t num = strtoull(value, &end, 10);
if (end == value) return -1;
while (*end && isspace((unsigned char)*end)) end++;
if (*end == '\0') { *out_bytes = num; return 0; }
if (*end == 'M' || *end == 'm') { *out_bytes = num * 1024ULL * 1024ULL; return 0; }
if (*end == 'G' || *end == 'g') { *out_bytes = num * 1024ULL * 1024ULL * 1024ULL; return 0; }
return -1;
}
static int parse_chatserver(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
if (strcmp(key, "join_password") == 0) {
strncpy(global->chatserver_join_password, value, sizeof(global->chatserver_join_password) - 1);
global->chatserver_join_password[sizeof(global->chatserver_join_password) - 1] = '\0';
return 0;
}
if (strcmp(key, "allowed_groups") == 0) {
strncpy(global->chatserver_allowed_groups, value, sizeof(global->chatserver_allowed_groups) - 1);
global->chatserver_allowed_groups[sizeof(global->chatserver_allowed_groups) - 1] = '\0';
return 0;
}
if (strcmp(key, "storage_total_size") == 0) {
uint64_t sz;
if (parse_size_with_suffix(value, &sz) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid chatserver storage_total_size '%s' (expected number with M/G suffix)", filename, line_num, value);
return -1;
}
global->chatserver_storage_total_size = sz;
return 0;
}
if (strcmp(key, "storage_unit_size") == 0) {
uint64_t sz;
if (parse_size_with_suffix(value, &sz) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid chatserver storage_unit_size '%s' (expected number with M/G suffix)", filename, line_num, value);
return -1;
}
global->chatserver_storage_unit_size = sz;
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown chatserver option '%s'. Valid: join_policy, join_password, allowed_groups, storage_total_size, storage_unit_size", filename, line_num, key);
return -1;
}
static int parse_network(const char *key, const char *value, struct CFG_NETWORK *net, const char *filename, int line_num) {
if (strcmp(key, "id") == 0) {
net->id = strtoull(value, NULL, 16);
@@ -758,6 +804,7 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
if (strcasecmp(section, "ntp") == 0) return SECTION_NTP;
if (strcasecmp(section, "gui") == 0) return SECTION_GUI;
if (strcasecmp(section, "chat") == 0) return SECTION_CHAT;
if (strcasecmp(section, "chatserver") == 0) return SECTION_CHATSERVER;
if (strcasecmp(section, "log_udp") == 0) return SECTION_LOG_UDP;
char *colon = strchr(section, ':');
@@ -802,6 +849,11 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
cfg->global.ntp_servers = NULL;
cfg->global.ntp_server_count = 0;
cfg->global.ntp_resync_interval = 3600;
cfg->global.chatserver_enabled = 0;
cfg->global.chatserver_join_password[0] = '\0';
cfg->global.chatserver_allowed_groups[0] = '\0';
cfg->global.chatserver_storage_total_size = 0;
cfg->global.chatserver_storage_unit_size = 0;
section_type_t cur_section = SECTION_UNKNOWN;
struct CFG_SERVER *cur_server = NULL;
@@ -993,6 +1045,12 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
/* chat_setting_set уже напечатал ошибку */
}
break;
case SECTION_CHATSERVER:
cfg->global.chatserver_enabled = 1;
if (parse_chatserver(key, value, &cfg->global, filename, line_num) < 0) {
/* parse_chatserver already printed the error */
}
break;
case SECTION_GUI:
break;
default:
@@ -1076,6 +1134,11 @@ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const cha
cfg->global.ntp_servers = NULL;
cfg->global.ntp_server_count = 0;
cfg->global.ntp_resync_interval = 3600;
cfg->global.chatserver_enabled = 0;
cfg->global.chatserver_join_password[0] = '\0';
cfg->global.chatserver_allowed_groups[0] = '\0';
cfg->global.chatserver_storage_total_size = 0;
cfg->global.chatserver_storage_unit_size = 0;
section_type_t cur_section = SECTION_UNKNOWN;
struct CFG_SERVER *cur_server = NULL;
@@ -1117,6 +1180,7 @@ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const cha
case SECTION_NETWORK: if (cur_network) parse_network(key, value, cur_network, filename, line_num); break;
case SECTION_NTP: if (strcmp(key, "enabled") == 0) cfg->global.ntp_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0; else if (strcmp(key, "server") == 0) { struct CFG_NTP_SERVER *ns = u_calloc(1, sizeof(struct CFG_NTP_SERVER)); if (ns) { strncpy(ns->name, value, sizeof(ns->name) - 1); ns->name[sizeof(ns->name) - 1] = '\0'; ns->next = cfg->global.ntp_servers; cfg->global.ntp_servers = ns; cfg->global.ntp_server_count++; } } else if (strcmp(key, "interval") == 0) { cfg->global.ntp_resync_interval = atoi(value); if (cfg->global.ntp_resync_interval < 60) cfg->global.ntp_resync_interval = 60; } else DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown ntp option '%s'", filename, line_num, key); break;
case SECTION_CHAT: chat_setting_set(key, value); break;
case SECTION_CHATSERVER: cfg->global.chatserver_enabled = 1; parse_chatserver(key, value, &cfg->global, filename, line_num); break;
case SECTION_GUI: break;
case SECTION_LOG_UDP:
if (strcmp(key, "ip") == 0) strncpy(cfg->global.log_udp_ip, value, sizeof(cfg->global.log_udp_ip) - 1);
@@ -1323,6 +1387,112 @@ void print_config(const struct utun_config *cfg) {
}
}
static const char invite_b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
void print_chatserver_invite_link(const struct utun_config *config, const struct CFG_SERVER *servers) {
if (!config || !servers) return;
const struct global_config *g = &config->global;
if (!g->chatserver_enabled) return;
if (strlen(g->my_public_key_hex) < SC_PUBKEY_SIZE * 2) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "print_chatserver_invite_link: no public key");
return;
}
uint8_t pub_bin[SC_PUBKEY_SIZE];
/* Use first SC_PUBKEY_SIZE*2 hex chars (X25519 pubkey, may be part of longer combined key) */
{
char pub_hex_first[SC_PUBKEY_SIZE * 2 + 1];
memcpy(pub_hex_first, g->my_public_key_hex, SC_PUBKEY_SIZE * 2);
pub_hex_first[SC_PUBKEY_SIZE * 2] = '\0';
if (hex_to_binary(pub_hex_first, pub_bin, SC_PUBKEY_SIZE) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "print_chatserver_invite_link: invalid pubkey hex");
return;
}
}
/* Collect server addresses */
struct {
int family;
uint8_t addr[16];
uint16_t port;
uint8_t proto;
} addrs[32];
int addr_count = 0;
for (const struct CFG_SERVER *s = servers; s && addr_count < 32; s = s->next) {
if (s->ip.ss_family == AF_INET) {
struct sockaddr_in *sin = (struct sockaddr_in *)&s->ip;
addrs[addr_count].family = 4;
memcpy(addrs[addr_count].addr, &sin->sin_addr, 4);
addrs[addr_count].port = ntohs(sin->sin_port);
addrs[addr_count].proto = (s->transport == 1) ? 0x02 : 0x01;
addr_count++;
} else if (s->ip.ss_family == AF_INET6) {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&s->ip;
addrs[addr_count].family = 6;
memcpy(addrs[addr_count].addr, &sin6->sin6_addr, 16);
addrs[addr_count].port = ntohs(sin6->sin6_port);
addrs[addr_count].proto = (s->transport == 1) ? 0x02 : 0x01;
addr_count++;
}
}
if (addr_count == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "print_chatserver_invite_link: no server addresses");
return;
}
/* Build binary blob */
uint8_t raw[4096];
size_t pos = 0;
int has_pass = g->chatserver_join_password[0] ? 1 : 0;
raw[pos++] = has_pass ? 0x02 : 0x01; /* version */
if (has_pass) {
size_t plen = strlen(g->chatserver_join_password);
raw[pos++] = (uint8_t)plen;
memcpy(raw + pos, g->chatserver_join_password, plen); pos += plen;
}
/* channel_id = 0 (8 bytes BE) */
for (int i = 0; i < 8; i++) raw[pos++] = 0;
int ia = 0;
while (ia < addr_count) {
int rem = addr_count - ia;
int cnt = (rem > 4) ? 4 : rem;
uint8_t header = (uint8_t)((cnt - 1) & 0x03);
for (int j = 0; j < cnt; j++) if (addrs[ia + j].family == 6) header |= (uint8_t)(1 << (2 + j));
raw[pos++] = header;
memcpy(raw + pos, pub_bin, SC_PUBKEY_SIZE); pos += SC_PUBKEY_SIZE;
for (int j = 0; j < cnt; j++) {
int ip_len = (addrs[ia + j].family == 6) ? 16 : 4;
raw[pos++] = 0; /* socketId */
raw[pos++] = addrs[ia + j].proto; /* proto */
memcpy(raw + pos, addrs[ia + j].addr, (size_t)ip_len); pos += (size_t)ip_len;
raw[pos++] = (uint8_t)(addrs[ia + j].port >> 8);
raw[pos++] = (uint8_t)(addrs[ia + j].port & 0xFF);
}
ia += cnt;
}
/* Base64 encode */
char link[2048];
memcpy(link, "utun://", 7);
size_t opos = 7;
size_t i = 0;
while (i < pos) {
uint32_t val = (uint32_t)raw[i] << 16;
val |= (i + 1 < pos) ? (uint32_t)raw[i + 1] << 8 : 0;
val |= (i + 2 < pos) ? (uint32_t)raw[i + 2] : 0;
link[opos++] = invite_b64[(val >> 18) & 0x3F];
link[opos++] = invite_b64[(val >> 12) & 0x3F];
link[opos++] = (i + 1 < pos) ? invite_b64[(val >> 6) & 0x3F] : '=';
link[opos++] = (i + 2 < pos) ? invite_b64[val & 0x3F] : '=';
i += 3;
}
link[opos] = '\0';
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Chatserver invite link: %s", link);
fprintf(stderr, "Chatserver invite link: %s\n", link);
}
int update_config_keys(const char *filename, const char *priv_key, const char *pub_key) {
// Minimal implementation: just append to file
FILE *fp = fopen(filename, "a");