Merged from silc_1_0_branch.
[silc.git] / lib / silcutil / silcconfig.c
1 /*
2
3   silcconfig.c
4
5   Author: Johnny Mnemonic <johnny@themnemonic.org>
6
7   Copyright (C) 1997 - 2002 Pekka Riikonen
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 /* ... */
176 static void *silc_config_marshall(SilcConfigType type, const char *val)
177 {
178   void *pt;
179   int val_int;
180   bool val_bool;
181   char *val_tmp;
182   SilcUInt32 val_size;
183
184   switch (type) {
185     case SILC_CONFIG_ARG_TOGGLE:
186       if (!strcasecmp(val, "yes") || !strcasecmp(val, "true") ||
187                 !strcasecmp(val, "on") || !strcasecmp(val, "1")) {
188         val_bool = TRUE;
189       }
190       else if (!strcasecmp(val, "no") || !strcasecmp(val, "false") ||
191                 !strcasecmp(val, "off") || !strcasecmp(val, "0")) {
192         val_bool = FALSE;
193       }
194       else
195         return NULL;
196       pt = silc_calloc(1, sizeof(val_bool));
197       *(bool *)pt = (bool) val_bool;
198       return pt;
199     case SILC_CONFIG_ARG_INT:
200       val_int = (int) strtol(val, &val_tmp, 0);
201       if (*val_tmp) /* error converting string */
202         return NULL;
203       pt = silc_calloc(1, sizeof(val_int));
204       *(int *)pt = val_int;
205       return pt;
206     case SILC_CONFIG_ARG_SIZE:
207       val_size = (SilcUInt32) strtol(val, &val_tmp, 0);
208       if (val == val_tmp)
209         return NULL; /* really wrong, there must be at least one digit */
210       /* Search for a designator */
211       switch (tolower(val_tmp[0])) {
212         case '\0': /* None */
213           break;
214         case 'k': /* Kilobytes */
215           val_size *= (SilcUInt32) 1024;
216           break;
217         case 'm': /* Megabytes */
218           val_size *= (SilcUInt32) (1024 * 1024);
219           break;
220         case 'g':
221           val_size *= (SilcUInt32) (1024 * 1024 * 1024);
222           break;
223         default:
224           return NULL;
225       }
226       /* the string must die here */
227       if (val_tmp[1])
228         return NULL;
229       pt = silc_calloc(1, sizeof(val_size));
230       *(SilcUInt32 *)pt = val_size;
231       return pt;
232     case SILC_CONFIG_ARG_STR: /* the only difference between STR and STRE is */
233       if (!val[0])            /* that STR cannot be empty, while STRE can.  */
234         return NULL;
235     case SILC_CONFIG_ARG_STRE:
236       pt = (void *) strdup(val);
237       return pt;
238     /* following types are not supposed to have a return value */
239     case SILC_CONFIG_ARG_BLOCK:
240     case SILC_CONFIG_ARG_NONE:
241       return NULL;
242     default:
243       return NULL;
244   }
245
246   return NULL;
247 }
248
249 /* End of internal functions */
250
251
252 /* Tries to open the config file and returns a valid SilcConfigFile object
253  * or NULL if failed */
254
255 SilcConfigFile *silc_config_open(const char *configfile)
256 {
257   char *buffer;
258   SilcUInt32 filelen;
259   SilcConfigFile *ret;
260
261   if (!(buffer = silc_file_readfile(configfile, &filelen)))
262     return NULL;
263
264   ret = silc_calloc(1, sizeof(*ret));
265   ret->filename = strdup(configfile);
266   ret->base = ret->p = buffer;
267   ret->len = filelen;
268   ret->line = 1; /* line count, start from first line */
269   return ret;
270 }
271
272 /* Frees a file object */
273
274 void silc_config_close(SilcConfigFile *file)
275 {
276   if (file) {
277     silc_free(file->filename);
278     memset(file->base, 'F', file->len);
279     silc_free(file->base);
280     memset(file, 'F', sizeof(*file));
281     silc_free(file);
282   }
283 }
284
285 /* initializes a SilcConfigEntity pointer allocation */
286
287 SilcConfigEntity silc_config_init(SilcConfigFile *file)
288 {
289   SilcConfigEntity ret;
290
291   if (!file)
292     return NULL;
293
294   SILC_CONFIG_DEBUG(("Allocating new config entity"));
295   ret = silc_calloc(1, sizeof(*ret));
296   ret->file = file;
297   return ret;
298 };
299
300 /* Returns the original filename of the object file */
301
302 char *silc_config_get_filename(SilcConfigFile *file)
303 {
304   if (file)
305     return file->filename;
306   return NULL;
307 }
308
309 /* Returns the current line that file parsing arrived at */
310
311 SilcUInt32 silc_config_get_line(SilcConfigFile *file)
312 {
313   if (file)
314     return file->line;
315   return 0;
316 }
317
318 /* Returns a pointer to the beginning of the requested line.  If the line
319  * was not found, NULL is returned */
320
321 char *silc_config_read_line(SilcConfigFile *file, SilcUInt32 line)
322 {
323   register char *p;
324   int len;
325   char *ret = NULL, *endbuf;
326
327   if (!file || (line <= 0))
328     return NULL;
329   for (p = file->base; *p && (*p != EOF); p++) {
330     if (line <= 1)
331       goto found;
332     if (*p == '\n')
333       line--;
334   }
335   return NULL;
336
337  found:
338   if ((endbuf = strchr(p, '\n'))) {
339     len = endbuf - p;
340     if (len > 0)
341       ret = silc_memdup(p, len);
342   } else {
343     ret = silc_memdup(p, strlen(p));
344   }
345   return ret;
346 }
347
348 /* Convenience function to read the current parsed line */
349
350 char *silc_config_read_current_line(SilcConfigFile *file)
351 {
352   return silc_config_read_line(file, file->line);
353 }
354
355 /* (Private) destroy a SilcConfigEntity */
356
357 static void silc_config_destroy(SilcConfigEntity ent, bool destroy_opts)
358 {
359   SilcConfigOption *oldopt, *nextopt;
360   SILC_CONFIG_DEBUG(("Freeing config entity [ent=0x%x] [opts=0x%x]",
361                         (SilcUInt32) ent, (SilcUInt32) ent->opts));
362
363   /* if she wants to preserve options just free the object struct */
364   if (!destroy_opts)
365     goto skip_sect;
366
367   for (oldopt = ent->opts; oldopt; oldopt = nextopt) {
368     nextopt = oldopt->next;
369     memset(oldopt->name, 'F', strlen(oldopt->name) + 1);
370     silc_free(oldopt->name);
371     memset(oldopt, 'F', sizeof(*oldopt));
372     silc_free(oldopt);
373   }
374
375  skip_sect:
376   memset(ent, 'F', sizeof(*ent));
377   silc_free(ent);
378 }
379
380 /* Registers a new option in the specified entity.
381  * Returns TRUE on success, FALSE if already registered. */
382
383 bool silc_config_register(SilcConfigEntity ent, const char *name,
384                           SilcConfigType type, SilcConfigCallback cb,
385                           const SilcConfigTable *subtable, void *context)
386 {
387   SilcConfigOption *newopt;
388   SILC_CONFIG_DEBUG(("Register new option=\"%s\" "
389                      "type=%u cb=0x%08x context=0x%08x",
390                      name, type, (SilcUInt32) cb, (SilcUInt32) context));
391
392   /* if we are registering a block, make sure there is a specified sub-table */
393   if (!ent || !name || ((type == SILC_CONFIG_ARG_BLOCK) && !subtable))
394     return FALSE;
395
396   /* don't register a reserved tag */
397   if (!strcasecmp(name, "include"))
398     return FALSE;
399
400   /* check if an option was previously registered */
401   if (silc_config_find_option(ent, name)) {
402     SILC_LOG_DEBUG(("Error: Can't register \"%s\" twice.", name));
403     return FALSE;
404   }
405
406   /* allocate and append the new option */
407   newopt = silc_calloc(1, sizeof(*newopt));
408   newopt->name = strdup(name);
409   newopt->type = type;
410   newopt->cb = cb;
411   newopt->subtable = subtable;
412   newopt->context = context;
413
414   /* append this option to the list */
415   if (!ent->opts)
416     ent->opts = newopt;
417   else {
418     SilcConfigOption *tmp;
419     for (tmp = ent->opts; tmp->next; tmp = tmp->next);
420     tmp->next = newopt;
421   }
422   return TRUE;
423 }
424
425 /* Register a new option table in the specified config entity */
426
427 bool silc_config_register_table(SilcConfigEntity ent,
428                                 const SilcConfigTable table[], void *context)
429 {
430   int i;
431   if (!ent || !table)
432     return FALSE;
433   SILC_CONFIG_DEBUG(("Registering table"));
434   /* XXX FIXME: some potability checks needed - really? */
435   for (i = 0; table[i].name; i++) {
436     if (!silc_config_register(ent, table[i].name, table[i].type,
437                               table[i].callback, table[i].subtable, context))
438       return FALSE;
439   }
440   return TRUE;
441 }
442
443 /* ... */
444
445 static int silc_config_main_internal(SilcConfigEntity ent)
446 {
447   SilcConfigFile *file = ent->file;
448   char **p = &file->p;
449
450   /* loop throught statements */
451   while (1) {
452     char buf[255];
453     SilcConfigOption *thisopt;
454
455     /* makes it pointing to the next interesting char */
456     my_skip_comments(file);
457     /* got eof? */
458     if (**p == '\0' || **p == EOF) {
459       if (file->level > 1) /* cannot get eof in a sub-level! */
460         return SILC_CONFIG_EEXPECTED;
461       goto finish;
462     }
463     /* check if we completed this (sub) section (it doesn't matter if this
464      * is the main section) */
465     if (**p == '}') {
466       if (file->level < 2) /* can't be! must be at least one sub-block */
467         return SILC_CONFIG_EUNEXPECTED;
468       (*p)++;
469       goto finish;
470     }
471     //SILC_LOG_HEXDUMP(("Preparing lookup at line=%lu", file->line), *p, 16);
472
473     /* obtain the keyword */
474     my_next_token(file, buf);
475     SILC_CONFIG_DEBUG(("Looking up keyword=\"%s\" [line=%lu]",
476                        buf, file->line));
477
478     /* handle special directive */
479     if (!strcasecmp(buf, "include")) {
480       int ret;
481       SilcConfigFile *inc_file;
482       SilcConfigEntity inc_ent;
483
484       my_trim_spaces(file); /* prepare next char */
485
486       /* Now trying to include the specified file.  The included file will
487        * be allowed to include sub-files but it will preserve the block-level
488        * of the including block. Note that the included file won't be allowed
489        * to raise the block level of the including block. */
490
491       my_get_string(file, buf); /* get the filename */
492       SILC_LOG_DEBUG(("Including file \"%s\"", buf));
493       /* before getting on, check if this row is REALLY complete */
494       if (*(*p)++ != ';')
495         return SILC_CONFIG_EMISSCOLON;
496
497       /* open the file and start the parsing */
498       inc_file = silc_config_open(buf);
499       if (!inc_file) /* does it point a valid filename? */
500         return SILC_CONFIG_ECANTOPEN;
501       inc_file->included = TRUE;
502
503       /* create a new entity and hack it to use the same options */
504       inc_ent = silc_config_init(inc_file);
505       inc_ent->opts = ent->opts;
506       ret = silc_config_main(inc_ent);
507
508       /* Cleanup.
509        * If the included file returned an error, the application will probably
510        * want to output some kind of error message. Because of this, we can't
511        * destroy THIS file object. The hack works this way: The application
512        * expects to destroy the originally created object file, so we'll swap
513        * the original file with the included file. */
514       if (ret) {
515         SilcConfigFile tmp_file;
516         SILC_CONFIG_DEBUG(("SWAPPING FILE OBJECTS"));
517         memcpy(&tmp_file, inc_file, sizeof(tmp_file));
518         memcpy(inc_file, file, sizeof(tmp_file));
519         silc_config_close(inc_file);
520         memcpy(file, &tmp_file, sizeof(tmp_file));
521         return ret;
522       }
523       /* otherwise if no errors encoured, continue normally */
524       silc_config_close(inc_file);
525       continue; /* this one is handled */
526     }
527
528     /* we have a registered option (it can also be a sub-block) */
529     thisopt = silc_config_find_option(ent, buf);
530     if (!thisopt)
531       return SILC_CONFIG_EBADOPTION;
532
533     my_trim_spaces(file); /* prepare next char */
534
535     /* option type is a block? */
536     if (thisopt->type == SILC_CONFIG_ARG_BLOCK) {
537       int ret;
538       SilcConfigEntity sub_ent;
539
540       SILC_CONFIG_DEBUG(("Entering sub-block"));
541       if (*(*p)++ != '{')
542         return SILC_CONFIG_EOPENBRACE;
543       /* build the new entity for this sub-block */
544       sub_ent = silc_config_init(ent->file);
545       /* use the previous specified table to describe this block's options */
546       silc_config_register_table(sub_ent, thisopt->subtable, thisopt->context);
547       /* run this block! */
548       ret = silc_config_main(sub_ent);
549       SILC_CONFIG_DEBUG(("Returned from sub-block [ret=%d]", ret));
550
551       if (ret) /* now check the result */
552         return ret;
553
554       /* now call block clean-up callback (if any) */
555       if (thisopt->cb) {
556         int ret;
557         SILC_CONFIG_DEBUG(("Now calling clean-up callback"));
558         ret = thisopt->cb(thisopt->type, thisopt->name, file->line, NULL,
559                           thisopt->context);
560         if (ret) {
561           SILC_CONFIG_DEBUG(("Callback refused the value [ret=%d]", ret));
562           return ret;
563         }
564       }
565       /* Do we want ';' to be mandatory after close brace? */
566       if (*(*p)++ != ';')
567         return SILC_CONFIG_EMISSCOLON;
568     }
569     else if (thisopt->type == SILC_CONFIG_ARG_NONE) {
570       /* before getting on, check if this row is REALLY complete */
571       if (*(*p)++ != ';')
572         return SILC_CONFIG_EMISSCOLON;
573       SILC_CONFIG_DEBUG(("Triggering callback for none"));
574       if (thisopt->cb) {
575         thisopt->cb(thisopt->type, thisopt->name, file->line,
576                     NULL, thisopt->context);
577       }
578     }
579     else {
580       void *pt;
581       int ret;
582
583       if (*(*p)++ != '=')
584         return SILC_CONFIG_EEXPECTEDEQUAL;
585
586       my_get_string(file, buf); /* get the option argument */
587       SILC_CONFIG_DEBUG(("With argument=\"%s\"", buf));
588
589       /* before getting on, check if this row is REALLY complete */
590       if (*(*p)++ != ';')
591         return SILC_CONFIG_EMISSCOLON;
592
593       /* convert the option argument to the right format */
594       pt = silc_config_marshall(thisopt->type, buf);
595       if (!pt)
596         return SILC_CONFIG_EINVALIDTEXT;
597       if (thisopt->cb) {
598         ret = thisopt->cb(thisopt->type, thisopt->name, file->line,
599                           pt, thisopt->context);
600         if (ret) {
601           SILC_CONFIG_DEBUG(("Callback refused the value [ret=%d]", ret));
602           return ret;
603         }
604       }
605       silc_free(pt);
606     }
607     continue;
608
609  finish:
610     break;
611   }
612
613   return SILC_CONFIG_OK;
614 }
615
616 /* ... */
617
618 int silc_config_main(SilcConfigEntity ent)
619 {
620   SilcConfigFile *file = ent->file;
621   int ret;
622
623   /* don't silently accept a NULL entity */
624   if (!ent) {
625     ret = SILC_CONFIG_EGENERIC;
626     goto main_cleanup;
627   }
628
629   /* call the real main and store the result */
630   file->level++;
631   SILC_CONFIG_DEBUG(("[Lev=%d] Entering config parsing core", file->level));
632   ret = silc_config_main_internal(ent);
633   SILC_CONFIG_DEBUG(("[Lev=%d] Quitting main [ret=%d]", file->level, ret));
634   if (!file->level) /* when swap happens, we could close a file twice */
635     goto main_end;
636   file->level--;
637
638   /* If this file was included don't destroy the options set because it is
639    * the same of the including block. Although if this entity is in a
640    * sub-block created inside the included file, this options set must be
641    * destroyed. */
642  main_cleanup:
643   if ((file->level != 0) || (file->included != TRUE))
644     silc_config_destroy(ent, TRUE);
645   else
646     silc_config_destroy(ent, FALSE);
647
648  main_end:
649   return ret;
650 }