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"));
if (channel->internal.private_keys) {
if (key) {
/* Use key application specified */
- cipher = key->cipher;
+ cipher = key->send_key;
hmac = key->hmac;
} else if (channel->mode & SILC_CHANNEL_MODE_PRIVKEY &&
channel->internal.curr_key) {
/* Use current private key */
- cipher = channel->internal.curr_key->cipher;
+ cipher = channel->internal.curr_key->send_key;
hmac = channel->internal.curr_key->hmac;
} else if (channel->mode & SILC_CHANNEL_MODE_PRIVKEY &&
!channel->internal.curr_key &&
and private keys are set. */
silc_dlist_start(channel->internal.private_keys);
key = silc_dlist_get(channel->internal.private_keys);
- cipher = key->cipher;
+ cipher = key->send_key;
hmac = key->hmac;
/* Use this key as current private key */
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);
/* Parse the message payload. This also decrypts the payload */
payload = silc_message_payload_parse(silc_buffer_data(buffer),
silc_buffer_len(buffer),
- FALSE, FALSE, key->cipher,
- key->hmac, NULL, FALSE, NULL);
+ FALSE, FALSE, key->receive_key,
+ 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 ?
return FALSE;
}
+ channel->cipher = silc_cipher_get_name(channel->internal.send_key);
+ channel->hmac = silc_hmac_get_name(channel->internal.hmac);
+
/* Set HMAC key */
silc_hash_make(silc_hmac_get_hash(channel->internal.hmac), key,
tmp_len, hash);
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);
entry->name = name ? strdup(name) : NULL;
/* Allocate the cipher and set the key */
- if (!silc_cipher_alloc(cipher, &entry->cipher)) {
+ if (!silc_cipher_alloc(cipher, &entry->send_key)) {
silc_free(entry);
silc_free(entry->name);
silc_ske_free_key_material(keymat);
return FALSE;
}
- silc_cipher_set_key(entry->cipher, keymat->send_enc_key,
+ if (!silc_cipher_alloc(cipher, &entry->receive_key)) {
+ silc_free(entry);
+ silc_free(entry->name);
+ silc_cipher_free(entry->send_key);
+ silc_ske_free_key_material(keymat);
+ return FALSE;
+ }
+ silc_cipher_set_key(entry->send_key, keymat->send_enc_key,
keymat->enc_key_len, TRUE);
+ silc_cipher_set_key(entry->receive_key, keymat->send_enc_key,
+ keymat->enc_key_len, FALSE);
/* Generate HMAC key from the channel key data and set it */
if (!silc_hmac_alloc(hmac, NULL, &entry->hmac)) {
silc_free(entry);
silc_free(entry->name);
- silc_cipher_free(entry->cipher);
+ silc_cipher_free(entry->send_key);
+ silc_cipher_free(entry->receive_key);
silc_ske_free_key_material(keymat);
return FALSE;
}
/* Add to the private keys list */
silc_dlist_add(channel->internal.private_keys, entry);
- if (!channel->internal.curr_key)
+ if (!channel->internal.curr_key) {
channel->internal.curr_key = entry;
+ channel->cipher = silc_cipher_get_name(entry->send_key);
+ channel->hmac = silc_cipher_get_name(entry->send_key);
+ }
/* Free the key material */
silc_ske_free_key_material(keymat);
while ((entry = silc_dlist_get(channel->internal.private_keys))) {
silc_dlist_del(channel->internal.private_keys, entry);
silc_free(entry->name);
- silc_cipher_free(entry->cipher);
+ silc_cipher_free(entry->send_key);
+ silc_cipher_free(entry->receive_key);
silc_hmac_free(entry->hmac);
silc_free(entry);
}
channel->internal.curr_key = NULL;
+ if (channel->internal.send_key)
+ channel->cipher = silc_cipher_get_name(channel->internal.send_key);
+ else
+ channel->cipher = NULL;
+ if (channel->hmac)
+ channel->hmac = silc_hmac_get_name(channel->internal.hmac);
+ else
+ channel->hmac = NULL;
silc_dlist_uninit(channel->internal.private_keys);
channel->internal.private_keys = NULL;
if (entry != key)
continue;
- if (channel->internal.curr_key == entry)
+ if (channel->internal.curr_key == entry) {
channel->internal.curr_key = NULL;
+ channel->cipher = silc_cipher_get_name(channel->internal.send_key);
+ channel->hmac = silc_hmac_get_name(channel->internal.hmac);
+ }
silc_dlist_del(channel->internal.private_keys, entry);
silc_free(entry->name);
- silc_cipher_free(entry->cipher);
+ silc_cipher_free(entry->send_key);
+ silc_cipher_free(entry->receive_key);
silc_hmac_free(entry->hmac);
silc_free(entry);
if (!channel)
return;
channel->internal.curr_key = key;
+ channel->cipher = silc_cipher_get_name(key->send_key);
+ channel->hmac = silc_hmac_get_name(key->hmac);
}
/***************************** Utility routines *****************************/
}
/* 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,
+ SilcBool remove_all)
+{
+ SilcArgumentDecodedList a, b;
+ SilcDList chpks;
+ SilcBool found;
+
+ if (remove_all) {
+ /* Remove all channel public keys */
+ if (!channel->channel_pubkeys)
+ return FALSE;
+
+ silc_dlist_start(channel->channel_pubkeys);
+ while ((b = silc_dlist_get(channel->channel_pubkeys)))
+ silc_dlist_del(channel->channel_pubkeys, b);
+
+ silc_dlist_uninit(channel->channel_pubkeys);
+ channel->channel_pubkeys = NULL;
+
+ return TRUE;
+ }
+
+ /* Parse channel public key list and add or remove public keys */
+ 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;
+}