diff options
author | Reinhard Tartler <siretart@tauware.de> | 2011-04-27 19:24:15 +0200 |
---|---|---|
committer | Reinhard Tartler <siretart@tauware.de> | 2011-04-27 19:24:15 +0200 |
commit | 181cec4348da40331b3e8ab365732c025ec149b2 (patch) | |
tree | e2e749be67c8253a8096aaf92aab9c51f1a17f7f /cfg.c | |
download | lcd4linux-181cec4348da40331b3e8ab365732c025ec149b2.tar.gz |
Import upstream version 0.11.0~svn1143
Diffstat (limited to 'cfg.c')
-rw-r--r-- | cfg.c | 714 |
1 files changed, 714 insertions, 0 deletions
@@ -0,0 +1,714 @@ +/* $Id: cfg.c 1109 2010-02-12 13:16:27Z mjona $ + * $URL: https://ssl.bulix.org/svn/lcd4linux/trunk/cfg.c $ + * $URL: https://ssl.bulix.org/svn/lcd4linux/trunk/cfg.c $ + * + * config file stuff + * + * Copyright (C) 1999, 2000 Michael Reinelt <michael@reinelt.co.at> + * Copyright (C) 2004, 2009 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net> + * + * This file is part of LCD4Linux. + * + * LCD4Linux 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 2, or (at your option) + * any later version. + * + * LCD4Linux 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* + * exported functions: + * + * cfg_init (source) + * read configuration from source + * returns 0 if successful + * returns -1 in case of an error + * + * cfg_source (void) + * returns the file the configuration was read from + * + * cfg_cmd (arg) + * allows us to overwrite entries in the + * config-file from the command line. + * arg is 'key=value' + * cfg_cmd can be called _before_ cfg_read() + * returns 0 if ok, -1 if arg cannot be parsed + * + * cfg_list (section) + * returns a list of all keys in the specified section + * This list was allocated be cfg_list() and must be + * freed by the caller! + * + * cfg_rename (section, old, new) + * changes the key of a existing entry + * + * cfg_get_raw (section, key, defval) + * return the a value for a given key in a given section + * or <defval> if key does not exist. Does NOT evaluate + * the expression. Therefore used to get the expression + * itself! + * + * cfg_get (section, key, defval) + * return the a value for a given key in a given section + * or <defval> if key does not exist. The specified + * value in the config is treated as a expression and + * is evaluated! + * + * cfg_number (section, key, defval, min, int max, *value) + * return the a value for a given key in a given section + * convert it into a number with syntax checking + * check if its in a given range. As it uses cfg_get() + * internally, the evaluator is used here, too. + * + */ + + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> + +#include <unistd.h> +#include <sys/stat.h> + +#include "debug.h" +#include "evaluator.h" +#include "cfg.h" + +#ifdef WITH_DMALLOC +#include <dmalloc.h> +#endif + +typedef struct { + char *key; + char *val; + int lock; +} ENTRY; + + +static char *Config_File = NULL; +static ENTRY *Config = NULL; +static int nConfig = 0; + + +/* bsearch compare function for config entries */ +static int c_lookup(const void *a, const void *b) +{ + char *key = (char *) a; + ENTRY *entry = (ENTRY *) b; + + return strcasecmp(key, entry->key); +} + + +/* qsort compare function for variables */ +static int c_sort(const void *a, const void *b) +{ + ENTRY *ea = (ENTRY *) a; + ENTRY *eb = (ENTRY *) b; + + return strcasecmp(ea->key, eb->key); +} + + +/* remove leading and trailing whitespace */ +static char *strip(char *s, const int strip_comments) +{ + char *p; + + while (isblank(*s)) + s++; + + for (p = s; *p; p++) { + if (*p == '"') + do + p++; + while (*p && *p != '\n' && *p != '\r' && *p != '"'); + if (*p == '\'') + do + p++; + while (*p && *p != '\n' && *p != '\r' && *p != '\''); + if (*p == '\n' || (strip_comments && *p == '#' && (p == s || *(p - 1) != '\\'))) { + *p = '\0'; + break; + } + if (*p == '\r' && *(p + 1) == '\n') { + /* replace <CR> from DOS <CR><LF> with blank */ + *p = ' '; + } + } + + for (p--; p > s && isblank(*p); p--) + *p = '\0'; + + return s; +} + + +/* which if a string contains only valid chars */ +/* i.e. start with a char and contains chars and nums */ +static int validchars(const char *string, const int numstart) +{ + const char *c; + + for (c = string; *c; c++) { + /* first and following chars */ + if ((*c >= 'A' && *c <= 'Z') || (*c >= 'a' && *c <= 'z') || (*c == '_')) + continue; + /* number as first or following char */ + if ((numstart || c > string) && *c >= '0' && *c <= '9') + continue; + /* only following chars */ + if ((c > string) && ((*c == '.') || (*c == '-'))) + continue; + return 0; + } + return 1; +} + + +static void cfg_add(const char *section, const char *key, const char *val, const int lock) +{ + char *buffer; + ENTRY *entry; + + /* allocate buffer */ + buffer = malloc(strlen(section) + strlen(key) + 2); + *buffer = '\0'; + + /* prepare section.key */ + if (section != NULL && *section != '\0') { + strcpy(buffer, section); + strcat(buffer, "."); + } + strcat(buffer, key); + + /* does the key already exist? */ + entry = bsearch(buffer, Config, nConfig, sizeof(ENTRY), c_lookup); + + if (entry != NULL) { + if (entry->lock > lock) + return; + debug("Warning: key <%s>: value <%s> overwritten with <%s>", buffer, entry->val, val); + free(buffer); + if (entry->val) + free(entry->val); + entry->val = strdup(val); + return; + } + + nConfig++; + Config = realloc(Config, nConfig * sizeof(ENTRY)); + Config[nConfig - 1].key = buffer; + Config[nConfig - 1].val = strdup(val); + Config[nConfig - 1].lock = lock; + + qsort(Config, nConfig, sizeof(ENTRY), c_sort); + +} + + +int cfg_cmd(const char *arg) +{ + char *key, *val; + char *buffer; + + buffer = strdup(arg); + key = strip(buffer, 0); + for (val = key; *val; val++) { + if (*val == '=') { + *val++ = '\0'; + break; + } + } + if (*key == '\0' || *val == '\0') { + free(buffer); + return -1; + } + + if (!validchars(key, 0)) { + free(buffer); + return -1; + } + + cfg_add("", key, val, 1); + + free(buffer); + return 0; +} + + +char *cfg_list(const char *section) +{ + int i, len; + char *key, *list; + + /* calculate key length */ + len = strlen(section) + 1; + + /* prepare search key */ + key = malloc(len + 1); + strcpy(key, section); + strcat(key, "."); + + /* start with empty string */ + list = malloc(1); + *list = '\0'; + + /* search matching entries */ + for (i = 0; i < nConfig; i++) { + if (strncasecmp(Config[i].key, key, len) == 0) { + list = realloc(list, strlen(list) + strlen(Config[i].key) - len + 2); + if (*list != '\0') + strcat(list, "|"); + strcat(list, Config[i].key + len); + } + } + + free(key); + return list; +} + + +int cfg_rename(const char *section, const char *old, const char *new) +{ + char *buffer; + ENTRY *old_entry, *new_entry; + + /* prepare old section.key */ + buffer = malloc(strlen(section) + strlen(old) + 2); + *buffer = '\0'; + if (section != NULL && *section != '\0') { + strcpy(buffer, section); + strcat(buffer, "."); + } + strcat(buffer, old); + + /* lookup old entry */ + old_entry = bsearch(buffer, Config, nConfig, sizeof(ENTRY), c_lookup); + free(buffer); + + if (old_entry == NULL) { + error("internal error: cfg_rename(%s, %s, %s) failed: entry not found!", section, old, new); + return -1; + } + + /* prepare new section.key */ + buffer = malloc(strlen(section) + strlen(new) + 2); + *buffer = '\0'; + if (section != NULL && *section != '\0') { + strcpy(buffer, section); + strcat(buffer, "."); + } + strcat(buffer, new); + + /* lookup new entry */ + new_entry = bsearch(buffer, Config, nConfig, sizeof(ENTRY), c_lookup); + + if (new_entry != NULL) { + info("cfg_rename(%s, %s, %s) failed: entry already exists!", section, old, new); + free(buffer); + return -1; + } + + /* replace key */ + free(old_entry->key); + old_entry->key = buffer; + + /* sort table again */ + qsort(Config, nConfig, sizeof(ENTRY), c_sort); + + return 0; +} + + +static char *cfg_lookup(const char *section, const char *key) +{ + int len; + char *buffer; + ENTRY *entry; + + /* calculate key length */ + len = strlen(key) + 1; + if (section != NULL) + len += strlen(section) + 1; + + /* allocate buffer */ + buffer = malloc(len); + *buffer = '\0'; + + /* prepare section:key */ + if (section != NULL && *section != '\0') { + strcpy(buffer, section); + strcat(buffer, "."); + } + strcat(buffer, key); + + /* search entry */ + entry = bsearch(buffer, Config, nConfig, sizeof(ENTRY), c_lookup); + + /* free buffer again */ + free(buffer); + + if (entry != NULL) + return entry->val; + + return NULL; +} + + +char *cfg_get_raw(const char *section, const char *key, const char *defval) +{ + char *val = cfg_lookup(section, key); + + if (val != NULL) + return val; + + return (char *) defval; +} + + +char *cfg_get(const char *section, const char *key, const char *defval) +{ + char *expression; + char *retval; + void *tree = NULL; + RESULT result = { 0, 0, 0, NULL }; + + expression = cfg_lookup(section, key); + + if (expression != NULL) { + if (*expression == '\0') + return strdup(""); + if (Compile(expression, &tree) == 0 && Eval(tree, &result) == 0) { + retval = strdup(R2S(&result)); + DelTree(tree); + DelResult(&result); + return (retval); + } + DelTree(tree); + DelResult(&result); + } + if (defval) + return strdup(defval); + return NULL; +} + + +int cfg_number(const char *section, const char *key, const int defval, const int min, const int max, int *value) +{ + char *expression; + void *tree = NULL; + RESULT result = { 0, 0, 0, NULL }; + + /* start with default value */ + /* in case of an (uncatched) error, you have the */ + /* default value set, which may be handy... */ + *value = defval; + + expression = cfg_get_raw(section, key, NULL); + if (expression == NULL || *expression == '\0') { + return 0; + } + + if (Compile(expression, &tree) != 0) { + DelTree(tree); + return -1; + } + if (Eval(tree, &result) != 0) { + DelTree(tree); + DelResult(&result); + return -1; + } + *value = R2N(&result); + DelTree(tree); + DelResult(&result); + + if (*value < min) { + error("bad '%s.%s' value '%d' in %s, minimum is %d", section, key, *value, cfg_source(), min); + *value = min; + return -1; + } + + if (max > min && max != -1 && *value > max) { + error("bad '%s.%s' value '%d' in %s, maximum is %d", section, key, *value, cfg_source(), max); + *value = max; + return -1; + } + + return 1; +} + + +static int cfg_check_source(const char *file) +{ + /* as passwords and commands are stored in the config file, + * we will check that: + * - file is a normal file (or /dev/null) + * - file owner is owner of program + * - file is not accessible by group + * - file is not accessible by other + */ + + struct stat stbuf; + uid_t uid, gid; + int error; + + uid = geteuid(); + gid = getegid(); + + if (stat(file, &stbuf) == -1) { + error("stat(%s) failed: %s", file, strerror(errno)); + return -1; + } + if (S_ISCHR(stbuf.st_mode) && strcmp(file, "/dev/null") == 0) + return 0; + + error = 0; + if (!S_ISREG(stbuf.st_mode)) { + error("security error: '%s' is not a regular file", file); + error = -1; + } + if (stbuf.st_uid != uid || stbuf.st_gid != gid) { + error("security error: owner and/or group of '%s' don't match", file); + error = -1; + } +#if ! defined(__CYGWIN__) + if (stbuf.st_mode & S_IRWXG || stbuf.st_mode & S_IRWXO) { + error("security error: group or other have access to '%s'", file); + error = -1; + } +#endif + return error; +} + + +static int cfg_read(const char *file) +{ + FILE *stream; + char buffer[256]; + char section[256]; + char *line, *key, *val, *end; + int section_open, section_close; + int error, lineno; + + stream = fopen(file, "r"); + if (stream == NULL) { + error("open(%s) failed: %s", file, strerror(errno)); + return -1; + } + + /* start with empty section */ + strcpy(section, ""); + + error = 0; + lineno = 0; + while ((line = fgets(buffer, 256, stream)) != NULL) { + + /* increment line number */ + lineno++; + + /* skip empty lines */ + if (*(line = strip(line, 1)) == '\0') + continue; + + /* reset section flags */ + section_open = 0; + section_close = 0; + + /* key is first word */ + key = line; + + /* search first blank between key and value */ + for (val = line; *val; val++) { + if (isblank(*val)) { + *val++ = '\0'; + break; + } + } + + /* strip value */ + val = strip(val, 1); + + /* search end of value */ + if (*val) + for (end = val; *(end + 1); end++); + else + end = val; + + /* if last char is '{', a section has been opened */ + if (*end == '{') { + section_open = 1; + *end = '\0'; + val = strip(val, 0); + } + + /* provess "value" in double-quotes */ + if (*val == '"' && *end == '"') { + *end = '\0'; + val++; + } + + /* if key is '}', a section has been closed */ + if (strcmp(key, "}") == 0) { + section_close = 1; + *key = '\0'; + } + + /* sanity check: '}' should be the only char in a line */ + if (section_close && (section_open || *val != '\0')) { + error("error in config file '%s' line %d: garbage after '}'", file, lineno); + error = 1; + break; + } + + /* check key for valid chars */ + if (!validchars(key, 0)) { + error("error in config file '%s' line %d: key '%s' is invalid", file, lineno, key); + error = 1; + break; + } + + /* on section-open, check value for valid chars */ + if (section_open && !validchars(val, 1)) { + error("error in config file '%s' line %d: section '%s' is invalid", file, lineno, val); + error = 1; + break; + } + + /* on section-open, append new section name */ + if (section_open) { + /* is the section[] array big enough? */ + if (strlen(section) + strlen(key) + 3 > sizeof(section)) { + error("error in config file '%s' line %d: section buffer overflow", file, lineno); + error = 1; + break; + } + if (*section != '\0') + strcat(section, "."); + strcat(section, key); + if (*val != '\0') { + strcat(section, ":"); + strcat(section, val); + } + continue; + } + + /* on section-close, remove last section name */ + if (section_close) { + /* sanity check: section already empty? */ + if (*section == '\0') { + error("error in config file '%s' line %d: unmatched closing brace", file, lineno); + error = 1; + break; + } + + end = strrchr(section, '.'); + if (end == NULL) + *section = '\0'; + else + *end = '\0'; + continue; + } + + /* finally: add key */ + cfg_add(section, key, val, 0); + + } + + /* sanity check: are the braces balanced? */ + if (!error && *section != '\0') { + error("error in config file '%s' line %d: unbalanced braces", file, lineno); + error = 1; + } + + fclose(stream); + + return -error; +} + + +static void cfg_dump(void) +{ + int i, len; + + /* find longest key for pretty output */ + len = 1; + for (i = 0; i < nConfig; i++) { + int l = strlen(Config[i].key); + if (l > len) + len = l; + } + + info("Dump of %s:", Config_File); + for (i = 0; i < nConfig; i++) { + info(" %-*s %s", len, Config[i].key, Config[i].val); + } + info(" "); +} + + +int cfg_init(const char *file) +{ + if (cfg_check_source(file) == -1) { + return -1; + } + + if (cfg_read(file) < 0) + return -1; + + if (Config_File) + free(Config_File); + + Config_File = strdup(file); + + if (verbose_level > 1) + cfg_dump(); + + return 0; +} + + +char *cfg_source(void) +{ + if (Config_File) + return Config_File; + else + return ""; +} + + +int cfg_exit(void) +{ + int i; + for (i = 0; i < nConfig; i++) { + if (Config[i].key) + free(Config[i].key); + if (Config[i].val) + free(Config[i].val); + } + + if (Config) { + free(Config); + Config = NULL; + } + + if (Config_File) { + free(Config_File); + Config_File = NULL; + } + + return 0; +} |