X-Git-Url: http://git.silcnet.org/gitweb/?p=silc.git;a=blobdiff_plain;f=lib%2Fsilcclient%2Fclient_channel.c;h=7dd4f0a6ad59a23572262fb48970de4decda3616;hp=4d320bfbe10abc364b8c4e685185cc65250acfe0;hb=HEAD;hpb=c93d979c288b14b8ad95dc49eecf3016ba9eb22d diff --git a/lib/silcclient/client_channel.c b/lib/silcclient/client_channel.c index 4d320bfb..7dd4f0a6 100644 --- a/lib/silcclient/client_channel.c +++ b/lib/silcclient/client_channel.c @@ -24,6 +24,29 @@ /************************** Channel Message Send ****************************/ +typedef struct { + SilcClient client; + SilcClientConnection conn; + SilcChannelEntry channel; +} *SilcClientChannelMessageContext; + +/* Message payload encoding callback */ + +static void silc_client_send_channel_message_final(SilcBuffer message, + void *context) +{ + SilcClientChannelMessageContext c = context; + + /* Send the channel message */ + if (message) + silc_packet_send_ext(c->conn->stream, SILC_PACKET_CHANNEL_MESSAGE, 0, + 0, NULL, SILC_ID_CHANNEL, &c->channel->id, + silc_buffer_datalen(message), NULL, NULL); + + silc_client_unref_channel(c->client, c->conn, c->channel); + silc_free(c); +} + /* Sends channel message to `channel'. */ SilcBool silc_client_send_channel_message(SilcClient client, @@ -35,18 +58,21 @@ SilcBool silc_client_send_channel_message(SilcClient client, unsigned char *data, SilcUInt32 data_len) { + SilcClientChannelMessageContext c; SilcChannelUser chu; - SilcBuffer buffer; SilcCipher cipher; SilcHmac hmac; - SilcBool ret; + SilcID sid, rid; SILC_LOG_DEBUG(("Sending channel message")); if (silc_unlikely(!client || !conn || !channel)) return FALSE; - if (silc_unlikely(flags & SILC_MESSAGE_FLAG_SIGNED && !hash)) + if (silc_unlikely(flags & SILC_MESSAGE_FLAG_SIGNED && !hash)) { + SILC_LOG_ERROR(("Cannot send signed message without hash, missing " + "arguments")); return FALSE; + } if (silc_unlikely(conn->internal->disconnected)) return FALSE; @@ -73,12 +99,12 @@ SilcBool silc_client_send_channel_message(SilcClient client, 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 && @@ -87,7 +113,7 @@ SilcBool silc_client_send_channel_message(SilcClient client, 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 */ @@ -108,22 +134,26 @@ SilcBool silc_client_send_channel_message(SilcClient client, return FALSE; } - /* Encode the message payload. This also encrypts the message payload. */ - buffer = silc_message_payload_encode(flags, data, data_len, TRUE, FALSE, - cipher, hmac, client->rng, NULL, - conn->private_key, hash, NULL); - if (silc_unlikely(!buffer)) { - SILC_LOG_ERROR(("Error encoding channel message")); + c = silc_calloc(1, sizeof(*c)); + if (!c) return FALSE; - } - /* Send the channel message */ - ret = silc_packet_send_ext(conn->stream, SILC_PACKET_CHANNEL_MESSAGE, 0, - 0, NULL, SILC_ID_CHANNEL, &channel->id, - silc_buffer_datalen(buffer), NULL, NULL); + c->client = client; + c->conn = conn; + c->channel = silc_client_ref_channel(client, conn, channel); + + sid.type = SILC_ID_CLIENT; + sid.u.client_id = chu->client->id; + rid.type = SILC_ID_CHANNEL; + rid.u.channel_id = chu->channel->id; + + /* Encode the message payload. This also encrypts the message payload. */ + silc_message_payload_encode(flags, data, data_len, TRUE, FALSE, + cipher, hmac, client->rng, NULL, + conn->private_key, hash, &sid, &rid, NULL, + silc_client_send_channel_message_final, c); - silc_buffer_free(buffer); - return ret; + return TRUE; } /************************* Channel Message Receive **************************/ @@ -182,7 +212,7 @@ SILC_FSM_STATE(silc_client_channel_message) /* 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( @@ -225,8 +255,10 @@ SILC_FSM_STATE(silc_client_channel_message) payload = silc_message_payload_parse(silc_buffer_data(buffer), silc_buffer_len(buffer), FALSE, FALSE, channel->internal.receive_key, - channel->internal.hmac, NULL, - FALSE, NULL); + 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 @@ -251,6 +283,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; @@ -266,8 +302,12 @@ SILC_FSM_STATE(silc_client_channel_message) silc_buffer_len(buffer), FALSE, FALSE, channel->internal.receive_key, - channel->internal.hmac, NULL, - FALSE, NULL); + 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); @@ -275,8 +315,12 @@ SILC_FSM_STATE(silc_client_channel_message) /* 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; } @@ -443,12 +487,16 @@ SilcBool silc_client_save_channel_key(SilcClient client, 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); @@ -524,20 +572,30 @@ SilcBool silc_client_add_channel_private_key(SilcClient client, 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; + } + 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->cipher, keymat->send_enc_key, + 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; } @@ -550,8 +608,11 @@ SilcBool silc_client_add_channel_private_key(SilcClient client, /* 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); @@ -582,12 +643,21 @@ SilcBool silc_client_del_channel_private_keys(SilcClient client, 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->internal.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; @@ -619,12 +689,16 @@ SilcBool silc_client_del_channel_private_key(SilcClient client, 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); @@ -681,6 +755,8 @@ void silc_client_current_channel_private_key(SilcClient client, 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 *****************************/ @@ -702,7 +778,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, @@ -734,7 +811,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, @@ -749,6 +827,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); @@ -757,13 +838,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, @@ -777,8 +862,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); @@ -786,15 +875,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, @@ -803,6 +896,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); @@ -811,5 +906,73 @@ 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, + 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; +}