/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Monkey HTTP Server * ================== * Copyright 2001-2017 Eduardo Silva * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #ifndef _MSC_VER #include #endif #include #include #include #include #ifdef _MSC_VER #define PATH_MAX MAX_PATH #endif /* Raise a configuration schema error */ static void mk_config_error(const char *path, int line, const char *msg) { mk_err("File %s", path); mk_err("Error in line %i: %s", line, msg); exit(EXIT_FAILURE); } /* Raise a warning */ static void mk_rconf_warning(const char *path, int line, const char *msg) { mk_warn("Config file warning '%s':\n" "\t\t\t\tat line %i: %s", path, line, msg); } /* Returns a configuration section by [section name] */ struct mk_rconf_section *mk_rconf_section_get(struct mk_rconf *conf, const char *name) { struct mk_list *head; struct mk_rconf_section *section; mk_list_foreach(head, &conf->sections) { section = mk_list_entry(head, struct mk_rconf_section, _head); if (strcasecmp(section->name, name) == 0) { return section; } } return NULL; } /* Register a key/value entry in the last section available of the struct */ static void mk_rconf_section_entry_add(struct mk_rconf *conf, const char *key, const char *val) { struct mk_rconf_section *section; struct mk_rconf_entry *new; struct mk_list *head = &conf->sections; if (mk_list_is_empty(&conf->sections) == 0) { mk_err("Error: there are not sections available on %s!", conf->file); return; } /* Last section */ section = mk_list_entry_last(head, struct mk_rconf_section, _head); /* Alloc new entry */ new = mk_mem_alloc(sizeof(struct mk_rconf_entry)); new->key = mk_string_dup(key); new->val = mk_string_dup(val); mk_list_add(&new->_head, §ion->entries); } /* Create a configuration schema */ struct mk_rconf *mk_rconf_create(const char *name) { struct mk_rconf *conf = NULL; /* Alloc configuration node */ conf = mk_mem_alloc_z(sizeof(struct mk_rconf)); conf->created = time(NULL); conf->file = mk_string_dup(name); mk_list_init(&conf->sections); return conf; } static int is_file_included(struct mk_rconf *conf, const char *path) { struct mk_list *head; struct mk_rconf_file *file; mk_list_foreach(head, &conf->includes) { file = mk_list_entry(head, struct mk_rconf_file, _head); if (strcmp(file->path, path) == 0) { return MK_TRUE; } } return MK_FALSE; } char *mk_rconf_meta_get(struct mk_rconf *conf, char *key) { struct mk_list *head; struct mk_rconf_entry *meta; mk_list_foreach(head, &conf->metas) { meta = mk_list_entry(head, struct mk_rconf_entry, _head); if (strcmp(meta->key, key) == 0) { return meta->val; } } return NULL; } static int mk_rconf_meta_add(struct mk_rconf *conf, char *buf, int len) { int xlen; char *p; char *tmp; struct mk_rconf_entry *meta; if (buf[0] != '@') { return -1; } meta = mk_mem_alloc(sizeof(struct mk_rconf_entry)); if (!meta) { perror("malloc"); return -1; } p = buf; tmp = strchr(p, ' '); xlen = (tmp - p); meta->key = mk_string_copy_substr(buf, 1, xlen); mk_string_trim(&meta->key); meta->val = mk_string_copy_substr(buf, xlen + 1, len); mk_string_trim(&meta->val); mk_list_add(&meta->_head, &conf->metas); return 0; } /* To call this function from mk_rconf_read */ static int mk_rconf_read_glob(struct mk_rconf *conf, const char * path); static int mk_rconf_read(struct mk_rconf *conf, const char *path) { int i; int len; int ret; int line = 0; int indent_len = -1; int n_keys = 0; char *buf; char tmp[PATH_MAX]; char *section = NULL; char *indent = NULL; char *key, *val; char *cfg_file = (char *) path; struct stat st; struct mk_rconf_file *file; struct mk_rconf_section *current = NULL; FILE *f; /* Check if the path exists (relative cases for included files) */ if (conf->level >= 0) { ret = stat(path, &st); if (ret == -1 && errno == ENOENT) { /* Try to resolve the real path (if exists) */ if (path[0] == '/') { return -1; } if (conf->root_path) { snprintf(tmp, PATH_MAX, "%s/%s", conf->root_path, path); cfg_file = tmp; } } } /* Check this file have not been included before */ ret = is_file_included(conf, cfg_file); if (ret == MK_TRUE) { mk_err("[config] file already included %s", cfg_file); return -1; } conf->level++; /* Open configuration file */ if ((f = fopen(cfg_file, "r")) == NULL) { mk_warn("[config] I cannot open %s file", cfg_file); return -1; } /* Allocate temporal buffer to read file content */ buf = mk_mem_alloc(MK_RCONF_KV_SIZE); if (!buf) { perror("malloc"); return -1; } /* looking for configuration directives */ while (fgets(buf, MK_RCONF_KV_SIZE, f)) { len = strlen(buf); if (buf[len - 1] == '\n') { buf[--len] = 0; if (len && buf[len - 1] == '\r') { buf[--len] = 0; } } /* Line number */ line++; if (!buf[0]) { continue; } /* Skip commented lines */ if (buf[0] == '#') { continue; } if (len > 9 && strncasecmp(buf, "@INCLUDE ", 9) == 0) { if (strchr(buf + 9, '*') != NULL) { ret = mk_rconf_read_glob(conf, buf + 9); } else { ret = mk_rconf_read(conf, buf + 9); } if (ret == -1) { conf->level--; fclose(f); if (indent) { mk_mem_free(indent); } mk_mem_free(buf); return -1; } continue; } else if (buf[0] == '@' && len > 3) { ret = mk_rconf_meta_add(conf, buf, len); if (ret == -1) { fclose(f); if (indent) { mk_mem_free(indent); } mk_mem_free(buf); return -1; } continue; } /* Section definition */ if (buf[0] == '[') { int end = -1; end = mk_string_char_search(buf, ']', len); if (end > 0) { /* * Before to add a new section, lets check the previous * one have at least one key set */ if (current && n_keys == 0) { mk_rconf_warning(path, line, "Previous section did not have keys"); } /* Create new section */ section = mk_string_copy_substr(buf, 1, end); current = mk_rconf_section_add(conf, section); mk_mem_free(section); n_keys = 0; continue; } else { mk_config_error(path, line, "Bad header definition"); } } /* No separator defined */ if (!indent) { i = 0; do { i++; } while (i < len && isblank(buf[i])); indent = mk_string_copy_substr(buf, 0, i); indent_len = strlen(indent); /* Blank indented line */ if (i == len) { continue; } } /* Validate indentation level */ if (strncmp(buf, indent, indent_len) != 0 || isblank(buf[indent_len]) != 0) { mk_config_error(path, line, "Invalid indentation level"); } if (buf[indent_len] == '#' || indent_len == len) { continue; } /* Get key and val */ i = mk_string_char_search(buf + indent_len, ' ', len - indent_len); key = mk_string_copy_substr(buf + indent_len, 0, i); val = mk_string_copy_substr(buf + indent_len + i, 1, len - indent_len - i); if (!key || !val || i < 0) { mk_config_error(path, line, "Each key must have a value"); } /* Trim strings */ mk_string_trim(&key); mk_string_trim(&val); if (strlen(val) == 0) { mk_config_error(path, line, "Key has an empty value"); } /* Register entry: key and val are copied as duplicated */ mk_rconf_section_entry_add(conf, key, val); /* Free temporal key and val */ mk_mem_free(key); mk_mem_free(val); n_keys++; } if (section && n_keys == 0) { /* No key, no warning */ } /* struct mk_config_section *s; struct mk_rconf_entry *e; s = conf->section; while(s) { printf("\n[%s]", s->name); e = s->entry; while(e) { printf("\n %s = %s", e->key, e->val); e = e->next; } s = s->next; } fflush(stdout); */ fclose(f); if (indent) { mk_mem_free(indent); } mk_mem_free(buf); /* Append this file to the list */ file = mk_mem_alloc(sizeof(struct mk_rconf_file)); if (!file) { perror("malloc"); conf->level--; return -1; } file->path = mk_string_dup(path); mk_list_add(&file->_head, &conf->includes); conf->level--; return 0; } #ifndef _MSC_VER static int mk_rconf_read_glob(struct mk_rconf *conf, const char * path) { int ret = -1; glob_t glb; char tmp[PATH_MAX]; const char *glb_path; size_t i; int ret_glb = -1; if (conf->root_path) { snprintf(tmp, PATH_MAX, "%s/%s", conf->root_path, path); glb_path = tmp; } else { glb_path = path; } ret_glb = glob(glb_path, GLOB_NOSORT, NULL, &glb); if (ret_glb != 0) { switch(ret_glb){ case GLOB_NOSPACE: mk_warn("[%s] glob: no space", __FUNCTION__); break; case GLOB_NOMATCH: mk_warn("[%s] glob: no match", __FUNCTION__); break; case GLOB_ABORTED: mk_warn("[%s] glob: aborted", __FUNCTION__); break; default: mk_warn("[%s] glob: other error", __FUNCTION__); } return ret; } for (i = 0; i < glb.gl_pathc; i++) { ret = mk_rconf_read(conf, glb.gl_pathv[i]); if (ret < 0) { break; } } globfree(&glb); return ret; } #else static int mk_rconf_read_glob(struct mk_rconf *conf, const char * path) { mk_err("[config] wildcard is not supported on Windows"); mk_err("[config] path: %s", path); return -1; } #endif static int mk_rconf_path_set(struct mk_rconf *conf, char *file) { char *p; char *end; char path[PATH_MAX + 1]; #ifdef _MSC_VER p = _fullpath(path, file, PATH_MAX + 1); #else p = realpath(file, path); #endif if (!p) { return -1; } /* lookup path ending and truncate */ end = strrchr(path, '/'); if (!end) { return -1; } end++; *end = '\0'; conf->root_path = mk_string_dup(path); return 0; } struct mk_rconf *mk_rconf_open(const char *path) { int ret; struct mk_rconf *conf = NULL; if (!path) { mk_warn("[config] invalid path file %s", path); return NULL; } /* Alloc configuration node */ conf = mk_mem_alloc_z(sizeof(struct mk_rconf)); conf->created = time(NULL); conf->file = mk_string_dup(path); conf->level = -1; mk_list_init(&conf->sections); mk_list_init(&conf->includes); mk_list_init(&conf->metas); /* Set the absolute path for the entrypoint file */ mk_rconf_path_set(conf, (char *) path); /* Read entrypoint */ ret = mk_rconf_read(conf, path); if (ret == -1) { mk_rconf_free(conf); return NULL; } return conf; } void mk_rconf_free(struct mk_rconf *conf) { struct mk_list *head, *tmp; struct mk_rconf_section *section; struct mk_rconf_entry *entry; struct mk_rconf_file *file; /* Remove included files */ mk_list_foreach_safe(head, tmp, &conf->includes) { file = mk_list_entry(head, struct mk_rconf_file, _head); mk_list_del(&file->_head); mk_mem_free(file->path); mk_mem_free(file); } /* Remove metas */ mk_list_foreach_safe(head, tmp, &conf->metas) { entry = mk_list_entry(head, struct mk_rconf_entry, _head); mk_list_del(&entry->_head); mk_mem_free(entry->key); mk_mem_free(entry->val); mk_mem_free(entry); } /* Free sections */ mk_list_foreach_safe(head, tmp, &conf->sections) { section = mk_list_entry(head, struct mk_rconf_section, _head); mk_list_del(§ion->_head); /* Free section entries */ mk_rconf_free_entries(section); /* Free section node */ mk_mem_free(section->name); mk_mem_free(section); } if (conf->file) { mk_mem_free(conf->file); } mk_mem_free(conf->root_path); mk_mem_free(conf); } void mk_rconf_free_entries(struct mk_rconf_section *section) { struct mk_rconf_entry *entry; struct mk_list *head, *tmp; mk_list_foreach_safe(head, tmp, §ion->entries) { entry = mk_list_entry(head, struct mk_rconf_entry, _head); mk_list_del(&entry->_head); /* Free memory assigned */ mk_mem_free(entry->key); mk_mem_free(entry->val); mk_mem_free(entry); } } /* Register a new section into the configuration struct */ struct mk_rconf_section *mk_rconf_section_add(struct mk_rconf *conf, char *name) { struct mk_rconf_section *new; /* Alloc section node */ new = mk_mem_alloc(sizeof(struct mk_rconf_section)); new->name = mk_string_dup(name); mk_list_init(&new->entries); mk_list_add(&new->_head, &conf->sections); return new; } /* Return the value of a key of a specific section */ void *mk_rconf_section_get_key(struct mk_rconf_section *section, char *key, int mode) { int on, off; struct mk_rconf_entry *entry; struct mk_list *head; mk_list_foreach(head, §ion->entries) { entry = mk_list_entry(head, struct mk_rconf_entry, _head); if (strcasecmp(entry->key, key) == 0) { switch (mode) { case MK_RCONF_STR: return (void *) mk_string_dup(entry->val); case MK_RCONF_NUM: return (void *) strtol(entry->val, (char **) NULL, 10); case MK_RCONF_BOOL: on = strcasecmp(entry->val, MK_RCONF_ON); off = strcasecmp(entry->val, MK_RCONF_OFF); if (on != 0 && off != 0) { return (void *) -1; } else if (on >= 0) { return (void *) MK_TRUE; } else { return (void *) MK_FALSE; } case MK_RCONF_LIST: return (void *)mk_string_split_line(entry->val); } } } return NULL; }