From a29b46c83462e1ffc42ddfece7fb2b520fd19d4e Mon Sep 17 00:00:00 2001 From: Pekka Riikonen Date: Sun, 11 Mar 2001 16:57:58 +0000 Subject: [PATCH] updates. --- CHANGES | 67 +++++++++ TODO | 6 + apps/silc/client_ops.c | 72 +++++++++- apps/silc/silc.c | 2 +- apps/silcd/command.c | 147 +++++--------------- apps/silcd/command_reply.c | 139 +++++++++---------- apps/silcd/packet_receive.c | 8 +- apps/silcd/server.c | 124 ++++++++++++++++- apps/silcd/server.h | 12 ++ doc/draft-riikonen-silc-spec-01.nroff | 27 ++-- lib/silcclient/client.c | 1 + lib/silcclient/client_channel.c | 188 ++++++++++++++++++++++++-- lib/silcclient/client_keyagr.c | 2 + lib/silcclient/command_reply.c | 145 ++++++++++++-------- lib/silcclient/idlist.c | 161 ++++++++++++++++++++++ lib/silcclient/idlist.h | 20 ++- lib/silcclient/protocol.c | 14 +- lib/silcclient/protocol.h | 6 + lib/silcclient/silcapi.h | 63 +++++---- 19 files changed, 894 insertions(+), 310 deletions(-) diff --git a/CHANGES b/CHANGES index 5b1a5015..9870d7c4 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,70 @@ +Sun Mar 11 14:59:05 EET 2001 Pekka Riikonen + + * Added silc_client_get_clients_by_list to get client entries + from Client ID list, that is returned for example by JOIN + and USERS command replies. The application should use this + function for example when JOIN command reply is received to + resolve the clients already on the channel (library does not + do that anymore as USERS command reply is not used in the JOIN + procedure anymore). Affected files lib/silcclient/silcapi.h and + lib/silcclient/idlist.c. + + * JOIN command reply and USERS command reply returns now SilcBuffer + pointers instead of unsigned char pointers when returning + the client list and mode list. + + * Added argument to the JOIN command reply, mainly + for the server to identify for which client the command was + originally sent. Updated protocol specs accordingly. + + * Added SilcDlist private_key pointer to the SilcChannelEntry + in the client to support the channel private keys. Affected + file is lib/silcclient/idlist.h. + + * Added SilcChannelPrivateKey argument to the function + silc_client_send_channel_message so that application can choose + to use specific private ke if it wants to. If it is not provided, + the normal channel key is used, unless private keys are set. + In this case the first (key that was added first) is used + as the encryption key. + + * Implemented the support for channel private key handling. + Implemented the following functions: + + silc_client_add_channel_private_key, + silc_client_del_channel_private_keys, + silc_client_del_channel_private_key, + silc_client_list_channel_private_keys and + silc_client_free_channel_private_keys + + Affected file lib/silcclient/client_channel.c. + + * Added the support for the private keys in the channel message + sending and encryption and in the message reception and + decryption. Affected funtions are + silc_client_send_channel_message and silc_client_channel_message. + +Sat Mar 10 21:36:22 EET 2001 Pekka Riikonen + + * Added SKE's key verify callback to the client library's + KE protocol context. Affected files lib/silcclient/protocol.[ch]. + + * Removed the statement that server (or router) must send USERS + command reply when joining to the channel so that the client + knows who are on the channel. Instead, the client list and + client's mode list is now sent in the JOIN command reply to the + client who joined channel. This is better solution. + + * Added function silc_server_get_users_on_channel and function + silc_server_save_users_on_channel to the silcd/server.[ch]. + + * Removed function silc_server_command_send_users from the + silcd/command.c. + + * Do not show topic on the client library anymore. The topic is + sent in the command reply notify to the application and the + application must show the topic now. + Sat Mar 10 00:07:37 EET 2001 Pekka Riikonen * Added client searching by nickname hash into the IDENTIFY and diff --git a/TODO b/TODO index c408e032..201673dc 100644 --- a/TODO +++ b/TODO @@ -62,6 +62,12 @@ New features TODO TODO In SILC Client Library =========================== + o Add client library parameters or options that handle what kind of + messages the library should print out (using `say' client operation, + for example) and what is left for the application to print. The + appliation could for example set that it handles all command printing + but all error printing should be handled by the library, etc... + o Non-blocking connection on the background must be stopped if some other connection on same window has established. Now it is possible that some non-blocking connection timeouts on the background when diff --git a/apps/silc/client_ops.c b/apps/silc/client_ops.c index acf3186a..5524529b 100644 --- a/apps/silc/client_ops.c +++ b/apps/silc/client_ops.c @@ -294,6 +294,53 @@ void silc_command(SilcClient client, SilcClientConnection conn, } } +/* We've resolved all clients we don't know about, now just print the + users from the channel on the screen. */ + +void silc_client_show_users(SilcClient client, + SilcClientConnection conn, + SilcClientEntry *clients, + unsigned int clients_count, + void *context) +{ + SilcChannelEntry channel = (SilcChannelEntry)context; + SilcChannelUser chu; + int k = 0, len1 = 0, len2 = 0; + char *name_list = NULL; + + if (!clients) + return; + + silc_list_start(channel->clients); + while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) { + char *m, *n = chu->client->nickname; + len2 = strlen(n); + len1 += len2; + + name_list = silc_realloc(name_list, sizeof(*name_list) * (len1 + 3)); + + m = silc_client_chumode_char(chu->mode); + if (m) { + memcpy(name_list + (len1 - len2), m, strlen(m)); + len1 += strlen(m); + silc_free(m); + } + + memcpy(name_list + (len1 - len2), n, len2); + name_list[len1] = 0; + + if (k == silc_list_count(channel->clients) - 1) + break; + memcpy(name_list + len1, " ", 1); + len1++; + k++; + } + + client->ops->say(client, conn, "Users on %s: %s", channel->channel_name, + name_list); + silc_free(name_list); +} + /* Command reply handler. This function is called always in the command reply function. If error occurs it will be called as well. Normal scenario is that it will be called after the received command data has been parsed @@ -329,12 +376,34 @@ void silc_command_reply(SilcClient client, SilcClientConnection conn, case SILC_COMMAND_JOIN: { unsigned int mode; + char *topic; + SilcBuffer client_id_list; + unsigned int list_count; + SilcChannelEntry channel; app->screen->bottom_line->channel = va_arg(vp, char *); - (void)va_arg(vp, void *); + channel = va_arg(vp, SilcChannelEntry); mode = va_arg(vp, unsigned int); + (void)va_arg(vp, unsigned int); + (void)va_arg(vp, unsigned char *); + (void)va_arg(vp, unsigned char *); + (void)va_arg(vp, unsigned char *); + topic = va_arg(vp, char *); + (void)va_arg(vp, unsigned char *); + list_count = va_arg(vp, unsigned int); + client_id_list = va_arg(vp, SilcBuffer); + + if (topic) + client->ops->say(client, conn, "Topic for %s: %s", + app->screen->bottom_line->channel, topic); + app->screen->bottom_line->channel_mode = silc_client_chmode(mode); silc_screen_print_bottom_line(app->screen, 0); + + /* Resolve the client information */ + silc_client_get_clients_by_list(client, conn, list_count, + client_id_list, + silc_client_show_users, channel); } break; @@ -628,7 +697,6 @@ int silc_get_auth_method(SilcClient client, SilcClientConnection conn, void silc_failure(SilcClient client, SilcClientConnection conn, SilcProtocol protocol, void *failure) { - SilcClientInternal app = (SilcClientInternal)client->application; } diff --git a/apps/silc/silc.c b/apps/silc/silc.c index bfb4ab72..ff7e28dc 100644 --- a/apps/silc/silc.c +++ b/apps/silc/silc.c @@ -566,7 +566,7 @@ static void silc_client_process_message(SilcClientInternal app) silc_print(app->client, "> %s", data); silc_client_send_channel_message(app->client, app->conn, - app->conn->current_channel, + app->conn->current_channel, NULL, data, strlen(data), TRUE); } } diff --git a/apps/silcd/command.c b/apps/silcd/command.c index 1a89aae0..2850f39d 100644 --- a/apps/silcd/command.c +++ b/apps/silcd/command.c @@ -37,10 +37,6 @@ silc_server_command_send_status_data(SilcServerCommandContext cmd, unsigned int arg_type, unsigned char *arg, unsigned int arg_len); -void silc_server_command_send_users(SilcServer server, - SilcSocketConnection sock, - SilcChannelEntry channel, - int pending); /* Server command list. */ SilcServerCommand silc_command_list[] = @@ -1627,6 +1623,9 @@ SILC_SERVER_CMD_FUNC(quit) SILC_SERVER_COMMAND_CHECK_ARGC(SILC_COMMAND_QUIT, cmd, 0, 1); + if (cmd->sock->type != SILC_SOCKET_TYPE_CLIENT) + goto out; + /* Get destination ID */ tmp = silc_argument_get_arg_type(cmd->args, 1, &len); if (len > 128) @@ -1642,6 +1641,7 @@ SILC_SERVER_CMD_FUNC(quit) silc_server_command_quit_cb, (void *)q, 0, 200000, SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW); + out: silc_server_command_free(cmd); } @@ -1747,66 +1747,6 @@ SILC_SERVER_CMD_FUNC(ping) silc_server_command_free(cmd); } -/* Assembles USERS command and executes it. This is called when client - joins to a channel and we wan't to send USERS command reply to the - client. */ - -void silc_server_command_send_users(SilcServer server, - SilcSocketConnection sock, - SilcChannelEntry channel, - int pending) -{ - SilcServerCommandContext cmd; - SilcBuffer buffer, idp; - SilcPacketContext *packet = silc_packet_context_alloc(); - - SILC_LOG_DEBUG(("Start")); - - /* Create USERS command packet and process it. */ - idp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL); - buffer = silc_command_payload_encode_va(SILC_COMMAND_USERS, 0, 1, - 1, idp->data, idp->len); - - packet->buffer = silc_buffer_copy(buffer); - packet->sock = sock; - packet->type = SILC_PACKET_COMMAND; - - cmd = silc_server_command_alloc(); - cmd->payload = silc_command_payload_parse(buffer); - if (!cmd->payload) { - silc_free(cmd); - silc_buffer_free(buffer); - silc_buffer_free(idp); - silc_packet_context_free(packet); - return; - } - cmd->args = silc_command_get_args(cmd->payload); - cmd->server = server; - cmd->sock = silc_socket_dup(sock); - cmd->packet = silc_packet_context_dup(packet); - cmd->pending = FALSE; - - if (pending) { - /* If this function was called from pending command then instead of - processing the command now, register a pending command callback which - will process it after we've received the automatic USERS command - reply which server will send in JOIN. */ - silc_server_command_pending(server, SILC_COMMAND_USERS, 0, NULL, - silc_server_command_users, cmd); - cmd->pending = TRUE; - silc_buffer_free(buffer); - silc_buffer_free(idp); - return; - } - - /* Process USERS command. */ - silc_server_command_users((void *)cmd); - - silc_buffer_free(buffer); - silc_buffer_free(idp); - silc_packet_context_free(packet); -} - /* Internal routine to join channel. The channel sent to this function has been either created or resolved from ID lists. This joins the sent client to the channel. */ @@ -1820,11 +1760,11 @@ static void silc_server_command_join_channel(SilcServer server, { SilcSocketConnection sock = cmd->sock; unsigned char *tmp; - unsigned int tmp_len; - unsigned char *passphrase = NULL, mode[4], tmp2[4]; + unsigned int tmp_len, user_count; + unsigned char *passphrase = NULL, mode[4], tmp2[4], tmp3[4]; SilcClientEntry client; SilcChannelClientEntry chl; - SilcBuffer reply, chidp, clidp, keyp; + SilcBuffer reply, chidp, clidp, keyp, user_list, mode_list; unsigned short ident = silc_command_get_ident(cmd->payload); SILC_LOG_DEBUG(("Start")); @@ -1927,6 +1867,10 @@ static void silc_server_command_join_channel(SilcServer server, silc_list_add(channel->user_list, chl); silc_list_add(client->channels, chl); + /* Get users on the channel */ + silc_server_get_users_on_channel(server, channel, &user_list, &mode_list, + &user_count); + /* Encode Client ID Payload of the original client who wants to join */ clidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT); @@ -1934,6 +1878,7 @@ static void silc_server_command_join_channel(SilcServer server, chidp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL); SILC_PUT32_MSB(channel->mode, mode); SILC_PUT32_MSB(created, tmp2); + SILC_PUT32_MSB(user_count, tmp3); tmp = silc_id_id2str(channel->id, SILC_ID_CHANNEL); keyp = silc_channel_key_payload_encode(SILC_ID_CHANNEL_LEN, tmp, strlen(channel->channel_key-> @@ -1944,25 +1889,35 @@ static void silc_server_command_join_channel(SilcServer server, if (!channel->topic) { reply = silc_command_reply_payload_encode_va(SILC_COMMAND_JOIN, - SILC_STATUS_OK, ident, 5, + SILC_STATUS_OK, ident, 9, 2, channel->channel_name, strlen(channel->channel_name), 3, chidp->data, chidp->len, - 4, mode, 4, - 5, tmp2, 4, - 6, keyp->data, keyp->len); + 4, clidp->data, clidp->len, + 5, mode, 4, + 6, tmp2, 4, + 7, keyp->data, keyp->len, + 12, tmp3, 4, + 13, user_list->data, user_list->len, + 14, mode_list->data, + mode_list->len); } else { reply = silc_command_reply_payload_encode_va(SILC_COMMAND_JOIN, - SILC_STATUS_OK, ident, 6, + SILC_STATUS_OK, ident, 10, 2, channel->channel_name, strlen(channel->channel_name), 3, chidp->data, chidp->len, - 4, mode, 4, - 5, tmp2, 4, - 6, keyp->data, keyp->len, - 9, channel->topic, - strlen(channel->topic)); + 4, clidp->data, clidp->len, + 5, mode, 4, + 6, tmp2, 4, + 7, keyp->data, keyp->len, + 10, channel->topic, + strlen(channel->topic), + 12, tmp3, 4, + 13, user_list->data, user_list->len, + 14, mode_list->data, + mode_list->len); } /* Send command reply */ @@ -1984,14 +1939,12 @@ static void silc_server_command_join_channel(SilcServer server, SILC_ID_CLIENT_LEN); } - /* Send USERS command reply to the joined channel so the user sees who - is currently on the channel. */ - silc_server_command_send_users(server, sock, channel, cmd->pending); - silc_buffer_free(reply); silc_buffer_free(clidp); silc_buffer_free(chidp); silc_buffer_free(keyp); + silc_buffer_free(user_list); + silc_buffer_free(mode_list); out: if (passphrase) @@ -3255,14 +3208,12 @@ SILC_SERVER_CMD_FUNC(users) SilcServerCommandContext cmd = (SilcServerCommandContext)context; SilcServer server = cmd->server; SilcChannelEntry channel; - SilcChannelClientEntry chl; SilcChannelID *id; SilcBuffer packet; unsigned char *channel_id; unsigned int channel_id_len; SilcBuffer client_id_list; SilcBuffer client_mode_list; - SilcBuffer idp; unsigned char lc[4]; unsigned int list_count = 0; unsigned short ident = silc_command_get_ident(cmd->payload); @@ -3324,33 +3275,9 @@ SILC_SERVER_CMD_FUNC(users) } } - /* Assemble the lists now */ - - client_id_list = silc_buffer_alloc((SILC_ID_CLIENT_LEN + 4) * - silc_list_count(channel->user_list)); - silc_buffer_pull_tail(client_id_list, SILC_BUFFER_END(client_id_list)); - client_mode_list = - silc_buffer_alloc(4 * silc_list_count(channel->user_list)); - silc_buffer_pull_tail(client_mode_list, SILC_BUFFER_END(client_mode_list)); - - silc_list_start(channel->user_list); - while ((chl = silc_list_get(channel->user_list)) != SILC_LIST_END) { - /* Client ID */ - idp = silc_id_payload_encode(chl->client->id, SILC_ID_CLIENT); - silc_buffer_put(client_id_list, idp->data, idp->len); - silc_buffer_pull(client_id_list, idp->len); - silc_buffer_free(idp); - - /* Client's mode on channel */ - SILC_PUT32_MSB(chl->mode, client_mode_list->data); - silc_buffer_pull(client_mode_list, 4); - - list_count++; - } - silc_buffer_push(client_id_list, - client_id_list->data - client_id_list->head); - silc_buffer_push(client_mode_list, - client_mode_list->data - client_mode_list->head); + /* Get the users list */ + silc_server_get_users_on_channel(server, channel, &client_id_list, + &client_mode_list, &list_count); /* List count */ SILC_PUT32_MSB(list_count, lc); diff --git a/apps/silcd/command_reply.c b/apps/silcd/command_reply.c index 60e9f9f7..360b85d2 100644 --- a/apps/silcd/command_reply.c +++ b/apps/silcd/command_reply.c @@ -375,13 +375,14 @@ SILC_SERVER_CMD_REPLY_FUNC(join) SilcServer server = cmd->server; SilcCommandStatus status; SilcChannelID *id; + SilcClientID *client_id = NULL; SilcChannelEntry entry; SilcHmac hmac = NULL; - unsigned int id_len, len; + unsigned int id_len, len, list_count; unsigned char *id_string; char *channel_name, *tmp; unsigned int mode, created; - SilcBuffer keyp; + SilcBuffer keyp, client_id_list, client_mode_list; COMMAND_CHECK_STATUS; @@ -395,14 +396,22 @@ SILC_SERVER_CMD_REPLY_FUNC(join) if (!id_string) goto out; + /* Get client ID */ + tmp = silc_argument_get_arg_type(cmd->args, 4, &len); + if (!tmp) + goto out; + client_id = silc_id_payload_parse_id(tmp, len); + if (!client_id) + goto out; + /* Get mode mask */ - tmp = silc_argument_get_arg_type(cmd->args, 4, NULL); + tmp = silc_argument_get_arg_type(cmd->args, 5, NULL); if (!tmp) goto out; SILC_GET32_MSB(mode, tmp); /* Get created boolean value */ - tmp = silc_argument_get_arg_type(cmd->args, 5, NULL); + tmp = silc_argument_get_arg_type(cmd->args, 6, NULL); if (!tmp) goto out; SILC_GET32_MSB(created, tmp); @@ -410,7 +419,7 @@ SILC_SERVER_CMD_REPLY_FUNC(join) goto out; /* Get channel key */ - tmp = silc_argument_get_arg_type(cmd->args, 6, &len); + tmp = silc_argument_get_arg_type(cmd->args, 7, &len); if (!tmp) goto out; keyp = silc_buffer_alloc(len); @@ -422,12 +431,36 @@ SILC_SERVER_CMD_REPLY_FUNC(join) goto out; /* Get hmac */ - tmp = silc_argument_get_arg_type(cmd->args, 10, NULL); + tmp = silc_argument_get_arg_type(cmd->args, 11, NULL); if (tmp) { if (!silc_hmac_alloc(tmp, NULL, &hmac)) goto out; } + /* Get the list count */ + tmp = silc_argument_get_arg_type(cmd->args, 12, &len); + if (!tmp) + goto out; + SILC_GET32_MSB(list_count, tmp); + + /* Get Client ID list */ + tmp = silc_argument_get_arg_type(cmd->args, 13, &len); + if (!tmp) + goto out; + + client_id_list = silc_buffer_alloc(len); + silc_buffer_pull_tail(client_id_list, len); + silc_buffer_put(client_id_list, tmp, len); + + /* Get client mode list */ + tmp = silc_argument_get_arg_type(cmd->args, 14, &len); + if (!tmp) + goto out; + + client_mode_list = silc_buffer_alloc(len); + silc_buffer_pull_tail(client_mode_list, len); + silc_buffer_put(client_mode_list, tmp, len); + /* See whether we already have the channel. */ entry = silc_idlist_find_channel_by_id(server->local_list, id, NULL); if (!entry) { @@ -467,11 +500,21 @@ SILC_SERVER_CMD_REPLY_FUNC(join) silc_server_save_channel_key(server, keyp, entry); silc_buffer_free(keyp); + /* Save the users to the channel */ + silc_server_save_users_on_channel(server, cmd->sock, entry, + client_id, client_id_list, + client_mode_list, list_count); + + silc_buffer_free(client_id_list); + silc_buffer_free(client_mode_list); + /* Execute any pending commands */ SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_JOIN); out: SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_JOIN); + if (client_id) + silc_free(client_id); silc_server_command_reply_free(cmd); } @@ -486,7 +529,7 @@ SILC_SERVER_CMD_REPLY_FUNC(users) SilcBuffer client_mode_list; unsigned char *tmp; unsigned int tmp_len; - unsigned int list_count, i; + unsigned int list_count; COMMAND_CHECK_STATUS; @@ -498,6 +541,16 @@ SILC_SERVER_CMD_REPLY_FUNC(users) if (!channel_id) goto out; + /* Get channel entry */ + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->global_list, + channel_id, NULL); + if (!channel) + goto out; + } + /* Get the list count */ tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len); if (!tmp) @@ -522,74 +575,10 @@ SILC_SERVER_CMD_REPLY_FUNC(users) silc_buffer_pull_tail(client_mode_list, tmp_len); silc_buffer_put(client_mode_list, tmp, tmp_len); - /* Get channel entry */ - channel = silc_idlist_find_channel_by_id(server->local_list, - channel_id, NULL); - if (!channel) { - channel = silc_idlist_find_channel_by_id(server->global_list, - channel_id, NULL); - if (!channel) - goto out; - } - - /* Cache the received Client ID's and modes. This cache expires - whenever server sends notify message to channel. It means two things; - some user has joined or leaved the channel. XXX! */ - for (i = 0; i < list_count; i++) { - unsigned short idp_len; - unsigned int mode; - SilcClientID *client_id; - SilcClientEntry client; - - /* Client ID */ - SILC_GET16_MSB(idp_len, client_id_list->data + 2); - idp_len += 4; - client_id = silc_id_payload_parse_id(client_id_list->data, idp_len); - if (!client_id) - continue; - silc_buffer_pull(client_id_list, idp_len); - - /* Mode */ - SILC_GET32_MSB(mode, client_mode_list->data); - silc_buffer_pull(client_mode_list, 4); - - /* Check if we have this client cached already. */ - client = silc_idlist_find_client_by_id(server->local_list, client_id, - NULL); - if (!client) - client = silc_idlist_find_client_by_id(server->global_list, - client_id, NULL); - if (!client) { - /* If router did not find such Client ID in its lists then this must - be bogus client or some router in the net is buggy. */ - if (server->server_type == SILC_ROUTER) - goto out; - - /* We don't have that client anywhere, add it. The client is added - to global list since server didn't have it in the lists so it must be - global. */ - client = silc_idlist_add_client(server->global_list, NULL, NULL, - NULL, client_id, cmd->sock->user_data, - NULL); - if (!client) { - silc_free(client_id); - continue; - } - } else { - /* We have the client already. */ - silc_free(client_id); - } - - if (!silc_server_client_on_channel(client, channel)) { - /* Client was not on the channel, add it. */ - SilcChannelClientEntry chl = silc_calloc(1, sizeof(*chl)); - chl->client = client; - chl->mode = mode; - chl->channel = channel; - silc_list_add(channel->user_list, chl); - silc_list_add(client->channels, chl); - } - } + /* Save the users to the channel */ + silc_server_save_users_on_channel(server, cmd->sock, channel, NULL, + client_id_list, client_mode_list, + list_count); silc_buffer_free(client_id_list); silc_buffer_free(client_mode_list); diff --git a/apps/silcd/packet_receive.c b/apps/silcd/packet_receive.c index eb3f9455..7c8df0e8 100644 --- a/apps/silcd/packet_receive.c +++ b/apps/silcd/packet_receive.c @@ -174,8 +174,12 @@ void silc_server_notify(SilcServer server, channel = silc_idlist_find_channel_by_id(server->local_list, channel_id, NULL); if (!channel) { - silc_free(channel_id); - goto out; + channel = silc_idlist_find_channel_by_id(server->global_list, + channel_id, NULL); + if (!channel) { + silc_free(channel_id); + goto out; + } } /* Get client ID */ diff --git a/apps/silcd/server.c b/apps/silcd/server.c index a7385384..8fafd242 100644 --- a/apps/silcd/server.c +++ b/apps/silcd/server.c @@ -2000,7 +2000,7 @@ void silc_server_free_sock_user_data(SilcServer server, /* If this was our primary router connection then we're lost to the outside world. */ - if (server->server_type == SILC_SERVER && server->router == user_data) { + if (server->router == user_data) { server->id_entry->router = NULL; server->router = NULL; server->standalone = TRUE; @@ -2899,3 +2899,125 @@ SILC_TASK_CALLBACK(silc_server_failure_callback) silc_free(f); } + +/* Assembles user list and users mode list from the `channel'. */ + +void silc_server_get_users_on_channel(SilcServer server, + SilcChannelEntry channel, + SilcBuffer *user_list, + SilcBuffer *mode_list, + unsigned int *user_count) +{ + SilcChannelClientEntry chl; + SilcBuffer client_id_list; + SilcBuffer client_mode_list; + SilcBuffer idp; + unsigned int list_count = 0; + + client_id_list = silc_buffer_alloc((SILC_ID_CLIENT_LEN + 4) * + silc_list_count(channel->user_list)); + client_mode_list = silc_buffer_alloc(4 * + silc_list_count(channel->user_list)); + silc_buffer_pull_tail(client_id_list, SILC_BUFFER_END(client_id_list)); + silc_buffer_pull_tail(client_mode_list, SILC_BUFFER_END(client_mode_list)); + silc_list_start(channel->user_list); + while ((chl = silc_list_get(channel->user_list)) != SILC_LIST_END) { + /* Client ID */ + idp = silc_id_payload_encode(chl->client->id, SILC_ID_CLIENT); + silc_buffer_put(client_id_list, idp->data, idp->len); + silc_buffer_pull(client_id_list, idp->len); + silc_buffer_free(idp); + + /* Client's mode on channel */ + SILC_PUT32_MSB(chl->mode, client_mode_list->data); + silc_buffer_pull(client_mode_list, 4); + + list_count++; + } + silc_buffer_push(client_id_list, + client_id_list->data - client_id_list->head); + silc_buffer_push(client_mode_list, + client_mode_list->data - client_mode_list->head); + + *user_list = client_id_list; + *mode_list = client_mode_list; + *user_count = list_count; +} + +/* Saves users and their modes to the `channel'. */ + +void silc_server_save_users_on_channel(SilcServer server, + SilcSocketConnection sock, + SilcChannelEntry channel, + SilcClientID *noadd, + SilcBuffer user_list, + SilcBuffer mode_list, + unsigned int user_count) +{ + int i; + + /* Cache the received Client ID's and modes. This cache expires + whenever server sends notify message to channel. It means two things; + some user has joined or leaved the channel. XXX TODO! */ + for (i = 0; i < user_count; i++) { + unsigned short idp_len; + unsigned int mode; + SilcClientID *client_id; + SilcClientEntry client; + + /* Client ID */ + SILC_GET16_MSB(idp_len, user_list->data + 2); + idp_len += 4; + client_id = silc_id_payload_parse_id(user_list->data, idp_len); + silc_buffer_pull(user_list, idp_len); + if (!client_id) + continue; + + /* Mode */ + SILC_GET32_MSB(mode, mode_list->data); + silc_buffer_pull(mode_list, 4); + + if (noadd && !SILC_ID_CLIENT_COMPARE(client_id, noadd)) { + silc_free(client_id); + continue; + } + + /* Check if we have this client cached already. */ + client = silc_idlist_find_client_by_id(server->local_list, client_id, + NULL); + if (!client) + client = silc_idlist_find_client_by_id(server->global_list, + client_id, NULL); + if (!client) { + /* If router did not find such Client ID in its lists then this must + be bogus client or some router in the net is buggy. */ + if (server->server_type == SILC_ROUTER) { + silc_free(client_id); + continue; + } + + /* We don't have that client anywhere, add it. The client is added + to global list since server didn't have it in the lists so it must be + global. */ + client = silc_idlist_add_client(server->global_list, NULL, NULL, NULL, + silc_id_dup(client_id, SILC_ID_CLIENT), + sock->user_data, NULL); + if (!client) { + silc_free(client_id); + continue; + } + } + + silc_free(client_id); + + if (!silc_server_client_on_channel(client, channel)) { + /* Client was not on the channel, add it. */ + SilcChannelClientEntry chl = silc_calloc(1, sizeof(*chl)); + chl->client = client; + chl->mode = mode; + chl->channel = channel; + silc_list_add(channel->user_list, chl); + silc_list_add(client->channels, chl); + } + } +} diff --git a/apps/silcd/server.h b/apps/silcd/server.h index e8b31ca7..ce1194cb 100644 --- a/apps/silcd/server.h +++ b/apps/silcd/server.h @@ -141,5 +141,17 @@ void silc_server_perform_heartbeat(SilcSocketConnection sock, void silc_server_announce_servers(SilcServer server); void silc_server_announce_clients(SilcServer server); void silc_server_announce_channels(SilcServer server); +void silc_server_get_users_on_channel(SilcServer server, + SilcChannelEntry channel, + SilcBuffer *user_list, + SilcBuffer *mode_list, + unsigned int *user_count); +void silc_server_save_users_on_channel(SilcServer server, + SilcSocketConnection sock, + SilcChannelEntry channel, + SilcClientID *noadd, + SilcBuffer user_list, + SilcBuffer mode_list, + unsigned int user_count); #endif diff --git a/doc/draft-riikonen-silc-spec-01.nroff b/doc/draft-riikonen-silc-spec-01.nroff index d8163389..6032a360 100644 --- a/doc/draft-riikonen-silc-spec-01.nroff +++ b/doc/draft-riikonen-silc-spec-01.nroff @@ -1580,12 +1580,6 @@ send information about newly joined client to all routers in the SILC network. This is done by broadcasting the SILC_NOTIFY_TYPE_JOIN notify type to the router's primary route. -After joining the client to the channel server or router must send -command reply packet for SILC_COMMAND_USERS command. This way the -client gets the list of users on the channel. If the router joined -the client to the channel then the router sends this command reply -to the server which must send it further to the original client. - It is important to note that new channel key is created always when new client joins to channel, whether the channel has existed previously or not. This way the new client on the channel is not able to decrypt @@ -2433,20 +2427,27 @@ List of all defined commands in SILC follows. Reply messages to the command: - Max Arguments: 10 - Arguments: (1) (2) - (3) (4) - (5) (6) - (7) [] (8) [] - (9) [] (10) [] + Max Arguments: 14 + Arguments: (1) (2) + (3) (4) + (5) (6) + (7) (8) [] + (9) [] (10) [] + (11) [] (12) + (13) (14) This command replies with the channel name requested by the client, channel ID of the channel and topic of the channel - if it exists. It also replies with the channel mode mask + if it exists. The is the Client ID which was joined + to the channel. It also replies with the channel mode mask which tells all the modes set on the channel. If the channel is created the mode mask is zero (0). If ban mask and/or invite list is set they are sent as well. + The , and are + the clients curerntly on the channel and their modes on the + channel. + Client receives the channel key in the reply message as well inside . diff --git a/lib/silcclient/client.c b/lib/silcclient/client.c index 33d51e10..1e9d13e0 100644 --- a/lib/silcclient/client.c +++ b/lib/silcclient/client.c @@ -276,6 +276,7 @@ int silc_client_start_key_exchange(SilcClient client, proto_ctx->rng = client->rng; proto_ctx->responder = FALSE; proto_ctx->send_packet = silc_client_protocol_ke_send_packet; + proto_ctx->verify = silc_client_protocol_ke_verify_key; /* Perform key exchange protocol. silc_client_connect_to_server_final will be called after the protocol is finished. */ diff --git a/lib/silcclient/client_channel.c b/lib/silcclient/client_channel.c index c12532ca..81c9ca9a 100644 --- a/lib/silcclient/client_channel.c +++ b/lib/silcclient/client_channel.c @@ -35,6 +35,7 @@ void silc_client_send_channel_message(SilcClient client, SilcClientConnection conn, SilcChannelEntry channel, + SilcChannelPrivateKey key, unsigned char *data, unsigned int data_len, int force_send) @@ -50,14 +51,40 @@ void silc_client_send_channel_message(SilcClient client, SILC_LOG_DEBUG(("Sending packet to channel")); - if (!channel || !channel->key || !channel->hmac) { + if (!channel || !channel->hmac || + (!channel->channel_key && !key && !channel->private_keys)) { client->ops->say(client, conn, "Cannot talk to channel: key does not exist"); return; } + /* Take the key to be used */ + if (key) { + /* Use key application specified */ + cipher = key->cipher; + hmac = key->hmac; + } else if (channel->curr_key) { + /* Use current private key */ + cipher = channel->curr_key->cipher; + hmac = channel->curr_key->hmac; + } else if (!channel->curr_key && channel->private_keys) { + /* Use just some private key since we don't know what to use + and private keys are set. */ + silc_dlist_start(channel->private_keys); + key = silc_dlist_get(channel->private_keys); + cipher = key->cipher; + hmac = key->hmac; + + /* Use this key as current private key */ + channel->curr_key = key; + } else { + /* Use normal channel key generated by the server */ + cipher = channel->channel_key; + hmac = channel->hmac; + } + /* Generate IV */ - iv_len = silc_cipher_get_block_len(channel->channel_key); + iv_len = silc_cipher_get_block_len(cipher); if (channel->iv[0] == '\0') for (i = 0; i < iv_len; i++) channel->iv[i] = silc_rng_get_byte(client->rng); @@ -66,8 +93,8 @@ void silc_client_send_channel_message(SilcClient client, /* Encode the channel payload. This also encrypts the message payload. */ payload = silc_channel_payload_encode(data_len, data, iv_len, - channel->iv, channel->channel_key, - channel->hmac, client->rng); + channel->iv, cipher, hmac, + client->rng); /* Get data used in packet header encryption, keys and stuff. */ cipher = conn->send_key; @@ -162,11 +189,29 @@ void silc_client_channel_message(SilcClient client, channel = (SilcChannelEntry)id_cache->context; - /* Parse the channel message payload. This also decrypts the payload */ - payload = silc_channel_payload_parse(buffer, channel->channel_key, - channel->hmac); - if (!payload) - goto out; + /* If there is no channel private key then just decrypt the message + with the channel key. If private keys are set then just go through + all private keys and check what decrypts correctly. */ + if (!channel->private_keys) { + /* Parse the channel message payload. This also decrypts the payload */ + payload = silc_channel_payload_parse(buffer, channel->channel_key, + channel->hmac); + if (!payload) + goto out; + } else { + SilcChannelPrivateKey entry; + + silc_dlist_start(channel->private_keys); + while ((entry = silc_dlist_get(channel->private_keys)) != SILC_LIST_END) { + /* Parse the channel message payload. This also decrypts the payload */ + payload = silc_channel_payload_parse(buffer, entry->cipher, + entry->hmac); + if (payload) + break; + } + if (entry == SILC_LIST_END) + goto out; + } message = silc_channel_get_data(payload, NULL); @@ -285,7 +330,10 @@ void silc_client_receive_channel_key(SilcClient client, encrypted using that key. All clients on the channel must also know the key in order to decrypt the messages. However, it is possible to have several private keys per one channel. In this case only some of the - clients on the channel may now the one key and only some the other key. + clients on the channel may know the one key and only some the other key. + + if `cipher' and/or `hmac' is NULL then default values will be used + (aes-256-cbc for cipher and hmac-sha1-96 for hmac). The private key for channel is optional. If it is not set then the channel messages are encrypted using the channel key generated by the @@ -309,9 +357,63 @@ int silc_client_add_channel_private_key(SilcClient client, SilcClientConnection conn, SilcChannelEntry channel, char *cipher, + char *hmac, unsigned char *key, unsigned int key_len) { + SilcChannelPrivateKey entry; + unsigned char hash[32]; + + if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY)) + return FALSE; + + if (!cipher) + cipher = "aes-256-cbc"; + if (!hmac) + hmac = "hmac-sha1-96"; + + if (!silc_cipher_is_supported(cipher)) + return FALSE; + + if (!silc_hmac_is_supported(hmac)) + return FALSE; + + /* Remove the current key, if it exists. */ + if (channel->channel_key) { + silc_cipher_free(channel->channel_key); + memset(channel->key, 0, channel->key_len / 8); + silc_free(channel->key); + channel->channel_key = NULL; + channel->key = NULL; + channel->key_len = 0; + } + if (channel->hmac) + silc_hmac_free(channel->hmac); + + if (!channel->private_keys) + channel->private_keys = silc_dlist_init(); + + /* Save the key */ + entry = silc_calloc(1, sizeof(*entry)); + entry->key = silc_calloc(key_len, sizeof(*entry->key)); + memcpy(entry->key, key, key_len); + entry->key_len = key_len; + + /* Allocate the cipher and set the key*/ + silc_cipher_alloc(cipher, &entry->cipher); + silc_cipher_set_key(entry->cipher, key, key_len * 8); + + /* Generate HMAC key from the channel key data and set it */ + silc_hmac_alloc(hmac, NULL, &entry->hmac); + silc_hash_make(entry->hmac->hash, key, key_len, hash); + silc_hmac_set_key(entry->hmac, hash, silc_hash_len(entry->hmac->hash)); + memset(hash, 0, sizeof(hash)); + + /* Add to the private keys list */ + silc_dlist_add(channel->private_keys, entry); + + if (!channel->curr_key) + channel->curr_key = entry; return TRUE; } @@ -324,6 +426,25 @@ int silc_client_del_channel_private_keys(SilcClient client, SilcClientConnection conn, SilcChannelEntry channel) { + SilcChannelPrivateKey entry; + + if (!channel->private_keys) + return FALSE; + + silc_dlist_start(channel->private_keys); + while ((entry = silc_dlist_get(channel->private_keys)) != SILC_LIST_END) { + silc_dlist_del(channel->private_keys, entry); + memset(entry->key, 0, entry->key_len); + silc_free(entry->key); + silc_cipher_free(entry->cipher); + silc_hmac_free(entry->hmac); + silc_free(entry); + } + + channel->curr_key = NULL; + + silc_dlist_uninit(channel->private_keys); + channel->private_keys = NULL; return TRUE; } @@ -339,8 +460,34 @@ int silc_client_del_channel_private_key(SilcClient client, SilcChannelEntry channel, SilcChannelPrivateKey key) { + SilcChannelPrivateKey entry; + + if (!channel->private_keys) + return FALSE; + + silc_dlist_start(channel->private_keys); + while ((entry = silc_dlist_get(channel->private_keys)) != SILC_LIST_END) { + if (entry == key) { + if (channel->curr_key == entry) + channel->curr_key = NULL; + + silc_dlist_del(channel->private_keys, entry); + memset(entry->key, 0, entry->key_len); + silc_free(entry->key); + silc_cipher_free(entry->cipher); + silc_hmac_free(entry->hmac); + silc_free(entry); + + if (silc_dlist_count(channel->private_keys) == 0) { + silc_dlist_uninit(channel->private_keys); + channel->private_keys = NULL; + } + + return TRUE; + } + } - return TRUE; + return FALSE; } /* Returns array (pointers) of private keys associated to the `channel'. @@ -355,8 +502,23 @@ silc_client_list_channel_private_keys(SilcClient client, SilcChannelEntry channel, unsigned int *key_count) { + SilcChannelPrivateKey *keys = NULL, entry; + unsigned int count = 0; - return NULL; + if (!channel->private_keys) + return NULL; + + silc_dlist_start(channel->private_keys); + while ((entry = silc_dlist_get(channel->private_keys)) != SILC_LIST_END) { + keys = silc_realloc(keys, sizeof(*keys) * (count + 1)); + keys[count] = entry; + count++; + } + + if (key_count) + *key_count = count; + + return keys; } /* Frees the SilcChannelPrivateKey array. */ @@ -364,5 +526,5 @@ silc_client_list_channel_private_keys(SilcClient client, void silc_client_free_channel_private_keys(SilcChannelPrivateKey *keys, unsigned int key_count) { - + silc_free(keys); } diff --git a/lib/silcclient/client_keyagr.c b/lib/silcclient/client_keyagr.c index 487b8b5b..c80b1c62 100644 --- a/lib/silcclient/client_keyagr.c +++ b/lib/silcclient/client_keyagr.c @@ -178,6 +178,7 @@ SILC_TASK_CALLBACK(silc_client_process_key_agreement) proto_ctx->responder = TRUE; proto_ctx->context = context; proto_ctx->send_packet = silc_client_key_agreement_send_packet; + proto_ctx->verify = silc_client_protocol_ke_verify_key; /* Prepare the connection for key exchange protocol. We allocate the protocol but will not start it yet. The connector will be the @@ -514,6 +515,7 @@ void silc_client_perform_key_agreement_fd(SilcClient client, proto_ctx->responder = FALSE; proto_ctx->context = ke; proto_ctx->send_packet = silc_client_key_agreement_send_packet; + proto_ctx->verify = silc_client_protocol_ke_verify_key; /* Perform key exchange protocol. */ silc_protocol_alloc(SILC_PROTOCOL_CLIENT_KEY_EXCHANGE, diff --git a/lib/silcclient/command_reply.c b/lib/silcclient/command_reply.c index 4f966617..d6247a82 100644 --- a/lib/silcclient/command_reply.c +++ b/lib/silcclient/command_reply.c @@ -741,13 +741,15 @@ SILC_CLIENT_CMD_REPLY_FUNC(join) { SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context; SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data; - SilcClient client = cmd->client; SilcCommandStatus status; SilcIDPayload idp = NULL; SilcChannelEntry channel; - unsigned int argc, mode, len; + SilcIDCacheEntry id_cache = NULL; + SilcChannelUser chu; + unsigned int argc, mode, len, list_count; char *topic, *tmp, *channel_name = NULL, *hmac; - SilcBuffer keyp; + SilcBuffer keyp, client_id_list, client_mode_list; + int i; SILC_LOG_DEBUG(("Start")); @@ -760,7 +762,7 @@ SILC_CLIENT_CMD_REPLY_FUNC(join) } argc = silc_argument_get_arg_num(cmd->args); - if (argc < 3 || argc > 9) { + if (argc < 7 || argc > 14) { cmd->client->ops->say(cmd->client, conn, "Cannot join channel: Bad reply packet"); COMMAND_REPLY_ERROR; @@ -794,14 +796,14 @@ SILC_CLIENT_CMD_REPLY_FUNC(join) } /* Get channel mode */ - tmp = silc_argument_get_arg_type(cmd->args, 4, NULL); + tmp = silc_argument_get_arg_type(cmd->args, 5, NULL); if (tmp) SILC_GET32_MSB(mode, tmp); else mode = 0; /* Get channel key */ - tmp = silc_argument_get_arg_type(cmd->args, 6, &len); + tmp = silc_argument_get_arg_type(cmd->args, 7, &len); if (!tmp) { silc_id_payload_free(idp); silc_free(channel_name); @@ -812,7 +814,7 @@ SILC_CLIENT_CMD_REPLY_FUNC(join) silc_buffer_put(keyp, tmp, len); /* Get topic */ - topic = silc_argument_get_arg_type(cmd->args, 9, NULL); + topic = silc_argument_get_arg_type(cmd->args, 10, NULL); /* Save received Channel ID. This actually creates the channel */ channel = silc_client_new_channel_id(cmd->client, cmd->sock, channel_name, @@ -820,7 +822,7 @@ SILC_CLIENT_CMD_REPLY_FUNC(join) silc_id_payload_free(idp); /* Get hmac */ - hmac = silc_argument_get_arg_type(cmd->args, 10, NULL); + hmac = silc_argument_get_arg_type(cmd->args, 11, NULL); if (hmac) { if (!silc_hmac_alloc(hmac, NULL, &channel->hmac)) { cmd->client->ops->say(cmd->client, conn, @@ -832,20 +834,92 @@ SILC_CLIENT_CMD_REPLY_FUNC(join) } } + /* Get the list count */ + tmp = silc_argument_get_arg_type(cmd->args, 12, &len); + if (!tmp) + goto out; + SILC_GET32_MSB(list_count, tmp); + + /* Get Client ID list */ + tmp = silc_argument_get_arg_type(cmd->args, 13, &len); + if (!tmp) + goto out; + + client_id_list = silc_buffer_alloc(len); + silc_buffer_pull_tail(client_id_list, len); + silc_buffer_put(client_id_list, tmp, len); + + /* Get client mode list */ + tmp = silc_argument_get_arg_type(cmd->args, 14, &len); + if (!tmp) + goto out; + + client_mode_list = silc_buffer_alloc(len); + silc_buffer_pull_tail(client_mode_list, len); + silc_buffer_put(client_mode_list, tmp, len); + + /* Add clients we received in the reply to the channel */ + for (i = 0; i < list_count; i++) { + unsigned short idp_len; + unsigned int mode; + SilcClientID *client_id; + SilcClientEntry client_entry; + + /* Client ID */ + SILC_GET16_MSB(idp_len, client_id_list->data + 2); + idp_len += 4; + client_id = silc_id_payload_parse_id(client_id_list->data, idp_len); + if (!client_id) + continue; + + /* Mode */ + SILC_GET32_MSB(mode, client_mode_list->data); + + /* Check if we have this client cached already. */ + if (!silc_idcache_find_by_id_one(conn->client_cache, (void *)client_id, + SILC_ID_CLIENT, &id_cache)) { + /* No, we don't have it, add entry for it. */ + client_entry = silc_calloc(1, sizeof(*client_entry)); + client_entry->id = silc_id_dup(client_id, SILC_ID_CLIENT); + silc_idcache_add(conn->client_cache, NULL, SILC_ID_CLIENT, + client_entry->id, (void *)client_entry, FALSE); + } else { + /* Yes, we have it already */ + client_entry = (SilcClientEntry)id_cache->context; + if (client_entry == conn->local_entry) + continue; + } + + /* Join the client to the channel */ + chu = silc_calloc(1, sizeof(*chu)); + chu->client = client_entry; + chu->mode = mode; + silc_list_add(channel->clients, chu); + silc_free(client_id); + + silc_buffer_pull(client_id_list, idp_len); + silc_buffer_pull(client_mode_list, 4); + } + silc_buffer_push(client_id_list, client_id_list->data - + client_id_list->head); + silc_buffer_push(client_mode_list, client_mode_list->data - + client_mode_list->head); + /* Save channel key */ silc_client_save_channel_key(conn, keyp, channel); - silc_buffer_free(keyp); - - if (topic) - client->ops->say(cmd->client, conn, - "Topic for %s: %s", channel_name, topic); /* Notify application */ - COMMAND_REPLY((ARGS, channel_name, channel, mode, NULL, NULL, topic, hmac)); + COMMAND_REPLY((ARGS, channel_name, channel, mode, 0, keyp->head, NULL, + NULL, topic, hmac, list_count, client_id_list, + client_mode_list)); /* Execute any pending command callbacks */ SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_JOIN); + silc_buffer_free(keyp); + silc_buffer_free(client_id_list); + silc_buffer_free(client_mode_list); + out: SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_JOIN); silc_client_command_reply_free(cmd); @@ -1341,49 +1415,8 @@ SILC_CLIENT_CMD_REPLY_FUNC(users) return; } - /* We have all the clients on the channel cached now. Create a nice - output for user interface and notify application. */ - - if (!cmd->callback) { - /* Server has sent us USERS reply even when we haven't actually sent - USERS command. This is normal behaviour when joining to a channel. - Display some nice information on the user interface. */ - int k = 0, len1 = 0, len2 = 0; - char *name_list = NULL; - - silc_list_start(channel->clients); - while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) { - char *m, *n = chu->client->nickname; - len2 = strlen(n); - len1 += len2; - - name_list = silc_realloc(name_list, sizeof(*name_list) * (len1 + 3)); - - m = silc_client_chumode_char(chu->mode); - if (m) { - memcpy(name_list + (len1 - len2), m, strlen(m)); - len1 += strlen(m); - silc_free(m); - } - - memcpy(name_list + (len1 - len2), n, len2); - name_list[len1] = 0; - - if (k == silc_list_count(channel->clients) - 1) - break; - memcpy(name_list + len1, " ", 1); - len1++; - k++; - } - - cmd->client->ops->say(cmd->client, conn, "Users on %s: %s", - channel->channel_name, name_list); - silc_free(name_list); - } - /* Notify application */ - COMMAND_REPLY((ARGS, channel, client_id_list->head, - client_mode_list->head)); + COMMAND_REPLY((ARGS, channel, list_count, client_id_list, client_mode_list)); /* Execute any pending command callbacks */ SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_USERS); diff --git a/lib/silcclient/idlist.c b/lib/silcclient/idlist.c index 7a2fc657..29752426 100644 --- a/lib/silcclient/idlist.c +++ b/lib/silcclient/idlist.c @@ -170,6 +170,167 @@ SilcClientEntry *silc_client_get_clients_local(SilcClient client, return clients; } +typedef struct { + SilcClient client; + SilcClientConnection conn; + unsigned int list_count; + SilcBuffer client_id_list; + SilcGetClientCallback completion; + void *context; + int found; +} *GetClientsByListInternal; + +SILC_CLIENT_CMD_FUNC(get_clients_list_callback) +{ + GetClientsByListInternal i = (GetClientsByListInternal)context; + SilcIDCacheEntry id_cache = NULL; + SilcBuffer client_id_list = i->client_id_list; + SilcClientEntry *clients = NULL; + unsigned int clients_count = 0; + int c; + + for (c = 0; c < i->list_count; c++) { + unsigned short idp_len; + SilcClientID *client_id; + + /* Get Client ID */ + SILC_GET16_MSB(idp_len, client_id_list->data + 2); + idp_len += 4; + client_id = silc_id_payload_parse_id(client_id_list->data, idp_len); + if (!client_id) + continue; + + /* Get the client entry */ + if (silc_idcache_find_by_id_one(i->conn->client_cache, (void *)client_id, + SILC_ID_CLIENT, &id_cache)) { + clients = silc_realloc(clients, sizeof(*clients) * + (clients_count + 1)); + clients[clients_count] = (SilcClientEntry)id_cache->context; + clients_count++; + i->found = TRUE; + } + + silc_free(client_id); + silc_buffer_pull(client_id_list, idp_len); + } + + if (i->found) { + i->completion(i->client, i->conn, clients, clients_count, i->context); + silc_free(clients); + } +} + +static void silc_client_get_clients_list_destructor(void *context) +{ + GetClientsByListInternal i = (GetClientsByListInternal)context; + + if (i->found == FALSE) + i->completion(i->client, i->conn, NULL, 0, i->context); + + if (i->client_id_list) + silc_buffer_free(i->client_id_list); + silc_free(i); +} + +/* Gets client entries by the list of client ID's `client_id_list'. This + always resolves those client ID's it does not know yet from the server + so this function might take a while. The `client_id_list' is a list + of ID Payloads added one after other. JOIN command reply and USERS + command reply for example returns this sort of list. The `completion' + will be called after the entries are available. */ + +void silc_client_get_clients_by_list(SilcClient client, + SilcClientConnection conn, + unsigned int list_count, + SilcBuffer client_id_list, + SilcGetClientCallback completion, + void *context) +{ + SilcIDCacheEntry id_cache = NULL; + int i; + unsigned char **res_argv = NULL; + unsigned int *res_argv_lens = NULL, *res_argv_types = NULL, res_argc = 0; + GetClientsByListInternal in; + + in = silc_calloc(1, sizeof(*in)); + in->client = client; + in->conn = conn; + in->list_count = list_count; + in->client_id_list = silc_buffer_copy(client_id_list); + in->completion = completion; + in->context = context; + + for (i = 0; i < list_count; i++) { + unsigned short idp_len; + SilcClientID *client_id; + SilcClientEntry entry; + + /* Get Client ID */ + SILC_GET16_MSB(idp_len, client_id_list->data + 2); + idp_len += 4; + client_id = silc_id_payload_parse_id(client_id_list->data, idp_len); + if (!client_id) + continue; + + /* Check if we have this client cached already. */ + id_cache = NULL; + silc_idcache_find_by_id_one(conn->client_cache, (void *)client_id, + SILC_ID_CLIENT, &id_cache); + + /* If we don't have the entry or it has incomplete info, then resolve + it from the server. */ + entry = id_cache ? (SilcClientEntry)id_cache->context : NULL; + if (!id_cache || !entry->nickname) { + /* No we don't have it, query it from the server. Assemble argument + table that will be sent fr the IDENTIFY command later. */ + res_argv = silc_realloc(res_argv, sizeof(*res_argv) * + (res_argc + 1)); + res_argv_lens = silc_realloc(res_argv_lens, sizeof(*res_argv_lens) * + (res_argc + 1)); + res_argv_types = silc_realloc(res_argv_types, sizeof(*res_argv_types) * + (res_argc + 1)); + res_argv[res_argc] = client_id_list->data; + res_argv_lens[res_argc] = idp_len; + res_argv_types[res_argc] = res_argc + 3; + res_argc++; + } + + silc_free(client_id); + silc_buffer_pull(client_id_list, idp_len); + } + silc_buffer_push(client_id_list, client_id_list->data - + client_id_list->head); + + /* Query the client information from server if the list included clients + that we don't know about. */ + if (res_argc) { + SilcBuffer res_cmd; + + /* Send the IDENTIFY command to server */ + res_cmd = silc_command_payload_encode(SILC_COMMAND_IDENTIFY, + res_argc, res_argv, res_argv_lens, + res_argv_types, ++conn->cmd_ident); + silc_client_packet_send(client, conn->sock, SILC_PACKET_COMMAND, + NULL, 0, NULL, NULL, res_cmd->data, res_cmd->len, + TRUE); + + silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, + conn->cmd_ident, + silc_client_get_clients_list_destructor, + silc_client_command_get_clients_list_callback, + (void *)in); + + silc_buffer_free(res_cmd); + silc_free(res_argv); + silc_free(res_argv_lens); + silc_free(res_argv_types); + return; + } + + /* We have the clients in cache, get them and call the completion */ + silc_client_command_get_clients_list_callback((void *)in); +} + /* The old style function to find client entry. This is used by the library internally. If `query' is TRUE then the client information is requested by the server. The pending command callback must be set diff --git a/lib/silcclient/idlist.h b/lib/silcclient/idlist.h index f7d69337..513a52ef 100644 --- a/lib/silcclient/idlist.h +++ b/lib/silcclient/idlist.h @@ -49,10 +49,16 @@ typedef struct SilcChannelUserStruct { struct SilcChannelUserStruct *next; } *SilcChannelUser; +/* Structure to hold one channel private key. */ +typedef struct { + SilcCipher cipher; /* The cipher and key */ + SilcHmac hmac; /* The HMAC and hmac key */ + unsigned char *key; /* The key data */ + unsigned int key_len; /* The key length */ +} *SilcChannelPrivateKey; + /* Channel entry context. This is allocate for every channel client has joined to. This includes for example the channel specific keys */ -/* XXX channel_key is the server generated key. Later this context must - include the channel private key. */ typedef struct SilcChannelEntryStruct { char *channel_name; SilcChannelID *id; @@ -63,11 +69,13 @@ typedef struct SilcChannelEntryStruct { SilcList clients; /* Channel keys */ - SilcCipher channel_key; - unsigned char *key; + SilcCipher channel_key; /* The channel key */ + unsigned char *key; /* Raw key data */ unsigned int key_len; - unsigned char iv[SILC_CIPHER_MAX_IV_SIZE]; - SilcHmac hmac; + unsigned char iv[SILC_CIPHER_MAX_IV_SIZE]; /* Current IV */ + SilcHmac hmac; /* Current HMAC */ + SilcDList private_keys; /* List of private keys or NULL */ + SilcChannelPrivateKey curr_key; /* Current private key */ } *SilcChannelEntry; /* Prototypes (some functions are defined in the silcapi.h) */ diff --git a/lib/silcclient/protocol.c b/lib/silcclient/protocol.c index a4088026..5040a301 100644 --- a/lib/silcclient/protocol.c +++ b/lib/silcclient/protocol.c @@ -55,12 +55,11 @@ void silc_client_protocol_ke_send_packet(SilcSKE ske, /* Callback that is called when we have received KE2 payload from responder. We try to verify the public key now. */ -static SilcSKEStatus -silc_client_protocol_ke_verify_key(SilcSKE ske, - unsigned char *pk_data, - unsigned int pk_len, - SilcSKEPKType pk_type, - void *context) +SilcSKEStatus silc_client_protocol_ke_verify_key(SilcSKE ske, + unsigned char *pk_data, + unsigned int pk_len, + SilcSKEPKType pk_type, + void *context) { SilcProtocol protocol = (SilcProtocol)context; SilcClientKEInternalContext *ctx = @@ -313,8 +312,7 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange) /* Finish the protocol. This verifies the Key Exchange 2 payload sent by responder. */ status = silc_ske_initiator_finish(ctx->ske, ctx->packet->buffer, - silc_client_protocol_ke_verify_key, - context, NULL, NULL); + ctx->verify, context, NULL, NULL); } if (status != SILC_SKE_STATUS_OK) { diff --git a/lib/silcclient/protocol.h b/lib/silcclient/protocol.h index 92c195ab..4fa009fb 100644 --- a/lib/silcclient/protocol.h +++ b/lib/silcclient/protocol.h @@ -41,6 +41,7 @@ typedef struct { SilcPacketContext *packet; SilcSKESendPacketCb send_packet; /* SKE's packet sending callback */ + SilcSKEVerifyCb verify; /* SKE's key verify callback */ SilcSKE ske; /* The SKE object */ SilcSKEKeyMaterial *keymat; /* The negotiated key material */ void *context; /* Internal context */ @@ -78,6 +79,11 @@ void silc_client_protocol_ke_send_packet(SilcSKE ske, SilcBuffer packet, SilcPacketType type, void *context); +SilcSKEStatus silc_client_protocol_ke_verify_key(SilcSKE ske, + unsigned char *pk_data, + unsigned int pk_len, + SilcSKEPKType pk_type, + void *context); void silc_client_protocol_ke_set_keys(SilcSKE ske, SilcSocketConnection sock, SilcSKEKeyMaterial *keymat, diff --git a/lib/silcclient/silcapi.h b/lib/silcclient/silcapi.h index 67372a55..bdef1c76 100644 --- a/lib/silcclient/silcapi.h +++ b/lib/silcclient/silcapi.h @@ -35,6 +35,8 @@ of how to use the SILC Client Library. */ +/* General definitions */ + /* Key agreement callback that is called after the key agreement protocol has been performed. This is called also if error occured during the key agreement protocol. The `key' is the allocated key material and @@ -48,6 +50,19 @@ typedef void (*SilcKeyAgreementCallback)(SilcClient client, SilcSKEKeyMaterial *key, void *context); +/* Structure to hold the list of private message keys. The array of this + structure is returned by the silc_client_list_private_message_keys + function. */ +typedef struct { + SilcClientEntry client_entry; /* The remote client entry */ + char *cipher; /* The cipher name */ + unsigned char *key; /* The original key, If the appliation + provided it. This is NULL if the + library generated the key or if + the SKE key material was used. */ + unsigned int key_len; /* The key length */ +} *SilcPrivateMessageKeys; + /****************************************************************************** SILC Client Operations @@ -263,10 +278,17 @@ void silc_client_close_connection(SilcClient client, encrypted with the next receiver's key and the rest of the packet is encrypted with the channel specific key. Padding and HMAC is computed with the next receiver's key. The `data' is the channel message. If - the `force_send' is TRUE then the packet is sent immediately. */ + the `force_send' is TRUE then the packet is sent immediately. + + If `key' is provided then that private key is used to encrypt the + channel message. If it is not provided, private keys has not been + set at all, the normal channel key is used automatically. If private + keys are set then the first key (the key that was added first as + private key) is used. */ void silc_client_send_channel_message(SilcClient client, SilcClientConnection conn, SilcChannelEntry channel, + SilcChannelPrivateKey key, unsigned char *data, unsigned int data_len, int force_send); @@ -321,6 +343,19 @@ SilcClientEntry *silc_client_get_clients_local(SilcClient client, char *server, unsigned int *clients_count); +/* Gets client entries by the list of client ID's `client_id_list'. This + always resolves those client ID's it does not know yet from the server + so this function might take a while. The `client_id_list' is a list + of ID Payloads added one after other. JOIN command reply and USERS + command reply for example returns this sort of list. The `completion' + will be called after the entries are available. */ +void silc_client_get_clients_by_list(SilcClient client, + SilcClientConnection conn, + unsigned int list_count, + SilcBuffer client_id_list, + SilcGetClientCallback completion, + void *context); + /* Find entry for client by the client's ID. Returns the entry or NULL if the entry was not found. */ SilcClientEntry silc_client_get_client_by_id(SilcClient client, @@ -447,19 +482,6 @@ int silc_client_del_private_message_key(SilcClient client, SilcClientConnection conn, SilcClientEntry client_entry); -/* Structure to hold the list of private message keys. The array of this - structure is returned by the silc_client_list_private_message_keys - function. */ -typedef struct { - SilcClientEntry client_entry; /* The remote client entry */ - char *cipher; /* The cipher name */ - unsigned char *key; /* The original key, If the appliation - provided it. This is NULL if the - library generated the key or if - the SKE key material was used. */ - unsigned int key_len; /* The key length */ -} *SilcPrivateMessageKeys; - /* Returns array of set private message keys associated to the connection `conn'. Returns allocated SilcPrivateMessageKeys array and the array count to the `key_count' argument. The array must be freed by the caller @@ -478,7 +500,8 @@ void silc_client_free_private_message_keys(SilcPrivateMessageKeys keys, unsigned int key_count); -/* Channel private key management (client_channel.c) */ +/* Channel private key management (client_channel.c, + SilcChannelPrivateKey is defined in idlist.h) */ /* Adds private key for channel. This may be set only if the channel's mode mask includes the SILC_CHANNEL_MODE_PRIVKEY. This returns FALSE if the @@ -486,7 +509,7 @@ void silc_client_free_private_message_keys(SilcPrivateMessageKeys keys, encrypted using that key. All clients on the channel must also know the key in order to decrypt the messages. However, it is possible to have several private keys per one channel. In this case only some of the - clients on the channel may now the one key and only some the other key. + clients on the channel may know the one key and only some the other key. The private key for channel is optional. If it is not set then the channel messages are encrypted using the channel key generated by the @@ -509,6 +532,7 @@ int silc_client_add_channel_private_key(SilcClient client, SilcClientConnection conn, SilcChannelEntry channel, char *cipher, + char *hmac, unsigned char *key, unsigned int key_len); @@ -519,13 +543,6 @@ int silc_client_del_channel_private_keys(SilcClient client, SilcClientConnection conn, SilcChannelEntry channel); -/* Structure to hold one channel private key. */ -typedef struct { - char *cipher; /* The cipher name */ - unsigned char *key; /* The key */ - unsigned int key_len; /* The key length */ -} *SilcChannelPrivateKey; - /* Removes and frees private key `key' from the channel `channel'. The `key' is retrieved by calling the function silc_client_list_channel_private_keys. The key is not used after this. If the key was last private key then the -- 2.24.0