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