+/* config.c - NextIRCd
+ * Copyright (C) 2012-2013 Philipp Kreil (pk910)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "config.h"
+#include "tools.h"
+
+static char *config_file = "ircd.conf";
+
+struct GlobalConfig global_config;
+static struct ConfigReloadCallback *config_reload_callbacks = NULL;
+
+struct ConfigReloadCallback {
+ confreload_callback_t *callback;
+ struct ConfigReloadCallback *next;
+};
+
+#define CONFIG_READ_BUF 512
+
+struct ConfigFile {
+ FILE *file;
+ int pending_bytes;
+ char *content_buffer;
+ int return_value;
+};
+
+#define ENTRYTYPE_BLOCK 1
+#define ENTRYTYPE_STRING 2
+#define ENTRYTYPE_INTEGER 3
+
+struct ConfigEntry {
+ char *name;
+ int type;
+ union {
+ struct ConfigEntry *elements;
+ char *str_value;
+ int int_value;
+ } data;
+ struct ConfigEntry *next;
+};
+
+static char *parse_config_recursive(struct ConfigEntry *centry, char *buffer, struct ConfigFile *configfile);
+static void free_entry_rekursiv(struct ConfigEntry *centry);
+static int parse_config_block(struct ConfigEntry *centry);
+
+#define PARSER_FLAG_ESCAPED 0x01
+#define PARSER_FLAG_BLOCK 0x02
+#define PARSER_FLAG_STRING 0x04
+#define PARSER_FLAG_EXPECT_END 0x08
+#define PARSER_FLAG_NEXTCHAR 0x10
+#define PARSER_FLAG_INTEGER 0x20
+#define PARSER_FLAG_EOBN 0x40 /* End of Block name */
+#define PARSER_FLAG_COMMAND 0x80
+static char *parse_config_recursive(struct ConfigEntry *centry, char *buffer, struct ConfigFile *configfile) {
+ int flags = 0;
+ int type = (centry ? 0 : ENTRYTYPE_BLOCK);
+ char cbuf[1024];
+ int cbufpos = 0;
+ struct ConfigEntry *sub_entrys = NULL;
+ parse_config_recursive_process:
+ while(*buffer) {
+ if(flags & PARSER_FLAG_NEXTCHAR) {
+ buffer++;
+ if(*buffer == '\0')
+ break;
+ }
+ flags |= PARSER_FLAG_NEXTCHAR;
+ if(flags & PARSER_FLAG_EOBN) {
+ flags &= ~(PARSER_FLAG_EOBN | PARSER_FLAG_NEXTCHAR);
+ struct ConfigEntry *new_entry = malloc(sizeof(*new_entry));
+ if (!new_entry) return buffer;
+ new_entry->name = strdup(cbuf);
+ buffer = parse_config_recursive(new_entry, buffer, 0);
+ if(centry) {
+ if(sub_entrys)
+ new_entry->next = sub_entrys;
+ else
+ new_entry->next = NULL;
+ sub_entrys = new_entry;
+ centry->data.elements = sub_entrys;
+ } else {
+ configfile->return_value |= parse_config_block(new_entry);
+ free_entry_rekursiv(new_entry);
+ }
+ }
+ if(flags & PARSER_FLAG_ESCAPED) {
+ cbuf[cbufpos++] = *buffer;
+ flags &= ~PARSER_FLAG_ESCAPED;
+ continue;
+ }
+ if(flags & PARSER_FLAG_EXPECT_END) {
+ if(*buffer == ';') {
+ if(centry)
+ centry->type = type;
+ return (buffer+1);
+ }
+ continue;
+ }
+ if(flags & PARSER_FLAG_STRING) {
+ if(*buffer == '"') {
+ cbuf[cbufpos] = '\0';
+ flags &= ~PARSER_FLAG_STRING;
+ if(type == ENTRYTYPE_STRING) {
+ flags |= PARSER_FLAG_EXPECT_END;
+ if(centry)
+ centry->data.str_value = strdup(cbuf);
+ } else if(type == ENTRYTYPE_BLOCK) {
+ flags |= PARSER_FLAG_EOBN;
+ }
+ } else if(*buffer == '\\')
+ flags |= PARSER_FLAG_ESCAPED;
+ else
+ cbuf[cbufpos++] = *buffer;
+ continue;
+ }
+ if(flags & PARSER_FLAG_INTEGER) {
+ if(!isdigit(*buffer)) {
+ cbuf[cbufpos] = '\0';
+ flags &= ~PARSER_FLAG_INTEGER;
+ if(type == ENTRYTYPE_INTEGER) {
+ flags |= PARSER_FLAG_EXPECT_END;
+ /* a pointer should be big enough to store a int value in it ;) */
+ if(centry)
+ centry->data.int_value = atoi(cbuf);
+ }
+ if(*buffer == ';') {
+ if(centry)
+ centry->type = type;
+ return (buffer+1);
+ }
+ } else
+ cbuf[cbufpos++] = *buffer;
+ continue;
+ }
+ if(flags & PARSER_FLAG_COMMAND) {
+ int found_command = 0;
+ char *tmp_buffer;
+ switch(*buffer) {
+ case '/':
+ tmp_buffer = buffer;
+ buffer = strchr(buffer, '\r');
+ if(!buffer)
+ buffer = strchr(tmp_buffer, '\n');
+ if(!buffer)
+ buffer = tmp_buffer + strlen(tmp_buffer)-1;
+ found_command = 1;
+ break;
+ case '*':
+ //simple search for the next */
+ buffer = strstr(buffer, "*/")+1;
+ found_command = 1;
+ }
+ flags &= ~PARSER_FLAG_COMMAND;
+ if(found_command)
+ continue;
+ }
+ switch(*buffer) {
+ case '\\':
+ flags |= PARSER_FLAG_ESCAPED;
+ break;
+ case '/':
+ if(!(flags & PARSER_FLAG_STRING)) {
+ flags |= PARSER_FLAG_COMMAND;
+ }
+ break;
+ case '{':
+ flags |= PARSER_FLAG_BLOCK;
+ type = ENTRYTYPE_BLOCK;
+ break;
+ case '}':
+ if(flags & PARSER_FLAG_BLOCK)
+ flags &= ~PARSER_FLAG_BLOCK;
+ flags |= PARSER_FLAG_EXPECT_END;
+ break;
+ case '"':
+ flags |= PARSER_FLAG_STRING;
+ if(!type)
+ type = ENTRYTYPE_STRING;
+ cbufpos = 0;
+ break;
+ case ';':
+ if(centry)
+ centry->type = type;
+ return (buffer+1);
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if(!type)
+ type = ENTRYTYPE_INTEGER;
+ flags |= PARSER_FLAG_INTEGER;
+ cbufpos = 0;
+ cbuf[cbufpos++] = *buffer;
+ break;
+ default:
+ break;
+ }
+ }
+ /* try to get more data */
+ if(configfile->pending_bytes) {
+ int read_bytes = configfile->pending_bytes;
+ if(read_bytes > CONFIG_READ_BUF)
+ read_bytes = CONFIG_READ_BUF;
+ read_bytes = fread(configfile->content_buffer, 1, read_bytes, configfile->file);
+ configfile->content_buffer[read_bytes] = '\0';
+ configfile->pending_bytes -= read_bytes;
+ buffer = configfile->content_buffer;
+ goto parse_config_recursive_process;
+ }
+ if(centry)
+ centry->type = type;
+ return buffer; //end of the buffer
+}
+
+static void free_entry_rekursiv(struct ConfigEntry *centry) {
+ if(centry->type == ENTRYTYPE_BLOCK) {
+ struct ConfigEntry *subentry, *nextentry;
+ for(subentry = centry->data.elements; subentry; subentry = nextentry) {
+ nextentry = subentry->next;
+ free_entry_rekursiv(subentry);
+ }
+ } else if(centry->type == ENTRYTYPE_STRING && centry->data.str_value) {
+ free(centry->data.str_value);
+ }
+ free(centry->name);
+ free(centry);
+}
+
+static int parse_config_port_block(struct ConfigEntry *centry);
+
+static int parse_config_block(struct ConfigEntry *centry) {
+ int ret = 0;
+ if(!stricmp(centry->name, "Port"))
+ ret |= parse_config_port_block(centry);
+ else {
+ // ERROR - unknown block name
+ }
+ return ret;
+}
+
+static int parse_config_port_block(struct ConfigEntry *centry) {
+ struct ConfigPortObject *port;
+ struct ConfigEntry *sentry;
+ int ret = 0;
+ if(centry->type != ENTRYTYPE_BLOCK)
+ return 1;
+ port = calloc(1, sizeof(*port));
+ for(sentry = centry->data.elements; sentry; sentry = sentry->next) {
+ if(!stricmp(sentry->name, "port") && sentry->type == ENTRYTYPE_INTEGER)
+ port->port = sentry->data.int_value;
+ else if(!stricmp(sentry->name, "secure") && sentry->type == ENTRYTYPE_INTEGER)
+ port->secure = (sentry->data.int_value != 0);
+ else if(!stricmp(sentry->name, "server") && sentry->type == ENTRYTYPE_INTEGER)
+ port->server = (sentry->data.int_value != 0);
+ else if(!stricmp(sentry->name, "ip4only") && sentry->type == ENTRYTYPE_INTEGER)
+ port->ip4only = (sentry->data.int_value != 0);
+ else if(!stricmp(sentry->name, "ip6only") && sentry->type == ENTRYTYPE_INTEGER)
+ port->ip6only = (sentry->data.int_value != 0);
+ else if(!stricmp(sentry->name, "bind") && sentry->type == ENTRYTYPE_STRING) {
+ port->bind_addr = sentry->data.str_value;
+ sentry->data.str_value = NULL; //prevents freeing later
+ } else {
+ // ERROR - unknown field in port block
+ ret = 1;
+ }
+ }
+ if(port->port && !(port->ip4only && port->ip6only)) {
+ port->next = global_config.ports;
+ global_config.ports = port;
+ } else
+ ret = 1;
+ return ret;
+}
+
+
+
+
+void init_config(char *configfile) {
+ if(configfile)
+ config_file = configfile;
+ memset(&global_config, 0, sizeof(global_config));
+ reload_config();
+}
+
+void reload_config() {
+ struct ConfigFile config;
+ config.file = fopen(config_file, "rb");
+ if(!config.file) {
+ //ERROR
+ printf("Error loading config file.\n");
+ return;
+ }
+ fseek(config.file, 0, SEEK_END);
+ config.pending_bytes = ftell(config.file);
+ rewind(config.file);
+
+ char content_buffer[CONFIG_READ_BUF+1];
+ config.content_buffer = content_buffer;
+
+ int read_bytes = config.pending_bytes;
+ if(read_bytes > CONFIG_READ_BUF)
+ read_bytes = CONFIG_READ_BUF;
+ read_bytes = fread(config.content_buffer, 1, read_bytes, config.file);
+ config.content_buffer[read_bytes] = '\0';
+ config.pending_bytes -= read_bytes;
+
+ config.return_value = 0;
+ parse_config_recursive(NULL, config.content_buffer, &config);
+
+ fclose(config.file);
+
+ if(!config.return_value) {
+ //call all the callbacks
+ } else {
+ printf("Error loading config file (%d).\n", config.return_value);
+ }
+}
+
+void reload_config_callback(confreload_callback_t *callback) {
+ struct ConfigReloadCallback *reload_callback = malloc(sizeof(*reload_callback));
+ reload_callback->callback = callback;
+ reload_callback->next = config_reload_callbacks;
+ config_reload_callbacks = reload_callback;
+}
+