/*

  serverconfig.c

  Author: Giovanni Giacobbi <giovanni@giacobbi.net>

  Copyright (C) 1997 - 2003 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
  GNU General Public License for more details.

*/
/* $Id: serverconfig.c,v 1.68.2.12 2003/11/08 11:10:05 priikone Exp $ */

#include "serverincludes.h"
#include "server_internal.h"
#include <dirent.h>

#if 0
#define SERVER_CONFIG_DEBUG(fmt) SILC_LOG_DEBUG(fmt)
#else
#define SERVER_CONFIG_DEBUG(fmt)
#endif

/* auto-declare needed variables for the common list parsing */
#define SILC_SERVER_CONFIG_SECTION_INIT(__type__)			\
  SilcServerConfig config = (SilcServerConfig) context;			\
  __type__ *findtmp, *tmp = (__type__ *) config->tmp;			\
  int got_errno = 0

/* allocate the tmp field for fetching data */
#define SILC_SERVER_CONFIG_ALLOCTMP(__type__)				\
  if (!tmp) {								\
    config->tmp = silc_calloc(1, sizeof(*findtmp));			\
    tmp = (__type__ *) config->tmp;					\
  }

/* append the tmp field to the specified list */
#define SILC_SERVER_CONFIG_LIST_APPENDTMP(__list__)			\
  if (!__list__) {							\
    __list__ = tmp;							\
  } else {								\
    for (findtmp = __list__; findtmp->next; findtmp = findtmp->next);	\
    findtmp->next = tmp;						\
  }

/* loops all elements in a list and provides a di struct pointer of the
 * specified type containing the current element */
#define SILC_SERVER_CONFIG_LIST_DESTROY(__type__, __list__)		\
  for (tmp = (void *) __list__; tmp;) {					\
    __type__ *di = (__type__ *) tmp;					\
    tmp = (void *) di->next;

/* Set EDOUBLE error value and bail out if necessary */
#define CONFIG_IS_DOUBLE(__x__)						\
  if ((__x__)) {							\
    got_errno = SILC_CONFIG_EDOUBLE; 					\
    goto got_err;							\
  }

/* Free the authentication fields in the specified struct
 * Expands to two instructions */
#define CONFIG_FREE_AUTH(__section__)			\
  silc_free(__section__->passphrase);			\
  if (__section__->publickeys)				\
    silc_hash_table_free(__section__->publickeys);

static void my_free_public_key(void *key, void *context, void *user_data)
{
  silc_pkcs_public_key_free(context);
}

/* Set default values to those parameters that have not been defined */
static void
my_set_param_defaults(SilcServerConfigConnParams *params,
		      SilcServerConfigConnParams *defaults)
{
#define SET_PARAM_DEFAULT(p, d)	params->p =				\
  (params->p ? params->p : (defaults && defaults->p ? defaults->p : d))

  SET_PARAM_DEFAULT(connections_max, SILC_SERVER_MAX_CONNECTIONS);
  SET_PARAM_DEFAULT(connections_max_per_host,
		    SILC_SERVER_MAX_CONNECTIONS_SINGLE);
  SET_PARAM_DEFAULT(keepalive_secs, SILC_SERVER_KEEPALIVE);
  SET_PARAM_DEFAULT(reconnect_count, SILC_SERVER_RETRY_COUNT);
  SET_PARAM_DEFAULT(reconnect_interval, SILC_SERVER_RETRY_INTERVAL_MIN);
  SET_PARAM_DEFAULT(reconnect_interval_max, SILC_SERVER_RETRY_INTERVAL_MAX);
  SET_PARAM_DEFAULT(key_exchange_rekey, SILC_SERVER_REKEY);
  SET_PARAM_DEFAULT(qos_rate_limit, SILC_SERVER_QOS_RATE_LIMIT);
  SET_PARAM_DEFAULT(qos_bytes_limit, SILC_SERVER_QOS_BYTES_LIMIT);
  SET_PARAM_DEFAULT(qos_limit_sec, SILC_SERVER_QOS_LIMIT_SEC);
  SET_PARAM_DEFAULT(qos_limit_usec, SILC_SERVER_QOS_LIMIT_USEC);

#undef SET_PARAM_DEFAULT
}

/* Find connection parameters by the parameter block name. */
static SilcServerConfigConnParams *
my_find_param(SilcServerConfig config, const char *name)
{
  SilcServerConfigConnParams *param;

  for (param = config->conn_params; param; param = param->next) {
    if (!strcasecmp(param->name, name))
      return param;
  }

  SILC_SERVER_LOG_ERROR(("Error while parsing config file: "
			 "Cannot find Params \"%s\".", name));

  return NULL;
}

/* parse an authdata according to its auth method */
static bool my_parse_authdata(SilcAuthMethod auth_meth, const char *p,
			      void **auth_data, SilcUInt32 *auth_data_len)
{
  if (auth_meth == SILC_AUTH_PASSWORD) {
    /* p is a plain text password */
    if (auth_data && auth_data_len) {
      if (!silc_utf8_valid(p, strlen(p))) {
	*auth_data_len = silc_utf8_encoded_len(p, strlen(p),
					       SILC_STRING_LANGUAGE);
	*auth_data = silc_calloc(*auth_data_len, sizeof(unsigned char));
	silc_utf8_encode(p, strlen(p), SILC_STRING_LANGUAGE, *auth_data,
			 *auth_data_len);
      } else {
	*auth_data = (void *) strdup(p);
	*auth_data_len = (SilcUInt32) strlen(p);
      }
    }
  } else if (auth_meth == SILC_AUTH_PUBLIC_KEY) {
    /* p is a public key file name */
    SilcPublicKey public_key;
    SilcPublicKey cached_key;

    if (!silc_pkcs_load_public_key(p, &public_key, SILC_PKCS_FILE_PEM))
      if (!silc_pkcs_load_public_key(p, &public_key, SILC_PKCS_FILE_BIN)) {
	SILC_SERVER_LOG_ERROR(("Error while parsing config file: "
			       "Could not load public key file!"));
	return FALSE;
      }

    if (*auth_data &&
	silc_hash_table_find_ext(*auth_data, public_key, (void *)&cached_key,
				 NULL, silc_hash_public_key, NULL,
				 silc_hash_public_key_compare, NULL)) {
      silc_pkcs_public_key_free(public_key);
      SILC_SERVER_LOG_WARNING(("Warning: public key file \"%s\" already "
			       "configured, ignoring this key", p));
      return TRUE; /* non fatal error */
    }

    /* The auth_data is a pointer to the hash table of public keys. */
    if (auth_data) {
      if (*auth_data == NULL)
	*auth_data = silc_hash_table_alloc(1, silc_hash_public_key, NULL,
					   NULL, NULL,
					   my_free_public_key, NULL,
					   TRUE);
      silc_hash_table_add(*auth_data, public_key, public_key);
    }
  } else
    abort();

  return TRUE;
}

static bool my_parse_publickeydir(const char *dirname, void **auth_data)
{
  int total = 0;
  struct dirent *get_file;
  DIR *dp;

  if (!(dp = opendir(dirname))) {
    SILC_SERVER_LOG_ERROR(("Error while parsing config file: "
			   "Could not open directory \"%s\"", dirname));
    return FALSE;
  }

  /* errors are not considered fatal */
  while ((get_file = readdir(dp))) {
    const char *filename = get_file->d_name;
    char buf[1024];
    int dirname_len = strlen(dirname), filename_len = strlen(filename);
    struct stat check_file;

    /* Ignore "." and "..", and take files only with ".pub" suffix. */
    if (!strcmp(filename, ".") || !strcmp(filename, "..") ||
	(filename_len < 5) || strcmp(filename + filename_len - 4, ".pub"))
      continue;

    memset(buf, 0, sizeof(buf));
    snprintf(buf, sizeof(buf) - 1, "%s%s%s", dirname,
	     (dirname[dirname_len - 1] == '/' ? "" : "/"), filename);

    if (stat(buf, &check_file) < 0) {
      SILC_SERVER_LOG_ERROR(("Error stating file %s: %s", buf,
			     strerror(errno)));
    } else if (S_ISREG(check_file.st_mode)) {
      my_parse_authdata(SILC_AUTH_PUBLIC_KEY, buf, auth_data, NULL);
      total++;
    }
  }

  SILC_LOG_DEBUG(("Tried to load %d public keys in \"%s\"", total, dirname));
  return TRUE;
}

/* Callbacks */

SILC_CONFIG_CALLBACK(fetch_generic)
{
  SilcServerConfig config = (SilcServerConfig) context;
  int got_errno = 0;

  if (!strcmp(name, "module_path")) {
    CONFIG_IS_DOUBLE(config->module_path);
    config->module_path = (*(char *)val ? strdup((char *) val) : NULL);
  }
  else if (!strcmp(name, "prefer_passphrase_auth")) {
    config->prefer_passphrase_auth = *(bool *)val;
  }
  else if (!strcmp(name, "require_reverse_lookup")) {
    config->require_reverse_lookup = *(bool *)val;
  }
  else if (!strcmp(name, "connections_max")) {
    config->param.connections_max = (SilcUInt32) *(int *)val;
  }
  else if (!strcmp(name, "connections_max_per_host")) {
    config->param.connections_max_per_host = (SilcUInt32) *(int *)val;
  }
  else if (!strcmp(name, "keepalive_secs")) {
    config->param.keepalive_secs = (SilcUInt32) *(int *)val;
  }
  else if (!strcmp(name, "reconnect_count")) {
    config->param.reconnect_count = (SilcUInt32) *(int *)val;
  }
  else if (!strcmp(name, "reconnect_interval")) {
    config->param.reconnect_interval = (SilcUInt32) *(int *)val;
  }
  else if (!strcmp(name, "reconnect_interval_max")) {
    config->param.reconnect_interval_max = (SilcUInt32) *(int *)val;
  }
  else if (!strcmp(name, "reconnect_keep_trying")) {
    config->param.reconnect_keep_trying = *(bool *)val;
  }
  else if (!strcmp(name, "key_exchange_rekey")) {
    config->param.key_exchange_rekey = (SilcUInt32) *(int *)val;
  }
  else if (!strcmp(name, "key_exchange_pfs")) {
    config->param.key_exchange_pfs = *(bool *)val;
  }
  else if (!strcmp(name, "channel_rekey_secs")) {
    config->channel_rekey_secs = (SilcUInt32) *(int *)val;
  }
  else if (!strcmp(name, "key_exchange_timeout")) {
    config->key_exchange_timeout = (SilcUInt32) *(int *)val;
  }
  else if (!strcmp(name, "conn_auth_timeout")) {
    config->conn_auth_timeout = (SilcUInt32) *(int *)val;
  }
  else if (!strcmp(name, "version_protocol")) {
    CONFIG_IS_DOUBLE(config->param.version_protocol);
    config->param.version_protocol =
      (*(char *)val ? strdup((char *) val) : NULL);
  }
  else if (!strcmp(name, "version_software")) {
    CONFIG_IS_DOUBLE(config->param.version_software);
    config->param.version_software =
      (*(char *)val ? strdup((char *) val) : NULL);
  }
  else if (!strcmp(name, "version_software_vendor")) {
    CONFIG_IS_DOUBLE(config->param.version_software_vendor);;
    config->param.version_software_vendor =
      (*(char *)val ? strdup((char *) val) : NULL);
  }
  else if (!strcmp(name, "detach_disabled")) {
    config->detach_disabled = *(bool *)val;
  }
  else if (!strcmp(name, "detach_timeout")) {
    config->detach_timeout = (SilcUInt32) *(int *)val;
  }
  else if (!strcmp(name, "qos")) {
    config->param.qos = *(bool *)val;
  }
  else if (!strcmp(name, "qos_rate_limit")) {
    config->param.qos_rate_limit = *(SilcUInt32 *)val;
  }
  else if (!strcmp(name, "qos_bytes_limit")) {
    config->param.qos_bytes_limit = *(SilcUInt32 *)val;
  }
  else if (!strcmp(name, "qos_limit_sec")) {
    config->param.qos_limit_sec = *(SilcUInt32 *)val;
  }
  else if (!strcmp(name, "qos_limit_usec")) {
    config->param.qos_limit_usec = *(SilcUInt32 *)val;
  }
  else if (!strcmp(name, "debug_string")) {
    CONFIG_IS_DOUBLE(config->debug_string);
    config->debug_string = (*(char *)val ? strdup((char *) val) : NULL);
  }
  else
    return SILC_CONFIG_EINTERNAL;

  return SILC_CONFIG_OK;

 got_err:
  return got_errno;
}

SILC_CONFIG_CALLBACK(fetch_cipher)
{
  SILC_SERVER_CONFIG_SECTION_INIT(SilcServerConfigCipher);

  SERVER_CONFIG_DEBUG(("Received CIPHER type=%d name=\"%s\" (val=%x)",
		       type, name, context));
  if (type == SILC_CONFIG_ARG_BLOCK) {
    /* check the temporary struct's fields */
    if (!tmp) /* discard empty sub-blocks */
      return SILC_CONFIG_OK;
    if (!tmp->name) {
      got_errno = SILC_CONFIG_EMISSFIELDS;
      goto got_err;
    }

    SILC_SERVER_CONFIG_LIST_APPENDTMP(config->cipher);
    config->tmp = NULL;
    return SILC_CONFIG_OK;
  }
  SILC_SERVER_CONFIG_ALLOCTMP(SilcServerConfigCipher);

  /* Identify and save this value */
  if (!strcmp(name, "name")) {
    CONFIG_IS_DOUBLE(tmp->name);
    tmp->name = strdup((char *) val);
  }
  else if (!strcmp(name, "module")) {
    CONFIG_IS_DOUBLE(tmp->module);
    tmp->module = (*(char *)val ? strdup((char *) val) : NULL);
  }
  else if (!strcmp(name, "keylength")) {
    tmp->key_length = *(SilcUInt32 *)val;
  }
  else if (!strcmp(name, "blocklength")) {
    tmp->block_length = *(SilcUInt32 *)val;
  }
  else
    return SILC_CONFIG_EINTERNAL;
  return SILC_CONFIG_OK;

 got_err:
  silc_free(tmp->name);
  silc_free(tmp->module);
  silc_free(tmp);
  config->tmp = NULL;
  return got_errno;
}

SILC_CONFIG_CALLBACK(fetch_hash)
{
  SILC_SERVER_CONFIG_SECTION_INIT(SilcServerConfigHash);

  SERVER_CONFIG_DEBUG(("Received HASH type=%d name=%s (val=%x)",
		       type, name, context));
  if (type == SILC_CONFIG_ARG_BLOCK) {
    /* check the temporary struct's fields */
    if (!tmp) /* discard empty sub-blocks */
      return SILC_CONFIG_OK;
    if (!tmp->name || (tmp->block_length == 0) || (tmp->digest_length == 0)) {
      got_errno = SILC_CONFIG_EMISSFIELDS;
      goto got_err;
    }

    SILC_SERVER_CONFIG_LIST_APPENDTMP(config->hash);
    config->tmp = NULL;
    return SILC_CONFIG_OK;
  }
  SILC_SERVER_CONFIG_ALLOCTMP(SilcServerConfigHash);

  /* Identify and save this value */
  if (!strcmp(name, "name")) {
    CONFIG_IS_DOUBLE(tmp->name);
    tmp->name = strdup((char *) val);
  }
  else if (!strcmp(name, "module")) {
    CONFIG_IS_DOUBLE(tmp->module);
    tmp->module = (*(char *)val ? strdup((char *) val) : NULL);
  }
  else if (!strcmp(name, "blocklength")) {
    tmp->block_length = *(int *)val;
  }
  else if (!strcmp(name, "digestlength")) {
    tmp->digest_length = *(int *)val;
  }
  else
    return SILC_CONFIG_EINTERNAL;
  return SILC_CONFIG_OK;

 got_err:
  silc_free(tmp->name);
  silc_free(tmp->module);
  silc_free(tmp);
  config->tmp = NULL;
  return got_errno;
}

SILC_CONFIG_CALLBACK(fetch_hmac)
{
  SILC_SERVER_CONFIG_SECTION_INIT(SilcServerConfigHmac);

  SERVER_CONFIG_DEBUG(("Received HMAC type=%d name=\"%s\" (val=%x)",
		       type, name, context));
  if (type == SILC_CONFIG_ARG_BLOCK) {
    /* check the temporary struct's fields */
    if (!tmp) /* discard empty sub-blocks */
      return SILC_CONFIG_OK;
    if (!tmp->name || !tmp->hash || (tmp->mac_length == 0)) {
      got_errno = SILC_CONFIG_EMISSFIELDS;
      goto got_err;
    }

    SILC_SERVER_CONFIG_LIST_APPENDTMP(config->hmac);
    config->tmp = NULL;
    return SILC_CONFIG_OK;
  }
  SILC_SERVER_CONFIG_ALLOCTMP(SilcServerConfigHmac);

  /* Identify and save this value */
  if (!strcmp(name, "name")) {
    CONFIG_IS_DOUBLE(tmp->name);
    tmp->name = strdup((char *) val);
  }
  else if (!strcmp(name, "hash")) {
    CONFIG_IS_DOUBLE(tmp->hash);
    tmp->hash = strdup((char *) val);
  }
  else if (!strcmp(name, "maclength")) {
    tmp->mac_length = *(int *)val;
  }
  else
    return SILC_CONFIG_EINTERNAL;
  return SILC_CONFIG_OK;

 got_err:
  silc_free(tmp->name);
  silc_free(tmp->hash);
  silc_free(tmp);
  config->tmp = NULL;
  return got_errno;
}

SILC_CONFIG_CALLBACK(fetch_pkcs)
{
  SILC_SERVER_CONFIG_SECTION_INIT(SilcServerConfigPkcs);

  SERVER_CONFIG_DEBUG(("Received PKCS type=%d name=\"%s\" (val=%x)",
		       type, name, context));
  if (type == SILC_CONFIG_ARG_BLOCK) {
    /* Check the temporary struct's fields */
    if (!tmp) /* discard empty sub-blocks */
      return SILC_CONFIG_OK;
    if (!tmp->name) {
      got_errno = SILC_CONFIG_EMISSFIELDS;
      goto got_err;
    }

    SILC_SERVER_CONFIG_LIST_APPENDTMP(config->pkcs);
    config->tmp = NULL;
    return SILC_CONFIG_OK;
  }
  SILC_SERVER_CONFIG_ALLOCTMP(SilcServerConfigPkcs);

  /* Identify and save this value */
  if (!strcmp(name, "name")) {
    CONFIG_IS_DOUBLE(tmp->name);
    tmp->name = strdup((char *) val);
  }
  else
    return SILC_CONFIG_EINTERNAL;
  return SILC_CONFIG_OK;

 got_err:
  silc_free(tmp->name);
  silc_free(tmp);
  config->tmp = NULL;
  return got_errno;
}

SILC_CONFIG_CALLBACK(fetch_serverinfo)
{
  SILC_SERVER_CONFIG_SECTION_INIT(SilcServerConfigServerInfoInterface);
  SilcServerConfigServerInfo *server_info = config->server_info;

  SERVER_CONFIG_DEBUG(("Received SERVERINFO type=%d name=\"%s\" (val=%x)",
		       type, name, context));

  /* If there isn't the main struct alloc it */
  if (!server_info)
    config->server_info = server_info = (SilcServerConfigServerInfo *)
		silc_calloc(1, sizeof(*server_info));

  if (type == SILC_CONFIG_ARG_BLOCK) {
    if (!strcmp(name, "primary")) {
      if (server_info->primary) {
	SILC_SERVER_LOG_ERROR(("Error while parsing config file: "
			       "Double primary specification."));
	got_errno = SILC_CONFIG_EPRINTLINE;
	goto got_err;
      }
      CONFIG_IS_DOUBLE(server_info->primary);

      /* now check the temporary struct, don't accept empty block and
         make sure all fields are there */
      if (!tmp || !tmp->server_ip || !tmp->port) {
	got_errno = SILC_CONFIG_EMISSFIELDS;
	goto got_err;
      }
      server_info->primary = tmp;
      config->tmp = NULL;
      return SILC_CONFIG_OK;
    } else if (!strcmp(name, "secondary")) {
      if (!tmp)
	return SILC_CONFIG_OK;
      if (!tmp || !tmp->server_ip || !tmp->port) {
	got_errno = SILC_CONFIG_EMISSFIELDS;
	goto got_err;
      }
      SILC_SERVER_CONFIG_LIST_APPENDTMP(server_info->secondary);
      config->tmp = NULL;
      return SILC_CONFIG_OK;
    } else if (!server_info->public_key || !server_info->private_key) {
      got_errno = SILC_CONFIG_EMISSFIELDS;
      goto got_err;
    }
    return SILC_CONFIG_OK;
  }
  if (!strcmp(name, "hostname")) {
    CONFIG_IS_DOUBLE(server_info->server_name);
    server_info->server_name = strdup((char *) val);
  }
  else if (!strcmp(name, "ip")) {
    SILC_SERVER_CONFIG_ALLOCTMP(SilcServerConfigServerInfoInterface);
    CONFIG_IS_DOUBLE(tmp->server_ip);
    tmp->server_ip = strdup((char *) val);
  }
  else if (!strcmp(name, "port")) {
    int port = *(int *)val;
    SILC_SERVER_CONFIG_ALLOCTMP(SilcServerConfigServerInfoInterface);
    if ((port <= 0) || (port > 65535)) {
      SILC_SERVER_LOG_ERROR(("Error while parsing config file: "
			     "Invalid port number!"));
      got_errno = SILC_CONFIG_EPRINTLINE;
      goto got_err;
    }
    tmp->port = (SilcUInt16) port;
  }
  else if (!strcmp(name, "servertype")) {
    CONFIG_IS_DOUBLE(server_info->server_type);
    server_info->server_type = strdup((char *) val);
  }
  else if (!strcmp(name, "admin")) {
    CONFIG_IS_DOUBLE(server_info->admin);
    server_info->admin = strdup((char *) val);
  }
  else if (!strcmp(name, "adminemail")) {
    CONFIG_IS_DOUBLE(server_info->email);
    server_info->email = strdup((char *) val);
  }
  else if (!strcmp(name, "location")) {
    CONFIG_IS_DOUBLE(server_info->location);
    server_info->location = strdup((char *) val);
  }
  else if (!strcmp(name, "user")) {
    CONFIG_IS_DOUBLE(server_info->user);
    server_info->user = strdup((char *) val);
  }
  else if (!strcmp(name, "group")) {
    CONFIG_IS_DOUBLE(server_info->group);
    server_info->group = strdup((char *) val);
  }
  else if (!strcmp(name, "motdfile")) {
    CONFIG_IS_DOUBLE(server_info->motd_file);
    server_info->motd_file = strdup((char *) val);
  }
  else if (!strcmp(name, "pidfile")) {
    CONFIG_IS_DOUBLE(server_info->pid_file);
    server_info->pid_file = strdup((char *) val);
  }
  else if (!strcmp(name, "publickey")) {
    char *file_tmp = (char *) val;
    CONFIG_IS_DOUBLE(server_info->public_key);

    /* Try to load specified file, if fail stop config parsing */
    if (!silc_pkcs_load_public_key(file_tmp, &server_info->public_key,
				   SILC_PKCS_FILE_PEM))
      if (!silc_pkcs_load_public_key(file_tmp, &server_info->public_key,
				     SILC_PKCS_FILE_BIN)) {
	SILC_SERVER_LOG_ERROR(("Error: Could not load public key file."));
	return SILC_CONFIG_EPRINTLINE;
      }
  }
  else if (!strcmp(name, "privatekey")) {
    struct stat st;
    char *file_tmp = (char *) val;
    CONFIG_IS_DOUBLE(server_info->private_key);

    /* Check the private key file permissions. */
    if ((stat(file_tmp, &st)) != -1) {
      if ((st.st_mode & 0777) != 0600) {
	SILC_SERVER_LOG_ERROR(("Wrong permissions in private key "
			      "file \"%s\".  The permissions must be "
			      "0600.", file_tmp));
        return SILC_CONFIG_ESILENT;
      }
    }

    /* Try to load specified file, if fail stop config parsing */
    if (!silc_pkcs_load_private_key(file_tmp, &server_info->private_key,
				    "", 0, SILC_PKCS_FILE_BIN))
      if (!silc_pkcs_load_private_key(file_tmp, &server_info->private_key,
				      "", 0, SILC_PKCS_FILE_PEM)) {
	SILC_SERVER_LOG_ERROR(("Error: Could not load private key file."));
	return SILC_CONFIG_EPRINTLINE;
      }
  }
  else
    return SILC_CONFIG_EINTERNAL;
  return SILC_CONFIG_OK;

 got_err:
  /* Here we need to check if tmp exists because this function handles
   * misc data (multiple fields and single-only fields) */
  if (tmp) {
    silc_free(tmp->server_ip);
    silc_free(tmp);
    config->tmp = NULL;
  }
  return got_errno;
}

SILC_CONFIG_CALLBACK(fetch_logging)
{
  SILC_SERVER_CONFIG_SECTION_INIT(SilcServerConfigLogging);

  if (!strcmp(name, "timestamp")) {
    config->logging_timestamp = *(bool *)val;
  }
  else if (!strcmp(name, "quicklogs")) {
    config->logging_quick = *(bool *)val;
  }
  else if (!strcmp(name, "flushdelay")) {
    int flushdelay = *(int *)val;
    if (flushdelay < 2) { /* this value was taken from silclog.h (min delay) */
      SILC_SERVER_LOG_ERROR(("Error while parsing config file: "
			    "Invalid flushdelay value, use quicklogs if you "
			    "want real-time logging."));
      return SILC_CONFIG_EPRINTLINE;
    }
    config->logging_flushdelay = (long) flushdelay;
  }

  /* The following istances happens only in Logging's sub-blocks, a match
     for the sub-block name means that you should store the filename/maxsize
     temporary struct to the proper logging channel.
     If we get a match for "file" or "maxsize" this means that we are inside
     a sub-sub-block and it is safe to alloc a new tmp. */
#define FETCH_LOGGING_CHAN(__chan__, __member__)		\
  else if (!strcmp(name, __chan__)) {				\
    if (!tmp) return SILC_CONFIG_OK;				\
    if (!tmp->file) {						\
      got_errno = SILC_CONFIG_EMISSFIELDS; goto got_err;	\
    }								\
    config->__member__ = tmp;					\
    config->tmp = NULL;						\
  }
  FETCH_LOGGING_CHAN("info", logging_info)
  FETCH_LOGGING_CHAN("warnings", logging_warnings)
  FETCH_LOGGING_CHAN("errors", logging_errors)
  FETCH_LOGGING_CHAN("fatals", logging_fatals)
#undef FETCH_LOGGING_CHAN
  else if (!strcmp(name, "file")) {
    SILC_SERVER_CONFIG_ALLOCTMP(SilcServerConfigLogging);
    CONFIG_IS_DOUBLE(tmp->file);
    tmp->file = strdup((char *) val);
  }
  else if (!strcmp(name, "size")) {
    if (!tmp) {
      config->tmp = silc_calloc(1, sizeof(*tmp));
      tmp = (SilcServerConfigLogging *) config->tmp;
    }
    tmp->maxsize = *(SilcUInt32 *) val;
  }
  else
    return SILC_CONFIG_EINTERNAL;
  return SILC_CONFIG_OK;

 got_err:
  silc_free(tmp->file);
  silc_free(tmp);
  config->tmp = NULL;
  return got_errno;
}

SILC_CONFIG_CALLBACK(fetch_connparam)
{
  SILC_SERVER_CONFIG_SECTION_INIT(SilcServerConfigConnParams);

  SERVER_CONFIG_DEBUG(("Received CONNPARAM type=%d name=\"%s\" (val=%x)",
		       type, name, context));
  if (type == SILC_CONFIG_ARG_BLOCK) {
    /* check the temporary struct's fields */
    if (!tmp) /* discard empty sub-blocks */
      return SILC_CONFIG_OK;
    if (!tmp->name) {
      got_errno = SILC_CONFIG_EMISSFIELDS;
      goto got_err;
    }
    /* Set defaults */
    my_set_param_defaults(tmp, &config->param);

    SILC_SERVER_CONFIG_LIST_APPENDTMP(config->conn_params);
    config->tmp = NULL;
    return SILC_CONFIG_OK;
  }
  SILC_SERVER_CONFIG_ALLOCTMP(SilcServerConfigConnParams);

  if (!strcmp(name, "name")) {
    CONFIG_IS_DOUBLE(tmp->name);
    tmp->name = (*(char *)val ? strdup((char *) val) : NULL);
  }
  else if (!strcmp(name, "connections_max")) {
    tmp->connections_max = *(SilcUInt32 *)val;
  }
  else if (!strcmp(name, "connections_max_per_host")) {
    tmp->connections_max_per_host = *(SilcUInt32 *)val;
  }
  else if (!strcmp(name, "keepalive_secs")) {
    tmp->keepalive_secs = *(SilcUInt32 *)val;
  }
  else if (!strcmp(name, "reconnect_count")) {
    tmp->reconnect_count = *(SilcUInt32 *)val;
  }
  else if (!strcmp(name, "reconnect_interval")) {
    tmp->reconnect_interval = *(SilcUInt32 *)val;
  }
  else if (!strcmp(name, "reconnect_interval_max")) {
    tmp->reconnect_interval_max = *(SilcUInt32 *)val;
  }
  else if (!strcmp(name, "reconnect_keep_trying")) {
    tmp->reconnect_keep_trying = *(bool *)val;
  }
  else if (!strcmp(name, "key_exchange_rekey")) {
    tmp->key_exchange_rekey = *(SilcUInt32 *)val;
  }
  else if (!strcmp(name, "key_exchange_pfs")) {
    tmp->key_exchange_pfs = *(bool *)val;
  }
  else if (!strcmp(name, "version_protocol")) {
    CONFIG_IS_DOUBLE(tmp->version_protocol);
    tmp->version_protocol = (*(char *)val ? strdup((char *) val) : NULL);
  }
  else if (!strcmp(name, "version_software")) {
    CONFIG_IS_DOUBLE(tmp->version_software);
    tmp->version_software = (*(char *)val ? strdup((char *) val) : NULL);
  }
  else if (!strcmp(name, "version_software_vendor")) {
    CONFIG_IS_DOUBLE(tmp->version_software_vendor);;
    tmp->version_software_vendor =
      (*(char *)val ? strdup((char *) val) : NULL);
  }
  else if (!strcmp(name, "anonymous")) {
    tmp->anonymous = *(bool *)val;
  }
  else if (!strcmp(name, "qos")) {
    tmp->qos = *(bool *)val;
  }
  else if (!strcmp(name, "qos_rate_limit")) {
    tmp->qos_rate_limit = *(SilcUInt32 *)val;
  }
  else if (!strcmp(name, "qos_bytes_limit")) {
    tmp->qos_bytes_limit = *(SilcUInt32 *)val;
  }
  else if (!strcmp(name, "qos_limit_sec")) {
    tmp->qos_limit_sec = *(SilcUInt32 *)val;
  }
  else if (!strcmp(name, "qos_limit_usec")) {
    tmp->qos_limit_usec = *(SilcUInt32 *)val;
  }
  else
    return SILC_CONFIG_EINTERNAL;

  return SILC_CONFIG_OK;

 got_err:
  silc_free(tmp->name);
  silc_free(tmp);
  config->tmp = NULL;
  return got_errno;
}

SILC_CONFIG_CALLBACK(fetch_client)
{
  SILC_SERVER_CONFIG_SECTION_INIT(SilcServerConfigClient);

  SERVER_CONFIG_DEBUG(("Received CLIENT type=%d name=\"%s\" (val=%x)",
		       type, name, context));

  /* Alloc before block checking, because empty sub-blocks are welcome here */
  SILC_SERVER_CONFIG_ALLOCTMP(SilcServerConfigClient);

  if (type == SILC_CONFIG_ARG_BLOCK) {
    /* empty sub-blocks are welcome */
    SILC_SERVER_CONFIG_LIST_APPENDTMP(config->clients);
    config->tmp = NULL;
    return SILC_CONFIG_OK;
  }

  /* Identify and save this value */
  if (!strcmp(name, "host")) {
    CONFIG_IS_DOUBLE(tmp->host);
    tmp->host = (*(char *)val ? strdup((char *) val) : NULL);
  }
  else if (!strcmp(name, "passphrase")) {
    CONFIG_IS_DOUBLE(tmp->passphrase);
    if (!my_parse_authdata(SILC_AUTH_PASSWORD, (char *) val,
			   (void *)&tmp->passphrase,
			   &tmp->passphrase_len)) {
      got_errno = SILC_CONFIG_EPRINTLINE;
      goto got_err;
    }
  }
  else if (!strcmp(name, "publickey")) {
    if (!my_parse_authdata(SILC_AUTH_PUBLIC_KEY, (char *) val,
			   (void *)&tmp->publickeys, NULL)) {
      got_errno = SILC_CONFIG_EPRINTLINE;
      goto got_err;
    }
  }
  else if (!strcmp(name, "publickeydir")) {
    if (!my_parse_publickeydir((char *) val, (void *)&tmp->publickeys)) {
      got_errno = SILC_CONFIG_EPRINTLINE;
      goto got_err;
    }
  }
  else if (!strcmp(name, "params")) {
    CONFIG_IS_DOUBLE(tmp->param);
    tmp->param = my_find_param(config, (char *) val);
    if (!tmp->param) { /* error message already output */
      got_errno = SILC_CONFIG_EPRINTLINE;
      goto got_err;
    }
  }
  else
    return SILC_CONFIG_EINTERNAL;
  return SILC_CONFIG_OK;

 got_err:
  silc_free(tmp->host);
  CONFIG_FREE_AUTH(tmp);
  silc_free(tmp);
  config->tmp = NULL;
  return got_errno;
}

SILC_CONFIG_CALLBACK(fetch_admin)
{
  SILC_SERVER_CONFIG_SECTION_INIT(SilcServerConfigAdmin);

  SERVER_CONFIG_DEBUG(("Received CLIENT type=%d name=\"%s\" (val=%x)",
		       type, name, context));
  if (type == SILC_CONFIG_ARG_BLOCK) {
    /* check the temporary struct's fields */
    if (!tmp) /* discard empty sub-blocks */
      return SILC_CONFIG_OK;

    SILC_SERVER_CONFIG_LIST_APPENDTMP(config->admins);
    config->tmp = NULL;
    return SILC_CONFIG_OK;
  }
  SILC_SERVER_CONFIG_ALLOCTMP(SilcServerConfigAdmin);

  /* Identify and save this value */
  if (!strcmp(name, "host")) {
    CONFIG_IS_DOUBLE(tmp->host);
    tmp->host = (*(char *)val ? strdup((char *) val) : NULL);
  }
  else if (!strcmp(name, "user")) {
    CONFIG_IS_DOUBLE(tmp->user);
    tmp->user = (*(char *)val ? strdup((char *) val) : NULL);
  }
  else if (!strcmp(name, "nick")) {
    CONFIG_IS_DOUBLE(tmp->nick);
    tmp->nick = (*(char *)val ? strdup((char *) val) : NULL);
  }
  else if (!strcmp(name, "passphrase")) {
    CONFIG_IS_DOUBLE(tmp->passphrase);
    if (!my_parse_authdata(SILC_AUTH_PASSWORD, (char *) val,
			   (void *)&tmp->passphrase,
			   &tmp->passphrase_len)) {
      got_errno = SILC_CONFIG_EPRINTLINE;
      goto got_err;
    }
  }
  else if (!strcmp(name, "publickey")) {
    if (!my_parse_authdata(SILC_AUTH_PUBLIC_KEY, (char *) val,
			   (void *)&tmp->publickeys, NULL)) {
      got_errno = SILC_CONFIG_EPRINTLINE;
      goto got_err;
    }
  }
  else if (!strcmp(name, "publickeydir")) {
    if (!my_parse_publickeydir((char *) val, (void *)&tmp->publickeys)) {
      got_errno = SILC_CONFIG_EPRINTLINE;
      goto got_err;
    }
  }
  else
    return SILC_CONFIG_EINTERNAL;
  return SILC_CONFIG_OK;

 got_err:
  silc_free(tmp->host);
  silc_free(tmp->user);
  silc_free(tmp->nick);
  CONFIG_FREE_AUTH(tmp);
  silc_free(tmp);
  config->tmp = NULL;
  return got_errno;
}

SILC_CONFIG_CALLBACK(fetch_deny)
{
  SILC_SERVER_CONFIG_SECTION_INIT(SilcServerConfigDeny);

  SERVER_CONFIG_DEBUG(("Received DENY type=%d name=\"%s\" (val=%x)",
		       type, name, context));
  if (type == SILC_CONFIG_ARG_BLOCK) {
    /* check the temporary struct's fields */
    if (!tmp) /* discard empty sub-blocks */
      return SILC_CONFIG_OK;
    if (!tmp->reason) {
      got_errno = SILC_CONFIG_EMISSFIELDS;
      goto got_err;
    }

    SILC_SERVER_CONFIG_LIST_APPENDTMP(config->denied);
    config->tmp = NULL;
    return SILC_CONFIG_OK;
  }
  SILC_SERVER_CONFIG_ALLOCTMP(SilcServerConfigDeny);

  /* Identify and save this value */
  if (!strcmp(name, "host")) {
    CONFIG_IS_DOUBLE(tmp->host);
    tmp->host = (*(char *)val ? strdup((char *) val) : strdup("*"));
  }
  else if (!strcmp(name, "reason")) {
    CONFIG_IS_DOUBLE(tmp->reason);
    tmp->reason = strdup((char *) val);
  }
  else
    return SILC_CONFIG_EINTERNAL;
  return SILC_CONFIG_OK;

 got_err:
  silc_free(tmp->host);
  silc_free(tmp->reason);
  silc_free(tmp);
  config->tmp = NULL;
  return got_errno;
}

SILC_CONFIG_CALLBACK(fetch_server)
{
  SILC_SERVER_CONFIG_SECTION_INIT(SilcServerConfigServer);

  SERVER_CONFIG_DEBUG(("Received SERVER type=%d name=\"%s\" (val=%x)",
		       type, name, context));
  if (type == SILC_CONFIG_ARG_BLOCK) {
    /* check the temporary struct's fields */
    if (!tmp) /* discard empty sub-blocks */
      return SILC_CONFIG_OK;
    if (!tmp->host) {
      got_errno = SILC_CONFIG_EMISSFIELDS;
      goto got_err;
    }

    /* the temporary struct is ok, append it to the list */
    SILC_SERVER_CONFIG_LIST_APPENDTMP(config->servers);
    config->tmp = NULL;
    return SILC_CONFIG_OK;
  }
  SILC_SERVER_CONFIG_ALLOCTMP(SilcServerConfigServer);

  /* Identify and save this value */
  if (!strcmp(name, "host")) {
    CONFIG_IS_DOUBLE(tmp->host);
    tmp->host = (*(char *)val ? strdup((char *) val) : strdup("*"));
  }
  else if (!strcmp(name, "passphrase")) {
    CONFIG_IS_DOUBLE(tmp->passphrase);
    if (!my_parse_authdata(SILC_AUTH_PASSWORD, (char *) val,
			   (void *)&tmp->passphrase,
			   &tmp->passphrase_len)) {
      got_errno = SILC_CONFIG_EPRINTLINE;
      goto got_err;
    }
  }
  else if (!strcmp(name, "publickey")) {
    CONFIG_IS_DOUBLE(tmp->publickeys);
    if (!my_parse_authdata(SILC_AUTH_PUBLIC_KEY, (char *) val,
			   (void *)&tmp->publickeys, NULL)) {
      got_errno = SILC_CONFIG_EPRINTLINE;
      goto got_err;
    }
  }
  else if (!strcmp(name, "params")) {
    CONFIG_IS_DOUBLE(tmp->param);
    tmp->param = my_find_param(config, (char *) val);
    if (!tmp->param) { /* error message already output */
      got_errno = SILC_CONFIG_EPRINTLINE;
      goto got_err;
    }
  }
  else if (!strcmp(name, "backup")) {
    tmp->backup_router = *(bool *)val;
  }
  else
    return SILC_CONFIG_EINTERNAL;

  return SILC_CONFIG_OK;

 got_err:
  silc_free(tmp->host);
  CONFIG_FREE_AUTH(tmp);
  silc_free(tmp);
  config->tmp = NULL;
  return got_errno;
}

SILC_CONFIG_CALLBACK(fetch_router)
{
  SILC_SERVER_CONFIG_SECTION_INIT(SilcServerConfigRouter);

  SERVER_CONFIG_DEBUG(("Received ROUTER type=%d name=\"%s\" (val=%x)",
		       type, name, context));
  if (type == SILC_CONFIG_ARG_BLOCK) {
    if (!tmp) /* discard empty sub-blocks */
      return SILC_CONFIG_OK;
    if (!tmp->host) {
      got_errno = SILC_CONFIG_EMISSFIELDS;
      goto got_err;
    }

    SILC_SERVER_CONFIG_LIST_APPENDTMP(config->routers);
    config->tmp = NULL;
    return SILC_CONFIG_OK;
  }
  SILC_SERVER_CONFIG_ALLOCTMP(SilcServerConfigRouter);

  /* Identify and save this value */
  if (!strcmp(name, "host")) {
    CONFIG_IS_DOUBLE(tmp->host);
    tmp->host = strdup((char *) val);
  }
  else if (!strcmp(name, "port")) {
    int port = *(int *)val;
    if ((port <= 0) || (port > 65535)) {
      SILC_SERVER_LOG_ERROR(("Error while parsing config file: "
			     "Invalid port number!"));
      got_errno = SILC_CONFIG_EPRINTLINE;
      goto got_err;
    }
    tmp->port = (SilcUInt16) port;
  }
  else if (!strcmp(name, "passphrase")) {
    CONFIG_IS_DOUBLE(tmp->passphrase);
    if (!my_parse_authdata(SILC_AUTH_PASSWORD, (char *) val,
			   (void *)&tmp->passphrase,
			   &tmp->passphrase_len)) {
      got_errno = SILC_CONFIG_EPRINTLINE;
      goto got_err;
    }
  }
  else if (!strcmp(name, "publickey")) {
    CONFIG_IS_DOUBLE(tmp->publickeys);
    if (!my_parse_authdata(SILC_AUTH_PUBLIC_KEY, (char *) val,
			   (void *)&tmp->publickeys, NULL)) {
      got_errno = SILC_CONFIG_EPRINTLINE;
      goto got_err;
    }
  }
  else if (!strcmp(name, "params")) {
    CONFIG_IS_DOUBLE(tmp->param);
    tmp->param = my_find_param(config, (char *) val);
    if (!tmp->param) { /* error message already output */
      got_errno = SILC_CONFIG_EPRINTLINE;
      goto got_err;
    }
  }
  else if (!strcmp(name, "initiator")) {
    tmp->initiator = *(bool *)val;
  }
  else if (!strcmp(name, "backuphost")) {
    CONFIG_IS_DOUBLE(tmp->backup_replace_ip);
    tmp->backup_replace_ip = (*(char *)val ? strdup((char *) val) :
			      strdup("*"));
    tmp->backup_router = TRUE;
  }
  else if (!strcmp(name, "backupport")) {
    int port = *(int *)val;
    if ((port <= 0) || (port > 65535)) {
      SILC_SERVER_LOG_ERROR(("Error while parsing config file: "
			     "Invalid port number!"));
      got_errno = SILC_CONFIG_EPRINTLINE;
      goto got_err;
    }
    tmp->backup_replace_port = (SilcUInt16) port;
  }
  else if (!strcmp(name, "backuplocal")) {
    tmp->backup_local = *(bool *)val;
  }
  else
    return SILC_CONFIG_EINTERNAL;

  return SILC_CONFIG_OK;

 got_err:
  silc_free(tmp->host);
  silc_free(tmp->backup_replace_ip);
  CONFIG_FREE_AUTH(tmp);
  silc_free(tmp);
  config->tmp = NULL;
  return got_errno;
}

/* known config options tables */
static const SilcConfigTable table_general[] = {
  { "module_path",		SILC_CONFIG_ARG_STRE,	fetch_generic,	NULL },
  { "prefer_passphrase_auth",	SILC_CONFIG_ARG_TOGGLE,	fetch_generic,	NULL },
  { "require_reverse_lookup",	SILC_CONFIG_ARG_TOGGLE,	fetch_generic,	NULL },
  { "connections_max",		SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { "connections_max_per_host", SILC_CONFIG_ARG_INT,    fetch_generic,	NULL },
  { "keepalive_secs",		SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { "reconnect_count",		SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { "reconnect_interval",      	SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { "reconnect_interval_max",   SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { "reconnect_keep_trying",	SILC_CONFIG_ARG_TOGGLE,	fetch_generic,	NULL },
  { "key_exchange_rekey",	SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { "key_exchange_pfs",		SILC_CONFIG_ARG_TOGGLE,	fetch_generic,	NULL },
  { "channel_rekey_secs",	SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { "key_exchange_timeout",   	SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { "conn_auth_timeout",   	SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { "version_protocol",	        SILC_CONFIG_ARG_STR,	fetch_generic,	NULL },
  { "version_software",		SILC_CONFIG_ARG_STR,	fetch_generic,	NULL },
  { "version_software_vendor",	SILC_CONFIG_ARG_STR,	fetch_generic,	NULL },
  { "detach_disabled",    	SILC_CONFIG_ARG_TOGGLE,	fetch_generic,	NULL },
  { "detach_timeout",    	SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { "qos",    	                SILC_CONFIG_ARG_TOGGLE,	fetch_generic,	NULL },
  { "qos_rate_limit",    	SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { "qos_bytes_limit",    	SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { "qos_limit_sec",    	SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { "qos_limit_usec",    	SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { "debug_string",    		SILC_CONFIG_ARG_STR,	fetch_generic,	NULL },
  { 0, 0, 0, 0 }
};

static const SilcConfigTable table_cipher[] = {
  { "name",		SILC_CONFIG_ARG_STR,	fetch_cipher,	NULL },
  { "module",		SILC_CONFIG_ARG_STRE,	fetch_cipher,	NULL },
  { "keylength",	SILC_CONFIG_ARG_INT,	fetch_cipher,	NULL },
  { "blocklength",	SILC_CONFIG_ARG_INT,	fetch_cipher,	NULL },
  { 0, 0, 0, 0 }
};

static const SilcConfigTable table_hash[] = {
  { "name",		SILC_CONFIG_ARG_STR,	fetch_hash,	NULL },
  { "module",		SILC_CONFIG_ARG_STRE,	fetch_hash,	NULL },
  { "blocklength",	SILC_CONFIG_ARG_INT,	fetch_hash,	NULL },
  { "digestlength",	SILC_CONFIG_ARG_INT,	fetch_hash,	NULL },
  { 0, 0, 0, 0 }
};

static const SilcConfigTable table_hmac[] = {
  { "name",		SILC_CONFIG_ARG_STR,	fetch_hmac,	NULL },
  { "hash",		SILC_CONFIG_ARG_STR,	fetch_hmac,	NULL },
  { "maclength",	SILC_CONFIG_ARG_INT,	fetch_hmac,	NULL },
  { 0, 0, 0, 0 }
};

static const SilcConfigTable table_pkcs[] = {
  { "name",		SILC_CONFIG_ARG_STR,	fetch_pkcs,	NULL },
  { 0, 0, 0, 0 }
};

static const SilcConfigTable table_serverinfo_c[] = {
  { "ip",		SILC_CONFIG_ARG_STR,	fetch_serverinfo, NULL},
  { "port",		SILC_CONFIG_ARG_INT,	fetch_serverinfo, NULL},
  { 0, 0, 0, 0 }
};

static const SilcConfigTable table_serverinfo[] = {
  { "hostname",		SILC_CONFIG_ARG_STR,	fetch_serverinfo, NULL},
  { "primary",		SILC_CONFIG_ARG_BLOCK,	fetch_serverinfo, table_serverinfo_c},
  { "secondary",	SILC_CONFIG_ARG_BLOCK,	fetch_serverinfo, table_serverinfo_c},
  { "servertype",	SILC_CONFIG_ARG_STR,	fetch_serverinfo, NULL},
  { "location",		SILC_CONFIG_ARG_STR,	fetch_serverinfo, NULL},
  { "admin",		SILC_CONFIG_ARG_STR,	fetch_serverinfo, NULL},
  { "adminemail",	SILC_CONFIG_ARG_STR,	fetch_serverinfo, NULL},
  { "user",		SILC_CONFIG_ARG_STR,	fetch_serverinfo, NULL},
  { "group",		SILC_CONFIG_ARG_STR,	fetch_serverinfo, NULL},
  { "publickey",	SILC_CONFIG_ARG_STR,	fetch_serverinfo, NULL},
  { "privatekey",	SILC_CONFIG_ARG_STR,	fetch_serverinfo, NULL},
  { "motdfile",		SILC_CONFIG_ARG_STRE,	fetch_serverinfo, NULL},
  { "pidfile",		SILC_CONFIG_ARG_STRE,	fetch_serverinfo, NULL},
  { 0, 0, 0, 0 }
};

static const SilcConfigTable table_logging_c[] = {
  { "file",		SILC_CONFIG_ARG_STR,	fetch_logging,	NULL },
  { "size",		SILC_CONFIG_ARG_SIZE,	fetch_logging,	NULL },
/*{ "quicklog",		SILC_CONFIG_ARG_NONE,	fetch_logging,	NULL }, */
  { 0, 0, 0, 0 }
};

static const SilcConfigTable table_logging[] = {
  { "timestamp",	SILC_CONFIG_ARG_TOGGLE,	fetch_logging,	NULL },
  { "quicklogs",	SILC_CONFIG_ARG_TOGGLE,	fetch_logging,	NULL },
  { "flushdelay",	SILC_CONFIG_ARG_INT,	fetch_logging,	NULL },
  { "info",		SILC_CONFIG_ARG_BLOCK,	fetch_logging,	table_logging_c },
  { "warnings",		SILC_CONFIG_ARG_BLOCK,	fetch_logging,	table_logging_c },
  { "errors",		SILC_CONFIG_ARG_BLOCK,	fetch_logging,	table_logging_c },
  { "fatals",		SILC_CONFIG_ARG_BLOCK,	fetch_logging,	table_logging_c },
  { 0, 0, 0, 0 }
};

static const SilcConfigTable table_connparam[] = {
  { "name",		       SILC_CONFIG_ARG_STR,    fetch_connparam, NULL },
  { "require_reverse_lookup",  SILC_CONFIG_ARG_TOGGLE, fetch_connparam,	NULL },
  { "connections_max",	       SILC_CONFIG_ARG_INT,    fetch_connparam, NULL },
  { "connections_max_per_host",SILC_CONFIG_ARG_INT,    fetch_connparam, NULL },
  { "keepalive_secs",	       SILC_CONFIG_ARG_INT,    fetch_connparam, NULL },
  { "reconnect_count",	       SILC_CONFIG_ARG_INT,    fetch_connparam, NULL },
  { "reconnect_interval",      SILC_CONFIG_ARG_INT,    fetch_connparam,	NULL },
  { "reconnect_interval_max",  SILC_CONFIG_ARG_INT,    fetch_connparam,	NULL },
  { "reconnect_keep_trying",   SILC_CONFIG_ARG_TOGGLE, fetch_connparam,	NULL },
  { "key_exchange_rekey",      SILC_CONFIG_ARG_INT,    fetch_connparam,	NULL },
  { "key_exchange_pfs",	       SILC_CONFIG_ARG_TOGGLE, fetch_connparam,	NULL },
  { "version_protocol",	       SILC_CONFIG_ARG_STR,    fetch_connparam,	NULL },
  { "version_software",	       SILC_CONFIG_ARG_STR,    fetch_connparam,	NULL },
  { "version_software_vendor", SILC_CONFIG_ARG_STR,    fetch_connparam,	NULL },
  { "anonymous",               SILC_CONFIG_ARG_TOGGLE, fetch_connparam,	NULL },
  { "qos",    	               SILC_CONFIG_ARG_TOGGLE,	fetch_generic,	NULL },
  { "qos_rate_limit",          SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { "qos_bytes_limit",         SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { "qos_limit_sec",           SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { "qos_limit_usec",          SILC_CONFIG_ARG_INT,	fetch_generic,	NULL },
  { 0, 0, 0, 0 }
};

static const SilcConfigTable table_client[] = {
  { "host",		SILC_CONFIG_ARG_STRE,	fetch_client,	NULL },
  { "passphrase",	SILC_CONFIG_ARG_STR,	fetch_client,	NULL },
  { "publickey",	SILC_CONFIG_ARG_STR,	fetch_client,	NULL },
  { "publickeydir",	SILC_CONFIG_ARG_STR,	fetch_client,	NULL },
  { "params",		SILC_CONFIG_ARG_STR,	fetch_client,	NULL },
  { 0, 0, 0, 0 }
};

static const SilcConfigTable table_admin[] = {
  { "host",		SILC_CONFIG_ARG_STRE,	fetch_admin,	NULL },
  { "user",		SILC_CONFIG_ARG_STRE,	fetch_admin,	NULL },
  { "nick",		SILC_CONFIG_ARG_STRE,	fetch_admin,	NULL },
  { "passphrase",	SILC_CONFIG_ARG_STR,	fetch_admin,	NULL },
  { "publickey",	SILC_CONFIG_ARG_STR,	fetch_admin,	NULL },
  { "publickeydir",	SILC_CONFIG_ARG_STR,	fetch_admin,	NULL },
  { "port",		SILC_CONFIG_ARG_INT,	fetch_admin,	NULL },
  { "params",		SILC_CONFIG_ARG_STR,	fetch_admin,	NULL },
  { 0, 0, 0, 0 }
};

static const SilcConfigTable table_deny[] = {
  { "host",		SILC_CONFIG_ARG_STRE,	fetch_deny,	NULL },
  { "reason",		SILC_CONFIG_ARG_STR,	fetch_deny,	NULL },
  { 0, 0, 0, 0 }
};

static const SilcConfigTable table_serverconn[] = {
  { "host",		SILC_CONFIG_ARG_STRE,	fetch_server,	NULL },
  { "passphrase",	SILC_CONFIG_ARG_STR,	fetch_server,	NULL },
  { "publickey",	SILC_CONFIG_ARG_STR,	fetch_server,	NULL },
  { "params",		SILC_CONFIG_ARG_STR,	fetch_server,	NULL },
  { "backup",		SILC_CONFIG_ARG_TOGGLE,	fetch_server,	NULL },
  { 0, 0, 0, 0 }
};

static const SilcConfigTable table_routerconn[] = {
  { "host",		SILC_CONFIG_ARG_STRE,	fetch_router,	NULL },
  { "port",		SILC_CONFIG_ARG_INT,	fetch_router,	NULL },
  { "passphrase",	SILC_CONFIG_ARG_STR,	fetch_router,	NULL },
  { "publickey",	SILC_CONFIG_ARG_STR,	fetch_router,	NULL },
  { "params",		SILC_CONFIG_ARG_STR,	fetch_router,	NULL },
  { "initiator",	SILC_CONFIG_ARG_TOGGLE,	fetch_router,	NULL },
  { "backuphost",	SILC_CONFIG_ARG_STRE,	fetch_router,	NULL },
  { "backupport",	SILC_CONFIG_ARG_INT,	fetch_router,	NULL },
  { "backuplocal",	SILC_CONFIG_ARG_TOGGLE,	fetch_router,	NULL },
  { 0, 0, 0, 0 }
};

static const SilcConfigTable table_main[] = {
  { "cipher",		SILC_CONFIG_ARG_BLOCK,	fetch_cipher,  table_cipher },
  { "hash",		SILC_CONFIG_ARG_BLOCK,	fetch_hash,    table_hash },
  { "hmac",		SILC_CONFIG_ARG_BLOCK,	fetch_hmac,    table_hmac },
  { "pkcs",		SILC_CONFIG_ARG_BLOCK,	fetch_pkcs,    table_pkcs },
  { "general",		SILC_CONFIG_ARG_BLOCK,	NULL,	       table_general },
  { "serverinfo",	SILC_CONFIG_ARG_BLOCK,	fetch_serverinfo, table_serverinfo },
  { "logging",		SILC_CONFIG_ARG_BLOCK,	NULL,	       table_logging },
  { "connectionparams",	SILC_CONFIG_ARG_BLOCK,	fetch_connparam, table_connparam },
  { "client",		SILC_CONFIG_ARG_BLOCK,	fetch_client,  table_client },
  { "admin",		SILC_CONFIG_ARG_BLOCK,	fetch_admin,   table_admin },
  { "deny",		SILC_CONFIG_ARG_BLOCK,	fetch_deny,    table_deny },
  { "serverconnection",	SILC_CONFIG_ARG_BLOCK,	fetch_server,  table_serverconn },
  { "routerconnection",	SILC_CONFIG_ARG_BLOCK,	fetch_router,  table_routerconn },
  { 0, 0, 0, 0 }
};

/* Set default values to stuff that was not configured. */

static void silc_server_config_set_defaults(SilcServerConfig config)
{
  my_set_param_defaults(&config->param, NULL);

  config->channel_rekey_secs = (config->channel_rekey_secs ?
				config->channel_rekey_secs :
				SILC_SERVER_CHANNEL_REKEY);
  config->key_exchange_timeout = (config->key_exchange_timeout ?
				  config->key_exchange_timeout :
				  SILC_SERVER_SKE_TIMEOUT);
  config->conn_auth_timeout = (config->conn_auth_timeout ?
			       config->conn_auth_timeout :
			       SILC_SERVER_CONNAUTH_TIMEOUT);
}

/* Check for correctness of the configuration */

static bool silc_server_config_check(SilcServerConfig config)
{
  bool ret = TRUE;
  SilcServerConfigServer *s;
  SilcServerConfigRouter *r;
  bool b = FALSE;

  /* ServerConfig is mandatory */
  if (!config->server_info) {
    SILC_SERVER_LOG_ERROR(("\nError: Missing mandatory block `ServerInfo'"));
    ret = FALSE;
  }

  /* RouterConnection sanity checks */

  if (config->routers && config->routers->backup_router == TRUE &&
      !config->servers) {
    SILC_SERVER_LOG_ERROR((
         "\nError: First RouterConnection block must be primary router "
	 "connection. You have marked it incorrectly as backup router."));
    ret = FALSE;
  }
#if 0
  if (config->routers && config->routers->initiator == FALSE &&
      config->routers->backup_router == FALSE) {
    SILC_SERVER_LOG_ERROR((
         "\nError: First RouterConnection block must be primary router "
	 "connection and it must be marked as Initiator."));
    ret = FALSE;
  }
#endif
  if (config->routers && config->routers->backup_router == TRUE &&
      !config->servers && !config->routers->next) {
    SILC_SERVER_LOG_ERROR((
         "\nError: You have configured backup router but not primary router. "
	 "If backup router is configured also primary router must be "
	 "configured."));
    ret = FALSE;
  }

  /* Backup router sanity checks */

  for (r = config->routers; r; r = r->next) {
    if (r->backup_router && !strcmp(r->host, r->backup_replace_ip)) {
      SILC_SERVER_LOG_ERROR((
          "\nError: Backup router connection incorrectly configured to use "
	  "primary and backup router as same host `%s'. They must not be "
	  "same host.", r->host));
      ret = FALSE;
    }
  }

  /* ServerConnection sanity checks */

  for (s = config->servers; s; s = s->next) {
    if (s->backup_router) {
      b = TRUE;
      break;
    }
  }
  if (b) {
    for (s = config->servers; s; s = s->next) {
      if (!s->backup_router) {
	SILC_SERVER_LOG_ERROR((
          "\nError: Your server is backup router but not all ServerConnection "
	  "blocks were marked as backup connections. They all must be "
	  "marked as backup connections."));
	ret = FALSE;
	break;
      }
    }
  }

  return ret;
}

/* Allocates a new configuration object, opens configuration file and
   parses it. The parsed data is returned to the newly allocated
   configuration object. The SilcServerConfig must be freed by calling
   the silc_server_config_destroy function. */

SilcServerConfig silc_server_config_alloc(const char *filename)
{
  SilcServerConfig config_new;
  SilcConfigEntity ent;
  SilcConfigFile *file;
  int ret;
  SILC_LOG_DEBUG(("Loading config data from `%s'", filename));

  /* alloc a config object */
  config_new = silc_calloc(1, sizeof(*config_new));
  if (!config_new)
    return NULL;

  /* general config defaults */
  config_new->refcount = 1;
  config_new->logging_timestamp = TRUE;

  /* obtain a config file object */
  file = silc_config_open(filename);
  if (!file) {
    SILC_SERVER_LOG_ERROR(("\nError: can't open config file `%s'",
			   filename));
    return NULL;
  }

  /* obtain a SilcConfig entity, we can use it to start the parsing */
  ent = silc_config_init(file);

  /* load the known configuration options, give our empty object as context */
  silc_config_register_table(ent, table_main, (void *) config_new);

  /* enter the main parsing loop.  When this returns, we have the parsing
   * result and the object filled (or partially, in case of errors). */
  ret = silc_config_main(ent);
  SILC_LOG_DEBUG(("Parser returned [ret=%d]: %s", ret,
		  silc_config_strerror(ret)));

  /* Check if the parser returned errors */
  if (ret) {
    /* handle this special error return which asks to quietly return */
    if (ret != SILC_CONFIG_ESILENT) {
      char *linebuf, *filename = silc_config_get_filename(file);
      SilcUInt32 line = silc_config_get_line(file);
      if (ret != SILC_CONFIG_EPRINTLINE)
        SILC_SERVER_LOG_ERROR(("Error while parsing config file: %s.",
			       silc_config_strerror(ret)));
      linebuf = silc_config_read_line(file, line);
      if (linebuf) {
	SILC_SERVER_LOG_ERROR(("  file %s line %lu:  %s\n", filename,
			       line, linebuf));
	silc_free(linebuf);
      }
    }
    silc_server_config_destroy(config_new);
    silc_config_close(file);
    return NULL;
  }

  /* close (destroy) the file object */
  silc_config_close(file);

  /* Check the configuration */
  if (!silc_server_config_check(config_new)) {
    silc_server_config_destroy(config_new);
    return NULL;
  }

  /* Set default to configuration parameters */
  silc_server_config_set_defaults(config_new);

  return config_new;
}

/* Increments the reference counter of a config object */

void silc_server_config_ref(SilcServerConfigRef *ref, SilcServerConfig config,
			    void *ref_ptr)
{
  if (ref_ptr) {
    config->refcount++;
    ref->config = config;
    ref->ref_ptr = ref_ptr;
    SILC_LOG_DEBUG(("Referencing config [%p] refcnt %d->%d", config,
		    config->refcount - 1, config->refcount));
  }
}

/* Decrements the reference counter of a config object.  If the counter
   reaches 0, the config object is destroyed. */

void silc_server_config_unref(SilcServerConfigRef *ref)
{
  if (ref->ref_ptr)
    silc_server_config_destroy(ref->config);
}

/* Destroy a config object with all his children lists */

void silc_server_config_destroy(SilcServerConfig config)
{
  void *tmp;

  config->refcount--;
  SILC_LOG_DEBUG(("Unreferencing config [%p] refcnt %d->%d", config,
		  config->refcount + 1, config->refcount));
  if (config->refcount > 0)
    return;

  SILC_LOG_DEBUG(("Freeing config context"));

  /* Destroy general config stuff */
  silc_free(config->module_path);
  silc_free(config->debug_string);
  silc_free(config->param.version_protocol);
  silc_free(config->param.version_software);
  silc_free(config->param.version_software_vendor);

  /* Destroy Logging channels */
  if (config->logging_info)
    silc_free(config->logging_info->file);
  if (config->logging_warnings)
    silc_free(config->logging_warnings->file);
  if (config->logging_errors)
    silc_free(config->logging_errors->file);
  if (config->logging_fatals)
    silc_free(config->logging_fatals->file);
  silc_free(config->logging_info);
  silc_free(config->logging_warnings);
  silc_free(config->logging_errors);
  silc_free(config->logging_fatals);

  /* Destroy the ServerInfo struct */
  if (config->server_info) {
    register SilcServerConfigServerInfo *si = config->server_info;
    silc_free(si->server_name);
    if (si->primary) {
      silc_free(si->primary->server_ip);
      silc_free(si->primary);
    }
    SILC_SERVER_CONFIG_LIST_DESTROY(SilcServerConfigServerInfoInterface,
				  si->secondary)
      silc_free(di->server_ip);
      silc_free(di);
    }
    silc_free(si->server_type);
    silc_free(si->location);
    silc_free(si->admin);
    silc_free(si->email);
    silc_free(si->user);
    silc_free(si->group);
    silc_free(si->motd_file);
    silc_free(si->pid_file);
    silc_pkcs_public_key_free(si->public_key);
    silc_pkcs_private_key_free(si->private_key);
    silc_free(si);
  }

  /* Now let's destroy the lists */

  SILC_SERVER_CONFIG_LIST_DESTROY(SilcServerConfigCipher,
				  config->cipher)
    silc_free(di->name);
    silc_free(di->module);
    silc_free(di);
  }
  SILC_SERVER_CONFIG_LIST_DESTROY(SilcServerConfigHash, config->hash)
    silc_free(di->name);
    silc_free(di->module);
    silc_free(di);
  }
  SILC_SERVER_CONFIG_LIST_DESTROY(SilcServerConfigHmac, config->hmac)
    silc_free(di->name);
    silc_free(di->hash);
    silc_free(di);
  }
  SILC_SERVER_CONFIG_LIST_DESTROY(SilcServerConfigPkcs, config->pkcs)
    silc_free(di->name);
    silc_free(di);
  }
  SILC_SERVER_CONFIG_LIST_DESTROY(SilcServerConfigConnParams,
                                  config->conn_params)
    silc_free(di->name);
    silc_free(di->version_protocol);
    silc_free(di->version_software);
    silc_free(di->version_software_vendor);
    silc_free(di);
  }
  SILC_SERVER_CONFIG_LIST_DESTROY(SilcServerConfigClient, config->clients)
    silc_free(di->host);
    CONFIG_FREE_AUTH(di);
    silc_free(di);
  }
  SILC_SERVER_CONFIG_LIST_DESTROY(SilcServerConfigAdmin, config->admins)
    silc_free(di->host);
    silc_free(di->user);
    silc_free(di->nick);
    CONFIG_FREE_AUTH(di);
    silc_free(di);
  }
  SILC_SERVER_CONFIG_LIST_DESTROY(SilcServerConfigDeny, config->denied)
    silc_free(di->host);
    silc_free(di->reason);
    silc_free(di);
  }
  SILC_SERVER_CONFIG_LIST_DESTROY(SilcServerConfigServer,
				  config->servers)
    silc_free(di->host);
    CONFIG_FREE_AUTH(di);
    silc_free(di);
  }
  SILC_SERVER_CONFIG_LIST_DESTROY(SilcServerConfigRouter,
				  config->routers)
    silc_free(di->host);
    silc_free(di->backup_replace_ip);
    CONFIG_FREE_AUTH(di);
    silc_free(di);
  }

  memset(config, 'F', sizeof(*config));
  silc_free(config);
}

/* Registers configured ciphers. These can then be allocated by the
   server when needed. */

bool silc_server_config_register_ciphers(SilcServer server)
{
  SilcServerConfig config = server->config;
  SilcServerConfigCipher *cipher = config->cipher;
  char *module_path = config->module_path;

  SILC_LOG_DEBUG(("Registering configured ciphers"));

  if (!cipher) /* any cipher in the config file? */
    return FALSE;

  while (cipher) {
    /* if there isn't a module_path OR there isn't a module sim name try to
     * use buil-in functions */
    if (!module_path || !cipher->module) {
      int i;
      for (i = 0; silc_default_ciphers[i].name; i++)
	if (!strcmp(silc_default_ciphers[i].name, cipher->name)) {
	  silc_cipher_register((SilcCipherObject *)&silc_default_ciphers[i]);
	  break;
	}
      if (!silc_cipher_is_supported(cipher->name)) {
	SILC_LOG_ERROR(("Unknown cipher `%s'", cipher->name));
	silc_server_stop(server);
	exit(1);
      }
    } else {
#ifdef SILC_SIM
      /* Load (try at least) the crypto SIM module */
      char buf[1023], *alg_name;
      SilcCipherObject cipher_obj;
      SilcSim sim;

      memset(&cipher_obj, 0, sizeof(cipher_obj));
      cipher_obj.name = cipher->name;
      cipher_obj.block_len = cipher->block_length;
      cipher_obj.key_len = cipher->key_length * 8;

      /* build the libname */
      snprintf(buf, sizeof(buf), "%s/%s", config->module_path,
		cipher->module);
      sim = silc_sim_alloc(SILC_SIM_CIPHER, buf, 0);

      alg_name = strdup(cipher->name);
      if (strchr(alg_name, '-'))
	*strchr(alg_name, '-') = '\0';

      if (silc_sim_load(sim)) {
	cipher_obj.set_key =
	  silc_sim_getsym(sim, silc_sim_symname(alg_name,
						SILC_CIPHER_SIM_SET_KEY));
	SILC_LOG_DEBUG(("set_key=%p", cipher_obj.set_key));
	cipher_obj.set_key_with_string =
	  silc_sim_getsym(sim,
	    silc_sim_symname(alg_name,
	      SILC_CIPHER_SIM_SET_KEY_WITH_STRING));
	SILC_LOG_DEBUG(("set_key_with_string=%p",
	  cipher_obj.set_key_with_string));
	cipher_obj.encrypt =
	  silc_sim_getsym(sim, silc_sim_symname(alg_name,
						SILC_CIPHER_SIM_ENCRYPT_CBC));
	SILC_LOG_DEBUG(("encrypt_cbc=%p", cipher_obj.encrypt));
        cipher_obj.decrypt =
	  silc_sim_getsym(sim, silc_sim_symname(alg_name,
						SILC_CIPHER_SIM_DECRYPT_CBC));
	SILC_LOG_DEBUG(("decrypt_cbc=%p", cipher_obj.decrypt));
        cipher_obj.context_len =
	  silc_sim_getsym(sim, silc_sim_symname(alg_name,
						SILC_CIPHER_SIM_CONTEXT_LEN));
	SILC_LOG_DEBUG(("context_len=%p", cipher_obj.context_len));

	/* Put the SIM to the list of all SIM's in server */
	silc_dlist_add(server->sim, sim);

	silc_free(alg_name);
      } else {
	SILC_LOG_ERROR(("Error configuring ciphers"));
        silc_sim_free(sim);
	silc_server_stop(server);
	exit(1);
      }

      /* Register the cipher */
      silc_cipher_register(&cipher_obj);
#else
      SILC_LOG_ERROR(("Dynamic module support not compiled, "
			"can't load modules!"));
      silc_server_stop(server);
      exit(1);
#endif
    }
    cipher = cipher->next;
  } /* while */

  return TRUE;
}

/* Registers configured hash functions. These can then be allocated by the
   server when needed. */

bool silc_server_config_register_hashfuncs(SilcServer server)
{
  SilcServerConfig config = server->config;
  SilcServerConfigHash *hash = config->hash;
  char *module_path = config->module_path;

  SILC_LOG_DEBUG(("Registering configured hash functions"));

  if (!hash) /* any hash func in the config file? */
    return FALSE;

  while (hash) {
    /* if there isn't a module_path OR there isn't a module sim name try to
     * use buil-in functions */
    if (!module_path || !hash->module) {
      int i;
      for (i = 0; silc_default_hash[i].name; i++)
	if (!strcmp(silc_default_hash[i].name, hash->name)) {
	  silc_hash_register((SilcHashObject *)&silc_default_hash[i]);
	  break;
	}
      if (!silc_hash_is_supported(hash->name)) {
	SILC_LOG_ERROR(("Unknown hash funtion `%s'", hash->name));
	silc_server_stop(server);
	exit(1);
      }
    } else {
#ifdef SILC_SIM
      /* Load (try at least) the hash SIM module */
      SilcHashObject hash_obj;
      SilcSim sim;

      memset(&hash_obj, 0, sizeof(hash_obj));
      hash_obj.name = hash->name;
      hash_obj.block_len = hash->block_length;
      hash_obj.hash_len = hash->digest_length;

      sim = silc_sim_alloc(SILC_SIM_HASH, hash->module, 0);

      if ((silc_sim_load(sim))) {
	hash_obj.init =
	  silc_sim_getsym(sim, silc_sim_symname(hash->name,
						SILC_HASH_SIM_INIT));
	SILC_LOG_DEBUG(("init=%p", hash_obj.init));
	hash_obj.update =
	  silc_sim_getsym(sim, silc_sim_symname(hash->name,
						SILC_HASH_SIM_UPDATE));
	SILC_LOG_DEBUG(("update=%p", hash_obj.update));
        hash_obj.final =
	  silc_sim_getsym(sim, silc_sim_symname(hash->name,
						SILC_HASH_SIM_FINAL));
	SILC_LOG_DEBUG(("final=%p", hash_obj.final));
        hash_obj.context_len =
	  silc_sim_getsym(sim, silc_sim_symname(hash->name,
						SILC_HASH_SIM_CONTEXT_LEN));
	SILC_LOG_DEBUG(("context_len=%p", hash_obj.context_len));

	/* Put the SIM to the table of all SIM's in server */
	silc_dlist_add(server->sim, sim);
      } else {
	SILC_LOG_ERROR(("Error configuring hash functions"));
        silc_sim_free(sim);
	silc_server_stop(server);
	exit(1);
      }

      /* Register the hash function */
      silc_hash_register(&hash_obj);
#else
      SILC_LOG_ERROR(("Dynamic module support not compiled, "
			"can't load modules!"));
      silc_server_stop(server);
      exit(1);
#endif
    }
    hash = hash->next;
  } /* while */

  return TRUE;
}

/* Registers configure HMACs. These can then be allocated by the server
   when needed. */

bool silc_server_config_register_hmacs(SilcServer server)
{
  SilcServerConfig config = server->config;
  SilcServerConfigHmac *hmac = config->hmac;

  SILC_LOG_DEBUG(("Registering configured HMACs"));

  if (!hmac)
    return FALSE;

  while (hmac) {
    SilcHmacObject hmac_obj;
    if (!silc_hash_is_supported(hmac->hash)) {
      SILC_LOG_ERROR(("Unknown hash function `%s'", hmac->hash));
      silc_server_stop(server);
      exit(1);
    }

    /* Register the HMAC */
    memset(&hmac_obj, 0, sizeof(hmac_obj));
    hmac_obj.name = hmac->name;
    hmac_obj.len = hmac->mac_length;
    silc_hmac_register(&hmac_obj);

    hmac = hmac->next;
  } /* while */

  return TRUE;
}

/* Registers configured PKCS's. */

bool silc_server_config_register_pkcs(SilcServer server)
{
  SilcServerConfig config = server->config;
  SilcServerConfigPkcs *pkcs = config->pkcs;

  SILC_LOG_DEBUG(("Registering configured PKCS"));

  if (!pkcs)
    return FALSE;

  while (pkcs) {
    int i;
    for (i = 0; silc_default_pkcs[i].name; i++)
      if (!strcmp(silc_default_pkcs[i].name, pkcs->name)) {
	silc_pkcs_register((SilcPKCSObject *)&silc_default_pkcs[i]);
	break;
      }
    if (!silc_pkcs_is_supported(pkcs->name)) {
      SILC_LOG_ERROR(("Unknown PKCS `%s'", pkcs->name));
      silc_server_stop(server);
      exit(1);
    }
    pkcs = pkcs->next;
  } /* while */

  return TRUE;
}

/* Sets log files where log messages are saved by the server logger. */

void silc_server_config_setlogfiles(SilcServer server)
{
  SilcServerConfig config = server->config;
  SilcServerConfigLogging *this;

  SILC_LOG_DEBUG(("Setting configured log file names and options"));

  silc_log_timestamp = config->logging_timestamp;
  silc_log_quick = config->logging_quick;
  silc_log_flushdelay = (config->logging_flushdelay ?
			 config->logging_flushdelay :
			 SILC_SERVER_LOG_FLUSH_DELAY);

  if ((this = config->logging_fatals))
    silc_log_set_file(SILC_LOG_FATAL, this->file, this->maxsize,
		      server->schedule);
  if ((this = config->logging_errors))
    silc_log_set_file(SILC_LOG_ERROR, this->file, this->maxsize,
		      server->schedule);
  if ((this = config->logging_warnings))
    silc_log_set_file(SILC_LOG_WARNING, this->file, this->maxsize,
		      server->schedule);
  if ((this = config->logging_info))
    silc_log_set_file(SILC_LOG_INFO, this->file, this->maxsize,
		      server->schedule);
}

/* Returns client authentication information from configuration file by host
   (name or ip) */

SilcServerConfigClient *
silc_server_config_find_client(SilcServer server, char *host)
{
  SilcServerConfig config = server->config;
  SilcServerConfigClient *client;

  if (!config || !host)
    return NULL;

  for (client = config->clients; client; client = client->next) {
    if (client->host && !silc_string_compare(client->host, host))
      continue;
    break;
  }

  /* if none matched, then client is already NULL */
  return client;
}

/* Returns admin connection configuration by host, username and/or
   nickname. */

SilcServerConfigAdmin *
silc_server_config_find_admin(SilcServer server, char *host, char *user,
			      char *nick)
{
  SilcServerConfig config = server->config;
  SilcServerConfigAdmin *admin;

  /* make sure we have a value for the matching parameters */
  if (!host)
    host = "*";
  if (!user)
    user = "*";
  if (!nick)
    nick = "*";

  for (admin = config->admins; admin; admin = admin->next) {
    if (admin->host && !silc_string_compare(admin->host, host))
      continue;
    if (admin->user && !silc_string_compare(admin->user, user))
      continue;
    if (admin->nick && !silc_string_compare(admin->nick, nick))
      continue;
    /* no checks failed -> this entry matches */
    break;
  }

  /* if none matched, then admin is already NULL */
  return admin;
}

/* Returns the denied connection configuration entry by host. */

SilcServerConfigDeny *
silc_server_config_find_denied(SilcServer server, char *host)
{
  SilcServerConfig config = server->config;
  SilcServerConfigDeny *deny;

  /* make sure we have a value for the matching parameters */
  if (!config || !host)
    return NULL;

  for (deny = config->denied; deny; deny = deny->next) {
    if (deny->host && !silc_string_compare(deny->host, host))
      continue;
    break;
  }

  /* if none matched, then deny is already NULL */
  return deny;
}

/* Returns server connection info from server configuartion by host
   (name or ip). */

SilcServerConfigServer *
silc_server_config_find_server_conn(SilcServer server, char *host)
{
  SilcServerConfig config = server->config;
  SilcServerConfigServer *serv = NULL;

  if (!host)
    return NULL;

  if (!config->servers)
    return NULL;

  for (serv = config->servers; serv; serv = serv->next) {
    if (!silc_string_compare(serv->host, host))
      continue;
    break;
  }

  return serv;
}

/* Returns router connection info from server configuration by
   host (name or ip). */

SilcServerConfigRouter *
silc_server_config_find_router_conn(SilcServer server, char *host, int port)
{
  SilcServerConfig config = server->config;
  SilcServerConfigRouter *serv = NULL;

  if (!host)
    return NULL;

  if (!config->routers)
    return NULL;

  for (serv = config->routers; serv; serv = serv->next) {
    if (!silc_string_compare(serv->host, host))
      continue;
    if (port && serv->port && serv->port != port)
      continue;
    break;
  }

  return serv;
}

/* Find backup router connection by host (name or ip) */

SilcServerConfigRouter *
silc_server_config_find_backup_conn(SilcServer server, char *host)
{
  SilcServerConfig config = server->config;
  SilcServerConfigRouter *serv = NULL;

  if (!host)
    return NULL;

  if (!config->routers)
    return NULL;

  for (serv = config->routers; serv; serv = serv->next) {
    if (!serv->backup_router)
      continue;
    if (!silc_string_compare(serv->host, host))
      continue;
    break;
  }

  return serv;
}

/* Returns TRUE if configuration for a router connection that we are
   initiating exists. */

bool silc_server_config_is_primary_route(SilcServer server)
{
  SilcServerConfig config = server->config;
  SilcServerConfigRouter *serv = NULL;
  int i;
  bool found = FALSE;

  serv = config->routers;
  for (i = 0; serv; i++) {
    if (serv->initiator == TRUE && serv->backup_router == FALSE) {
      found = TRUE;
      break;
    }

    serv = serv->next;
  }

  return found;
}

/* Returns our primary connection configuration or NULL if we do not
   have primary router configured. */

SilcServerConfigRouter *
silc_server_config_get_primary_router(SilcServer server)
{
  SilcServerConfig config = server->config;
  SilcServerConfigRouter *serv = NULL;
  int i;

  serv = config->routers;
  for (i = 0; serv; i++) {
    if (serv->initiator == TRUE && serv->backup_router == FALSE)
      return serv;
    serv = serv->next;
  }

  return NULL;
}

/* If we have backup router configured that is going to replace us this
   function returns it. */

SilcServerConfigRouter *
silc_server_config_get_backup_router(SilcServer server)
{
  SilcServerConfig config = server->config;
  SilcServerConfigRouter *serv = NULL;
  int i;

  if (server->server_type != SILC_ROUTER)
    return NULL;

  serv = config->routers;
  for (i = 0; serv; i++) {
    if (serv->initiator == FALSE && serv->backup_router == TRUE &&
	serv->backup_local == TRUE &&
	!strcmp(server->config->server_info->primary->server_ip,
		serv->backup_replace_ip) &&
	server->config->server_info->primary->port ==
	serv->backup_replace_port)
      return serv;
    serv = serv->next;
  }

  return NULL;
}
