// config_parser.c - Configuration parser for utun application (updated for new structures) #define _POSIX_C_SOURCE 200809L #include "config_parser.h" #include #include #include #include #include "../lib/debug_config.h" #include #include "../lib/platform_compat.h" #include #ifndef _WIN32 #include #include #include #endif #include "../lib/mem.h" #define MAX_LINE_LEN 1024 #define INITIAL_ARRAY_CAPACITY 8 /* Forward declaration for new functions */ static uint32_t parse_debug_categories(const char *value); static int assign_string(char *dest, size_t dest_size, const char *src); typedef enum { SECTION_UNKNOWN, SECTION_GLOBAL, SECTION_SERVER, SECTION_CLIENT, SECTION_ROUTING, SECTION_DEBUG } section_type_t; /* Forward declaration for new functions */ static uint32_t parse_debug_categories(const char *value); /* Parse debug categories from comma-separated string */ static uint32_t parse_debug_categories(const char *value) { uint32_t categories = 0; char *value_copy = u_strdup(value); if (!value_copy) return DEBUG_CATEGORY_ALL; // Default to all on error char *token = strtok(value_copy, ","); while (token) { // Простое сравнение без пробелов if (strstr(token, "uasync")) { categories |= DEBUG_CATEGORY_UASYNC; } else if (strstr(token, "ll_queue")) { categories |= DEBUG_CATEGORY_LL_QUEUE; } else if (strstr(token, "connection")) { categories |= DEBUG_CATEGORY_CONNECTION; } else if (strstr(token, "etcp")) { categories |= DEBUG_CATEGORY_ETCP; } else if (strstr(token, "crypto")) { categories |= DEBUG_CATEGORY_CRYPTO; } else if (strstr(token, "memory")) { categories |= DEBUG_CATEGORY_MEMORY; } else if (strstr(token, "timing")) { categories |= DEBUG_CATEGORY_TIMING; } else if (strstr(token, "config")) { categories |= DEBUG_CATEGORY_CONFIG; } else if (strstr(token, "tun")) { categories |= DEBUG_CATEGORY_TUN; } else if (strstr(token, "routing")) { categories |= DEBUG_CATEGORY_ROUTING; } else if (strstr(token, "timers")) { categories |= DEBUG_CATEGORY_TIMERS; } else if (strstr(token, "all")) { categories |= DEBUG_CATEGORY_ALL; } else if (strstr(token, "none")) { categories = DEBUG_CATEGORY_NONE; } token = strtok(NULL, ","); } u_free(value_copy); return categories ? categories : DEBUG_CATEGORY_ALL; // Default to all if nothing parsed } static char* trim(char *str) { if (!str) return NULL; while (isspace((unsigned char)*str)) str++; char *end = str + strlen(str) - 1; while (end > str && isspace((unsigned char)*end)) end--; *(end + 1) = '\0'; return str; } static int parse_key_value(const char *line, char *key, size_t key_len, char *value, size_t value_len) { char *equal = strchr(line, '='); if (!equal) return -1; size_t key_size = equal - line; if (key_size >= key_len) return -1; strncpy(key, line, key_size); key[key_size] = '\0'; trim(key); const char *val_start = equal + 1; size_t val_len = strlen(val_start); if (val_len >= value_len) return -1; strcpy(value, val_start); char *comment = strchr(value, '#'); if (comment) *comment = '\0'; trim(value); return 0; } static int assign_string(char *dest, size_t dest_size, const char *src) { if (!dest || !src || strlen(src) >= dest_size) return -1; strcpy(dest, src); 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'; char *slash = strchr(ip_str, '/'); if (slash) { *slash = '\0'; *netmask = atoi(slash + 1); } else { *netmask = 32; // Default IPv4 netmask } // 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; } 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; struct addrinfo *result; if (getaddrinfo(addr_str, port_str, &hints, &result) != 0) { return -1; } memcpy(sockaddr, result->ai_addr, result->ai_addrlen); freeaddrinfo(result); return 0; } 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); char *port_str = strrchr(addr_copy, ':'); if (!port_str) return -1; *port_str = '\0'; port_str++; return parse_sockaddr(addr_copy, port_str, sockaddr); } static uint32_t get_netif_index(const char *ifname) { if (!ifname || strlen(ifname) == 0) return 0; #ifdef _WIN32 // Windows doesn't have if_nametoindex, return 0 to indicate no specific interface (void)ifname; return 0; #else return if_nametoindex(ifname); #endif } static struct CFG_CLIENT_LINK* create_client_link(struct CFG_SERVER* local_srv, const char *remote_addr) { struct CFG_CLIENT_LINK *link = u_calloc(1, sizeof(struct CFG_CLIENT_LINK)); if (!link) { DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "create_client_link: failed to allocate memory for client link"); return NULL; } link->local_srv = local_srv; if (parse_address_and_port(remote_addr, &link->remote_addr) < 0) { u_free(link); return NULL; } link->next = NULL; return link; } static void free_cfg_client_links(struct CFG_CLIENT_LINK *links) { while (links) { struct CFG_CLIENT_LINK *next = links->next; u_free(links); links = next; } } static struct CFG_ROUTE_ENTRY* create_route_entry(const char *subnet_str) { struct CFG_ROUTE_ENTRY *entry = u_calloc(1, sizeof(struct CFG_ROUTE_ENTRY)); if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "create_route_entry: failed to allocate memory for route entry"); return NULL; } if (parse_ip_with_netmask(subnet_str, &entry->ip, &entry->netmask) < 0) { u_free(entry); return NULL; } entry->next = NULL; return entry; } static void free_route_entries(struct CFG_ROUTE_ENTRY *entries) { while (entries) { struct CFG_ROUTE_ENTRY *next = entries->next; u_free(entries); entries = next; } } static int add_route_entry(struct CFG_ROUTE_ENTRY **list, const char *subnet_str) { struct CFG_ROUTE_ENTRY *new_entry = create_route_entry(subnet_str); if (!new_entry) return -1; // Add to head of list new_entry->next = *list; *list = new_entry; return 0; } static struct CFG_SERVER* find_server_by_name(struct CFG_SERVER *servers, const char *name) { struct CFG_SERVER *srv = servers; while (srv) { if (strcmp(srv->name, name) == 0) { return srv; } srv = srv->next; } return NULL; } static int parse_global(const char *key, const char *value, struct global_config *global) { if (strcmp(key, "my_private_key") == 0) { return assign_string(global->my_private_key_hex, MAX_KEY_LEN, value); } if (strcmp(key, "my_public_key") == 0) { return assign_string(global->my_public_key_hex, MAX_KEY_LEN, value); } if (strcmp(key, "my_node_id") == 0) { global->my_node_id = strtoull(value, NULL, 16); return 0; } if (strcmp(key, "tun_ifname") == 0) { snprintf(global->tun_ifname, sizeof(global->tun_ifname), "%s", value); return 0; } if (strcmp(key, "tun_ip") == 0) { uint8_t netmask; return parse_ip_with_netmask(value, &global->tun_ip, &netmask); } if (strcmp(key, "mtu") == 0) { global->mtu = atoi(value); return 0; } if (strcmp(key, "keepalive_timeout") == 0) { global->keepalive_timeout = atoi(value); return 0; } if (strcmp(key, "keepalive_interval") == 0) { global->keepalive_interval = atoi(value); return 0; } if (strcmp(key, "control_ip") == 0) { // Store for later processing with control_port return 0; // We'll handle this when we see control_port } if (strcmp(key, "control_port") == 0) { // This is tricky - we need to get control_ip from previous parsing // For now, we'll use a simple approach struct global_config temp_global = *global; // Assume we stored control_ip somewhere or use default char control_ip[MAX_ADDR_LEN] = "127.0.0.1"; // Default char port_str[16]; snprintf(port_str, sizeof(port_str), "%s", value); parse_sockaddr(control_ip, port_str, &global->control_sock); return 0; } if (strcmp(key, "net_debug") == 0) { global->net_debug = atoi(value); return 0; } if (strcmp(key, "log_file") == 0) { return assign_string(global->log_file, sizeof(global->log_file), value); } if (strcmp(key, "debug_level") == 0) { return assign_string(global->debug_level, sizeof(global->debug_level), value); } if (strcmp(key, "debug_categories") == 0) { // Parse comma-separated list of debug categories global->debug_categories = parse_debug_categories(value); return 0; } if (strcmp(key, "enable_timestamp") == 0) { global->enable_timestamp = atoi(value); return 0; } if (strcmp(key, "enable_function_names") == 0) { global->enable_function_names = atoi(value); return 0; } if (strcmp(key, "enable_file_lines") == 0) { global->enable_file_lines = atoi(value); return 0; } if (strcmp(key, "enable_colors") == 0) { global->enable_colors = atoi(value); return 0; } if (strcmp(key, "tun_test_mode") == 0) { global->tun_test_mode = atoi(value); return 0; } return 0; } static int parse_server(const char *key, const char *value, struct CFG_SERVER *srv) { if (strcmp(key, "addr") == 0) { return parse_address_and_port(value, &srv->ip); } if (strcmp(key, "so_mark") == 0) { srv->so_mark = atoi(value); return 0; } if (strcmp(key, "netif") == 0) { srv->netif_index = get_netif_index(value); return 0; } if (strcmp(key, "type") == 0) { if (strcmp(value, "public") == 0) { srv->type = CFG_SERVER_TYPE_PUBLIC; } else if (strcmp(value, "nat") == 0) { srv->type = CFG_SERVER_TYPE_NAT; } else if (strcmp(value, "private") == 0) { srv->type = CFG_SERVER_TYPE_PRIVATE; } else { srv->type = CFG_SERVER_TYPE_UNKNOWN; } return 0; } if (strcmp(key, "mtu") == 0) { srv->mtu = atoi(value); return 0; } return 0; } static int parse_client(const char *key, const char *value, struct CFG_CLIENT *cli, struct CFG_SERVER *servers) { if (strcmp(key, "link") == 0) { char link_copy[MAX_CONN_NAME_LEN + MAX_ADDR_LEN]; if (strlen(value) >= sizeof(link_copy)) return -1; strcpy(link_copy, value); // Find first colon (separator between server and ip:port) char *first_colon = strchr(link_copy, ':'); if (!first_colon) return -1; *first_colon = '\0'; // Find server by name struct CFG_SERVER *local_srv = find_server_by_name(servers, link_copy); if (!local_srv) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse_client: server '%s' not found for client link", link_copy); return -1; } struct CFG_CLIENT_LINK *new_link = create_client_link(local_srv, first_colon + 1); if (!new_link) return -1; // Add to linked list (prepend) new_link->next = cli->links; cli->links = new_link; return 0; } if (strcmp(key, "peer_public_key") == 0) { return assign_string(cli->peer_public_key_hex, MAX_KEY_LEN, value); } if (strcmp(key, "keepalive") == 0) { cli->keepalive = atoi(value); return 0; } return 0; } static section_type_t parse_section_header(const char *line, char *name, size_t name_len) { if (line[0] != '[') return SECTION_UNKNOWN; size_t line_len = strlen(line); if (line[line_len - 1] != ']') return SECTION_UNKNOWN; char section[128]; if (line_len - 2 >= sizeof(section)) return SECTION_UNKNOWN; strncpy(section, line + 1, line_len - 2); section[line_len - 2] = '\0'; trim(section); if (strcasecmp(section, "global") == 0) return SECTION_GLOBAL; if (strcasecmp(section, "routing") == 0) return SECTION_ROUTING; if (strcasecmp(section, "debug") == 0) return SECTION_DEBUG; char *colon = strchr(section, ':'); if (!colon) return SECTION_UNKNOWN; *colon = '\0'; char *type = trim(section); char *n = trim(colon + 1); if (strlen(n) >= name_len) return SECTION_UNKNOWN; strcpy(name, n); if (strcasecmp(type, "server") == 0) return SECTION_SERVER; if (strcasecmp(type, "client") == 0) return SECTION_CLIENT; return SECTION_UNKNOWN; } static struct utun_config* parse_config_internal(FILE *fp, const char *filename) { struct utun_config *cfg = u_calloc(1, sizeof(struct utun_config)); if (!cfg) { DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "parse_config_internal: failed to allocate memory for config structure"); return NULL; } // Set default values cfg->global.keepalive_timeout = 2000; // Default 2 seconds section_type_t cur_section = SECTION_UNKNOWN; struct CFG_SERVER *cur_server = NULL; struct CFG_CLIENT *cur_client = NULL; char line[MAX_LINE_LEN]; int line_num = 0; while (fgets(line, sizeof(line), fp)) { line_num++; char *trimmed = trim(line); if (trimmed[0] == '\0' || trimmed[0] == '#') continue; if (trimmed[0] == '[') { // Handle previous section if (cur_section == SECTION_SERVER && cur_server) { // Add server to linked list cur_server->next = cfg->servers; cfg->servers = cur_server; cur_server = NULL; } if (cur_section == SECTION_CLIENT && cur_client) { // Add client to linked list cur_client->next = cfg->clients; cfg->clients = cur_client; cur_client = NULL; } char name[MAX_CONN_NAME_LEN]; cur_section = parse_section_header(trimmed, name, sizeof(name)); if (cur_section == SECTION_SERVER) { cur_server = u_calloc(1, sizeof(struct CFG_SERVER)); if (!cur_server) { DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "parse_config_internal: failed to allocate memory for server %s", name); goto error; } strcpy(cur_server->name, name); } else if (cur_section == SECTION_CLIENT) { cur_client = u_calloc(1, sizeof(struct CFG_CLIENT)); if (!cur_client) { DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "parse_config_internal: failed to allocate memory for client %s", name); goto error; } strcpy(cur_client->name, name); } continue; } char key[MAX_LINE_LEN], value[MAX_LINE_LEN]; if (parse_key_value(trimmed, key, sizeof(key), value, sizeof(value)) < 0) { DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid key=value format", filename, line_num); continue; } switch (cur_section) { case SECTION_GLOBAL: if (parse_global(key, value, &cfg->global) < 0) { DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid global key '%s'", filename, line_num, key); } break; case SECTION_SERVER: if (cur_server && parse_server(key, value, cur_server) < 0) { DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid server key '%s'", filename, line_num, key); } break; case SECTION_CLIENT: if (cur_client && parse_client(key, value, cur_client, cfg->servers) < 0) { DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid client key '%s'", filename, line_num, key); } break; case SECTION_ROUTING: if (strcmp(key, "route_subnet") == 0) { add_route_entry(&cfg->route_subnets, value); } else if (strcmp(key, "my_subnet") == 0) { add_route_entry(&cfg->my_subnets, value); } break; case SECTION_DEBUG: // Format: category=level (e.g., etcp=debug, crypto=info) if (cfg->global.debug_levels.count < 16) { strncpy(cfg->global.debug_levels.category[cfg->global.debug_levels.count], key, 15); cfg->global.debug_levels.category[cfg->global.debug_levels.count][15] = '\0'; strncpy(cfg->global.debug_levels.level[cfg->global.debug_levels.count], value, 15); cfg->global.debug_levels.level[cfg->global.debug_levels.count][15] = '\0'; cfg->global.debug_levels.count++; } break; default: DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key); break; } } // Handle final sections if (cur_section == SECTION_SERVER && cur_server) { cur_server->next = cfg->servers; cfg->servers = cur_server; } if (cur_section == SECTION_CLIENT && cur_client) { cur_client->next = cfg->clients; cfg->clients = cur_client; } // Set default TUN interface name if not provided if (cfg->global.tun_ifname[0] == '\0') { snprintf(cfg->global.tun_ifname, sizeof(cfg->global.tun_ifname), "tun0"); DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Auto-generated TUN interface name: %s", cfg->global.tun_ifname); } return cfg; error: if (cur_server) u_free(cur_server); if (cur_client) { free_cfg_client_links(cur_client->links); u_free(cur_client); } free_config(cfg); return NULL; } struct utun_config* parse_config(const char *filename) { DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Opening config file: %s", filename); FILE *fp = fopen(filename, "r"); if (!fp) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to open config file: %s (errno=%d)", filename, errno); return NULL; } DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Successfully opened config file"); struct utun_config *config = parse_config_internal(fp, filename); fclose(fp); return config; } void free_config(struct utun_config *config) { if (!config) return; // Free servers struct CFG_SERVER *server = config->servers; while (server) { struct CFG_SERVER *next = server->next; u_free(server); server = next; } // Free clients and their links struct CFG_CLIENT *client = config->clients; while (client) { struct CFG_CLIENT *next = client->next; free_cfg_client_links(client->links); u_free(client); client = next; } // Free route entries free_route_entries(config->route_subnets); free_route_entries(config->my_subnets); u_free(config); } static const char* ip_to_string(const struct IP *ip, char *buffer, size_t buffer_size) { if (ip->family == AF_INET) { inet_ntop(AF_INET, &ip->addr.v4, buffer, buffer_size); } else if (ip->family == AF_INET6) { inet_ntop(AF_INET6, &ip->addr.v6, buffer, buffer_size); } else { snprintf(buffer, buffer_size, "invalid"); } return buffer; } void print_config(const struct utun_config *cfg) { if (!cfg) return; const struct global_config *g = &cfg->global; char ip_buffer[64]; DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Global: priv_key=%s, pub_key=%s, node_id=%llx, tun_if=%s, tun_ip=%s, mtu=%d, keepalive=%d, test_mode=%d, net_debug=%d", g->my_private_key_hex, g->my_public_key_hex, (unsigned long long)g->my_node_id, g->tun_ifname[0] ? g->tun_ifname : "auto", ip_to_string(&g->tun_ip, ip_buffer, sizeof(ip_buffer)), g->mtu, g->keepalive_timeout, g->tun_test_mode, g->net_debug); DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Servers:"); struct CFG_SERVER *s = cfg->servers; while (s) { struct sockaddr_in *sin = (struct sockaddr_in *)&s->ip; DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: %s:%d (mark=%d, netif=%u, type=%u, mtu=%d)", s->name, ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port), s->so_mark, s->netif_index, s->type, s->mtu); s = s->next; } DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Clients:"); struct CFG_CLIENT *c = cfg->clients; while (c) { DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: peer_key=%s, keepalive=%d", c->name, c->peer_public_key_hex, c->keepalive); struct CFG_CLIENT_LINK *link = c->links; while (link) { struct sockaddr_in *sin = (struct sockaddr_in *)&link->remote_addr; DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " Link: %s:%d (via %s)", ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port), link->local_srv->name); link = link->next; } c = c->next; } DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Route Subnets:"); struct CFG_ROUTE_ENTRY *allowed = cfg->route_subnets; while (allowed) { DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s/%d", ip_to_string(&allowed->ip, ip_buffer, sizeof(ip_buffer)), allowed->netmask); allowed = allowed->next; } DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "My Subnets:"); struct CFG_ROUTE_ENTRY *my = cfg->my_subnets; while (my) { DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s/%d", ip_to_string(&my->ip, ip_buffer, sizeof(ip_buffer)), my->netmask); my = my->next; } } 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"); if (!fp) return -1; fprintf(fp, "\n[global]\n"); fprintf(fp, "my_private_key=%s\n", priv_key); fprintf(fp, "my_public_key=%s\n", pub_key); fclose(fp); return 0; }