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