Fixed entry resolving while processing incoming notify packets,
[silc.git] / lib / silcclient / client_channel.c
index e7753b9400e8c99fec30b2043739ea3bd89c9c49..8f8772e1d36f51d530ca9466d88220ee6e97b6c1 100644 (file)
@@ -40,6 +40,7 @@ SilcBool silc_client_send_channel_message(SilcClient client,
   SilcCipher cipher;
   SilcHmac hmac;
   SilcBool ret;
+  SilcID sid, rid;
 
   SILC_LOG_DEBUG(("Sending channel message"));
 
@@ -94,12 +95,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;
   }
 
@@ -109,9 +110,14 @@ SilcBool silc_client_send_channel_message(SilcClient client,
   }
 
   /* Encode the message payload. This also encrypts the message payload. */
+  sid.type = SILC_ID_CLIENT;
+  sid.u.client_id = chu->client->id;
+  rid.type = SILC_ID_CHANNEL;
+  rid.u.channel_id = chu->channel->id;
   buffer = silc_message_payload_encode(flags, data, data_len, TRUE, FALSE,
                                       cipher, hmac, client->rng, NULL,
-                                      conn->private_key, hash, NULL);
+                                      conn->private_key, hash, &sid, &rid,
+                                      NULL);
   if (silc_unlikely(!buffer)) {
     SILC_LOG_ERROR(("Error encoding channel message"));
     return FALSE;
@@ -163,10 +169,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,12 +183,12 @@ 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 */
   client_entry = silc_client_get_client_by_id(client, conn, &remote_id);
-  if (!client_entry || !client_entry->nickname[0]) {
+  if (!client_entry || !client_entry->internal.valid) {
     /** Resolve client info */
     silc_client_unref_client(client, conn, client_entry);
     SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
@@ -194,7 +203,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 +211,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 +220,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,9 +230,11 @@ 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,
-                                        channel->internal.hmac, NULL,
-                                        FALSE, NULL);
+                                        FALSE, channel->internal.receive_key,
+                                        channel->internal.hmac,
+                                        packet->src_id, packet->src_id_len,
+                                        packet->dst_id, packet->dst_id_len,
+                                        NULL, FALSE, NULL);
 
     /* 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
@@ -248,6 +259,10 @@ SILC_FSM_STATE(silc_client_channel_message)
        payload = silc_message_payload_parse(silc_buffer_data(buffer),
                                             silc_buffer_len(buffer),
                                             FALSE, FALSE, cipher, hmac,
+                                            packet->src_id,
+                                            packet->src_id_len,
+                                            packet->dst_id,
+                                            packet->dst_id_len,
                                             NULL, FALSE, NULL);
        if (payload)
          break;
@@ -262,9 +277,13 @@ 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.hmac, NULL,
-                                          FALSE, NULL);
+                                          channel->internal.receive_key,
+                                          channel->internal.hmac,
+                                          packet->src_id,
+                                          packet->src_id_len,
+                                          packet->dst_id,
+                                          packet->dst_id_len,
+                                          NULL, FALSE, NULL);
 
     if (!payload) {
       silc_dlist_start(channel->internal.private_keys);
@@ -273,7 +292,11 @@ SILC_FSM_STATE(silc_client_channel_message)
        payload = silc_message_payload_parse(silc_buffer_data(buffer),
                                             silc_buffer_len(buffer),
                                             FALSE, FALSE, key->cipher,
-                                            key->hmac, NULL, FALSE, NULL);
+                                            key->hmac, packet->src_id,
+                                            packet->src_id_len,
+                                            packet->dst_id,
+                                            packet->dst_id_len,
+                                            NULL, FALSE, NULL);
        if (payload)
          break;
       }
@@ -295,7 +318,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 +327,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 +413,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 +422,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 +443,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 +489,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 +722,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,
@@ -720,7 +755,8 @@ SilcBool silc_client_add_to_channel(SilcClient client,
   return TRUE;
 }
 
-/* Removes client from a channel */
+/* Removes client from a channel.  Returns FALSE if user is not on channel.
+   This handles entry locking internally. */
 
 SilcBool silc_client_remove_from_channel(SilcClient client,
                                         SilcClientConnection conn,
@@ -735,6 +771,9 @@ SilcBool silc_client_remove_from_channel(SilcClient client,
 
   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);
@@ -743,13 +782,17 @@ SilcBool silc_client_remove_from_channel(SilcClient client,
   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,
@@ -763,8 +806,12 @@ void silc_client_remove_from_channels(SilcClient client,
 
   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);
 
@@ -772,15 +819,19 @@ void silc_client_remove_from_channels(SilcClient client,
     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,
@@ -789,6 +840,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);
@@ -797,5 +850,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;
+}