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
SilcCipher cipher;
SilcHmac hmac;
SilcBool ret;
+ SilcID sid, rid;
SILC_LOG_DEBUG(("Sending channel message"));
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;
}
}
/* 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;
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);
/* 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(
/* 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
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;
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);
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;
}
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,
/* 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,
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 ?
silc_hmac_set_key(channel->internal.hmac, hash,
silc_hash_len(silc_hmac_get_hash(channel->internal.hmac)));
memset(hash, 0, sizeof(hash));
+ silc_channel_key_payload_free(payload);
silc_client_unref_channel(client, conn, 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,
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;
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,
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,
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,
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);
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 = silc_dlist_init();
+ if (!channel->channel_pubkeys) {
+ silc_argument_list_free(chpks, SILC_ARGUMENT_PUBLIC_KEY);
+ return FALSE;
+ }
+ }
+
+ 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;
+}