// config_parser.c - Configuration parser for utun application #define _POSIX_C_SOURCE 200809L #include "config_parser.h" #include #include #include #include #include #include #define MAX_LINE_LEN 1024 #define INITIAL_CONNECTION_CAPACITY 4 // Helper function to trim whitespace static char* trim(char *str) { if (!str) return NULL; // Trim leading spaces while (isspace((unsigned char)*str)) str++; // Trim trailing spaces char *end = str + strlen(str) - 1; while (end > str && isspace((unsigned char)*end)) end--; *(end + 1) = '\0'; return str; } // Parse a key-value pair 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; // Extract key size_t key_size = equal - line; if (key_size >= key_len) return -1; strncpy(key, line, key_size); key[key_size] = '\0'; trim(key); // Extract value const char *val_start = equal + 1; size_t val_len = strlen(val_start); if (val_len >= value_len) return -1; strcpy(value, val_start); trim(value); return 0; } // Parse connection mode string static config_conn_mode_t parse_mode(const char *mode_str) { if (!mode_str) return CONFIG_MODE_UNKNOWN; if (strcasecmp(mode_str, "client") == 0) { return CONFIG_MODE_CLIENT; } else if (strcasecmp(mode_str, "server") == 0) { return CONFIG_MODE_SERVER; } return CONFIG_MODE_UNKNOWN; } // Add a connection to the config static int add_connection(utun_config_t *config, const connection_config_t *conn) { if (config->connection_count >= config->connection_capacity) { // Resize array int new_capacity = config->connection_capacity * 2; connection_config_t *new_connections = realloc(config->connections, new_capacity * sizeof(connection_config_t)); if (!new_connections) return -1; config->connections = new_connections; config->connection_capacity = new_capacity; } // Copy connection data memcpy(&config->connections[config->connection_count], conn, sizeof(connection_config_t)); config->connection_count++; return 0; } // Parse configuration file utun_config_t* parse_config(const char *filename) { FILE *fp = fopen(filename, "r"); if (!fp) { fprintf(stderr, "Failed to open config file: %s\n", filename); return NULL; } // Allocate config structure utun_config_t *config = calloc(1, sizeof(utun_config_t)); if (!config) { fclose(fp); return NULL; } // Initialize connection array config->connection_capacity = INITIAL_CONNECTION_CAPACITY; config->connections = malloc(config->connection_capacity * sizeof(connection_config_t)); if (!config->connections) { free(config); fclose(fp); return NULL; } char line[MAX_LINE_LEN]; char current_section[128] = ""; connection_config_t current_conn = {0}; int in_connection_section = 0; while (fgets(line, sizeof(line), fp)) { // Remove newline line[strcspn(line, "\n")] = '\0'; char *trimmed = trim(line); // Skip empty lines and comments if (strlen(trimmed) == 0 || trimmed[0] == ';' || trimmed[0] == '#') { continue; } // Check for section header if (trimmed[0] == '[' && trimmed[strlen(trimmed) - 1] == ']') { // End previous connection section if any if (in_connection_section) { if (strlen(current_conn.name) > 0) { if (add_connection(config, ¤t_conn) != 0) { fprintf(stderr, "Failed to add connection: %s\n", current_conn.name); } } memset(¤t_conn, 0, sizeof(current_conn)); in_connection_section = 0; } // Extract section name strncpy(current_section, trimmed + 1, sizeof(current_section) - 1); current_section[sizeof(current_section) - 1] = '\0'; current_section[strcspn(current_section, "]")] = '\0'; trim(current_section); // Check if it's a connection section if (strncmp(current_section, "connection:", 11) == 0) { in_connection_section = 1; // Extract connection name char *name = trim(current_section + 11); if (strlen(name) > 0) { strncpy(current_conn.name, name, sizeof(current_conn.name) - 1); } } continue; } // Parse key-value pair char key[256], value[256]; if (parse_key_value(trimmed, key, sizeof(key), value, sizeof(value)) != 0) { fprintf(stderr, "Invalid key-value line: %s\n", trimmed); continue; } // Process based on current section if (strcasecmp(current_section, "global") == 0) { if (strcasecmp(key, "my_private_key") == 0) { strncpy(config->global.my_private_key_hex, value, sizeof(config->global.my_private_key_hex) - 1); } else if (strcasecmp(key, "my_public_key") == 0) { strncpy(config->global.my_public_key_hex, value, sizeof(config->global.my_public_key_hex) - 1); } else if (strcasecmp(key, "option") == 0) { strncpy(config->global.option_value, value, sizeof(config->global.option_value) - 1); } else if (strcasecmp(key, "control_ip") == 0) { strncpy(config->global.control_ip, value, sizeof(config->global.control_ip) - 1); } else if (strcasecmp(key, "control_port") == 0) { config->global.control_port = atoi(value); } else if (strcasecmp(key, "net_debug") == 0) { config->global.net_debug = atoi(value); } } else if (strcasecmp(current_section, "routing") == 0) { if (strcasecmp(key, "allowed_subnet") == 0) { if (config->allowed_subnet_count < MAX_ALLOWED_SUBNETS) { strncpy(config->allowed_subnets[config->allowed_subnet_count].subnet, value, sizeof(config->allowed_subnets[0].subnet) - 1); config->allowed_subnet_count++; } else { fprintf(stderr, "Too many allowed subnets, maximum is %d\n", MAX_ALLOWED_SUBNETS); } } } else if (in_connection_section) { if (strcasecmp(key, "mode") == 0) { current_conn.mode = parse_mode(value); } else if (strcasecmp(key, "addr") == 0) { // For server mode if (current_conn.mode == CONFIG_MODE_SERVER) { strncpy(current_conn.local_addr, value, sizeof(current_conn.local_addr) - 1); } } else if (strcasecmp(key, "from_addr") == 0) { // For client mode if (current_conn.mode == CONFIG_MODE_CLIENT) { strncpy(current_conn.local_addr, value, sizeof(current_conn.local_addr) - 1); } } else if (strcasecmp(key, "to_addr") == 0) { // For client mode if (current_conn.mode == CONFIG_MODE_CLIENT) { strncpy(current_conn.remote_addr, value, sizeof(current_conn.remote_addr) - 1); } } else if (strcasecmp(key, "peer_public_key") == 0) { strncpy(current_conn.peer_public_key_hex, value, sizeof(current_conn.peer_public_key_hex) - 1); } else if (strcasecmp(key, "so_mark") == 0) { current_conn.so_mark = atoi(value); } else if (strcasecmp(key, "netif") == 0) { strncpy(current_conn.netif, value, sizeof(current_conn.netif) - 1); } else if (strcasecmp(key, "tun") == 0) { strncpy(current_conn.tun_ifname, value, sizeof(current_conn.tun_ifname) - 1); } else if (strcasecmp(key, "tun_ip") == 0) { strncpy(current_conn.tun_ip, value, sizeof(current_conn.tun_ip) - 1); } } } // Add last connection if any if (in_connection_section && strlen(current_conn.name) > 0) { if (add_connection(config, ¤t_conn) != 0) { fprintf(stderr, "Failed to add connection: %s\n", current_conn.name); } } fclose(fp); return config; } // Free configuration structure void free_config(utun_config_t *config) { if (!config) return; if (config->connections) { free(config->connections); } free(config); } // Print configuration for debugging void print_config(const utun_config_t *config) { if (!config) { printf("Configuration is NULL\n"); return; } printf("Global configuration:\n"); printf(" my_private_key: %s\n", config->global.my_private_key_hex); printf(" my_public_key: %s\n", config->global.my_public_key_hex); printf(" option: %s\n", config->global.option_value); printf(" control_ip: %s\n", config->global.control_ip); printf(" control_port: %u\n", config->global.control_port); printf(" net_debug: %d\n", config->global.net_debug); printf("\n"); printf("Allowed subnets (%d):\n", config->allowed_subnet_count); for (int i = 0; i < config->allowed_subnet_count; i++) { printf(" %s\n", config->allowed_subnets[i].subnet); } printf("\n"); printf("Connections (%d):\n", config->connection_count); for (int i = 0; i < config->connection_count; i++) { const connection_config_t *conn = &config->connections[i]; printf(" [%d] %s:\n", i, conn->name); printf(" mode: %s\n", conn->mode == CONFIG_MODE_CLIENT ? "client" : conn->mode == CONFIG_MODE_SERVER ? "server" : "unknown"); printf(" local_addr: %s\n", conn->local_addr); printf(" remote_addr: %s\n", conn->remote_addr); printf(" peer_public_key: %s\n", conn->peer_public_key_hex); printf(" so_mark: %d\n", conn->so_mark); printf(" netif: %s\n", conn->netif); printf(" tun: %s\n", conn->tun_ifname); printf(" tun_ip: %s\n", conn->tun_ip); } } // Update keys in configuration file int update_config_keys(const char *filename, const char *private_key_hex, const char *public_key_hex) { if (!filename || !private_key_hex || !public_key_hex) { errno = EINVAL; return -1; } // Read entire file FILE *fp = fopen(filename, "r"); if (!fp) { // Try to create new file fp = fopen(filename, "w"); if (!fp) return -1; fprintf(fp, "[global]\n"); fprintf(fp, "my_private_key=%s\n", private_key_hex); fprintf(fp, "my_public_key=%s\n", public_key_hex); fclose(fp); return 0; } // Read all lines char **lines = NULL; size_t line_count = 0; size_t capacity = 0; char line[MAX_LINE_LEN]; int in_global_section = 0; int private_key_found = 0; int public_key_found = 0; size_t private_key_line = 0; size_t public_key_line = 0; while (fgets(line, sizeof(line), fp)) { // Add line to array if (line_count >= capacity) { size_t new_capacity = capacity ? capacity * 2 : 16; char **new_lines = realloc(lines, new_capacity * sizeof(char *)); if (!new_lines) { // Cleanup for (size_t i = 0; i < line_count; i++) free(lines[i]); free(lines); fclose(fp); return -1; } lines = new_lines; capacity = new_capacity; } lines[line_count] = strdup(line); if (!lines[line_count]) { // Cleanup for (size_t i = 0; i < line_count; i++) free(lines[i]); free(lines); fclose(fp); return -1; } char *trimmed = trim(line); // Track global section if (trimmed[0] == '[' && trimmed[strlen(trimmed) - 1] == ']') { char section[128]; strncpy(section, trimmed + 1, sizeof(section) - 1); section[sizeof(section) - 1] = '\0'; section[strcspn(section, "]")] = '\0'; trim(section); in_global_section = (strcasecmp(section, "global") == 0); } // Check for keys in global section if (in_global_section) { char key[256], value[256]; if (parse_key_value(trimmed, key, sizeof(key), value, sizeof(value)) == 0) { if (strcasecmp(key, "my_private_key") == 0) { private_key_found = 1; private_key_line = line_count; } else if (strcasecmp(key, "my_public_key") == 0) { public_key_found = 1; public_key_line = line_count; } } } line_count++; } fclose(fp); // Update or add keys if (private_key_found) { free(lines[private_key_line]); char new_line[MAX_LINE_LEN]; snprintf(new_line, sizeof(new_line), "my_private_key=%s\n", private_key_hex); lines[private_key_line] = strdup(new_line); } else { // Add after [global] line or at beginning // Simplified: add at end // We'll need to insert after [global] but for simplicity, append } if (public_key_found) { free(lines[public_key_line]); char new_line[MAX_LINE_LEN]; snprintf(new_line, sizeof(new_line), "my_public_key=%s\n", public_key_hex); lines[public_key_line] = strdup(new_line); } // Write back to file fp = fopen(filename, "w"); if (!fp) { // Cleanup for (size_t i = 0; i < line_count; i++) free(lines[i]); free(lines); return -1; } for (size_t i = 0; i < line_count; i++) { fputs(lines[i], fp); free(lines[i]); } // Add missing keys if (!private_key_found) { fprintf(fp, "my_private_key=%s\n", private_key_hex); } if (!public_key_found) { fprintf(fp, "my_public_key=%s\n", public_key_hex); } free(lines); fclose(fp); return 0; }