/*

  serverconfig.c

  Author: Pekka Riikonen <priikone@poseidon.pspt.fi>

  Copyright (C) 1997 - 2001 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: clientconfig.c,v 1.3 2001/09/15 12:09:55 priikone Exp $ */

#include "module.h"

#include "net-nonblock.h"
#include "net-sendbuffer.h"
#include "signals.h"
#include "servers.h"
#include "commands.h"
#include "levels.h"
#include "modules.h"
#include "rawlog.h"
#include "misc.h"
#include "settings.h"

#include "servers-setup.h"

#include "silc-servers.h"
#include "silc-channels.h"
#include "silc-queries.h"
#include "window-item-def.h"

#include "fe-common/core/printtext.h"

/* 
   All possible configuration sections for SILC client.
*/
SilcClientConfigSection silc_client_config_sections[] = {
  { "[cipher]", 
    SILC_CLIENT_CONFIG_SECTION_TYPE_CIPHER, 4 },
  { "[pkcs]", 
    SILC_CLIENT_CONFIG_SECTION_TYPE_PKCS, 1 },
  { "[hash]", 
    SILC_CLIENT_CONFIG_SECTION_TYPE_HASH_FUNCTION, 4 },
  { "[hmac]", 
    SILC_CLIENT_CONFIG_SECTION_TYPE_HMAC, 3 },
  { "[connection]", 
    SILC_CLIENT_CONFIG_SECTION_TYPE_CONNECTION, 4 },
  
  { NULL, SILC_CLIENT_CONFIG_SECTION_TYPE_NONE, 0 }
};

/* Allocates a new configuration object, opens configuration file and
   parses the file. The parsed data is returned to the newly allocated
   configuration object. */

SilcClientConfig silc_client_config_alloc(char *filename)
{
  SilcClientConfig new;
  SilcBuffer buffer;
  SilcClientConfigParse config_parse;
  char *str;

  SILC_LOG_DEBUG(("Allocating new configuration object"));

  new = silc_calloc(1, sizeof(*new));
  new->filename = filename;

  /* Open configuration file and parse it */
  config_parse = NULL;
  buffer = NULL;
  str = convert_home(filename);
  silc_config_open(str, &buffer);
  g_free(str);
  if (!buffer)
    goto fail;
  if ((silc_client_config_parse(new, buffer, &config_parse)) == FALSE)
    goto fail;
  if ((silc_client_config_parse_lines(new, config_parse)) == FALSE)
    goto fail;

  silc_free(buffer);

  return new;

 fail:
  silc_free(new);
  return NULL;
}

/* Free's a configuration object. */

void silc_client_config_free(SilcClientConfig config)
{
  if (config) {

    silc_free(config);
  }
}

/* Parses the the buffer and returns the parsed lines into return_config
   argument. The return_config argument doesn't have to be initialized 
   before calling this. It will be initialized during the parsing. The
   buffer sent as argument can be safely free'd after this function has
   succesfully returned. */

int silc_client_config_parse(SilcClientConfig config, SilcBuffer buffer, 
			     SilcClientConfigParse *return_config)
{
  int i, begin;
  int linenum;
  char line[1024], *cp;
  SilcClientConfigSection *cptr = NULL;
  SilcClientConfigParse parse = *return_config, first = NULL;

  SILC_LOG_DEBUG(("Parsing configuration file"));

  begin = 0;
  linenum = 0;
  while((begin = silc_gets(line, sizeof(line), 
			   buffer->data, buffer->len, begin)) != EOF) {
    cp = line;
    linenum++;

    /* Check for bad line */
    if (silc_check_line(cp))
      continue;

    /* Remove tabs and whitespaces from the line */
    if (strchr(cp, '\t')) {
      i = 0;
      while(strchr(cp + i, '\t')) {
	*strchr(cp + i, '\t') = ' ';
	i++;
      }
    }
    for (i = 0; i < strlen(cp); i++) {
      if (cp[i] != ' ') {
	if (i)
	  cp++;
	break;
      }
      cp++;
    }

    /* Parse line */
    switch(cp[0]) {
    case '[':
      /*
       * Start of a section
       */

      /* Remove new line sign */
      if (strchr(cp, '\n'))
	*strchr(cp, '\n') = '\0';
      
      /* Check for matching sections */
      for (cptr = silc_client_config_sections; cptr->section; cptr++)
	if (!strncasecmp(cp, cptr->section, strlen(cptr->section)))
	  break;

      if (!cptr->section) {
	fprintf(stderr, "%s:%d: Unknown section `%s'\n", 
			config->filename, linenum, cp);
	return FALSE;
      }

      break;
    default:
      /*
       * Start of a configuration line
       */

      if (!cptr) {
	fprintf(stderr, "%s:%d: Unknown start of a section `%s'\n", 
			config->filename, linenum, cp);
	return FALSE;
      }	

      /* Handle config section */
      if (cptr->type != SILC_CLIENT_CONFIG_SECTION_TYPE_NONE) {
	
	if (strchr(cp, '\n'))
	    *strchr(cp, '\n') = ':';

	if (parse == NULL) {
	  parse = silc_calloc(1, sizeof(*parse));
	  parse->line = NULL;
	  parse->section = NULL;
	  parse->next = NULL;
	  parse->prev = NULL;
	} else {
	  if (parse->next == NULL) {
	    parse->next = silc_calloc(1, sizeof(*parse->next));
	    parse->next->line = NULL;
	    parse->next->section = NULL;
	    parse->next->next = NULL;
	    parse->next->prev = parse;
	    parse = parse->next;
	  }
	}
	
	if (first == NULL)
	  first = parse;

	/* Add the line to parsing structure for further parsing. */
	if (parse) {
	  parse->section = cptr;
	  parse->line = silc_buffer_alloc(strlen(cp) + 1);
	  parse->linenum = linenum;
	  silc_buffer_pull_tail(parse->line, strlen(cp));
	  silc_buffer_put(parse->line, cp, strlen(cp));
	}
      }
      break;
    }
  }
  
  /* Set the return_config argument to its first value so that further
     parsing can be started from the first line. */
  *return_config = first;

  return TRUE;
}

/* Parses the lines earlier read from configuration file. The config object
   must not be initialized, it will be initialized in this function. The
   parse_config argument is uninitialized automatically during this
   function. */

int silc_client_config_parse_lines(SilcClientConfig config, 
				   SilcClientConfigParse parse_config)
{
  int ret, check = FALSE;
  char *tmp;
  SilcClientConfigParse pc = parse_config;
  SilcBuffer line;

  SILC_LOG_DEBUG(("Parsing configuration lines"));
  
  if (!config)
    return FALSE;
  
  while(pc) {
    check = FALSE;
    line = pc->line;

    /* Get number of tokens in line (command section is handeled
       specially and has no tokens at all). */
    ret = silc_config_check_num_token(line);
    if (ret != pc->section->maxfields) {
      /* Bad line */
      fprintf(stderr, "%s:%d: Missing tokens, %d tokens (should be %d)\n",
	      config->filename, pc->linenum, ret, 
	      pc->section->maxfields);
      break;
    }

    /* Parse the line */
    switch(pc->section->type) {
    case SILC_CLIENT_CONFIG_SECTION_TYPE_CIPHER:

      if (!config->cipher) {
	config->cipher = silc_calloc(1, sizeof(*config->cipher));
	config->cipher->next = NULL;
	config->cipher->prev = NULL;
      } else {
	if (!config->cipher->next) {
	  config->cipher->next = 
	    silc_calloc(1, sizeof(*config->cipher->next));
	  config->cipher->next->next = NULL;
	  config->cipher->next->prev = config->cipher;
	  config->cipher = config->cipher->next;
	}
      }

      /* Get cipher name */
      ret = silc_config_get_token(line, &config->cipher->alg_name);
      if (ret < 0)
	break;
      if (ret == 0) {
	fprintf(stderr, "%s:%d: Cipher name not defined\n",
		config->filename, pc->linenum);
	break;
      }

      /* Get module name */
      config->cipher->sim_name = NULL;
      ret = silc_config_get_token(line, &config->cipher->sim_name);
      if (ret < 0)
	break;

      /* Get key length */
      ret = silc_config_get_token(line, &tmp);
      if (ret < 0)
	break;
      if (ret == 0) {
	fprintf(stderr, "%s:%d: Cipher key length not defined\n",
		config->filename, pc->linenum);
	break;
      }
      config->cipher->key_len = atoi(tmp);
      silc_free(tmp);

      /* Get block length */
      ret = silc_config_get_token(line, &tmp);
      if (ret < 0)
	break;
      if (ret == 0) {
	fprintf(stderr, "%s:%d: Cipher block length not defined\n",
		config->filename, pc->linenum);
	break;
      }
      config->cipher->block_len = atoi(tmp);
      silc_free(tmp);

      check = TRUE;
      break;

    case SILC_CLIENT_CONFIG_SECTION_TYPE_PKCS:

      if (!config->pkcs) {
	config->pkcs = silc_calloc(1, sizeof(*config->pkcs));
	config->pkcs->next = NULL;
	config->pkcs->prev = NULL;
      } else {
	if (!config->pkcs->next) {
	  config->pkcs->next = 
	    silc_calloc(1, sizeof(*config->pkcs->next));
	  config->pkcs->next->next = NULL;
	  config->pkcs->next->prev = config->pkcs;
	  config->pkcs = config->pkcs->next;
	}
      }

      /* Get PKCS name */
      ret = silc_config_get_token(line, &config->pkcs->alg_name);
      if (ret < 0)
	break;
      if (ret == 0) {
	fprintf(stderr, "%s:%d: PKCS name not defined\n",
		config->filename, pc->linenum);
	break;
      }

      check = TRUE;
      break;

    case SILC_CLIENT_CONFIG_SECTION_TYPE_HASH_FUNCTION:

      if (!config->hash_func) {
	config->hash_func = silc_calloc(1, sizeof(*config->hash_func));
	config->hash_func->next = NULL;
	config->hash_func->prev = NULL;
      } else {
	if (!config->hash_func->next) {
	  config->hash_func->next = 
	    silc_calloc(1, sizeof(*config->hash_func->next));
	  config->hash_func->next->next = NULL;
	  config->hash_func->next->prev = config->hash_func;
	  config->hash_func = config->hash_func->next;
	}
      }

      /* Get Hash function name */
      ret = silc_config_get_token(line, &config->hash_func->alg_name);
      if (ret < 0)
	break;
      if (ret == 0) {
	fprintf(stderr, "%s:%d: Hash function name not defined\n",
		config->filename, pc->linenum);
	break;
      }
      
      /* Get Hash function module name */
      config->hash_func->sim_name = NULL;
      ret = silc_config_get_token(line, &config->hash_func->sim_name);
      if (ret < 0)
	break;

      /* Get block length */
      ret = silc_config_get_token(line, &tmp);
      if (ret < 0)
	break;
      if (ret == 0) {
	fprintf(stderr, "%s:%d: Hash function block length not defined\n",
		config->filename, pc->linenum);
	break;
      }
      config->hash_func->block_len = atoi(tmp);
      silc_free(tmp);

      /* Get hash length */
      ret = silc_config_get_token(line, &tmp);
      if (ret < 0)
	break;
      if (ret == 0) {
	fprintf(stderr, "%s:%d: Hash function hash length not defined\n",
		config->filename, pc->linenum);
	break;
      }
      config->hash_func->key_len = atoi(tmp);
      silc_free(tmp);

      check = TRUE;
      break;

    case SILC_CLIENT_CONFIG_SECTION_TYPE_HMAC:

      if (!config->hmac) {
	config->hmac = silc_calloc(1, sizeof(*config->hmac));
	config->hmac->next = NULL;
	config->hmac->prev = NULL;
      } else {
	if (!config->hmac->next) {
	  config->hmac->next = 
	    silc_calloc(1, sizeof(*config->hmac->next));
	  config->hmac->next->next = NULL;
	  config->hmac->next->prev = config->hmac;
	  config->hmac = config->hmac->next;
	}
      }

      /* Get HMAC name */
      ret = silc_config_get_token(line, &config->hmac->alg_name);
      if (ret < 0)
	break;
      if (ret == 0) {
	fprintf(stderr, "%s:%d: HMAC name not defined\n",
		config->filename, pc->linenum);
	break;
      }
      
      /* Get Hash function name */
      ret = silc_config_get_token(line, &config->hmac->sim_name);
      if (ret < 0)
	break;
      if (ret == 0) {
	fprintf(stderr, "%s:%d: Hash function name not defined\n",
		config->filename, pc->linenum);
	break;
      }

      /* Get MAC length */
      ret = silc_config_get_token(line, &tmp);
      if (ret < 0)
	break;
      if (ret == 0) {
	fprintf(stderr, "%s:%d: HMAC's MAC length not defined\n",
		config->filename, pc->linenum);
	break;
      }
      config->hmac->key_len = atoi(tmp);
      silc_free(tmp);

      check = TRUE;
      break;

    case SILC_CLIENT_CONFIG_SECTION_TYPE_CONNECTION:

      if (!config->conns) {
	config->conns = silc_calloc(1, sizeof(*config->conns));
	config->conns->next = NULL;
	config->conns->prev = NULL;
      } else {
	if (!config->conns->next) {
	  config->conns->next = silc_calloc(1, sizeof(*config->conns));
	  config->conns->next->next = NULL;
	  config->conns->next->prev = config->conns;
	  config->conns = config->conns->next;
	}
      }
      
      /* Get host */
      ret = silc_config_get_token(line, &config->conns->host);
      if (ret < 0)
	break;
      if (ret == 0)
	/* Any host */
	config->conns->host = strdup("*");

      /* Get authentication method */
      ret = silc_config_get_token(line, &tmp);
      if (ret < 0)
	break;
      if (ret) {
	if (strcmp(tmp, SILC_CLIENT_CONFIG_AUTH_METH_PASSWD) &&
	    strcmp(tmp, SILC_CLIENT_CONFIG_AUTH_METH_PUBKEY)) {
	  fprintf(stderr, "%s:%d: Unknown authentication method '%s'\n",
		  config->filename, pc->linenum, tmp);
	  break;
	}

	if (!strcmp(tmp, SILC_CLIENT_CONFIG_AUTH_METH_PASSWD))
	  config->conns->auth_meth = SILC_AUTH_PASSWORD;

	if (!strcmp(tmp, SILC_CLIENT_CONFIG_AUTH_METH_PUBKEY))
	  config->conns->auth_meth = SILC_AUTH_PUBLIC_KEY;

	silc_free(tmp);
      }

      /* Get authentication data */
      ret = silc_config_get_token(line, &config->conns->auth_data);
      if (ret < 0)
	break;

      /* Get port */
      ret = silc_config_get_token(line, &tmp);
      if (ret < 0)
	break;
      if (ret) {
	config->conns->port = atoi(tmp);
	silc_free(tmp);
      }

      check = TRUE;
      break;

    case SILC_CLIENT_CONFIG_SECTION_TYPE_NONE:
    default:
      break;
    }

    /* Check for error */
    if (check == FALSE) {
      /* Line could not be parsed */
      fprintf(stderr, "%s:%d: Parse error\n", config->filename, pc->linenum);
      break;
    }

    pc = pc->next;
  }

  if (check == FALSE)
    return FALSE;;

  /* Before returning all the lists in the config object must be set
     to their first values (the last value is first here). */
  while (config->cipher && config->cipher->prev)
    config->cipher = config->cipher->prev;
  while (config->pkcs && config->pkcs->prev)
    config->pkcs = config->pkcs->prev;
  while (config->hash_func && config->hash_func->prev)
    config->hash_func = config->hash_func->prev;
  while (config->hmac && config->hmac->prev)
    config->hmac = config->hmac->prev;
  while (config->conns && config->conns->prev)
    config->conns = config->conns->prev;
  
  SILC_LOG_DEBUG(("Done"));
  
  return TRUE;
}

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

bool silc_client_config_register_ciphers(SilcClientConfig config)
{
  SilcClientConfigSectionAlg *alg;
  SilcClient client = config->client;

  SILC_LOG_DEBUG(("Registering configured ciphers"));

  if (!config->cipher)
    return FALSE;

  alg = config->cipher;
  while(alg) {

    if (!alg->sim_name) {
      /* Crypto module is supposed to be built in. Get the pointer to the
	 built in cipher and register it. */
      int i;

      for (i = 0; silc_default_ciphers[i].name; i++)
	if (!strcmp(silc_default_ciphers[i].name, alg->alg_name)) {
	  silc_cipher_register(&silc_default_ciphers[i]);
	  break;
	}

      if (!silc_cipher_is_supported(alg->alg_name)) {
	SILC_LOG_ERROR(("Unknown cipher `%s'", alg->alg_name));
	silc_client_stop(client);
	exit(1);
      }

#ifdef SILC_SIM
    } else {
      /* Load (try at least) the crypto SIM module */
      SilcCipherObject cipher;
      SilcSimContext *sim;
      char *alg_name;

      memset(&cipher, 0, sizeof(cipher));
      cipher.name = alg->alg_name;
      cipher.block_len = alg->block_len;
      cipher.key_len = alg->key_len * 8;

      sim = silc_sim_alloc();
      sim->type = SILC_SIM_CIPHER;
      sim->libname = alg->sim_name;

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

      if ((silc_sim_load(sim))) {
	cipher.set_key = 
	  silc_sim_getsym(sim, silc_sim_symname(alg_name, 
						SILC_CIPHER_SIM_SET_KEY));
	SILC_LOG_DEBUG(("set_key=%p", cipher.set_key));
	cipher.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.set_key_with_string));
	cipher.encrypt = 
	  silc_sim_getsym(sim, silc_sim_symname(alg_name,
						SILC_CIPHER_SIM_ENCRYPT_CBC));
	SILC_LOG_DEBUG(("encrypt_cbc=%p", cipher.encrypt));
        cipher.decrypt = 
	  silc_sim_getsym(sim, silc_sim_symname(alg_name,
						SILC_CIPHER_SIM_DECRYPT_CBC));
	SILC_LOG_DEBUG(("decrypt_cbc=%p", cipher.decrypt));
        cipher.context_len = 
	  silc_sim_getsym(sim, silc_sim_symname(alg_name,
						SILC_CIPHER_SIM_CONTEXT_LEN));
	SILC_LOG_DEBUG(("context_len=%p", cipher.context_len));

	/* Put the SIM to the table of all SIM's in client */
	sims = silc_realloc(sims,
				   sizeof(*sims) * 
				   (sims_count + 1));
	sims[sims_count] = sim;
	sims_count++;

	silc_free(alg_name);
      } else {
	SILC_LOG_ERROR(("Error configuring ciphers"));
	silc_client_stop(client);
	exit(1);
      }

      /* Register the cipher */
      silc_cipher_register(&cipher);
#endif
    }

    alg = alg->next;
  }

  return TRUE;
}

/* Registers configured PKCS's. */

bool silc_client_config_register_pkcs(SilcClientConfig config)
{
  SilcClientConfigSectionAlg *alg = config->pkcs;
  SilcClient client = config->client;

  SILC_LOG_DEBUG(("Registering configured PKCS"));

  if (!alg)
    return FALSE;

  while(alg) {
    int i;
    
    for (i = 0; silc_default_pkcs[i].name; i++)
      if (!strcmp(silc_default_pkcs[i].name, alg->alg_name)) {
	silc_pkcs_register(&silc_default_pkcs[i]);
	break;
      }
    
    if (!silc_pkcs_is_supported(alg->alg_name)) {
      SILC_LOG_ERROR(("Unknown PKCS `%s'", alg->alg_name));
      silc_client_stop(client);
      exit(1);
    }

    alg = alg->next;
  }

  return TRUE;
}

/* Registers configured hash funtions. These can then be allocated by the
   client when needed. */

bool silc_client_config_register_hashfuncs(SilcClientConfig config)
{
  SilcClientConfigSectionAlg *alg;
  SilcClient client = config->client;

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

  if (!config->hash_func)
    return FALSE;

  alg = config->hash_func;
  while(alg) {
    if (!alg->sim_name) {
      int i;
      
      for (i = 0; silc_default_hash[i].name; i++)
	if (!strcmp(silc_default_hash[i].name, alg->alg_name)) {
	  silc_hash_register(&silc_default_hash[i]);
	  break;
	}
      
      if (!silc_hash_is_supported(alg->alg_name)) {
	SILC_LOG_ERROR(("Unknown hash function `%s'", alg->alg_name));
	silc_client_stop(client);
	exit(1);
      }
#ifdef SILC_SIM
    } else {
      /* Load (try at least) the hash SIM module */
      SilcHashObject hash;
      SilcSimContext *sim;

      memset(&hash, 0, sizeof(hash));
      hash.name = alg->alg_name;
      hash.block_len = alg->block_len;
      hash.hash_len = alg->key_len;

      sim = silc_sim_alloc();
      sim->type = SILC_SIM_HASH;
      sim->libname = alg->sim_name;

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

	/* Put the SIM to the table of all SIM's in client */
	sims = silc_realloc(sims,
				   sizeof(*sims) * 
				   (sims_count + 1));
	sims[sims_count] = sim;
	sims_count++;
      } else {
	SILC_LOG_ERROR(("Error configuring hash functions"));
	silc_client_stop(client);
	exit(1);
      }

      /* Register the hash function */
      silc_hash_register(&hash);
#endif
    }
    alg = alg->next;
  }

  return TRUE;
}

/* Registers configured HMACs. These can then be allocated by the
   client when needed. */

bool silc_client_config_register_hmacs(SilcClientConfig config)
{
  SilcClientConfigSectionAlg *alg;
  SilcClient client = config->client;

  SILC_LOG_DEBUG(("Registering configured HMACs"));

  if (!config->hmac)
    return FALSE;

  alg = config->hmac;
  while(alg) {
    SilcHmacObject hmac;
    
    if (!silc_hash_is_supported(alg->sim_name)) {
      SILC_LOG_ERROR(("Unknown hash function `%s' for HMAC `%s'", 
		      alg->sim_name, alg->alg_name));
      silc_client_stop(client);
      exit(1);
    }
    
    /* Register the HMAC */
    memset(&hmac, 0, sizeof(hmac));
    hmac.name = alg->alg_name;
    hmac.len = alg->key_len;
    silc_hmac_register(&hmac);

    alg = alg->next;
  }

  return TRUE;
}

SilcClientConfigSectionConnection *
silc_client_config_find_connection(SilcClientConfig config, 
				   char *host, int port)
{
  int i;
  SilcClientConfigSectionConnection *conn = NULL;

  SILC_LOG_DEBUG(("Finding connection"));

  if (!host)
    return NULL;

  if (!config->conns)
    return NULL;

  conn = config->conns;
  for (i = 0; conn; i++) {
    if (silc_string_compare(conn->host, host))
      break;
    conn = conn->next;
  }

  if (!conn)
    return NULL;

  SILC_LOG_DEBUG(("Found match"));

  return conn;
}
