Parse CIDR, control endpoints and storage sizes without truncation

This commit is contained in:
evgeny committed 2026-10-26 09:08:23 +01:00
1 parent 869421cc05
commit 7593120bd9
1 file changed
+70 -75
+70 -75
View File
@@ -52,56 +52,58 @@ typedef enum {
} section_type_t;
static int assign_string(char *dest, size_t dest_size, const char *src) {
if (!dest || !src || strlen(src) >= dest_size) return -1;
if (!dest || !src || strlen(src) >= dest_size) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "configuration string exceeds capacity=%zu or has a missing argument", dest_size);
return -1;
}
strcpy(dest, src);
return 0;
}
/* Полное беззнаковое число без знака и хвоста; значение меняется только после всех проверок. */
static int parse_unsigned(const char* text, int base, uint64_t maximum, uint64_t* out) {
char* end;
errno = 0;
unsigned long long value = strtoull(text, &end, base);
if ((base == 16 ? config_ini_nibble((unsigned char)*text) < 0 : !isdigit((unsigned char)*text)) ||
end == text || *end || errno || value > maximum) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "invalid unsigned base-%d value '%s', maximum=%llu", base, text, (unsigned long long)maximum);
return -1;
}
*out = value; return 0;
}
static int parse_ip_with_netmask(const char *str, struct IP *ip, uint8_t *netmask) {
char ip_str[64];
strncpy(ip_str, str, sizeof(ip_str) - 1);
ip_str[sizeof(ip_str) - 1] = '\0';
if (assign_string(ip_str, sizeof(ip_str), str)) return -1;
char *slash = strchr(ip_str, '/');
if (slash) {
*slash = '\0';
*netmask = atoi(slash + 1);
} else {
*netmask = 32; // Default IPv4 netmask
if (slash) *slash++ = '\0';
struct IP parsed = {0};
unsigned maximum;
if (inet_pton(AF_INET, ip_str, &parsed.addr.v4) == 1) { parsed.family = AF_INET; maximum = 32; }
else if (inet_pton(AF_INET6, ip_str, &parsed.addr.v6) == 1) { parsed.family = AF_INET6; maximum = 128; }
else {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "invalid IP address: %s", str); return -1;
}
// Try IPv4 first
struct in_addr addr4;
if (inet_pton(AF_INET, ip_str, &addr4) == 1) {
ip->family = AF_INET;
ip->addr.v4 = addr4;
return 0;
}
// Try IPv6
struct in6_addr addr6;
if (inet_pton(AF_INET6, ip_str, &addr6) == 1) {
ip->family = AF_INET6;
ip->addr.v6 = addr6;
if (*netmask == 32) *netmask = 128; // Default IPv6 netmask
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse_ip_with_netmask: invalid IP address format: %s", str);
return -1;
uint64_t prefix = maximum;
if (slash && parse_unsigned(slash, 10, maximum, &prefix)) return -1;
*ip = parsed; *netmask = (uint8_t)prefix; return 0;
}
static int parse_sockaddr(const char *addr_str, const char *port_str, struct sockaddr_storage *sockaddr) {
struct addrinfo hints = {0};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_NUMERICSERV;
uint64_t port;
if (!addr_str[0] || parse_unsigned(port_str, 10, UINT16_MAX, &port)) return -1;
char clean_addr[MAX_ADDR_LEN];
const char *a = addr_str;
if (addr_str[0] == '[') {
size_t len = strlen(addr_str);
if (len > 2 && addr_str[len - 1] == ']') {
if (len - 2 >= sizeof(clean_addr)) return -1;
if (len - 2 >= sizeof(clean_addr)) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "bracketed address is too long"); return -1; }
memcpy(clean_addr, addr_str + 1, len - 2);
clean_addr[len - 2] = '\0';
a = clean_addr;
@@ -109,10 +111,15 @@ static int parse_sockaddr(const char *addr_str, const char *port_str, struct soc
}
struct addrinfo *result;
if (getaddrinfo(a, port_str, &hints, &result) != 0) {
int rc = getaddrinfo(a, port_str, &hints, &result);
if (rc != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "cannot resolve configured endpoint host=%s port=%s error=%d", a, port_str, rc);
return -1;
}
if (!result || result->ai_addrlen > sizeof(*sockaddr)) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "resolved endpoint has an invalid address size"); freeaddrinfo(result); return -1;
}
memset(sockaddr, 0, sizeof(*sockaddr));
memcpy(sockaddr, result->ai_addr, result->ai_addrlen);
freeaddrinfo(result);
return 0;
@@ -120,11 +127,10 @@ static int parse_sockaddr(const char *addr_str, const char *port_str, struct soc
static int parse_address_and_port(const char *str, struct sockaddr_storage *sockaddr) {
char addr_copy[MAX_ADDR_LEN];
if (strlen(str) >= sizeof(addr_copy)) return -1;
strcpy(addr_copy, str);
if (assign_string(addr_copy, sizeof(addr_copy), str)) return -1;
char *port_str = strrchr(addr_copy, ':');
if (!port_str) return -1;
if (!port_str) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "endpoint requires host:port: %s", str); return -1; }
*port_str = '\0';
port_str++;
@@ -151,6 +157,7 @@ static struct CFG_CLIENT_LINK* create_client_link(struct CFG_SERVER* local_srv,
}
link->local_srv = local_srv;
if (local_srv) snprintf(link->server_name, sizeof(link->server_name), "%s", local_srv->name);
if (parse_address_and_port(remote_addr, &link->remote_addr) < 0) {
u_free(link);
@@ -271,31 +278,19 @@ static int parse_firewall_rule(const char *rule_str, struct global_config *globa
}
static int parse_control_allow(const char *rule_str, struct global_config *global) {
if (!rule_str || !global) return -1;
char rule_copy[64];
strncpy(rule_copy, rule_str, sizeof(rule_copy) - 1);
rule_copy[sizeof(rule_copy) - 1] = '\0';
config_ini_trim(rule_copy);
uint8_t cidr = 32;
char *slash = strchr(rule_copy, '/');
if (slash) {
*slash = '\0';
cidr = (uint8_t)atoi(slash + 1);
if (cidr > 32) cidr = 32;
}
struct in_addr addr;
if (inet_pton(AF_INET, rule_copy, &addr) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse_control_allow: invalid IP: %s", rule_str);
struct IP address;
uint8_t cidr;
if (parse_ip_with_netmask(rule_str, &address, &cidr) || address.family != AF_INET) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "control allow requires IPv4[/0..32]: %s", rule_str);
return -1;
}
uint32_t ip = ntohl(addr.s_addr);
uint32_t mask = (cidr == 32) ? 0xFFFFFFFF : (~0U << (32 - cidr));
uint32_t ip = ntohl(address.addr.v4.s_addr);
uint32_t mask = cidr ? UINT32_MAX << (32 - cidr) : 0;
if ((global->control_allow_count % INITIAL_ARRAY_CAPACITY) == 0) {
if ((uint32_t)global->control_allow_count > UINT32_MAX / sizeof(struct CFG_CONTROL_ALLOW) - INITIAL_ARRAY_CAPACITY) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "too many control allow rules"); return -1;
}
int new_cap = global->control_allow_count + INITIAL_ARRAY_CAPACITY;
struct CFG_CONTROL_ALLOW *new_allows = u_realloc(global->control_allows,
new_cap * sizeof(struct CFG_CONTROL_ALLOW));
@@ -461,21 +456,19 @@ static int parse_global(const char *key, const char *value, struct global_config
static int parse_control(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
if (strcmp(key, "ip") == 0 || strcmp(key, "control_ip") == 0) {
strncpy(global->control_ip, value, sizeof(global->control_ip) - 1);
global->control_ip[sizeof(global->control_ip) - 1] = '\0';
if (assign_string(global->control_ip, sizeof(global->control_ip), value)) return -1;
if (global->control_sock.ss_family) {
uint16_t port = global->control_sock.ss_family == AF_INET ?
ntohs(((struct sockaddr_in*)&global->control_sock)->sin_port) :
ntohs(((struct sockaddr_in6*)&global->control_sock)->sin6_port);
char port_str[6]; snprintf(port_str, sizeof(port_str), "%u", port);
return parse_sockaddr(value, port_str, &global->control_sock);
}
return 0;
}
if (strcmp(key, "port") == 0 || strcmp(key, "control_port") == 0) {
char control_ip[MAX_ADDR_LEN];
if (global->control_ip[0] != '\0') {
strncpy(control_ip, global->control_ip, sizeof(control_ip) - 1);
} else {
strcpy(control_ip, "127.0.0.1");
}
char port_str[16];
snprintf(port_str, sizeof(port_str), "%s", value);
parse_sockaddr(control_ip, port_str, &global->control_sock);
return 0;
const char* control_ip = global->control_ip[0] ? global->control_ip : "127.0.0.1";
return parse_sockaddr(control_ip, value, &global->control_sock);
}
if (strcmp(key, "allow") == 0 || strcmp(key, "control_allow") == 0) {
return parse_control_allow(value, global);
@@ -824,15 +817,17 @@ static int parse_client(const char *key, const char *value, struct CFG_CLIENT *c
}
static int parse_size_with_suffix(const char *value, uint64_t *out_bytes) {
if (!value || !out_bytes) return -1;
char *end = NULL;
char *end;
errno = 0;
uint64_t num = strtoull(value, &end, 10);
if (end == value) return -1;
if (!isdigit((unsigned char)*value) || errno || 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;
uint64_t scale = 1;
if (*end == 'M' || *end == 'm') { scale = UINT64_C(1024) * 1024; end++; }
else if (*end == 'G' || *end == 'g') { scale = UINT64_C(1024) * 1024 * 1024; end++; }
while (*end && isspace((unsigned char)*end)) end++;
if (*end || num > UINT64_MAX / scale) return -1;
*out_bytes = num * scale; return 0;
}
static int parse_chatserver(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {