/*

  serverid.c

  Author: Pekka Riikonen <priikone@silcnet.org>

  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
  GNU General Public License for more details.

*/
/* $Id: serverid.c,v 1.13.2.1 2003/01/26 11:48:04 priikone Exp $ */

#include "serverincludes.h"
#include "server_internal.h"

/* Creates a Server ID. Newly created Server ID is returned to the
   new_id argument. */

void silc_id_create_server_id(const char *ip, SilcUInt16 port, SilcRng rng,
			      SilcServerID **new_id)
{
  SILC_LOG_DEBUG(("Creating new Server ID"));

  *new_id = silc_calloc(1, sizeof(**new_id));

  /* Create the ID */

  if (!silc_net_addr2bin(ip, (*new_id)->ip.data,
			 sizeof((*new_id)->ip.data))) {
    silc_free(*new_id);
    *new_id = NULL;
    return;
  }

  (*new_id)->ip.data_len = silc_net_is_ip4(ip) ? 4 : 16;
  (*new_id)->port = SILC_SWAB_16(port);
  (*new_id)->rnd = silc_rng_get_rn16(rng);

  SILC_LOG_DEBUG(("New ID (%s)", silc_id_render(*new_id, SILC_ID_SERVER)));
}

/* Creates Client ID. This assures that there are no collisions in the
   created Client IDs.  If the collision would occur (meaning that there
   are 2^8 occurences of the `nickname' this will return FALSE, and the
   caller must recall the function with different nickname. If this returns
   TRUE the new ID was created successfully. */

bool silc_id_create_client_id(SilcServer server,
			      SilcServerID *server_id, SilcRng rng,
			      SilcHash md5hash, char *nickname, 
			      SilcClientID **new_id)
{
  unsigned char hash[16];
  bool finding = FALSE;
  char nick[128 + 1];

  SILC_LOG_DEBUG(("Creating new Client ID"));

  *new_id = silc_calloc(1, sizeof(**new_id));

  /* Create hash of the nickanem */
  memset(nick, 0, sizeof(nick));
  silc_to_lower(nickname, nick, sizeof(nick) - 1);
  silc_hash_make(md5hash, nick, strlen(nick), hash);

  /* Create the ID */
  memcpy((*new_id)->ip.data, server_id->ip.data, server_id->ip.data_len);
  (*new_id)->ip.data_len = server_id->ip.data_len;
  (*new_id)->rnd = silc_rng_get_byte(rng);
  memcpy((*new_id)->hash, hash, CLIENTID_HASH_LEN);

  /* Assure that the ID does not exist already */
  while (1) {
    if (!silc_idlist_find_client_by_id(server->local_list, 
				       *new_id, FALSE, NULL))
      if (!silc_idlist_find_client_by_id(server->global_list, 
					 *new_id, FALSE, NULL))
	break;

    /* The ID exists, start increasing the rnd from 0 until we find a
       ID that does not exist. If we wrap and it still exists then we
       will return FALSE and the caller must send some other nickname
       since this cannot be used anymore. */
    (*new_id)->rnd++;

    if (finding && (*new_id)->rnd == 0)
      return FALSE;

    if (!finding) {
      (*new_id)->rnd = 0;
      finding = TRUE;
    }
  }

  SILC_LOG_DEBUG(("New ID (%s)", silc_id_render(*new_id, SILC_ID_CLIENT)));

  return TRUE;
}

/* Creates Channel ID */

bool silc_id_create_channel_id(SilcServer server,
			       SilcServerID *router_id, SilcRng rng,
			       SilcChannelID **new_id)
{
  bool finding = TRUE;

  SILC_LOG_DEBUG(("Creating new Channel ID"));

  *new_id = silc_calloc(1, sizeof(**new_id));

  /* Create the ID */
  memcpy((*new_id)->ip.data, router_id->ip.data, router_id->ip.data_len);
  (*new_id)->ip.data_len = router_id->ip.data_len;
  (*new_id)->port = router_id->port;
  (*new_id)->rnd = silc_rng_get_rn16(rng);

  /* Assure that the ID does not exist already */
  while (1) {
    if (!silc_idlist_find_channel_by_id(server->local_list, 
					*new_id, NULL))
      break;

    (*new_id)->rnd++;
    
    if (finding && (*new_id)->rnd == 0)
      return FALSE;
    
    if (!finding) {
      (*new_id)->rnd = 0;
      finding = TRUE;
    }
  }

  SILC_LOG_DEBUG(("New ID (%s)", silc_id_render(*new_id, SILC_ID_CHANNEL)));

  return TRUE;
}

/* Checks whether the `server_id' is valid.  It must be based to the
   IP address provided in the `remote' socket connection. */

bool silc_id_is_valid_server_id(SilcServer server,
				SilcServerID *server_id,
				SilcSocketConnection remote)
{
  unsigned char ip[16];

  if (!silc_net_addr2bin(remote->ip, ip, sizeof(ip)))
    return FALSE;

  if (silc_net_is_ip4(remote->ip)) {
    if (!memcmp(server_id->ip.data, ip, 4))
      return TRUE;
  } else {
    if (!memcmp(server_id->ip.data, ip, 16))
      return TRUE;
  }

  return FALSE;
}
