2066f95719ed0531315ffaebdc7bb2e9d8c36346
[silc.git] / lib / silcutil / silcconfig.c
1 /*
2
3   silcconfig.c
4
5   Author: Giovanni Giacobbi <giovanni@giacobbi.net>
6
7   Copyright (C) 2002 - 2003 Giovanni Giacobbi
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; either version 2 of the License, or
12   (at your option) any later version.
13
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19 */
20 /* $Id$ */
21
22 #include "silcincludes.h"
23
24 /* limit debug logging verbosity */
25 #if 0
26 #define SILC_CONFIG_DEBUG(fmt) SILC_LOG_DEBUG(fmt)
27 #else
28 #define SILC_CONFIG_DEBUG(fmt)
29 #endif
30
31 /* this is the option struct and currently it is only used internally to
32  * the module and other structs. */
33 typedef struct SilcConfigOptionStruct {
34   char *name;                   /* *lowercase* name of the option */
35   SilcConfigType type;          /* important: the type of the returned value */
36   SilcConfigCallback cb;        /* the value handler */
37   const SilcConfigTable *subtable; /* used if type is SILC_CONFIG_ARG_BLOCK */
38   void *context;                /* context passed to the callback function */
39   struct SilcConfigOptionStruct *next;
40 } SilcConfigOption;
41
42 /* unique for each config file (and included ones) */
43 struct SilcConfigFileObject {
44   char *filename;       /* the original filename opened */
45   int level;            /* parsing level, how many nested
46                            silc_config_main we have */
47   char *base;           /* this is a fixed pointer to the base location */
48   char *p;              /* the Parser poitner */
49   SilcUInt32 len;       /* fixed length of the whole file */
50   SilcUInt32 line;      /* current parsing line, strictly linked to p */
51   bool included;        /* wether this file is main or included */
52 };
53
54 /* We need the entity to base our block-style parsing on */
55 struct SilcConfigEntityObject {
56   SilcConfigOption *opts;       /* known options list */
57   SilcConfigFile *file;         /* parsing file object */
58 };
59
60 /* access error descriptions only with silc_config_strerror() */
61 static char *errorstrs[] = {
62   "-OK",                                      /* SILC_CONFIG_OK */
63   "-SILENT",                                  /* SILC_CONFIG_ESILENT */
64   "-PRINTLINE",                               /* SILC_CONFIG_EPRINTLINE */
65   "Invalid syntax",                           /* SILC_CONFIG_EGENERIC */
66   "Internal error! Please report this bug",   /* SILC_CONFIG_EINTERNAL */
67   "Can't open specified file",                /* SILC_CONFIG_ECANTOPEN */
68   "Expected open-brace '{'",                  /* SILC_CONFIG_EOPENBRACE */
69   "Missing close-brace '}'",                  /* SILC_CONFIG_ECLOSEBRACE */
70   "Invalid data type",                        /* SILC_CONFIG_ETYPE */
71   "Unknown option",                           /* SILC_CONFIG_EBADOPTION */
72   "Invalid text",                             /* SILC_CONFIG_EINVALIDTEXT */
73   "Double option specification",              /* SILC_CONFIG_EDOUBLE */
74   "Expected data but not found",              /* SILC_CONFIG_EEXPECTED */
75   "Expected '='",                             /* SILC_CONFIG_EEXPECTEDEQUAL */
76   "Unexpected data",                          /* SILC_CONFIG_EUNEXPECTED */
77   "Missing mandatory fields",                 /* SILC_CONFIG_EMISSFIELDS */
78   "Missing ';'",                              /* SILC_CONFIG_EMISSCOLON */
79 };
80
81 /* return string describing SilcConfig's error code */
82 char *silc_config_strerror(int errnum)
83 {
84   if ((errnum < 0) || (errnum >= sizeof(errorstrs)/sizeof(*errorstrs)) ||
85     (errorstrs[errnum] == NULL)) {
86     char *defret = "-INVALIDERROR";
87     return defret;
88   }
89   return errorstrs[errnum];
90 }
91
92 /* Begin of internal SilcConfig's text util functions */
93
94 /* Points the first non-space character */
95 static void my_trim_spaces(SilcConfigFile *file)
96 {
97   register char *r = file->p;
98   while (isspace(*r))
99     if (*r++ == '\n') file->line++;
100   file->p = r;
101 }
102 /* Skips the current line until newline (lf or cr) */
103 static void my_skip_line(SilcConfigFile *file)
104 {
105   register char *r = file->p;
106   while (*r && (*r != '\n') && (*r != '\r')) r++;
107   file->p = (*r ? r + 1 : r);
108   file->line++;
109 }
110 /* Obtains a text token from the current position until first separator.
111  * a separator is any non alphanumeric character nor "_" or "-" */
112 static char *my_next_token(SilcConfigFile *file, char *to)
113 {
114   register char *o;
115   my_trim_spaces(file);
116   o = file->p;
117   while (isalnum(*o) || (*o == '_') || (*o == '-'))
118     *to++ = *o++;
119   *to = '\0';
120   file->p = o;
121   return to;
122 }
123 /* Obtains a string from the current position. The only difference from
124  * next_token() is that quoted-strings are also accepted */
125 static char *my_get_string(SilcConfigFile *file, char *to)
126 {
127   char *o;
128   my_trim_spaces(file);
129   o = file->p;
130   if (*o == '"') {
131     char *quot = strchr(++o, '"');
132     int len = quot - o;
133     if (!quot) { /* XXX FIXME: gotta do something here */
134       printf("Bullshit, missing matching \"");
135       exit(1);
136     }
137     if (len <= 0)
138       *to = '\0';
139     else {
140       strncpy(to, o, len);
141       to[len] = '\0';
142     }
143     /* update stream pointer */
144     file->p = quot + 1;
145     return to;
146   }
147   /* we don't need quote parsing, fall-back to token extractor */
148   my_next_token(file, to);
149   return to;
150 };
151 /* Skips all comment lines and spaces lines until first useful character */
152 static void my_skip_comments(SilcConfigFile *file)
153 {
154   while (1) {
155     my_trim_spaces(file);
156     if (*file->p != '#') return;
157     my_skip_line(file);
158   }
159 }
160
161 /* End of internal text functions
162  * Next section contains SilcConfig internal config utils */
163
164 /* find an option in the list by name and returns its pointer */
165 static SilcConfigOption *silc_config_find_option(SilcConfigEntity ent,
166         const char *name)
167 {
168   SilcConfigOption *tmp;
169   for (tmp = ent->opts; tmp; tmp = tmp->next) {
170     if (!strcasecmp(tmp->name, name))
171       return tmp;
172   }
173   return NULL;
174 }
175 /* Converts a string in the type specified. returns a dynamically
176  * allocated pointer. */
177 static void *silc_config_marshall(SilcConfigType type, const char *val)
178 {
179   void *pt;
180   int val_int;
181   bool val_bool;
182   char *val_tmp;
183   SilcUInt32 val_size;
184
185   switch (type) {
186     case SILC_CONFIG_ARG_TOGGLE:
187       if (!strcasecmp(val, "yes") || !strcasecmp(val, "true") ||
188                 !strcasecmp(val, "on") || !strcasecmp(val, "1")) {
189         val_bool = TRUE;
190       }
191       else if (!strcasecmp(val, "no") || !strcasecmp(val, "false") ||
192                 !strcasecmp(val, "off") || !strcasecmp(val, "0")) {
193         val_bool = FALSE;
194       }
195       else
196         return NULL;
197       pt = silc_calloc(1, sizeof(val_bool));
198       *(bool *)pt = (bool) val_bool;
199       return pt;
200     case SILC_CONFIG_ARG_INT:
201       val_int = (int) strtol(val, &val_tmp, 0);
202       if (*val_tmp) /* error converting string */
203         return NULL;
204       pt = silc_calloc(1, sizeof(val_int));
205       *(int *)pt = val_int;
206       return pt;
207     case SILC_CONFIG_ARG_SIZE:
208       val_size = (SilcUInt32) strtol(val, &val_tmp, 0);
209       if (val == val_tmp)
210         return NULL; /* really wrong, there must be at least one digit */
211       /* Search for a designator */
212       switch (tolower(val_tmp[0])) {
213         case '\0': /* None */
214           break;
215         case 'k': /* Kilobytes */
216           val_size *= (SilcUInt32) 1024;
217           break;
218         case 'm': /* Megabytes */
219           val_size *= (SilcUInt32) (1024 * 1024);
220           break;
221         case 'g':
222           val_size *= (SilcUInt32) (1024 * 1024 * 1024);
223           break;
224         default:
225           return NULL;
226       }
227       /* the string must die here */
228       if (val_tmp[1])
229         return NULL;
230       pt = silc_calloc(1, sizeof(val_size));
231       *(SilcUInt32 *)pt = val_size;
232       return pt;
233     case SILC_CONFIG_ARG_STR: /* the only difference between STR and STRE is */
234       if (!val[0])            /* that STR cannot be empty, while STRE can.  */
235         return NULL;
236     case SILC_CONFIG_ARG_STRE:
237       pt = (void *) strdup(val);
238       return pt;
239     /* following types are not supposed to have a return value */
240     case SILC_CONFIG_ARG_BLOCK:
241     case SILC_CONFIG_ARG_NONE:
242       return NULL;
243     default:
244       return NULL;
245   }
246
247   return NULL;
248 }
249
250 /* End of internal functions */
251
252
253 /* Tries to open the config file and returns a valid SilcConfigFile object
254  * or NULL if failed */
255
256 SilcConfigFile *silc_config_open(const char *configfile)
257 {
258   char *buffer;
259   SilcUInt32 filelen;
260   SilcConfigFile *ret;
261
262   if (!(buffer = silc_file_readfile(configfile, &filelen)))
263     return NULL;
264
265   ret = silc_calloc(1, sizeof(*ret));
266   ret->filename = strdup(configfile);
267   ret->base = ret->p = buffer;
268   ret->len = filelen;
269   ret->line = 1; /* line count, start from first line */
270   return ret;
271 }
272
273 /* Frees a file object */
274
275 void silc_config_close(SilcConfigFile *file)
276 {
277   if (file) {
278     silc_free(file->filename);
279     memset(file->base, 'F', file->len);
280     silc_free(file->base);
281     memset(file, 'F', sizeof(*file));
282     silc_free(file);
283   }
284 }
285
286 /* initializes a SilcConfigEntity pointer allocation */
287
288 SilcConfigEntity silc_config_init(SilcConfigFile *file)
289 {
290   SilcConfigEntity ret;
291
292   if (!file)
293     return NULL;
294
295   SILC_CONFIG_DEBUG(("Allocating new config entity"));
296   ret = silc_calloc(1, sizeof(*ret));
297   ret->file = file;
298   return ret;
299 };
300
301 /* Returns the original filename of the object file */
302
303 char *silc_config_get_filename(SilcConfigFile *file)
304 {
305   if (file)
306     return file->filename;
307   return NULL;
308 }
309
310 /* Returns the current line that file parsing arrived at */
311
312 SilcUInt32 silc_config_get_line(SilcConfigFile *file)
313 {
314   if (file)
315     return file->line;
316   return 0;
317 }
318
319 /* Returns a pointer to the beginning of the requested line.  If the line
320  * was not found, NULL is returned */
321
322 char *silc_config_read_line(SilcConfigFile *file, SilcUInt32 line)
323 {
324   register char *p;
325   int len;
326   char *ret = NULL, *endbuf;
327
328   if (!file || (line <= 0))
329     return NULL;
330   for (p = file->base; *p && (*p != EOF); p++) {
331     if (line <= 1)
332       goto found;
333     if (*p == '\n')
334       line--;
335   }
336   return NULL;
337
338  found:
339   if ((endbuf = strchr(p, '\n'))) {
340     len = endbuf - p;
341     if (len > 0)
342       ret = silc_memdup(p, len);
343   } else {
344     ret = silc_memdup(p, strlen(p));
345   }
346   return ret;
347 }
348
349 /* Convenience function to read the current parsed line */
350
351 char *silc_config_read_current_line(SilcConfigFile *file)
352 {
353   return silc_config_read_line(file, file->line);
354 }
355
356 /* (Private) destroy a SilcConfigEntity */
357
358 static void silc_config_destroy(SilcConfigEntity ent, bool destroy_opts)
359 {
360   SilcConfigOption *oldopt, *nextopt;
361   SILC_CONFIG_DEBUG(("Freeing config entity [ent=0x%x] [opts=0x%x]",
362                         (SilcUInt32) ent, (SilcUInt32) ent->opts));
363
364   /* if she wants to preserve options just free the object struct */
365   if (!destroy_opts)
366     goto skip_sect;
367
368   for (oldopt = ent->opts; oldopt; oldopt = nextopt) {
369     nextopt = oldopt->next;
370     memset(oldopt->name, 'F', strlen(oldopt->name) + 1);
371     silc_free(oldopt->name);
372     memset(oldopt, 'F', sizeof(*oldopt));
373     silc_free(oldopt);
374   }
375
376  skip_sect:
377   memset(ent, 'F', sizeof(*ent));
378   silc_free(ent);
379 }
380
381 /* Registers a new option in the specified entity.
382  * Returns TRUE on success, FALSE if already registered. */
383
384 bool silc_config_register(SilcConfigEntity ent, const char *name,
385                           SilcConfigType type, SilcConfigCallback cb,
386                           const SilcConfigTable *subtable, void *context)
387 {
388   SilcConfigOption *newopt;
389   SILC_CONFIG_DEBUG(("Register new option=\"%s\" "
390                      "type=%u cb=0x%08x context=0x%08x",
391                      name, type, (SilcUInt32) cb, (SilcUInt32) context));
392
393   /* if we are registering a block, make sure there is a specified sub-table */
394   if (!ent || !name || ((type == SILC_CONFIG_ARG_BLOCK) && !subtable))
395     return FALSE;
396
397   /* don't register a reserved tag */
398   if (!strcasecmp(name, "include"))
399     return FALSE;
400
401   /* check if an option was previously registered */
402   if (silc_config_find_option(ent, name)) {
403     SILC_LOG_DEBUG(("Error: Can't register \"%s\" twice.", name));
404     return FALSE;
405   }
406
407   /* allocate and append the new option */
408   newopt = silc_calloc(1, sizeof(*newopt));
409   newopt->name = strdup(name);
410   newopt->type = type;
411   newopt->cb = cb;
412   newopt->subtable = subtable;
413   newopt->context = context;
414
415   /* append this option to the list */
416   if (!ent->opts)
417     ent->opts = newopt;
418   else {
419     SilcConfigOption *tmp;
420     for (tmp = ent->opts; tmp->next; tmp = tmp->next);
421     tmp->next = newopt;
422   }
423   return TRUE;
424 }
425
426 /* Register a new option table in the specified config entity */
427
428 bool silc_config_register_table(SilcConfigEntity ent,
429                                 const SilcConfigTable table[], void *context)
430 {
431   int i;
432   if (!ent || !table)
433     return FALSE;
434   SILC_CONFIG_DEBUG(("Registering table"));
435   /* XXX FIXME: some potability checks needed - really? */
436   for (i = 0; table[i].name; i++) {
437     if (!silc_config_register(ent, table[i].name, table[i].type,
438                               table[i].callback, table[i].subtable, context))
439       return FALSE;
440   }
441   return TRUE;
442 }
443
444 /* ... */
445
446 static int silc_config_main_internal(SilcConfigEntity ent)
447 {
448   SilcConfigFile *file = ent->file;
449   char **p = &file->p;
450
451   /* loop throught statements */
452   while (1) {
453     char buf[255];
454     SilcConfigOption *thisopt;
455
456     /* makes it pointing to the next interesting char */
457     my_skip_comments(file);
458     /* got eof? */
459     if (**p == '\0' || **p == EOF) {
460       if (file->level > 1) /* cannot get eof in a sub-level! */
461         return SILC_CONFIG_EEXPECTED;
462       goto finish;
463     }
464     /* check if we completed this (sub) section (it doesn't matter if this
465      * is the main section) */
466     if (**p == '}') {
467       if (file->level < 2) /* can't be! must be at least one sub-block */
468         return SILC_CONFIG_EUNEXPECTED;
469       (*p)++;
470       goto finish;
471     }
472     //SILC_LOG_HEXDUMP(("Preparing lookup at line=%lu", file->line), *p, 16);
473
474     /* obtain the keyword */
475     my_next_token(file, buf);
476     SILC_CONFIG_DEBUG(("Looking up keyword=\"%s\" [line=%lu]",
477                        buf, file->line));
478
479     /* handle special directive */
480     if (!strcasecmp(buf, "include")) {
481       int ret;
482       SilcConfigFile *inc_file;
483       SilcConfigEntity inc_ent;
484
485       my_trim_spaces(file); /* prepare next char */
486
487       /* Now trying to include the specified file.  The included file will
488        * be allowed to include sub-files but it will preserve the block-level
489        * of the including block. Note that the included file won't be allowed
490        * to raise the block level of the including block. */
491
492       my_get_string(file, buf); /* get the filename */
493       SILC_LOG_DEBUG(("Including file \"%s\"", buf));
494       /* before getting on, check if this row is REALLY complete */
495       if (*(*p)++ != ';')
496         return SILC_CONFIG_EMISSCOLON;
497
498       /* open the file and start the parsing */
499       inc_file = silc_config_open(buf);
500       if (!inc_file) /* does it point a valid filename? */
501         return SILC_CONFIG_ECANTOPEN;
502       inc_file->included = TRUE;
503
504       /* create a new entity and hack it to use the same options */
505       inc_ent = silc_config_init(inc_file);
506       inc_ent->opts = ent->opts;
507       ret = silc_config_main(inc_ent);
508
509       /* Cleanup.
510        * If the included file returned an error, the application will probably
511        * want to output some kind of error message. Because of this, we can't
512        * destroy THIS file object. The hack works this way: The application
513        * expects to destroy the originally created object file, so we'll swap
514        * the original file with the included file. */
515       if (ret) {
516         SilcConfigFile tmp_file;
517         SILC_CONFIG_DEBUG(("SWAPPING FILE OBJECTS"));
518         memcpy(&tmp_file, inc_file, sizeof(tmp_file));
519         memcpy(inc_file, file, sizeof(tmp_file));
520         silc_config_close(inc_file);
521         memcpy(file, &tmp_file, sizeof(tmp_file));
522         return ret;
523       }
524       /* otherwise if no errors encoured, continue normally */
525       silc_config_close(inc_file);
526       continue; /* this one is handled */
527     }
528
529     /* we have a registered option (it can also be a sub-block) */
530     thisopt = silc_config_find_option(ent, buf);
531     if (!thisopt)
532       return SILC_CONFIG_EBADOPTION;
533
534     my_trim_spaces(file); /* prepare next char */
535
536     /* option type is a block? */
537     if (thisopt->type == SILC_CONFIG_ARG_BLOCK) {
538       int ret;
539       SilcConfigEntity sub_ent;
540
541       SILC_CONFIG_DEBUG(("Entering sub-block"));
542       if (*(*p)++ != '{')
543         return SILC_CONFIG_EOPENBRACE;
544       /* build the new entity for this sub-block */
545       sub_ent = silc_config_init(ent->file);
546       /* use the previous specified table to describe this block's options */
547       silc_config_register_table(sub_ent, thisopt->subtable, thisopt->context);
548       /* run this block! */
549       ret = silc_config_main(sub_ent);
550       SILC_CONFIG_DEBUG(("Returned from sub-block [ret=%d]", ret));
551
552       if (ret) /* now check the result */
553         return ret;
554
555       /* now call block clean-up callback (if any) */
556       if (thisopt->cb) {
557         int ret;
558         SILC_CONFIG_DEBUG(("Now calling clean-up callback"));
559         ret = thisopt->cb(thisopt->type, thisopt->name, file->line, NULL,
560                           thisopt->context);
561         if (ret) {
562           SILC_CONFIG_DEBUG(("Callback refused the value [ret=%d]", ret));
563           return ret;
564         }
565       }
566       /* Do we want ';' to be mandatory after close brace? */
567       if (*(*p)++ != ';')
568         return SILC_CONFIG_EMISSCOLON;
569     }
570     else if (thisopt->type == SILC_CONFIG_ARG_NONE) {
571       /* before getting on, check if this row is REALLY complete */
572       if (*(*p)++ != ';')
573         return SILC_CONFIG_EMISSCOLON;
574       SILC_CONFIG_DEBUG(("Triggering callback for none"));
575       if (thisopt->cb) {
576         thisopt->cb(thisopt->type, thisopt->name, file->line,
577                     NULL, thisopt->context);
578       }
579     }
580     else {
581       void *pt;
582       int ret = 0;      /* very important in case of no cb */
583
584       if (*(*p)++ != '=')
585         return SILC_CONFIG_EEXPECTEDEQUAL;
586
587       my_get_string(file, buf); /* get the option argument */
588       SILC_CONFIG_DEBUG(("With argument=\"%s\"", buf));
589
590       /* before getting on, check if this row is REALLY complete */
591       if (*(*p)++ != ';')
592         return SILC_CONFIG_EMISSCOLON;
593
594       /* convert the option argument to the right format */
595       pt = silc_config_marshall(thisopt->type, buf);
596       if (!pt)
597         return SILC_CONFIG_EINVALIDTEXT;
598       if (thisopt->cb)
599         ret = thisopt->cb(thisopt->type, thisopt->name, file->line,
600                           pt, thisopt->context);
601
602       /* since we have to free "pt" both on failure and on success, we
603          assume that ret == 0 if we didn't actually call any cb. */
604       silc_free(pt);
605       if (ret) {
606         SILC_CONFIG_DEBUG(("Callback refused the value [ret=%d]", ret));
607         return ret;
608       }
609     }
610     continue;
611
612  finish:
613     break;
614   }
615
616   return SILC_CONFIG_OK;
617 }
618
619 /* ... */
620
621 int silc_config_main(SilcConfigEntity ent)
622 {
623   SilcConfigFile *file = ent->file;
624   int ret;
625
626   /* don't silently accept a NULL entity */
627   if (!ent) {
628     ret = SILC_CONFIG_EGENERIC;
629     goto main_cleanup;
630   }
631
632   /* call the real main and store the result */
633   file->level++;
634   SILC_CONFIG_DEBUG(("[Lev=%d] Entering config parsing core", file->level));
635   ret = silc_config_main_internal(ent);
636   SILC_CONFIG_DEBUG(("[Lev=%d] Quitting main [ret=%d]", file->level, ret));
637   if (!file->level) /* when swap happens, we could close a file twice */
638     goto main_end;
639   file->level--;
640
641   /* If this file was included don't destroy the options set because it is
642    * the same of the including block. Although if this entity is in a
643    * sub-block created inside the included file, this options set must be
644    * destroyed. */
645  main_cleanup:
646   if ((file->level != 0) || (file->included != TRUE))
647     silc_config_destroy(ent, TRUE);
648   else
649     silc_config_destroy(ent, FALSE);
650
651  main_end:
652   return ret;
653 }