updates.
[silc.git] / lib / silcclient / client_channel.c
index 62396c5f0dda0a4e98acb598ddb8526ebbf2049d..0f952373a55b747708aa10c6ea73b551526e5577 100644 (file)
@@ -49,6 +49,7 @@ void silc_client_send_channel_message(SilcClient client,
   SilcHmac hmac;
   unsigned char *id_string;
   uint32 iv_len;
+  int block_len;
 
   SILC_LOG_DEBUG(("Sending packet to channel"));
 
@@ -86,13 +87,16 @@ void silc_client_send_channel_message(SilcClient client,
   if (!cipher || !hmac)
     return;
 
+  block_len = silc_cipher_get_block_len(cipher);
+
   /* Generate IV */
   iv_len = silc_cipher_get_block_len(cipher);
   if (channel->iv[0] == '\0')
     for (i = 0; i < iv_len; i++) channel->iv[i] = 
                                   silc_rng_get_byte(client->rng);
   else
-    silc_hash_make(client->md5hash, channel->iv, iv_len, channel->iv);
+    silc_hash_make(client->internal->md5hash, channel->iv, iv_len, 
+                  channel->iv);
 
   /* Encode the channel payload. This also encrypts the message payload. */
   payload = silc_channel_message_payload_encode(flags, data_len, data, iv_len, 
@@ -109,16 +113,16 @@ void silc_client_send_channel_message(SilcClient client,
   packetdata.flags = 0;
   packetdata.type = SILC_PACKET_CHANNEL_MESSAGE;
   packetdata.src_id = conn->local_id_data;
-  packetdata.src_id_len = SILC_ID_CLIENT_LEN;
+  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_CHANNEL_LEN;
+  packetdata.dst_id_len = silc_id_get_len(channel->id, SILC_ID_CHANNEL);
   packetdata.dst_id_type = SILC_ID_CHANNEL;
   packetdata.truelen = payload->len + SILC_PACKET_HEADER_LEN + 
     packetdata.src_id_len + packetdata.dst_id_len;
   packetdata.padlen = SILC_PACKET_PADLEN((SILC_PACKET_HEADER_LEN +
                                          packetdata.src_id_len +
-                                         packetdata.dst_id_len));
+                                         packetdata.dst_id_len), block_len);
 
   /* Prepare outgoing data buffer for packet sending */
   silc_packet_send_prepare(sock, 
@@ -134,11 +138,12 @@ void silc_client_send_channel_message(SilcClient client,
   silc_buffer_put(sock->outbuf, payload->data, payload->len);
 
   /* Create the outgoing packet */
-  silc_packet_assemble(&packetdata);
+  silc_packet_assemble(&packetdata, cipher);
 
   /* Encrypt the header and padding of the packet. This is encrypted 
      with normal session key shared with our server. */
-  silc_packet_encrypt(cipher, hmac, sock->outbuf, SILC_PACKET_HEADER_LEN + 
+  silc_packet_encrypt(cipher, hmac, conn->psn_send++,
+                     sock->outbuf, SILC_PACKET_HEADER_LEN + 
                      packetdata.src_id_len + packetdata.dst_id_len +
                      packetdata.padlen);
 
@@ -146,11 +151,49 @@ void silc_client_send_channel_message(SilcClient client,
                   sock->outbuf->data, sock->outbuf->len);
 
   /* Now actually send the packet */
-  silc_client_packet_send_real(client, sock, force_send, FALSE);
+  silc_client_packet_send_real(client, sock, force_send);
   silc_buffer_free(payload);
   silc_free(id_string);
 }
 
+typedef struct {
+  SilcChannelMessagePayload payload;
+  SilcChannelID *channel_id;
+} *SilcChannelClientResolve;
+
+static void silc_client_channel_message_cb(SilcClient client,
+                                          SilcClientConnection conn,
+                                          SilcClientEntry *clients,
+                                          uint32 clients_count,
+                                          void *context)
+{
+  SilcChannelClientResolve res = (SilcChannelClientResolve)context;
+
+  if (clients_count == 1) {
+    SilcIDCacheEntry id_cache = NULL;
+    SilcChannelEntry channel;
+    unsigned char *message;
+
+    if (!silc_idcache_find_by_id_one(conn->channel_cache, res->channel_id, 
+                                    &id_cache))
+      goto out;
+
+    channel = (SilcChannelEntry)id_cache->context;
+    message = silc_channel_message_get_data(res->payload, NULL);
+    
+    /* Pass the message to application */
+    client->internal->ops->channel_message(
+                              client, conn, clients[0], channel,
+                              silc_channel_message_get_flags(res->payload),
+                              message);
+  }
+
+ out:
+  silc_channel_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
    channel payload. Finally it displays the message on the screen. */
@@ -167,7 +210,7 @@ void silc_client_channel_message(SilcClient client,
   SilcChannelUser chu;
   SilcIDCacheEntry id_cache = NULL;
   SilcClientID *client_id = NULL;
-  int found = FALSE;
+  bool found = FALSE;
   unsigned char *message;
 
   SILC_LOG_DEBUG(("Start"));
@@ -185,8 +228,7 @@ void silc_client_channel_message(SilcClient client,
     goto out;
 
   /* Find the channel entry from channels on this connection */
-  if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)id,
-                                  SILC_ID_CHANNEL, &id_cache))
+  if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)id, &id_cache))
     goto out;
 
   channel = (SilcChannelEntry)id_cache->context;
@@ -196,17 +238,31 @@ void silc_client_channel_message(SilcClient client,
      all private keys and check what decrypts correctly. */
   if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY)) {
     /* Parse the channel message payload. This also decrypts the payload */
-    payload = silc_channel_message_payload_parse(buffer, channel->channel_key,
+    payload = silc_channel_message_payload_parse(buffer->data, buffer->len, 
+                                                channel->channel_key,
                                                 channel->hmac);
-    if (!payload)
-      goto out;
+
+    /* 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) {
+      if (!channel->old_channel_key)
+       goto out;
+
+      payload = silc_channel_message_payload_parse(buffer->data, buffer->len, 
+                                                  channel->old_channel_key,
+                                                  channel->old_hmac);
+      if (!payload)
+       goto out;
+    }
   } else if (channel->private_keys) {
     SilcChannelPrivateKey entry;
 
     silc_dlist_start(channel->private_keys);
     while ((entry = silc_dlist_get(channel->private_keys)) != SILC_LIST_END) {
       /* Parse the channel message payload. This also decrypts the payload */
-      payload = silc_channel_message_payload_parse(buffer, entry->cipher,
+      payload = silc_channel_message_payload_parse(buffer->data, buffer->len, 
+                                                  entry->cipher,
                                                   entry->hmac);
       if (payload)
        break;
@@ -217,22 +273,36 @@ void silc_client_channel_message(SilcClient client,
     goto out;
   }
 
-  message = silc_channel_message_get_data(payload, NULL);
-
   /* Find client entry */
   silc_list_start(channel->clients);
   while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
-    if (!SILC_ID_CLIENT_COMPARE(chu->client->id, client_id)) {
+    if (SILC_ID_CLIENT_COMPARE(chu->client->id, client_id) && 
+       chu->client->nickname) {
       found = TRUE;
       break;
     }
   }
 
+  if (!found) {
+    /* Resolve the client info */
+    SilcChannelClientResolve res = silc_calloc(1, sizeof(*res));
+    res->payload = payload;
+    res->channel_id = id;
+    silc_client_get_client_by_id_resolve(client, conn, client_id,
+                                        silc_client_channel_message_cb,
+                                        res);
+    payload = NULL;
+    id = NULL;
+    goto out;
+  }
+
+  message = silc_channel_message_get_data(payload, NULL);
+
   /* Pass the message to application */
-  client->ops->channel_message(client, conn, found ? chu->client : NULL,
-                              channel, 
-                              silc_channel_message_get_flags(payload),
-                              message);
+  client->internal->ops->channel_message(
+                                client, conn, chu->client, channel,
+                                silc_channel_message_get_flags(payload),
+                                message);
 
  out:
   if (id)
@@ -243,6 +313,23 @@ void silc_client_channel_message(SilcClient client,
     silc_channel_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 old channel key all
+   together. */
+
+SILC_TASK_CALLBACK(silc_client_save_channel_key_rekey)
+{
+  SilcChannelEntry channel = (SilcChannelEntry)context;
+
+  if (channel->old_channel_key)
+    silc_cipher_free(channel->old_channel_key);
+  if (channel->old_hmac)
+    silc_hmac_free(channel->old_hmac);
+  channel->old_channel_key = NULL;
+  channel->old_hmac = NULL;
+  channel->rekey_task = NULL;
+}
+
 /* Saves channel key from encoded `key_payload'. This is used when we
    receive Channel Key Payload and when we are processing JOIN command 
    reply. */
@@ -251,13 +338,14 @@ void silc_client_save_channel_key(SilcClientConnection conn,
                                  SilcBuffer key_payload, 
                                  SilcChannelEntry channel)
 {
-  unsigned char *id_string, *key, *cipher, hash[32];
+  unsigned char *id_string, *key, *cipher, *hmac, hash[32];
   uint32 tmp_len;
   SilcChannelID *id;
   SilcIDCacheEntry id_cache = NULL;
   SilcChannelKeyPayload payload;
 
-  payload = silc_channel_key_payload_parse(key_payload);
+  payload = silc_channel_key_payload_parse(key_payload->data,
+                                          key_payload->len);
   if (!payload)
     return;
 
@@ -275,14 +363,36 @@ void silc_client_save_channel_key(SilcClientConnection conn,
 
   /* Find channel. */
   if (!channel) {
-    if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)id,
-                                    SILC_ID_CHANNEL, &id_cache))
+    if (!silc_idcache_find_by_id_one(conn->channel_cache, 
+                                    (void *)id, &id_cache))
       goto out;
     
     /* Get channel entry */
     channel = (SilcChannelEntry)id_cache->context;
   }
 
+  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_key)
+    silc_cipher_free(channel->old_channel_key);
+  if (channel->old_hmac)
+    silc_hmac_free(channel->old_hmac);
+  if (channel->rekey_task)
+    silc_schedule_task_del(conn->client->schedule, channel->rekey_task);
+  channel->old_channel_key = channel->channel_key;
+  channel->old_hmac = channel->hmac;
+  channel->rekey_task = 
+    silc_schedule_task_add(conn->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);
@@ -291,8 +401,11 @@ void silc_client_save_channel_key(SilcClientConnection conn,
   memcpy(channel->key, key, tmp_len);
 
   if (!silc_cipher_alloc(cipher, &channel->channel_key)) {
-    conn->client->ops->say(conn->client, conn,
-                    "Cannot talk to channel: unsupported cipher %s", cipher);
+    conn->client->internal->ops->say(
+                          conn->client, conn, 
+                          SILC_CLIENT_MESSAGE_AUDIT,
+                          "Cannot talk to channel: unsupported cipher %s", 
+                          cipher);
     goto out;
   }
 
@@ -300,10 +413,10 @@ void silc_client_save_channel_key(SilcClientConnection conn,
   silc_cipher_set_key(channel->channel_key, key, channel->key_len);
 
   /* Generate HMAC key from the channel key data and set it */
-  if (!channel->hmac)
-    silc_hmac_alloc("hmac-sha1-96", NULL, &channel->hmac);
-  silc_hash_make(channel->hmac->hash, key, tmp_len, hash);
-  silc_hmac_set_key(channel->hmac, hash, silc_hash_len(channel->hmac->hash));
+  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:
@@ -372,9 +485,9 @@ int silc_client_add_channel_private_key(SilcClient client,
     return FALSE;
 
   if (!cipher)
-    cipher = "aes-256-cbc";
+    cipher = SILC_DEFAULT_CIPHER;
   if (!hmac)
-    hmac = "hmac-sha1-96";
+    hmac = SILC_DEFAULT_HMAC;
 
   if (!silc_cipher_is_supported(cipher))
     return FALSE;
@@ -385,7 +498,7 @@ int silc_client_add_channel_private_key(SilcClient client,
   /* Produce the key material */
   keymat = silc_calloc(1, sizeof(*keymat));
   if (silc_ske_process_key_material_data(key, key_len, 16, 256, 16, 
-                                        client->md5hash, keymat) 
+                                        client->internal->md5hash, keymat) 
       != SILC_SKE_STATUS_OK)
     return FALSE;
 
@@ -418,8 +531,10 @@ int silc_client_add_channel_private_key(SilcClient client,
 
   /* Generate HMAC key from the channel key data and set it */
   silc_hmac_alloc(hmac, NULL, &entry->hmac);
-  silc_hash_make(entry->hmac->hash, entry->key, entry->key_len, hash);
-  silc_hmac_set_key(entry->hmac, hash, silc_hash_len(entry->hmac->hash));
+  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 */