/*

  client_channel.c

  Author: Pekka Riikonen <priikone@silcnet.org>

  Copyright (C) 1997 - 2004 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; version 2 of the License.

  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: client_channel.c,v 1.43.2.7 2004/02/28 14:43:29 priikone Exp $ */
/* This file includes channel message sending and receiving routines,
   channel key receiving and setting, and channel private key handling
   routines. */

#include "silcincludes.h"
#include "silcclient.h"
#include "client_internal.h"

/* Sends packet to the `channel'. Packet to channel is always encrypted
   differently from "normal" packets. SILC header of the packet is
   encrypted with the next receiver's key and the rest of the packet is
   encrypted with the channel specific key. Padding and HMAC is computed
   with the next receiver's key. The `data' is the channel message. If
   the `force_send' is TRUE then the packet is sent immediately. */

bool silc_client_send_channel_message(SilcClient client,
				      SilcClientConnection conn,
				      SilcChannelEntry channel,
				      SilcChannelPrivateKey key,
				      SilcMessageFlags flags,
				      unsigned char *data,
				      SilcUInt32 data_len,
				      bool force_send)
{
  SilcSocketConnection sock;
  SilcBuffer payload;
  SilcPacketContext packetdata;
  const SilcBufferStruct packet;
  SilcCipher cipher;
  SilcHmac hmac;
  unsigned char *id_string;
  int block_len;
  SilcChannelUser chu;
  bool ret = FALSE;

  assert(client && conn && channel);
  sock = conn->sock;
  SILC_LOG_DEBUG(("Sending packet to channel"));

  chu = silc_client_on_channel(channel, conn->local_entry);
  if (!chu) {
    SILC_LOG_ERROR(("Cannot send message to channel we are not joined"));
    return FALSE;
  }

  /* Check if it is allowed to send messages to this channel by us. */
  if (channel->mode & SILC_CHANNEL_MODE_SILENCE_USERS && !chu->mode)
    return FALSE;
  if (channel->mode & SILC_CHANNEL_MODE_SILENCE_OPERS &&
      chu->mode & SILC_CHANNEL_UMODE_CHANOP &&
      !(chu->mode & SILC_CHANNEL_UMODE_CHANFO))
    return FALSE;
  if (chu->mode & SILC_CHANNEL_UMODE_QUIET)
    return FALSE;

  /* Take the key to be used */
  if (channel->private_keys) {
    if (key) {
      /* Use key application specified */
      cipher = key->cipher;
      hmac = key->hmac;
    } else if (channel->mode & SILC_CHANNEL_MODE_PRIVKEY &&
	       channel->curr_key) {
      /* Use current private key */
      cipher = channel->curr_key->cipher;
      hmac = channel->curr_key->hmac;
    } else if (channel->mode & SILC_CHANNEL_MODE_PRIVKEY &&
	       !channel->curr_key && channel->private_keys) {
      /* Use just some private key since we don't know what to use
	 and private keys are set. */
      silc_dlist_start(channel->private_keys);
      key = silc_dlist_get(channel->private_keys);
      cipher = key->cipher;
      hmac = key->hmac;

      /* Use this key as current private key */
      channel->curr_key = key;
    } else {
      /* Use normal channel key generated by the server */
      cipher = channel->channel_key;
      hmac = channel->hmac;
    }
  } else {
    /* Use normal channel key generated by the server */
    cipher = channel->channel_key;
    hmac = channel->hmac;
  }

  if (!cipher || !hmac) {
    SILC_LOG_ERROR(("No cipher and HMAC for channel"));
    return FALSE;
  }

  block_len = silc_cipher_get_block_len(cipher);

  /* Encode the message payload. This also encrypts the message payload. */
  payload = silc_message_payload_encode(flags, data, data_len, TRUE, FALSE,
					cipher, hmac, client->rng, NULL,
					client->private_key, client->sha1hash);
  if (!payload) {
    SILC_LOG_ERROR(("Error encoding channel message"));
    return FALSE;
  }

  /* Get data used in packet header encryption, keys and stuff. */
  cipher = conn->internal->send_key;
  hmac = conn->internal->hmac_send;
  id_string = silc_id_id2str(channel->id, SILC_ID_CHANNEL);

  /* Set the packet context pointers. The destination ID is always
     the Channel ID of the channel. Server and router will handle the
     distribution of the packet. */
  data = payload->data;
  data_len = payload->len;
  packetdata.flags = 0;
  packetdata.type = SILC_PACKET_CHANNEL_MESSAGE;
  packetdata.src_id = conn->local_id_data;
  packetdata.src_id_len = silc_id_get_len(conn->local_id, SILC_ID_CLIENT);
  packetdata.src_id_type = SILC_ID_CLIENT;
  packetdata.dst_id = id_string;
  packetdata.dst_id_len = silc_id_get_len(channel->id, SILC_ID_CHANNEL);
  packetdata.dst_id_type = SILC_ID_CHANNEL;
  data_len = SILC_PACKET_DATALEN(data_len, SILC_PACKET_HEADER_LEN +
				 packetdata.src_id_len +
				 packetdata.dst_id_len);
  packetdata.truelen = data_len + SILC_PACKET_HEADER_LEN +
    packetdata.src_id_len + packetdata.dst_id_len;
  SILC_PACKET_PADLEN((SILC_PACKET_HEADER_LEN +
		      packetdata.src_id_len +
		      packetdata.dst_id_len), block_len, packetdata.padlen);

  /* Create the outgoing packet */
  if (!silc_packet_assemble(&packetdata, client->rng, cipher, hmac, sock,
                            data, data_len, (const SilcBuffer)&packet)) {
    SILC_LOG_ERROR(("Error assembling packet"));
    goto out;
  }

  /* Encrypt the header and padding of the packet. This is encrypted
     with normal session key shared with our server. */
  silc_packet_encrypt(cipher, hmac, conn->internal->psn_send++,
		      (SilcBuffer)&packet, SILC_PACKET_HEADER_LEN +
		      packetdata.src_id_len + packetdata.dst_id_len +
		      packetdata.padlen);

  SILC_LOG_HEXDUMP(("Packet to channel, len %d", packet.len),
		    packet.data, packet.len);

  /* Now actually send the packet */
  silc_client_packet_send_real(client, sock, force_send);

  /* Check for mandatory rekey */
  if (conn->internal->psn_send == SILC_CLIENT_REKEY_THRESHOLD)
    silc_schedule_task_add(client->schedule, sock->sock,
			   silc_client_rekey_callback, sock, 0, 1,
			   SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);

  ret = TRUE;

 out:
  silc_buffer_free(payload);
  silc_free(id_string);

  return ret;
}

typedef struct {
  SilcMessagePayload payload;
  SilcChannelID *channel_id;
  SilcChannelPrivateKey key;
} *SilcChannelClientResolve;

static void silc_client_channel_message_cb(SilcClient client,
					   SilcClientConnection conn,
					   SilcClientEntry *clients,
					   SilcUInt32 clients_count,
					   void *context)
{
  SilcChannelClientResolve res = (SilcChannelClientResolve)context;

  if (clients_count == 1) {
    SilcChannelEntry channel;
    unsigned char *message;
    SilcUInt32 message_len;

    channel = silc_client_get_channel_by_id(client, conn, res->channel_id);
    if (!channel)
      goto out;

    /* If this client is not on channel, add it there since it clearly
       is there. */
    if (!silc_client_on_channel(channel, clients[0])) {
      SilcChannelUser chu = silc_calloc(1, sizeof(*chu));
      chu->client = clients[0];
      chu->channel = channel;
      silc_hash_table_add(channel->user_list, clients[0], chu);
      silc_hash_table_add(clients[0]->channels, channel, chu);
    }

    message = silc_message_get_data(res->payload, &message_len);

    /* Pass the message to application */
    client->internal->ops->channel_message(
			    client, conn, clients[0], channel, res->payload,
			    res->key, silc_message_get_flags(res->payload),
			    message, message_len);
  }

 out:
  silc_message_payload_free(res->payload);
  silc_free(res->channel_id);
  silc_free(res);
}

/* Process received message to a channel (or from a channel, really). This
   decrypts the channel message with channel specific key and parses the
   message payload. Finally it displays the message on the screen. */

void silc_client_channel_message(SilcClient client,
				 SilcSocketConnection sock,
				 SilcPacketContext *packet)
{
  SilcClientConnection conn = (SilcClientConnection)sock->user_data;
  SilcBuffer buffer = packet->buffer;
  SilcMessagePayload payload = NULL;
  SilcChannelID *id = NULL;
  SilcChannelEntry channel;
  SilcClientEntry client_entry;
  SilcClientID *client_id = NULL;
  unsigned char *message;
  SilcUInt32 message_len;
  SilcChannelPrivateKey key = NULL;

  SILC_LOG_DEBUG(("Received channel message"));

  /* Sanity checks */
  if (packet->dst_id_type != SILC_ID_CHANNEL)
    goto out;

  client_id = silc_id_str2id(packet->src_id, packet->src_id_len,
			     SILC_ID_CLIENT);
  if (!client_id)
    goto out;
  id = silc_id_str2id(packet->dst_id, packet->dst_id_len, SILC_ID_CHANNEL);
  if (!id)
    goto out;

  /* Find the channel entry from channels on this connection */
  channel = silc_client_get_channel_by_id(client, conn, id);
  if (!channel)
    goto out;

  /* If there is no channel private key then just decrypt the message
     with the channel key. If private keys are set then just go through
     all private keys and check what decrypts correctly. */
  if (!channel->private_keys) {
    /* Parse the channel message payload. This also decrypts the payload */
    payload = silc_message_payload_parse(buffer->data, buffer->len, FALSE,
					 FALSE, channel->channel_key,
					 channel->hmac);

    /* If decryption failed and we have just performed channel key rekey
       we will use the old key in decryption. If that fails too then we
       cannot do more and will drop the packet. */
    if (!payload) {
      SilcCipher key;
      SilcHmac hmac;
      int i;

      if (!channel->old_channel_keys ||
	  !silc_dlist_count(channel->old_channel_keys))
	goto out;

      SILC_LOG_DEBUG(("Attempting to decrypt with old channel key(s)"));

      silc_dlist_end(channel->old_channel_keys);
      silc_dlist_end(channel->old_hmacs);
      for (i = 0; i < silc_dlist_count(channel->old_channel_keys); i++) {
	key = silc_dlist_get(channel->old_channel_keys);
	hmac = silc_dlist_get(channel->old_hmacs);
	if (!key || !hmac)
	  break;

	payload = silc_message_payload_parse(buffer->data, buffer->len,
					     FALSE, FALSE, key, hmac);
	if (payload)
	  break;
      }
      if (!payload)
	goto out;
    }
  } else {
    /* If the private key mode, however is not set on the channel then
       try the actual channel key first before trying private keys. */
    if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY))
      payload = silc_message_payload_parse(buffer->data, buffer->len, FALSE,
					   FALSE, channel->channel_key,
					   channel->hmac);

    if (!payload) {
      silc_dlist_start(channel->private_keys);
      while ((key = silc_dlist_get(channel->private_keys))
	     != SILC_LIST_END) {
	/* Parse the message payload. This also decrypts the payload */
	payload = silc_message_payload_parse(buffer->data, buffer->len,
					     FALSE, FALSE,
					     key->cipher, key->hmac);
	if (payload)
	  break;
      }
      if (key == SILC_LIST_END)
	goto out;
    }
  }

  /* Find client entry */
  client_entry = silc_client_get_client_by_id(client, conn, client_id);
  if (!client_entry || !client_entry->nickname ||
      !silc_client_on_channel(channel, client_entry)) {
    /* Resolve the client info */
    SilcChannelClientResolve res = silc_calloc(1, sizeof(*res));
    res->payload = payload;
    res->channel_id = id;
    res->key = key;
    silc_client_get_client_by_id_resolve(client, conn, client_id, NULL,
					 silc_client_channel_message_cb,
					 res);
    payload = NULL;
    id = NULL;
    goto out;
  }

  message = silc_message_get_data(payload, &message_len);

  /* Pass the message to application */
  client->internal->ops->channel_message(
			     client, conn, client_entry, channel, payload,
			     key, silc_message_get_flags(payload),
			     message, message_len);

 out:
  silc_free(id);
  silc_free(client_id);
  if (payload)
    silc_message_payload_free(payload);
}

/* Timeout callback that is called after a short period of time after the
   new channel key has been created.  This removes the first channel key
   in the list. */

SILC_TASK_CALLBACK(silc_client_save_channel_key_rekey)
{
  SilcChannelEntry channel = (SilcChannelEntry)context;
  SilcCipher key;
  SilcHmac hmac;

  if (channel->old_channel_keys) {
    silc_dlist_start(channel->old_channel_keys);
    key = silc_dlist_get(channel->old_channel_keys);
    if (key) {
      silc_dlist_del(channel->old_channel_keys, key);
      silc_cipher_free(key);
    }
  }

  if (channel->old_hmacs) {
    silc_dlist_start(channel->old_hmacs);
    hmac = silc_dlist_get(channel->old_hmacs);
    if (hmac) {
      silc_dlist_del(channel->old_hmacs, hmac);
      silc_hmac_free(hmac);
    }
  }
}

/* Saves channel key from encoded `key_payload'. This is used when we
   receive Channel Key Payload and when we are processing JOIN command
   reply. */

void silc_client_save_channel_key(SilcClient client,
				  SilcClientConnection conn,
				  SilcBuffer key_payload,
				  SilcChannelEntry channel)
{
  unsigned char *id_string, *key, *cipher, *hmac, hash[32];
  SilcUInt32 tmp_len;
  SilcChannelID *id;
  SilcChannelKeyPayload payload;

  payload = silc_channel_key_payload_parse(key_payload->data,
					   key_payload->len);
  if (!payload)
    return;

  id_string = silc_channel_key_get_id(payload, &tmp_len);
  if (!id_string) {
    silc_channel_key_payload_free(payload);
    return;
  }

  id = silc_id_str2id(id_string, tmp_len, SILC_ID_CHANNEL);
  if (!id) {
    silc_channel_key_payload_free(payload);
    return;
  }

  /* Find channel. */
  if (!channel) {
    channel = silc_client_get_channel_by_id(client, conn, id);
    if (!channel)
      goto out;
  }

  hmac = (channel->hmac ? (char *)silc_hmac_get_name(channel->hmac) :
	  SILC_DEFAULT_HMAC);

  /* Save the old key for a short period of time so that we can decrypt
     channel message even after the rekey if some client would be sending
     messages with the old key after the rekey. */
  if (!channel->old_channel_keys)
    channel->old_channel_keys = silc_dlist_init();
  if (!channel->old_hmacs)
    channel->old_hmacs = silc_dlist_init();
  silc_dlist_add(channel->old_channel_keys, channel->channel_key);
  silc_dlist_add(channel->old_hmacs, channel->hmac);
  silc_schedule_task_add(client->schedule, 0,
			 silc_client_save_channel_key_rekey, channel,
			 10, 0, SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);

  /* Free the old channel key data */
  silc_free(channel->key);

  /* Save the key */
  key = silc_channel_key_get_key(payload, &tmp_len);
  cipher = silc_channel_key_get_cipher(payload, NULL);
  channel->key_len = tmp_len * 8;
  channel->key = silc_memdup(key, tmp_len);

  if (!silc_cipher_alloc(cipher, &channel->channel_key)) {
    client->internal->ops->say(
			   conn->client, conn,
			   SILC_CLIENT_MESSAGE_AUDIT,
			   "Cannot talk to channel: unsupported cipher %s",
			   cipher);
    goto out;
  }

  /* Set the cipher key */
  silc_cipher_set_key(channel->channel_key, key, channel->key_len);

  /* Generate HMAC key from the channel key data and set it */
  silc_hmac_alloc(hmac, NULL, &channel->hmac);
  silc_hash_make(silc_hmac_get_hash(channel->hmac), key, tmp_len, hash);
  silc_hmac_set_key(channel->hmac, hash,
		    silc_hash_len(silc_hmac_get_hash(channel->hmac)));
  memset(hash, 0, sizeof(hash));

 out:
  silc_free(id);
  silc_channel_key_payload_free(payload);
}

/* Processes received key for channel. The received key will be used
   to protect the traffic on the channel for now on. Client must receive
   the key to the channel before talking on the channel is possible.
   This is the key that server has generated, this is not the channel
   private key, it is entirely local setting. */

void silc_client_receive_channel_key(SilcClient client,
				     SilcSocketConnection sock,
				     SilcBuffer packet)
{
  SILC_LOG_DEBUG(("Received key for channel"));

  /* Save the key */
  silc_client_save_channel_key(client, sock->user_data, packet, NULL);
}

/* Adds private key for channel. When channel has private key then the
   messages are encrypted using that key. All clients on the channel must
   also know the key in order to decrypt the messages. However, it is
   possible to have several private keys per one channel. In this case
   only some of the clients on the channel may know the one key and only
   some the other key.

   If `cipher' and/or `hmac' is NULL then default values will be used
   (aes-256-cbc for cipher and hmac-sha1-96 for hmac).

   The private key for channel is optional. If it is not set then the
   channel messages are encrypted using the channel key generated by the
   server. However, setting the private key (or keys) for the channel
   significantly adds security. If more than one key is set the library
   will automatically try all keys at the message decryption phase. Note:
   setting many keys slows down the decryption phase as all keys has to
   be tried in order to find the correct decryption key. However, setting
   a few keys does not have big impact to the decryption performace.

   NOTE: that this is entirely local setting. The key set using this function
   is not sent to the network at any phase.

   NOTE: If the key material was originated by the SKE protocol (using
   silc_client_send_key_agreement) then the `key' MUST be the
   key->send_enc_key as this is dictated by the SILC protocol. However,
   currently it is not expected that the SKE key material would be used
   as channel private key. However, this API allows it. */

bool silc_client_add_channel_private_key(SilcClient client,
					 SilcClientConnection conn,
					 SilcChannelEntry channel,
					 const char *name,
					 char *cipher,
					 char *hmac,
					 unsigned char *key,
					 SilcUInt32 key_len,
					 SilcChannelPrivateKey *ret_key)
{
  SilcChannelPrivateKey entry;
  unsigned char hash[32];
  SilcSKEKeyMaterial *keymat;

  assert(client && channel);

  if (!cipher)
    cipher = SILC_DEFAULT_CIPHER;
  if (!hmac)
    hmac = SILC_DEFAULT_HMAC;

  if (!silc_cipher_is_supported(cipher))
    return FALSE;

  if (!silc_hmac_is_supported(hmac))
    return FALSE;

  /* Produce the key material */
  keymat = silc_calloc(1, sizeof(*keymat));
  if (silc_ske_process_key_material_data(key, key_len, 16, 256, 16,
					 client->sha1hash, keymat)
      != SILC_SKE_STATUS_OK)
    return FALSE;

  if (!channel->private_keys)
    channel->private_keys = silc_dlist_init();

  /* Save the key */
  entry = silc_calloc(1, sizeof(*entry));
  entry->name = name ? strdup(name) : NULL;
  entry->key = silc_memdup(keymat->send_enc_key, keymat->enc_key_len / 8);
  entry->key_len = keymat->enc_key_len / 8;

  /* Allocate the cipher and set the key*/
  silc_cipher_alloc(cipher, &entry->cipher);
  silc_cipher_set_key(entry->cipher, entry->key, keymat->enc_key_len);

  /* Generate HMAC key from the channel key data and set it */
  silc_hmac_alloc(hmac, NULL, &entry->hmac);
  silc_hash_make(silc_hmac_get_hash(entry->hmac), entry->key,
		 entry->key_len, hash);
  silc_hmac_set_key(entry->hmac, hash,
		    silc_hash_len(silc_hmac_get_hash(entry->hmac)));
  memset(hash, 0, sizeof(hash));

  /* Add to the private keys list */
  silc_dlist_add(channel->private_keys, entry);

  if (!channel->curr_key)
    channel->curr_key = entry;

  /* Free the key material */
  silc_ske_free_key_material(keymat);

  if (ret_key)
    *ret_key = entry;

  return TRUE;
}

/* Removes all private keys from the `channel'. The old channel key is used
   after calling this to protect the channel messages. Returns FALSE on
   on error, TRUE otherwise. */

bool silc_client_del_channel_private_keys(SilcClient client,
					 SilcClientConnection conn,
					 SilcChannelEntry channel)
{
  SilcChannelPrivateKey entry;

  assert(client && channel);

  if (!channel->private_keys)
    return FALSE;

  silc_dlist_start(channel->private_keys);
  while ((entry = silc_dlist_get(channel->private_keys)) != SILC_LIST_END) {
    silc_dlist_del(channel->private_keys, entry);
    memset(entry->key, 0, entry->key_len);
    silc_free(entry->key);
    silc_free(entry->name);
    silc_cipher_free(entry->cipher);
    silc_hmac_free(entry->hmac);
    silc_free(entry);
  }

  channel->curr_key = NULL;

  silc_dlist_uninit(channel->private_keys);
  channel->private_keys = NULL;

  return TRUE;
}

/* Removes and frees private key `key' from the channel `channel'. The `key'
   is retrieved by calling the function silc_client_list_channel_private_keys.
   The key is not used after this. If the key was last private key then the
   old channel key is used hereafter to protect the channel messages. This
   returns FALSE on error, TRUE otherwise. */

bool silc_client_del_channel_private_key(SilcClient client,
					SilcClientConnection conn,
					SilcChannelEntry channel,
					SilcChannelPrivateKey key)
{
  SilcChannelPrivateKey entry;

  assert(client && channel);

  if (!channel->private_keys)
    return FALSE;

  silc_dlist_start(channel->private_keys);
  while ((entry = silc_dlist_get(channel->private_keys)) != SILC_LIST_END) {
    if (entry == key) {
      if (channel->curr_key == entry)
	channel->curr_key = NULL;

      silc_dlist_del(channel->private_keys, entry);
      memset(entry->key, 0, entry->key_len);
      silc_free(entry->key);
      silc_free(entry->name);
      silc_cipher_free(entry->cipher);
      silc_hmac_free(entry->hmac);
      silc_free(entry);

      if (silc_dlist_count(channel->private_keys) == 0) {
	silc_dlist_uninit(channel->private_keys);
	channel->private_keys = NULL;
      }

      return TRUE;
    }
  }

  return FALSE;
}

/* Returns array (pointers) of private keys associated to the `channel'.
   The caller must free the array by calling the function
   silc_client_free_channel_private_keys. The pointers in the array may be
   used to delete the specific key by giving the pointer as argument to the
   function silc_client_del_channel_private_key. */

SilcChannelPrivateKey *
silc_client_list_channel_private_keys(SilcClient client,
				      SilcClientConnection conn,
				      SilcChannelEntry channel,
				      SilcUInt32 *key_count)
{
  SilcChannelPrivateKey *keys = NULL, entry;
  SilcUInt32 count = 0;

  assert(client && channel);

  if (!channel->private_keys)
    return NULL;

  silc_dlist_start(channel->private_keys);
  while ((entry = silc_dlist_get(channel->private_keys)) != SILC_LIST_END) {
    keys = silc_realloc(keys, sizeof(*keys) * (count + 1));
    keys[count] = entry;
    count++;
  }

  if (key_count)
    *key_count = count;

  return keys;
}

/* Frees the SilcChannelPrivateKey array. */

void silc_client_free_channel_private_keys(SilcChannelPrivateKey *keys,
					   SilcUInt32 key_count)
{
  silc_free(keys);
}

/* Sets the `key' to be used as current channel private key on the
   `channel'.  Packet sent after calling this function will be secured
   with `key'. */

void silc_client_current_channel_private_key(SilcClient client,
					     SilcClientConnection conn,
					     SilcChannelEntry channel,
					     SilcChannelPrivateKey key)
{
  assert(client && channel);
  channel->curr_key = key;
}

/* Returns the SilcChannelUser entry if the `client_entry' is joined on the
   channel indicated by the `channel'. NULL if client is not joined on
   the channel. */

SilcChannelUser silc_client_on_channel(SilcChannelEntry channel,
				       SilcClientEntry client_entry)
{
  SilcChannelUser chu;

  if (silc_hash_table_find(channel->user_list, client_entry, NULL,
			   (void *)&chu))
    return chu;

  return NULL;
}
