New silcconfig library and server parser. Merged silc-newconfig-final.patch.
[silc.git] / lib / silcutil / silcconfig.c
index 19ae0b975b8ce0569f2c4dfc2951e7fd5f3108f1..188ffa2b66a46e171b703db2a92162281b537d72 100644 (file)
@@ -2,15 +2,15 @@
 
   silcconfig.c
 
-  Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
+  Author: Johnny Mnemonic <johnny@themnemonic.org>
 
-  Copyright (C) 1997 - 2000 Pekka Riikonen
+  Copyright (C) 1997 - 2002 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
-  
+
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 #include "silcincludes.h"
 
-/* Opens and reads a configuration file to a buffer. The read data is 
-   returned to the ret_buffer argument. */
+/* limit debug logging verbosity */
+#if 0
+#define SILC_CONFIG_DEBUG(fmt) SILC_LOG_DEBUG(fmt)
+#else
+#define SILC_CONFIG_DEBUG(fmt)
+#endif
+
+/* this is the option struct and currently it is only used internally to
+ * the module and other structs. */
+typedef struct SilcConfigOptionStruct {
+  char *name;                  /* *lowercase* name of the option */
+  SilcConfigType type;         /* important: the type of the returned value */
+  SilcConfigCallback cb;       /* the value handler */
+  const SilcConfigTable *subtable; /* used if type is SILC_CONFIG_ARG_BLOCK */
+  void *context;               /* context passed to the callback function */
+  struct SilcConfigOptionStruct *next;
+} SilcConfigOption;
+
+/* unique for each config file (and included ones) */
+struct SilcConfigFileObject {
+  char *filename; /* the original filename opened */
+  int level;   /* parsing level, how many nested silc_config_main we have */
+  char *base;  /* this is a fixed pointer to the base location */
+  char *p;     /* the Parser poitner */
+  uint32 len;  /* fixed length of the whole file */
+  uint32 line; /* current parsing line, strictly linked to p */
+  bool included; /* wether this file is main or included */
+};
+
+/* We need the entity to base our block-style parsing on */
+struct SilcConfigEntityObject {
+  SilcConfigOption *opts;      /* known options list */
+  SilcConfigFile *file;                /* parsing file object */
+};
+
+/* access error descriptions only with silc_config_strerror() */
+static char *errorstrs[] = {
+  "-OK",                                       /* SILC_CONFIG_OK */
+  "-SILENT",                                   /* SILC_CONFIG_ESILENT */
+  "Invalid syntax",                            /* SILC_CONFIG_EGENERIC */
+  "Internal error! Please report this bug",    /* SILC_CONFIG_EINTERNAL */
+  "Can't open specified file",                 /* SILC_CONFIG_ECANTOPEN */
+  "Expected open-brace '{'",                   /* SILC_CONFIG_EOPENBRACE */
+  "Missing close-brace '}'",                   /* SILC_CONFIG_ECLOSEBRACE */
+  "Invalid data type",                         /* SILC_CONFIG_ETYPE */
+  "Unknown option",                            /* SILC_CONFIG_EBADOPTION */
+  "Invalid text",                              /* SILC_CONFIG_EINVALIDTEXT */
+  "Double option specification",               /* SILC_CONFIG_EDOUBLE */
+  "Expected data but not found",               /* SILC_CONFIG_EEXPECTED */
+  "Expected '='",                              /* SILC_CONFIG_EEXPECTEDEQUAL */
+  "Unexpected data",                           /* SILC_CONFIG_EUNEXPECTED */
+  "Missing needed fields",                     /* SILC_CONFIG_EMISSFIELDS */
+  "Missing ';'",                               /* SILC_CONFIG_EMISSCOLON */
+};
+
+/* return string describing SilcConfig's error code */
+char *silc_config_strerror(int errnum)
+{
+  if ((errnum < 0) || (errnum >= sizeof(errorstrs)/sizeof(*errorstrs)) ||
+    (errorstrs[errnum] == NULL)) {
+    char *defret = "-INVALIDERROR";
+    return defret;
+  }
+  return errorstrs[errnum];
+}
+
+/* Begin of internal SilcConfig's text util functions */
+
+/* Points the first non-space character */
+static void my_trim_spaces(SilcConfigFile *file)
+{
+  register char *r = file->p;
+  while (isspace(*r))
+    if (*r++ == '\n') file->line++;
+  file->p = r;
+}
+/* Skips the current line until newline (lf or cr) */
+static void my_skip_line(SilcConfigFile *file)
+{
+  register char *r = file->p;
+  while (*r && (*r != '\n') && (*r != '\r')) r++;
+  file->p = (*r ? r + 1 : r);
+  file->line++;
+}
+/* Obtains a text token from the current position until first separator.
+ * a separator is any non alphanumeric character nor "_" or "-" */
+static char *my_next_token(SilcConfigFile *file, char *to)
+{
+  register char *o;
+  my_trim_spaces(file);
+  o = file->p;
+  while (isalnum(*o) || (*o == '_') || (*o == '-'))
+    *to++ = *o++;
+  *to = '\0';
+  file->p = o;
+  return to;
+}
+/* Obtains a string from the current position. The only difference from
+ * next_token() is that quoted-strings are also accepted */
+static char *my_get_string(SilcConfigFile *file, char *to)
+{
+  char *o;
+  my_trim_spaces(file);
+  o = file->p;
+  if (*o == '"') {
+    char *quot = strchr(++o, '"');
+    int len = quot - o;
+    if (!quot) { /* XXX FIXME: gotta do something here */
+      printf("Bullshit, missing matching \"");
+      exit(1);
+    }
+    if (len <= 0)
+      *to = '\0';
+    else {
+      strncpy(to, o, len);
+      to[len] = '\0';
+    }
+    /* update stream pointer */
+    file->p = quot + 1;
+    return to;
+  }
+  /* we don't need quote parsing, fall-back to token extractor */
+  my_next_token(file, to);
+  return to;
+};
+/* Skips all comment lines and spaces lines until first useful character */
+static void my_skip_comments(SilcConfigFile *file)
+{
+  while (1) {
+    my_trim_spaces(file);
+    if (*file->p != '#') return;
+    my_skip_line(file);
+  }
+}
+
+/* End of internal text functions
+ * Next section contains SilcConfig internal config utils */
 
-void silc_config_open(char *filename, SilcBuffer *ret_buffer)
+/* find an option in the list by name and returns its pointer */
+static SilcConfigOption *silc_config_find_option(SilcConfigEntity ent,
+       const char *name)
+{
+  SilcConfigOption *tmp;
+  for (tmp = ent->opts; tmp; tmp = tmp->next) {
+    if (!strcasecmp(tmp->name, name))
+      return tmp;
+  }
+  return NULL;
+}
+/* ... */
+static void *silc_config_marshall(SilcConfigType type, const char *val)
+{
+  void *pt;
+  int val_int;
+  bool val_bool;
+  char *val_tmp;
+  uint32 val_size;
+
+  switch (type) {
+    case SILC_CONFIG_ARG_TOGGLE:
+      if (!strcasecmp(val, "yes") || !strcasecmp(val, "true") ||
+               !strcasecmp(val, "on") || !strcasecmp(val, "1")) {
+       val_bool = TRUE;
+      }
+      else if (!strcasecmp(val, "no") || !strcasecmp(val, "false") ||
+               !strcasecmp(val, "off") || !strcasecmp(val, "0")) {
+       val_bool = FALSE;
+      }
+      else
+       return NULL;
+      pt = silc_calloc(1, sizeof(val_bool));
+      *(bool *)pt = (bool) val_bool;
+      return pt;
+    case SILC_CONFIG_ARG_INT:
+      val_int = (int) strtol(val, &val_tmp, 0);
+      if (*val_tmp) /* error converting string */
+       return NULL;
+      pt = silc_calloc(1, sizeof(val_int));
+      *(int *)pt = val_int;
+      return pt;
+    case SILC_CONFIG_ARG_SIZE:
+      val_size = (uint32) strtol(val, &val_tmp, 0);
+      if (val == val_tmp)
+       return NULL; /* really wrong, there must be at least one digit */
+      /* Search for a designator */
+      switch (tolower(val_tmp[0])) {
+       case '\0': /* None */
+         break;
+       case 'k': /* Kilobytes */
+         val_size *= (uint32) 1024;
+         break;
+       case 'm': /* Megabytes */
+         val_size *= (uint32) (1024 * 1024);
+         break;
+       case 'g':
+         val_size *= (uint32) (1024 * 1024 * 1024);
+         break;
+       default:
+         return NULL;
+      }
+      /* the string must die here */
+      if (val_tmp[1])
+       return NULL;
+      pt = silc_calloc(1, sizeof(val_size));
+      *(uint32 *)pt = val_size;
+      return pt;
+    case SILC_CONFIG_ARG_STR: /* the only difference between STR and STRE is */
+      if (!val[0])           /* that STR cannot be empty, while STRE can.  */
+       return NULL;
+    case SILC_CONFIG_ARG_STRE:
+      pt = (void *) strdup(val);
+      return pt;
+    /* following types are not supposed to have a return value */
+    case SILC_CONFIG_ARG_BLOCK:
+    case SILC_CONFIG_ARG_NONE:
+      return NULL;
+    default:
+      return NULL;
+  }
+
+  return NULL;
+}
+
+/* End of internal functions */
+
+
+/* Tries to open the config file and returns a valid SilcConfigFile object
+ * or NULL if failed */
+
+SilcConfigFile *silc_config_open(char *configfile)
 {
   char *buffer;
   uint32 filelen;
+  SilcConfigFile *ret;
 
-  buffer = silc_file_readfile(filename, &filelen);
-  if (buffer == NULL)
-    return;
+  if (!(buffer = silc_file_readfile(configfile, &filelen)))
+    return NULL;
 
-  /* Buffer don't have EOF, but we'll need it. */
-  buffer[filelen] = EOF;
+  ret = (SilcConfigFile *) silc_calloc(1, sizeof(*ret));
+  ret->filename = strdup(configfile);
+  ret->base = ret->p = buffer;
+  ret->len = filelen;
+  ret->line = 1; /* line count, start from first line */
+  return ret;
+}
 
-  *ret_buffer = silc_buffer_alloc(filelen + 1);
-  silc_buffer_pull_tail(*ret_buffer, filelen + 1);
-  silc_buffer_put(*ret_buffer, buffer, filelen + 1);
+/* Frees a file object */
 
-  SILC_LOG_DEBUG(("Config file `%s' opened", filename));
+void silc_config_close(SilcConfigFile *file)
+{
+  if (file) {
+    /* XXX FIXME: this check could probably be removed later */
+    uint32 my_len = (uint32) (strchr(file->base, EOF) - file->base);
+    SILC_CONFIG_DEBUG(("file=0x%x name=\"%s\" level=%d line=%lu", (uint32) file,
+                       file->filename, file->level, file->line));
+    if (my_len != file->len) {
+      fprintf(stderr, "FATAL ERROR: saved len and current len does not match!\n");
+      abort();
+    }
+    silc_free(file->filename);
+    memset(file->base, 'F', file->len);
+    silc_free(file->base);
+    memset(file, 'F', sizeof(*file));
+    silc_free(file);
+  }
 }
 
-/* Returns next token from a buffer to the dest argument. Returns the
-   length of the token. This is used to take tokens from a configuration
-   line. */
+/* initializes a SilcConfigEntity pointer allocation */
+
+SilcConfigEntity silc_config_init(SilcConfigFile *file)
+{
+  SilcConfigEntity ret;
+  if (!file)
+    return NULL;
+  SILC_CONFIG_DEBUG(("Allocating new config entity"));
+  ret = (SilcConfigEntity) silc_calloc(1, sizeof(*ret));
+  ret->file = file;
+  return ret;
+};
+
+/* Returns the original filename of the object file */
 
-int silc_config_get_token(SilcBuffer buffer, char **dest)
+char *silc_config_get_filename(SilcConfigFile *file)
 {
+  if (file)
+    return file->filename;
+  return NULL;
+}
+
+/* Returns the current line that file parsing arrived at */
+
+uint32 silc_config_get_line(SilcConfigFile *file)
+{
+  if (file)
+    return file->line;
+  return 0;
+}
+
+/* Returns a pointer to the beginning of the requested line.  If the line
+ * was not found, NULL is returned */
+
+char *silc_config_read_line(SilcConfigFile *file, uint32 line)
+{
+  register char *p;
   int len;
+  char *ret, *endbuf;
 
-  if (strchr(buffer->data, ':')) {
-    len = strcspn(buffer->data, ":");
-    if (len) {
-      *dest = silc_calloc(len + 1, sizeof(char));
-      memcpy(*dest, buffer->data, len);
-    }
-    silc_buffer_pull(buffer, len + 1);
-    return len;
+  if (!file || (line <= 0))
+    return NULL;
+  for (p = file->base; *p && (*p != EOF); p++) {
+    if (line <= 1)
+      goto found;
+    if (*p == '\n')
+      line--;
   }
+  return NULL;
 
-  return -1;
+ found:
+  if ((endbuf = strchr(p, '\n'))) {
+    len = endbuf - p;
+    ret = silc_calloc(len, sizeof(*ret));
+    strncpy(ret, p, len);
+    ret[len] = '\0';
+  }
+  else {
+    ret = silc_calloc(strlen(p), sizeof(*ret));
+    strcpy(ret, p);
+  }
+  return ret;
 }
 
-/* Returns number of tokens in a buffer. */
+/* Convenience function to read the current parsed line */
 
-int silc_config_check_num_token(SilcBuffer buffer)
+char *silc_config_read_current_line(SilcConfigFile *file)
 {
-  int len, len2, num;
+  return silc_config_read_line(file, file->line);
+}
+
+/* (Private) destroy a SilcConfigEntity */
 
-  if (strchr(buffer->data, ':')) {
-    len = 0;
-    num = 0;
-    while (strchr(buffer->data + len, ':')) {
-      num++;
-      len2 = strcspn(buffer->data + len, ":") + 1;
-      len += len2;
+static void silc_config_destroy(SilcConfigEntity ent)
+{
+  SilcConfigOption *oldopt, *nextopt;
+  SILC_CONFIG_DEBUG(("Freeing config entity [ent=0x%x] [opts=0x%x]",
+                       (uint32) ent, (uint32) ent->opts));
+  for (oldopt = ent->opts; oldopt; oldopt = nextopt) {
+    nextopt = oldopt->next;
+    memset(oldopt->name, 'F', strlen(oldopt->name) + 1);
+    silc_free(oldopt->name);
+    memset(oldopt, 'F', sizeof(*oldopt));
+    silc_free(oldopt);
+  }
+  memset(ent, 'F', sizeof(*ent));
+  silc_free(ent);
+}
+
+/* Registers a new option in the specified entity */
+
+void silc_config_register(SilcConfigEntity ent, const char *name,
+                         SilcConfigType type, SilcConfigCallback cb,
+                         const SilcConfigTable *subtable, void *context)
+{
+  SilcConfigOption *newopt;
+  SILC_CONFIG_DEBUG(("Register new option=\"%s\" type=%u cb=0x%08x context=0x%08x",
+               name, type, (uint32) cb, (uint32) context));
+
+  if (!ent || !name)
+    return;
+  /* if we are registering a block, make sure there is a specified sub-table */
+  if ((type == SILC_CONFIG_ARG_BLOCK) && !subtable)
+    return;
+  /* refuse special tag */
+  if (!strcasecmp(name, "include"))
+    return;
+  if (silc_config_find_option(ent, name)) {
+    fprintf(stderr, "Internal Error: Option double registered\n");
+    abort();
+  }
+
+  /* allocate and append the new option */
+  newopt = (SilcConfigOption *) silc_calloc(1, sizeof(*newopt));
+  newopt->name = strdup(name);
+  newopt->type = type;
+  newopt->cb = cb;
+  newopt->subtable = subtable;
+  newopt->context = context;
+
+  if (!ent->opts)
+    ent->opts = newopt;
+  else {
+    SilcConfigOption *tmp;
+    for (tmp = ent->opts; tmp->next; tmp = tmp->next);
+    tmp->next = newopt;
+  }
+}
+
+/* Register a new option table in the specified config entity */
+
+void silc_config_register_table(SilcConfigEntity ent,
+                               const SilcConfigTable table[], void *context)
+{
+  int i;
+  if (!ent || !table) return;
+  SILC_CONFIG_DEBUG(("Registering table"));
+  /* FIXME: some potability checks needed */
+  for (i = 0; table[i].name; i++) {
+    silc_config_register(ent, table[i].name, table[i].type,
+                        table[i].callback, table[i].subtable, context);
+  }
+}
+
+/* ... */
+
+static int silc_config_main_internal(SilcConfigEntity ent)
+{
+  SilcConfigFile *file = ent->file;
+  char **p = &file->p;
+
+  /* loop throught statements */
+  while (1) {
+    char buf[255];
+    SilcConfigOption *thisopt;
+
+    /* makes it pointing to the next interesting char */
+    my_skip_comments(file);
+    /* got eof? */
+    if (**p == '\0' || **p == EOF) {
+      if (file->level > 1) /* cannot get eof in a sub-level! */
+       return SILC_CONFIG_EEXPECTED;
+      goto finish;
+    }
+    /* check if we completed this (sub) section (it doesn't matter if this
+     * is the main section) */
+    if (**p == '}') {
+      if (file->level < 2) /* can't be! must be at least one sub-block */
+       return SILC_CONFIG_EUNEXPECTED;
+      (*p)++;
+      goto finish;
+    }
+    //SILC_LOG_HEXDUMP(("Preparing lookup at line=%lu", file->line), *p, 16);
+
+    /* obtain the keyword */
+    my_next_token(file, buf);
+    SILC_CONFIG_DEBUG(("Looking up keyword=\"%s\" [line=%lu]", buf, file->line));
+
+    /* handle special directive */
+    if (!strcasecmp(buf, "include")) {
+      int ret;
+      SilcConfigFile *inc_file;
+      SilcConfigEntity inc_ent;
+
+      my_trim_spaces(file); /* prepare next char */
+
+      /* Now trying to include the specified file.  The included file will
+       * be allowed to include sub-files but it will preserve the block-level
+       * of the including block. Note that the included file won't be allowed
+       * to raise the block level of the including block. */
+
+      my_get_string(file, buf); /* get the filename */
+      SILC_LOG_DEBUG(("Including file \"%s\"", buf));
+      /* before getting on, check if this row is REALLY complete */
+      if (*(*p)++ != ';')
+       return SILC_CONFIG_EMISSCOLON;
+
+      /* open the file and start the parsing */
+      inc_file = silc_config_open(buf);
+      if (!inc_file) /* does it point a valid filename? */
+        return SILC_CONFIG_ECANTOPEN;
+      inc_file->included = TRUE;
+
+      /* create a new entity and hack it to use the same options */
+      inc_ent = silc_config_init(inc_file);
+      inc_ent->opts = ent->opts;
+      ret = silc_config_main(inc_ent);
+
+      /* Cleanup.
+       * If the included file returned an error, the application will probably
+       * want to output some kind of error message. Because of this, we can't
+       * destroy THIS file object. The hack works this way: The application
+       * expects to destroy the originally created object file, so we'll swap
+       * the original file with the included file. */
+      if (ret) {
+        SilcConfigFile tmp_file;
+        SILC_CONFIG_DEBUG(("SWAPPING FILE OBJECTS"));
+        memcpy(&tmp_file, inc_file, sizeof(tmp_file));
+        memcpy(inc_file, file, sizeof(tmp_file));
+        silc_config_close(inc_file);
+        memcpy(file, &tmp_file, sizeof(tmp_file));
+        return ret;
+      }
+      /* otherwise if no errors encoured, continue normally */
+      silc_config_close(inc_file);
+      continue; /* this one is handled */
     }
 
-    return num;
+    /* we have a registered option (it can also be a sub-block) */
+    thisopt = silc_config_find_option(ent, buf);
+    if (!thisopt)
+      return SILC_CONFIG_EBADOPTION;
+
+    my_trim_spaces(file); /* prepare next char */
+
+    /* option type is a block? */
+    if (thisopt->type == SILC_CONFIG_ARG_BLOCK) {
+      int ret;
+      SilcConfigEntity sub_ent;
+
+      SILC_CONFIG_DEBUG(("Entering sub-block"));
+      if (*(*p)++ != '{')
+       return SILC_CONFIG_EOPENBRACE;
+      /* build the new entity for this sub-block */
+      sub_ent = silc_config_init(ent->file);
+      /* use the previous specified table to describe this block's options */
+      silc_config_register_table(sub_ent, thisopt->subtable, thisopt->context);
+      /* run this block! */
+      ret = silc_config_main(sub_ent);
+      SILC_CONFIG_DEBUG(("Returned from sub-block [ret=%d]", ret));
+
+      if (ret) /* now check the result */
+       return ret;
+
+      /* now call block clean-up callback (if any) */
+      if (thisopt->cb) {
+       SILC_CONFIG_DEBUG(("Now calling clean-up callback (if any)"));
+       thisopt->cb(thisopt->type, thisopt->name, file->line,
+                   NULL, thisopt->context);
+      }
+      /* Do we want ';' to be mandatory after close brace? */
+      if (*(*p)++ != ';')
+       return SILC_CONFIG_EMISSCOLON;
+    }
+    else if (thisopt->type == SILC_CONFIG_ARG_NONE) {
+      /* before getting on, check if this row is REALLY complete */
+      if (*(*p)++ != ';')
+       return SILC_CONFIG_EMISSCOLON;
+      SILC_CONFIG_DEBUG(("Triggering callback for none"));
+      if (thisopt->cb) {
+       thisopt->cb(thisopt->type, thisopt->name, file->line,
+                   NULL, thisopt->context);
+      }
+    }
+    else {
+      void *pt;
+      int ret;
+
+      if (*(*p)++ != '=')
+       return SILC_CONFIG_EEXPECTEDEQUAL;
+
+      my_get_string(file, buf); /* get the option argument */
+      SILC_CONFIG_DEBUG(("With argument=\"%s\"", buf));
+
+      /* before getting on, check if this row is REALLY complete */
+      if (*(*p)++ != ';')
+       return SILC_CONFIG_EMISSCOLON;
+
+      /* convert the option argument to the right format */
+      pt = silc_config_marshall(thisopt->type, buf);
+      if (!pt)
+       return SILC_CONFIG_EINVALIDTEXT;
+      if (thisopt->cb) {
+       ret = thisopt->cb(thisopt->type, thisopt->name, file->line,
+                         pt, thisopt->context);
+       if (ret) {
+         SILC_CONFIG_DEBUG(("Callback refused the value [ret=%d]", ret));
+         return ret;
+       }
+      }
+      silc_free(pt);
+    }
+    continue;
+
+ finish:
+    break;
   }
 
-  return 0;
+  return SILC_CONFIG_OK;
+}
+
+/* ... */
+
+int silc_config_main(SilcConfigEntity ent)
+{
+  SilcConfigFile *file = ent->file;
+  int ret;
+
+  /* don't silently accept a NULL entity */
+  if (!ent) {
+    ret = SILC_CONFIG_EGENERIC;
+    goto main_cleanup;
+  }
+
+  /* call the real main and store the result */
+  file->level++;
+  SILC_CONFIG_DEBUG(("[Lev=%d] Entering config parsing core", file->level));
+  ret = silc_config_main_internal(ent);
+  SILC_CONFIG_DEBUG(("[Lev=%d] Quitting main [ret=%d]", file->level, ret));
+  if (!file->level) /* when swap happens, we could close a file twice */
+    goto main_end;
+  file->level--;
+
+  /* If this file was included don't destroy the options set because it is
+   * the same of the including block. Although if this entity is in a
+   * sub-block created inside the included file, this options set must be
+   * destroyed. */
+ main_cleanup:
+  if ((file->level != 0) || (file->included != TRUE))
+    silc_config_destroy(ent);
+
+ main_end:
+  return ret;
 }