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