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