Added entry locking using read/write locks.
[silc.git] / lib / silcclient / client_channel.c
index 59902d840c9e29bc99c20bccfd5d9bfe2d40be59..e8cfa7d1a4c06b07bd583e77b8335bf23eb8447c 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 1997 - 2004, 2006 Pekka Riikonen
+  Copyright (C) 1997 - 2007 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
@@ -94,12 +94,12 @@ SilcBool silc_client_send_channel_message(SilcClient client,
       channel->internal.curr_key = key;
     } else {
       /* Use normal channel key generated by the server */
-      cipher = channel->internal.channel_key;
+      cipher = channel->internal.send_key;
       hmac = channel->internal.hmac;
     }
   } else {
     /* Use normal channel key generated by the server */
-    cipher = channel->internal.channel_key;
+    cipher = channel->internal.send_key;
     hmac = channel->internal.hmac;
   }
 
@@ -163,10 +163,13 @@ SILC_FSM_STATE(silc_client_channel_message)
 
   SILC_LOG_DEBUG(("Received channel message"));
 
+  SILC_LOG_HEXDUMP(("Channel message"), silc_buffer_data(buffer),
+                  silc_buffer_len(buffer));
+
   if (silc_unlikely(packet->dst_id_type != SILC_ID_CHANNEL)) {
     /** Invalid packet */
     silc_fsm_next(fsm, silc_client_channel_message_error);
-    SILC_FSM_CONTINUE;
+    return SILC_FSM_CONTINUE;
   }
 
   if (silc_unlikely(!silc_id_str2id(packet->src_id,
@@ -174,7 +177,7 @@ SILC_FSM_STATE(silc_client_channel_message)
                                    &remote_id, sizeof(remote_id)))) {
     /** Invalid source ID */
     silc_fsm_next(fsm, silc_client_channel_message_error);
-    SILC_FSM_CONTINUE;
+    return SILC_FSM_CONTINUE;
   }
 
   /* Get sender client entry */
@@ -194,7 +197,7 @@ SILC_FSM_STATE(silc_client_channel_message)
                                    sizeof(channel_id)))) {
     /** Invalid destination ID */
     silc_fsm_next(fsm, silc_client_channel_message_error);
-    SILC_FSM_CONTINUE;
+    return SILC_FSM_CONTINUE;
   }
 
   /* Find the channel */
@@ -202,7 +205,7 @@ SILC_FSM_STATE(silc_client_channel_message)
   if (silc_unlikely(!channel)) {
     /** Unknown channel */
     silc_fsm_next(fsm, silc_client_channel_message_error);
-    SILC_FSM_CONTINUE;
+    return SILC_FSM_CONTINUE;
   }
 
   /* Check that user is on channel */
@@ -211,7 +214,7 @@ SILC_FSM_STATE(silc_client_channel_message)
     SILC_LOG_WARNING(("Message from user not on channel, client or "
                      "server bug"));
     silc_fsm_next(fsm, silc_client_channel_message_error);
-    SILC_FSM_CONTINUE;
+    return SILC_FSM_CONTINUE;
   }
 
   /* If there is no channel private key then just decrypt the message
@@ -221,7 +224,7 @@ SILC_FSM_STATE(silc_client_channel_message)
     /* Parse the channel message payload. This also decrypts the payload */
     payload = silc_message_payload_parse(silc_buffer_data(buffer),
                                         silc_buffer_len(buffer), FALSE,
-                                        FALSE, channel->internal.channel_key,
+                                        FALSE, channel->internal.receive_key,
                                         channel->internal.hmac, NULL,
                                         FALSE, NULL);
 
@@ -262,7 +265,7 @@ SILC_FSM_STATE(silc_client_channel_message)
       payload = silc_message_payload_parse(silc_buffer_data(buffer),
                                           silc_buffer_len(buffer),
                                           FALSE, FALSE,
-                                          channel->internal.channel_key,
+                                          channel->internal.receive_key,
                                           channel->internal.hmac, NULL,
                                           FALSE, NULL);
 
@@ -295,7 +298,7 @@ SILC_FSM_STATE(silc_client_channel_message)
   silc_client_unref_channel(client, conn, channel);
   if (payload)
     silc_message_payload_free(payload);
-  SILC_FSM_FINISH;
+  return SILC_FSM_FINISH;
 }
 
 /* Channel message error. */
@@ -304,7 +307,7 @@ SILC_FSM_STATE(silc_client_channel_message_error)
 {
   SilcPacket packet = state_context;
   silc_packet_free(packet);
-  SILC_FSM_FINISH;
+  return SILC_FSM_FINISH;
 }
 
 /******************************* Channel Key ********************************/
@@ -390,7 +393,7 @@ SilcBool silc_client_save_channel_key(SilcClient client,
     channel->internal.old_hmacs = silc_dlist_init();
   if (channel->internal.old_channel_keys && channel->internal.old_hmacs) {
     silc_dlist_add(channel->internal.old_channel_keys,
-                  channel->internal.channel_key);
+                  channel->internal.receive_key);
     silc_dlist_add(channel->internal.old_hmacs, channel->internal.hmac);
     silc_schedule_task_add_timeout(client->schedule,
                                   silc_client_save_channel_key_rekey,
@@ -399,7 +402,17 @@ SilcBool silc_client_save_channel_key(SilcClient client,
 
   /* Get channel cipher */
   cipher = silc_channel_key_get_cipher(payload, NULL);
-  if (!silc_cipher_alloc(cipher, &channel->internal.channel_key)) {
+  if (!silc_cipher_alloc(cipher, &channel->internal.send_key)) {
+    client->internal->ops->say(
+                          conn->client, conn,
+                          SILC_CLIENT_MESSAGE_AUDIT,
+                          "Cannot talk to channel: unsupported cipher %s",
+                          cipher);
+    silc_client_unref_channel(client, conn, channel);
+    silc_channel_key_payload_free(payload);
+    return FALSE;
+  }
+  if (!silc_cipher_alloc(cipher, &channel->internal.receive_key)) {
     client->internal->ops->say(
                           conn->client, conn,
                           SILC_CLIENT_MESSAGE_AUDIT,
@@ -410,9 +423,10 @@ SilcBool silc_client_save_channel_key(SilcClient client,
     return FALSE;
   }
 
-  /* Set the cipher key */
+  /* Set the cipher key.  Both sending and receiving keys are same */
   key = silc_channel_key_get_key(payload, &tmp_len);
-  silc_cipher_set_key(channel->internal.channel_key, key, tmp_len * 8, TRUE);
+  silc_cipher_set_key(channel->internal.send_key, key, tmp_len * 8, TRUE);
+  silc_cipher_set_key(channel->internal.receive_key, key, tmp_len * 8, FALSE);
 
   /* Get channel HMAC */
   hmac = (channel->internal.hmac ?
@@ -455,7 +469,7 @@ SILC_FSM_STATE(silc_client_channel_key)
   silc_client_save_channel_key(client, conn, &packet->buffer, NULL);
   silc_packet_free(packet);
 
-  SILC_FSM_FINISH;
+  return SILC_FSM_FINISH;
 }
 
 /**************************** Channel Private Key ***************************/
@@ -688,7 +702,8 @@ SilcChannelUser silc_client_on_channel(SilcChannelEntry channel,
 }
 
 /* Adds client to channel.  Returns TRUE if user was added or is already
-   added to the channel, FALSE on error. */
+   added to the channel, FALSE on error.  Must be called with both `channel'
+   and `client_entry' locked. */
 
 SilcBool silc_client_add_to_channel(SilcClient client,
                                    SilcClientConnection conn,
@@ -701,6 +716,8 @@ SilcBool silc_client_add_to_channel(SilcClient client,
   if (silc_client_on_channel(channel, client_entry))
     return TRUE;
 
+  SILC_LOG_DEBUG(("Add client %s to channel", client_entry->nickname));
+
   chu = silc_calloc(1, sizeof(*chu));
   if (!chu)
     return FALSE;
@@ -718,7 +735,7 @@ SilcBool silc_client_add_to_channel(SilcClient client,
   return TRUE;
 }
 
-/* Removes client from a channel */
+/* Removes client from a channel.  This handles entry locking internally. */
 
 SilcBool silc_client_remove_from_channel(SilcClient client,
                                         SilcClientConnection conn,
@@ -731,17 +748,30 @@ SilcBool silc_client_remove_from_channel(SilcClient client,
   if (!chu)
     return FALSE;
 
+  SILC_LOG_DEBUG(("Remove client %s from channel", client_entry->nickname));
+
+  silc_rwlock_wrlock(client_entry->internal.lock);
+  silc_rwlock_wrlock(channel->internal.lock);
+
   silc_hash_table_del(chu->client->channels, chu->channel);
   silc_hash_table_del(chu->channel->user_list, chu->client);
   silc_free(chu);
 
+  /* If channel became empty, delete it */
+  if (!silc_hash_table_count(channel->user_list))
+    silc_client_del_channel(client, conn, channel);
+
+  silc_rwlock_unlock(client_entry->internal.lock);
+  silc_rwlock_unlock(channel->internal.lock);
+
   silc_client_unref_client(client, conn, client_entry);
   silc_client_unref_channel(client, conn, channel);
 
   return TRUE;
 }
 
-/* Removes a client entry from all channels it has joined. */
+/* Removes a client entry from all channels it has joined.  This handles
+   entry locking internally. */
 
 void silc_client_remove_from_channels(SilcClient client,
                                      SilcClientConnection conn,
@@ -750,19 +780,37 @@ void silc_client_remove_from_channels(SilcClient client,
   SilcHashTableList htl;
   SilcChannelUser chu;
 
+  if (!silc_hash_table_count(client_entry->channels))
+    return;
+
+  SILC_LOG_DEBUG(("Remove client from all joined channels"));
+
+  silc_rwlock_wrlock(client_entry->internal.lock);
+
   silc_hash_table_list(client_entry->channels, &htl);
   while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+    silc_rwlock_wrlock(chu->channel->internal.lock);
+
     silc_hash_table_del(chu->client->channels, chu->channel);
     silc_hash_table_del(chu->channel->user_list, chu->client);
+
+    /* If channel became empty, delete it */
+    if (!silc_hash_table_count(chu->channel->user_list))
+      silc_client_del_channel(client, conn, chu->channel);
+
+    silc_rwlock_unlock(chu->channel->internal.lock);
+
     silc_client_unref_client(client, conn, chu->client);
     silc_client_unref_channel(client, conn, chu->channel);
     silc_free(chu);
   }
 
+  silc_rwlock_unlock(client_entry->internal.lock);
+
   silc_hash_table_list_reset(&htl);
 }
 
-/* Empties channel from users. */
+/* Empties channel from users.  This handles entry locking internally. */
 
 void silc_client_empty_channel(SilcClient client,
                               SilcClientConnection conn,
@@ -771,6 +819,8 @@ void silc_client_empty_channel(SilcClient client,
   SilcHashTableList htl;
   SilcChannelUser chu;
 
+  silc_rwlock_wrlock(channel->internal.lock);
+
   silc_hash_table_list(channel->user_list, &htl);
   while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
     silc_hash_table_del(chu->client->channels, chu->channel);
@@ -779,5 +829,53 @@ void silc_client_empty_channel(SilcClient client,
     silc_client_unref_channel(client, conn, chu->channel);
     silc_free(chu);
   }
+
+  silc_rwlock_unlock(channel->internal.lock);
+
   silc_hash_table_list_reset(&htl);
 }
+
+/* Save public keys to channel public key list.  Removes keys that are
+   marked to be removed.  Must be called with `channel' locked. */
+
+SilcBool silc_client_channel_save_public_keys(SilcChannelEntry channel,
+                                             unsigned char *chpk_list,
+                                             SilcUInt32 chpk_list_len)
+{
+  SilcArgumentDecodedList a, b;
+  SilcDList chpks;
+  SilcBool found;
+
+  chpks = silc_argument_list_parse_decoded(chpk_list, chpk_list_len,
+                                          SILC_ARGUMENT_PUBLIC_KEY);
+  if (!chpks)
+    return FALSE;
+
+  if (!channel->channel_pubkeys) {
+    channel->channel_pubkeys = chpks;
+    return TRUE;
+  }
+
+  silc_dlist_start(chpks);
+  while ((a = silc_dlist_get(chpks))) {
+    found = FALSE;
+    silc_dlist_start(channel->channel_pubkeys);
+    while ((b = silc_dlist_get(channel->channel_pubkeys))) {
+      if (silc_pkcs_public_key_compare(a->argument, b->argument)) {
+       found = TRUE;
+       break;
+      }
+    }
+
+    if ((a->arg_type == 0x00 || a->arg_type == 0x03) && !found) {
+      silc_dlist_add(channel->channel_pubkeys, a);
+      silc_dlist_del(chpks, a);
+    } else if (a->arg_type == 0x01 && found) {
+      silc_dlist_del(channel->channel_pubkeys, b);
+    }
+  }
+
+  silc_argument_list_free(chpks, SILC_ARGUMENT_PUBLIC_KEY);
+
+  return TRUE;
+}