root/branches/volker_dev/cfg.c

Revision 827, 14.0 kB (checked in by volker, 18 months ago)

includes for umask(); svnversion updated

  • Property svn:keywords set to Id URL Rev
Line 
1/* $Id$
2 * $URL$
3 * $URL$
4 *
5 * config file stuff
6 *
7 * Copyright (C) 1999, 2000 Michael Reinelt <reinelt@eunet.at>
8 * Copyright (C) 2004 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net>
9 *
10 * This file is part of LCD4Linux.
11 *
12 * LCD4Linux is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2, or (at your option)
15 * any later version.
16 *
17 * LCD4Linux is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 *
26 */
27
28/*
29 * exported functions:
30 *
31 * cfg_init (source)
32 *   read configuration from source
33 *   returns  0 if successful
34 *   returns -1 in case of an error
35 *
36 * cfg_source (void)
37 *   returns the file the configuration was read from
38 *
39 * cfg_cmd (arg)
40 *   allows us to overwrite entries in the
41 *   config-file from the command line.
42 *   arg is 'key=value'
43 *   cfg_cmd can be called _before_ cfg_read()
44 *   returns 0 if ok, -1 if arg cannot be parsed
45 *
46 * cfg_list (section)
47 *   returns a list of all keys in the specified section
48 *   This list was allocated be cfg_list() and must be
49 *   freed by the caller!
50 *
51 * cfg_get_raw (section, key, defval)
52 *   return the a value for a given key in a given section
53 *   or <defval> if key does not exist. Does NOT evaluate
54 *   the expression. Therefore used to get the expression
55 *   itself!
56 *
57 * cfg_get (section, key, defval)
58 *   return the a value for a given key in a given section
59 *   or <defval> if key does not exist. The specified
60 *   value in the config is treated as a expression and
61 *   is evaluated!
62 *
63 * cfg_number (section, key, defval, min, int max, *value)
64 *   return the a value for a given key in a given section
65 *   convert it into a number with syntax checking
66 *   check if its in a given range. As it uses cfg_get()
67 *   internally, the evaluator is used here, too.
68 *
69 */
70
71
72#include "config.h"
73
74#include <stdlib.h>
75#include <stdio.h>
76#include <string.h>
77#include <ctype.h>
78#include <errno.h>
79
80#include <unistd.h>
81#include <sys/stat.h>
82
83#include "debug.h"
84#include "evaluator.h"
85#include "cfg.h"
86
87#ifdef WITH_DMALLOC
88#include <dmalloc.h>
89#endif
90
91typedef struct {
92    char *key;
93    char *val;
94    int lock;
95} ENTRY;
96
97
98static char *Config_File = NULL;
99static ENTRY *Config = NULL;
100static int nConfig = 0;
101
102
103/* bsearch compare function for config entries */
104static int c_lookup(const void *a, const void *b)
105{
106    char *key = (char *) a;
107    ENTRY *entry = (ENTRY *) b;
108
109    return strcasecmp(key, entry->key);
110}
111
112
113/* qsort compare function for variables */
114static int c_sort(const void *a, const void *b)
115{
116    ENTRY *ea = (ENTRY *) a;
117    ENTRY *eb = (ENTRY *) b;
118
119    return strcasecmp(ea->key, eb->key);
120}
121
122
123/* remove leading and trailing whitespace */
124static char *strip(char *s, const int strip_comments)
125{
126    char *p;
127
128    while (isblank(*s))
129  s++;
130
131    for (p = s; *p; p++) {
132  if (*p == '"')
133      do
134    p++;
135      while (*p && *p != '\n' && *p != '\r' && *p != '"');
136  if (*p == '\'')
137      do
138    p++;
139      while (*p && *p != '\n' && *p != '\r' && *p != '\'');
140  if (*p == '\n' || (strip_comments && *p == '#' && (p == s || *(p - 1) != '\\'))) {
141      *p = '\0';
142      break;
143  }
144  if (*p == '\r' && *(p + 1) == '\n') {
145      /* replace <CR> from DOS <CR><LF> with blank */
146      *p = ' ';
147  }
148    }
149
150    for (p--; p > s && isblank(*p); p--)
151  *p = '\0';
152
153    return s;
154}
155
156
157/* unquote a string */
158static char *dequote(char *string)
159{
160    int quote = 0;
161    char *s = string;
162    char *p = string;
163
164    do {
165  if (*s == '\'') {
166      quote = !quote;
167      *p++ = *s;
168  } else if (quote && *s == '\\') {
169      s++;
170      if (*s >= '0' && *s <= '7') {
171    int n;
172    unsigned int c = 0;
173    sscanf(s, "%3o%n", &c, &n);
174    if (c == 0 || c > 255) {
175        error("WARNING: illegal '\\' in <%s>", string);
176    } else {
177        *p++ = c;
178        s += n - 1;
179    }
180      } else {
181    *p++ = *s;
182      }
183  } else {
184      *p++ = *s;
185  }
186    } while (*s++);
187
188    return string;
189}
190
191
192/* which if a string contains only valid chars */
193/* i.e. start with a char and contains chars and nums */
194static int validchars(const char *string, const int numstart)
195{
196    const char *c;
197
198    for (c = string; *c; c++) {
199  /* first and following chars */
200  if ((*c >= 'A' && *c <= 'Z') || (*c >= 'a' && *c <= 'z') || (*c == '_'))
201      continue;
202  /* number as first or following char */
203  if ((numstart || c > string) && *c >= '0' && *c <= '9')
204      continue;
205  /* only following chars */
206  if ((c > string) && ((*c == '.') || (*c == '-')))
207      continue;
208  return 0;
209    }
210    return 1;
211}
212
213
214static void cfg_add(const char *section, const char *key, const char *val, const int lock)
215{
216    char *buffer;
217    ENTRY *entry;
218
219    /* allocate buffer  */
220    buffer = malloc(strlen(section) + strlen(key) + 2);
221    *buffer = '\0';
222
223    /* prepare section.key */
224    if (section != NULL && *section != '\0') {
225  strcpy(buffer, section);
226  strcat(buffer, ".");
227    }
228    strcat(buffer, key);
229
230    /* does the key already exist? */
231    entry = bsearch(buffer, Config, nConfig, sizeof(ENTRY), c_lookup);
232
233    if (entry != NULL) {
234  if (entry->lock > lock)
235      return;
236  debug("Warning: key <%s>: value <%s> overwritten with <%s>", buffer, entry->val, val);
237  free(buffer);
238  if (entry->val)
239      free(entry->val);
240  entry->val = dequote(strdup(val));
241  return;
242    }
243
244    nConfig++;
245    Config = realloc(Config, nConfig * sizeof(ENTRY));
246    Config[nConfig - 1].key = buffer;
247    Config[nConfig - 1].val = dequote(strdup(val));
248    Config[nConfig - 1].lock = lock;
249
250    qsort(Config, nConfig, sizeof(ENTRY), c_sort);
251
252}
253
254
255int cfg_cmd(const char *arg)
256{
257    char *key, *val;
258    char *buffer;
259
260    buffer = strdup(arg);
261    key = strip(buffer, 0);
262    for (val = key; *val; val++) {
263  if (*val == '=') {
264      *val++ = '\0';
265      break;
266  }
267    }
268    if (*key == '\0' || *val == '\0') {
269  free(buffer);
270  return -1;
271    }
272
273    if (!validchars(key, 0)) {
274  free(buffer);
275  return -1;
276    }
277
278    cfg_add("", key, val, 1);
279
280    free(buffer);
281    return 0;
282}
283
284
285char *cfg_list(const char *section)
286{
287    int i, len;
288    char *key, *list;
289
290    /* calculate key length */
291    len = strlen(section) + 1;
292
293    /* prepare search key */
294    key = malloc(len + 1);
295    strcpy(key, section);
296    strcat(key, ".");
297
298    /* start with empty string */
299    list = malloc(1);
300    *list = '\0';
301
302    /* search matching entries */
303    for (i = 0; i < nConfig; i++) {
304  if (strncasecmp(Config[i].key, key, len) == 0) {
305      list = realloc(list, strlen(list) + strlen(Config[i].key) - len + 2);
306      if (*list != '\0')
307    strcat(list, "|");
308      strcat(list, Config[i].key + len);
309  }
310    }
311
312    free(key);
313    return list;
314}
315
316
317static char *cfg_lookup(const char *section, const char *key)
318{
319    int len;
320    char *buffer;
321    ENTRY *entry;
322
323    /* calculate key length */
324    len = strlen(key) + 1;
325    if (section != NULL)
326  len += strlen(section) + 1;
327
328    /* allocate buffer  */
329    buffer = malloc(len);
330    *buffer = '\0';
331
332    /* prepare section:key */
333    if (section != NULL && *section != '\0') {
334  strcpy(buffer, section);
335  strcat(buffer, ".");
336    }
337    strcat(buffer, key);
338
339    /* search entry */
340    entry = bsearch(buffer, Config, nConfig, sizeof(ENTRY), c_lookup);
341
342    /* free buffer again */
343    free(buffer);
344
345    if (entry != NULL)
346  return entry->val;
347
348    return NULL;
349}
350
351
352char *cfg_get_raw(const char *section, const char *key, const char *defval)
353{
354    char *val = cfg_lookup(section, key);
355
356    if (val != NULL)
357  return val;
358
359    return (char *) defval;
360}
361
362
363char *cfg_get(const char *section, const char *key, const char *defval)
364{
365    char *expression;
366    char *retval;
367    void *tree = NULL;
368    RESULT result = { 0, 0, 0, NULL };
369
370    expression = cfg_lookup(section, key);
371
372    if (expression != NULL) {
373  if (*expression == '\0')
374      return "";
375  if (Compile(expression, &tree) == 0 && Eval(tree, &result) == 0) {
376      retval = strdup(R2S(&result));
377      DelTree(tree);
378      DelResult(&result);
379      return (retval);
380  }
381  DelTree(tree);
382  DelResult(&result);
383    }
384    if (defval)
385  return strdup(defval);
386    return NULL;
387}
388
389
390int cfg_number(const char *section, const char *key, const int defval, const int min, const int max, int *value)
391{
392    char *expression;
393    void *tree = NULL;
394    RESULT result = { 0, 0, 0, NULL };
395
396    /* start with default value */
397    /* in case of an (uncatched) error, you have the */
398    /* default value set, which may be handy... */
399    *value = defval;
400
401    expression = cfg_get_raw(section, key, NULL);
402    if (expression == NULL || *expression == '\0') {
403  return 0;
404    }
405
406    if (Compile(expression, &tree) != 0) {
407  DelTree(tree);
408  return -1;
409    }
410    if (Eval(tree, &result) != 0) {
411  DelTree(tree);
412  DelResult(&result);
413  return -1;
414    }
415    *value = R2N(&result);
416    DelTree(tree);
417    DelResult(&result);
418
419    if (*value < min) {
420  error("bad '%s.%s' value '%d' in %s, minimum is %d", section, key, *value, cfg_source(), min);
421  *value = min;
422  return -1;
423    }
424
425    if (max > min && max != -1 && *value > max) {
426  error("bad '%s.%s' value '%d' in %s, maximum is %d", section, key, *value, cfg_source(), max);
427  *value = max;
428  return -1;
429    }
430
431    return 1;
432}
433
434
435static int cfg_check_source(const char *file)
436{
437    /* as passwords and commands are stored in the config file,
438     * we will check that:
439     * - file is a normal file (or /dev/null)
440     * - file owner is owner of program
441     * - file is not accessible by group
442     * - file is not accessible by other
443     */
444
445    struct stat stbuf;
446    uid_t uid, gid;
447    int error;
448
449    uid = geteuid();
450    gid = getegid();
451
452    if (stat(file, &stbuf) == -1) {
453  error("stat(%s) failed: %s", file, strerror(errno));
454  return -1;
455    }
456    if (S_ISCHR(stbuf.st_mode) && strcmp(file, "/dev/null") == 0)
457  return 0;
458
459    error = 0;
460    if (!S_ISREG(stbuf.st_mode)) {
461  error("security error: '%s' is not a regular file", file);
462  error = -1;
463    }
464    if (stbuf.st_uid != uid || stbuf.st_gid != gid) {
465  error("security error: owner and/or group of '%s' don't match", file);
466  error = -1;
467    }
468    if (stbuf.st_mode & S_IRWXG || stbuf.st_mode & S_IRWXO) {
469  error("security error: group or other have access to '%s'", file);
470  error = -1;
471    }
472    return error;
473}
474
475
476static int cfg_read(const char *file)
477{
478    FILE *stream;
479    char buffer[256];
480    char section[256];
481    char *line, *key, *val, *end;
482    int section_open, section_close;
483    int error, lineno;
484
485    stream = fopen(file, "r");
486    if (stream == NULL) {
487  error("open(%s) failed: %s", file, strerror(errno));
488  return -1;
489    }
490
491    /* start with empty section */
492    strcpy(section, "");
493
494    error = 0;
495    lineno = 0;
496    while ((line = fgets(buffer, 256, stream)) != NULL) {
497
498  /* increment line number */
499  lineno++;
500
501  /* skip empty lines */
502  if (*(line = strip(line, 1)) == '\0')
503      continue;
504
505  /* reset section flags */
506  section_open = 0;
507  section_close = 0;
508
509  /* key is first word */
510  key = line;
511
512  /* search first blank between key and value */
513  for (val = line; *val; val++) {
514      if (isblank(*val)) {
515    *val++ = '\0';
516    break;
517      }
518  }
519
520  /* strip value */
521  val = strip(val, 1);
522
523  /* search end of value */
524  if (*val)
525      for (end = val; *(end + 1); end++);
526  else
527      end = val;
528
529  /* if last char is '{', a section has been opened */
530  if (*end == '{') {
531      section_open = 1;
532      *end = '\0';
533      val = strip(val, 0);
534  }
535
536  /* provess "value" in double-quotes */
537  if (*val == '"' && *end == '"') {
538      *end = '\0';
539      val++;
540  }
541
542  /* if key is '}', a section has been closed */
543  if (strcmp(key, "}") == 0) {
544      section_close = 1;
545      *key = '\0';
546  }
547
548  /* sanity check: '}' should be the only char in a line */
549  if (section_close && (section_open || *val != '\0')) {
550      error("error in config file '%s' line %d: garbage after '}'", file, lineno);
551      error = 1;
552      break;
553  }
554
555  /* check key for valid chars */
556  if (!validchars(key, 0)) {
557      error("error in config file '%s' line %d: key '%s' is invalid", file, lineno, key);
558      error = 1;
559      break;
560  }
561
562  /* on section-open, check value for valid chars */
563  if (section_open && !validchars(val, 1)) {
564      error("error in config file '%s' line %d: section '%s' is invalid", file, lineno, val);
565      error = 1;
566      break;
567  }
568
569  /* on section-open, append new section name */
570  if (section_open) {
571      /* is the section[] array big enough? */
572      if (strlen(section) + strlen(key) + 3 > sizeof(section)) {
573    error("error in config file '%s' line %d: section buffer overflow", file, lineno);
574    error = 1;
575    break;
576      }
577      if (*section != '\0')
578    strcat(section, ".");
579      strcat(section, key);
580      if (*val != '\0') {
581    strcat(section, ":");
582    strcat(section, val);
583      }
584      continue;
585  }
586
587  /* on section-close, remove last section name */
588  if (section_close) {
589      /* sanity check: section already empty? */
590      if (*section == '\0') {
591    error("error in config file '%s' line %d: unmatched closing brace", file, lineno);
592    error = 1;
593    break;
594      }
595
596      end = strrchr(section, '.');
597      if (end == NULL)
598    *section = '\0';
599      else
600    *end = '\0';
601      continue;
602  }
603
604  /* finally: add key */
605  cfg_add(section, key, val, 0);
606
607    }
608
609    /* sanity check: are the braces balanced? */
610    if (!error && *section != '\0') {
611  error("error in config file '%s' line %d: unbalanced braces", file, lineno);
612  error = 1;
613    }
614
615    fclose(stream);
616
617    return -error;
618}
619
620
621int cfg_init(const char *file)
622{
623    if (cfg_check_source(file) == -1) {
624  return -1;
625    }
626
627    if (cfg_read(file) < 0)
628  return -1;
629
630    if (Config_File)
631  free(Config_File);
632    Config_File = strdup(file);
633
634    return 0;
635}
636
637
638char *cfg_source(void)
639{
640    if (Config_File)
641  return Config_File;
642    else
643  return "";
644}
645
646
647int cfg_exit(void)
648{
649    int i;
650    for (i = 0; i < nConfig; i++) {
651  if (Config[i].key)
652      free(Config[i].key);
653  if (Config[i].val)
654      free(Config[i].val);
655    }
656
657    if (Config) {
658  free(Config);
659  Config = NULL;
660    }
661
662    if (Config_File) {
663  free(Config_File);
664  Config_File = NULL;
665    }
666
667    return 0;
668}
Note: See TracBrowser for help on using the browser.