X-Git-Url: http://git.silcnet.org/gitweb/?p=silc.git;a=blobdiff_plain;f=lib%2Fsilcclient%2Fclient_register.c;h=efbeaa24d9cae4534d01d65ad53e221b9ec83fb7;hp=5d12f79bfff7671e31f6122b1fbd4d4fa63d170f;hb=HEAD;hpb=ce6ade69cd8e0aeca9ef097b2ceec9d43186d91f diff --git a/lib/silcclient/client_register.c b/lib/silcclient/client_register.c index 5d12f79b..efbeaa24 100644 --- a/lib/silcclient/client_register.c +++ b/lib/silcclient/client_register.c @@ -4,7 +4,7 @@ Author: Pekka Riikonen - Copyright (C) 2006 Pekka Riikonen + Copyright (C) 2006 - 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 @@ -28,17 +28,66 @@ typedef struct { SilcClient client; SilcClientConnection conn; SilcBufferStruct detach; + SilcBuffer auth; char *nickname; - SilcClientID client_id; + unsigned char *id; + SilcUInt32 id_len; SilcUInt32 channel_count; - SilcUInt32 *cmd_idents; - SilcUInt32 cmd_idents_count; - SilcBool success; } *SilcClientResumeSession; - /************************ Static utility functions **************************/ +/* Continues resuming after resolving. Continue after last reply. */ + +static SilcBool +silc_client_resume_continue(SilcClient client, + SilcClientConnection conn, + SilcCommand command, + SilcStatus status, + SilcStatus error, + void *context, + va_list ap) +{ + if (status == SILC_STATUS_OK || status == SILC_STATUS_LIST_END || + SILC_STATUS_IS_ERROR(status)) { + silc_fsm_continue(&conn->internal->event_thread); + return FALSE; + } + + return TRUE; +} + +/* Function used to call command replies back to application in resuming. */ + +static void +silc_client_resume_command_callback(SilcClient client, + SilcClientConnection conn, + SilcCommand command, ...) +{ + va_list ap; + va_start(ap, command); + client->internal->ops->command_reply(client, conn, command, + SILC_STATUS_OK, SILC_STATUS_OK, ap); + va_end(ap); +} + +/* Resume authentication data generation callback */ + +static void silc_client_resume_auth_generated(const SilcBuffer data, + void *context) +{ + SilcClientConnection conn = context; + SilcClientResumeSession resume = + silc_fsm_get_state_context(&conn->internal->event_thread); + + if (!data) + silc_fsm_next(&conn->internal->event_thread, silc_client_st_resume_error); + else + resume->auth = silc_buffer_copy(data); + + SILC_FSM_CALL_CONTINUE_SYNC(&conn->internal->event_thread); +} + /****************************** NEW_ID packet *******************************/ @@ -49,6 +98,7 @@ SILC_FSM_STATE(silc_client_new_id) SilcClientConnection conn = fsm_context; SilcClient client = conn->client; SilcPacket packet = state_context; + char *nick; SilcID id; if (conn->local_id) @@ -60,23 +110,28 @@ SILC_FSM_STATE(silc_client_new_id) silc_buffer_len(&packet->buffer), &id)) goto out; + SILC_LOG_DEBUG(("New ID %s", silc_id_render(&id.u.client_id, + SILC_ID_CLIENT))); + + /* From SILC protocol version 1.3, nickname is in NEW_CLIENT packet */ + if (conn->internal->remote_version >= 13) + nick = (conn->internal->params.nickname ? + conn->internal->params.nickname : client->username); + else + nick = client->username; + /* Create local client entry */ - conn->local_entry = silc_client_add_client(client, conn, - client->username, + conn->local_entry = silc_client_add_client(client, conn, nick, client->username, client->realname, &id.u.client_id, 0); if (!conn->local_entry) goto out; - /* Save the ID */ + /* Save the ID. Take reference to conn->local_id. */ conn->local_id = &conn->local_entry->id; conn->internal->local_idp = silc_buffer_copy(&packet->buffer); - /* Save cache entry */ - silc_idcache_find_by_id_one(conn->internal->client_cache, conn->local_id, - &conn->internal->local_entry); - /* Save remote ID */ if (packet->src_id_len) { conn->internal->remote_idp = @@ -90,6 +145,10 @@ SILC_FSM_STATE(silc_client_new_id) &conn->remote_id); } + /* Set IDs to the packet stream */ + silc_packet_set_ids(conn->stream, SILC_ID_CLIENT, conn->local_id, + conn->remote_id.type, SILC_ID_GET_ID(conn->remote_id)); + /* Signal connection that new ID was received so it can continue with the registering. */ if (conn->internal->registering) @@ -110,9 +169,15 @@ SILC_FSM_STATE(silc_client_st_register) { SilcClientConnection conn = fsm_context; SilcClient client = conn->client; + char *nick = NULL; SILC_LOG_DEBUG(("Register to network")); + /* From SILC protocol version 1.3, nickname is in NEW_CLIENT packet */ + if (conn->internal->remote_version >= 13) + nick = (conn->internal->params.nickname ? + conn->internal->params.nickname : client->username); + /* Send NEW_CLIENT packet to register to network */ if (!silc_packet_send_va(conn->stream, SILC_PACKET_NEW_CLIENT, 0, SILC_STR_UI_SHORT(strlen(client->username)), @@ -121,6 +186,8 @@ SILC_FSM_STATE(silc_client_st_register) SILC_STR_UI_SHORT(strlen(client->realname)), SILC_STR_DATA(client->realname, strlen(client->realname)), + SILC_STR_UI_SHORT(nick ? strlen(nick) : 0), + SILC_STR_DATA(nick, nick ? strlen(nick) : 0), SILC_STR_END)) { /** Error sending packet */ silc_fsm_next(fsm, silc_client_st_register_error); @@ -129,7 +196,8 @@ SILC_FSM_STATE(silc_client_st_register) /** Wait for new ID */ conn->internal->registering = TRUE; - silc_fsm_next_later(fsm, silc_client_st_register_complete, 15, 0); + silc_fsm_next_later(fsm, silc_client_st_register_complete, + conn->internal->retry_timer, 0); return SILC_FSM_WAIT; } @@ -140,32 +208,51 @@ SILC_FSM_STATE(silc_client_st_register_complete) SilcClientConnection conn = fsm_context; SilcClient client = conn->client; - if (!conn->local_id) { - /** Timeout, ID not received */ - conn->internal->registering = FALSE; + if (conn->internal->disconnected) { + /** Disconnected */ silc_fsm_next(fsm, silc_client_st_register_error); return SILC_FSM_CONTINUE; } + if (!conn->local_id) { + if (conn->internal->retry_count++ >= SILC_CLIENT_RETRY_COUNT) { + /** Timeout, ID not received */ + conn->internal->registering = FALSE; + conn->internal->retry_count = 0; + conn->internal->retry_timer = SILC_CLIENT_RETRY_MIN; + silc_fsm_next(fsm, silc_client_st_register_error); + return SILC_FSM_CONTINUE; + } + + /** Resend registering packet */ + silc_fsm_next(fsm, silc_client_st_register); + conn->internal->retry_timer = ((conn->internal->retry_timer * + SILC_CLIENT_RETRY_MUL) + + (silc_rng_get_rn16(client->rng) % + SILC_CLIENT_RETRY_RAND)); + return SILC_FSM_CONTINUE; + } + SILC_LOG_DEBUG(("Registered to network")); /* Issue IDENTIFY command for itself to get resolved hostname correctly from server. */ - silc_client_command_send(client, conn, SILC_COMMAND_IDENTIFY, NULL, NULL, + silc_client_command_send(client, conn, SILC_COMMAND_IDENTIFY, + silc_client_command_called_dummy, NULL, 1, 5, silc_buffer_data(conn->internal->local_idp), silc_buffer_len(conn->internal->local_idp)); - /* Send NICK command if the nickname was set by the application (and is - not same as the username). Send this with little timeout. */ - if (conn->internal->params.nickname && + /* With SILC protocol version 1.2 call NICK command if the nickname was + set by the application. */ + if (conn->internal->params.nickname && conn->internal->remote_version < 13 && !silc_utf8_strcasecmp(conn->internal->params.nickname, client->username)) - silc_client_command_send(client, conn, SILC_COMMAND_NICK, NULL, NULL, - 1, 1, conn->internal->params.nickname, - strlen(conn->internal->params.nickname)); + silc_client_command_call(client, conn, NULL, + "NICK", conn->internal->params.nickname, NULL); /* Issue INFO command to fetch the real server name and server information and other stuff. */ - silc_client_command_send(client, conn, SILC_COMMAND_INFO, NULL, NULL, + silc_client_command_send(client, conn, SILC_COMMAND_INFO, + silc_client_command_called_dummy, NULL, 1, 2, silc_buffer_data(conn->internal->remote_idp), silc_buffer_len(conn->internal->remote_idp)); @@ -174,24 +261,35 @@ SILC_FSM_STATE(silc_client_st_register_complete) conn->callback_context); conn->internal->registering = FALSE; + silc_schedule_task_del_by_all(conn->internal->schedule, 0, + silc_client_connect_timeout, conn); + silc_async_free(conn->internal->cop); + conn->internal->cop = NULL; + return SILC_FSM_FINISH; } +/* Error registering to network */ + SILC_FSM_STATE(silc_client_st_register_error) { SilcClientConnection conn = fsm_context; - SilcClient client = conn->client; - /* XXX */ - /* Close connection */ + SILC_LOG_DEBUG(("Error registering to network")); - conn->callback(client, conn, SILC_CLIENT_CONN_ERROR, 0, NULL, - conn->callback_context); + /* Signal to close connection */ + conn->internal->status = SILC_CLIENT_CONN_ERROR; + if (!conn->internal->disconnected) { + conn->internal->disconnected = TRUE; + SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event); + } + + silc_schedule_task_del_by_all(conn->internal->schedule, 0, + silc_client_connect_timeout, conn); return SILC_FSM_FINISH; } - /************************* Resume detached session **************************/ /* Resume detached session */ @@ -201,9 +299,9 @@ SILC_FSM_STATE(silc_client_st_resume) SilcClientConnection conn = fsm_context; SilcClient client = conn->client; SilcClientResumeSession resume; - SilcBuffer auth; unsigned char *id; SilcUInt16 id_len; + SilcClientID client_id; int ret; SILC_LOG_DEBUG(("Resuming detached session")); @@ -232,55 +330,75 @@ SILC_FSM_STATE(silc_client_st_resume) SILC_STR_END); if (ret < 0) { /** Malformed detach data */ + SILC_LOG_DEBUG(("Malformed detachment data")); silc_fsm_next(fsm, silc_client_st_resume_error); return SILC_FSM_CONTINUE; } - if (!silc_id_str2id(id, id_len, SILC_ID_CLIENT, &resume->client_id, - sizeof(resume->client_id))) { + if (!silc_id_str2id(id, id_len, SILC_ID_CLIENT, &client_id, + sizeof(client_id))) { /** Malformed ID */ + SILC_LOG_DEBUG(("Malformed ID")); silc_fsm_next(fsm, silc_client_st_resume_error); return SILC_FSM_CONTINUE; } + resume->id = id; + resume->id_len = id_len; /* Generate authentication data that server will verify */ - auth = silc_auth_public_key_auth_generate(conn->public_key, - conn->private_key, - client->rng, - conn->internal->hash, - &resume->client_id, - SILC_ID_CLIENT); - if (!auth) { - /** Out of memory */ - silc_fsm_next(fsm, silc_client_st_resume_error); - return SILC_FSM_CONTINUE; - } + silc_fsm_next(fsm, silc_client_st_resume_send); + SILC_FSM_CALL(silc_auth_public_key_auth_generate( + conn->public_key, conn->private_key, + client->rng, conn->internal->hash, + &client_id, SILC_ID_CLIENT, + silc_client_resume_auth_generated, conn)); + /* NOT REACHED */ +} + +/* Send RESUME_CLIENT packet */ + +SILC_FSM_STATE(silc_client_st_resume_send) +{ + SilcClientConnection conn = fsm_context; + SilcClientResumeSession resume = state_context; + + SILC_LOG_DEBUG(("Send RESUME_CLIENT packet")); /* Send RESUME_CLIENT packet to resume to network */ if (!silc_packet_send_va(conn->stream, SILC_PACKET_RESUME_CLIENT, 0, - SILC_STR_UI_SHORT(id_len), - SILC_STR_UI_XNSTRING(id, id_len), - SILC_STR_UI_XNSTRING(silc_buffer_data(auth), - silc_buffer_len(auth)), + SILC_STR_UI_SHORT(resume->id_len), + SILC_STR_DATA(resume->id, resume->id_len), + SILC_STR_DATA(silc_buffer_data(resume->auth), + silc_buffer_len(resume->auth)), SILC_STR_END)) { /** Error sending packet */ + SILC_LOG_DEBUG(("Error sending packet")); silc_fsm_next(fsm, silc_client_st_resume_error); return SILC_FSM_CONTINUE; } /** Wait for new ID */ conn->internal->registering = TRUE; - silc_fsm_next_later(fsm, silc_client_st_resume_resolve, 15, 0); + silc_fsm_next_later(fsm, silc_client_st_resume_resolve_channels, 15, 0); return SILC_FSM_WAIT; } -/* Resolve the old session information */ +/* Resolve the old session information, user mode and joined channels. */ -SILC_FSM_STATE(silc_client_st_resume_resolve) +SILC_FSM_STATE(silc_client_st_resume_resolve_channels) { -#if 0 SilcClientConnection conn = fsm_context; + SilcClient client = conn->client; SilcClientResumeSession resume = state_context; + SilcUInt32 *res_argv_lens = NULL, *res_argv_types = NULL, res_argc = 0; + unsigned char **res_argv = NULL; + int i; + + if (conn->internal->disconnected) { + /** Disconnected */ + silc_fsm_next(fsm, silc_client_st_resume_error); + return SILC_FSM_CONTINUE; + } if (!conn->local_id) { /** Timeout, ID not received */ @@ -289,45 +407,254 @@ SILC_FSM_STATE(silc_client_st_resume_resolve) return SILC_FSM_CONTINUE; } + /** Wait for channels */ + silc_fsm_next(fsm, silc_client_st_resume_resolve_cmodes); + + /* Change our nickname */ + silc_client_change_nickname(client, conn, conn->local_entry, + resume->nickname, NULL, NULL, 0); - for (i = 0; i < ch_count; i++) { - char *channel; + /* Send UMODE command to get our own user mode in the network */ + SILC_LOG_DEBUG(("Resolving user mode")); + silc_client_command_send(client, conn, SILC_COMMAND_UMODE, + silc_client_command_called_dummy, NULL, + 1, 1, silc_buffer_data(conn->internal->local_idp), + silc_buffer_len(conn->internal->local_idp)); + + if (!resume->channel_count) + return SILC_FSM_YIELD; + + /* Send IDENTIFY command for all channels we know about. These are the + channels we've joined to according our detachment data. */ + for (i = 0; i < resume->channel_count; i++) { + SilcChannelEntry channel; unsigned char *chid; SilcUInt16 chid_len; - SilcUInt32 ch_mode; - SilcChannelID *channel_id; - SilcChannelEntry channel_entry; - - len = silc_buffer_unformat(&detach, - SILC_STR_UI16_NSTRING_ALLOC(&channel, NULL), - SILC_STR_UI16_NSTRING(&chid, &chid_len), - SILC_STR_UI_INT(&ch_mode), - SILC_STR_END); - if (len == -1) - return FALSE; - - /* Add new channel */ - channel_id = silc_id_str2id(chid, chid_len, SILC_ID_CHANNEL); - channel_entry = silc_client_get_channel_by_id(client, conn, channel_id); - if (!channel_entry) { - channel_entry = silc_client_add_channel(client, conn, channel, ch_mode, - channel_id); - } else { - silc_free(channel); - silc_free(channel_id); - } + SilcBuffer idp; + SilcChannelID channel_id; + char *name; - silc_buffer_pull(&detach, len); + if (silc_buffer_unformat(&resume->detach, + SILC_STR_ADVANCE, + SILC_STR_UI16_NSTRING(&name, NULL), + SILC_STR_UI16_NSTRING(&chid, &chid_len), + SILC_STR_UI_INT(NULL), + SILC_STR_END) < 0) + continue; + + if (!silc_id_str2id(chid, chid_len, SILC_ID_CHANNEL, &channel_id, + sizeof(channel_id))) + continue; + idp = silc_id_payload_encode_data(chid, chid_len, SILC_ID_CHANNEL); + if (!idp) + continue; + + /* Add the channel to cache */ + channel = silc_client_get_channel_by_id(client, conn, &channel_id); + if (!channel) + silc_client_add_channel(client, conn, name, 0, &channel_id); + else + silc_client_unref_channel(client, conn, channel); + + 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] = silc_buffer_steal(idp, &res_argv_lens[res_argc]); + res_argv_types[res_argc] = res_argc + 5; + res_argc++; + silc_buffer_free(idp); + } + + /* Send IDENTIFY command */ + SILC_LOG_DEBUG(("Resolving joined channels")); + silc_client_command_send_argv(client, conn, SILC_COMMAND_IDENTIFY, + silc_client_resume_continue, conn, + res_argc, res_argv, res_argv_lens, + res_argv_types); + + for (i = 0; i < resume->channel_count; i++) + silc_free(res_argv[i]); + silc_free(res_argv); + silc_free(res_argv_lens); + silc_free(res_argv_types); + + return SILC_FSM_WAIT; +} + +/* Resolve joined channel modes, users and topics. */ + +SILC_FSM_STATE(silc_client_st_resume_resolve_cmodes) +{ + SilcClientConnection conn = fsm_context; + SilcClient client = conn->client; + SilcClientResumeSession resume = state_context; + SilcIDCacheEntry entry; + SilcChannelEntry channel; + SilcList channels; + SilcBuffer idp; + + if (conn->internal->disconnected) { + /** Disconnected */ + silc_fsm_next(fsm, silc_client_st_resume_error); + return SILC_FSM_CONTINUE; + } + + SILC_LOG_DEBUG(("Resolving channel details")); + + /** Wait for channel modes */ + silc_fsm_next(fsm, silc_client_st_resume_completed); + + if (!silc_idcache_get_all(conn->internal->channel_cache, &channels)) + return SILC_FSM_YIELD; + + /* Resolve channels' mode, users and topic */ + resume->channel_count = silc_list_count(channels) * 3; + silc_list_start(channels); + while ((entry = silc_list_get(channels))) { + channel = entry->context; + idp = silc_id_payload_encode(&channel->id, SILC_ID_CHANNEL); + if (!idp) + continue; + + silc_client_command_send(client, conn, SILC_COMMAND_CMODE, + silc_client_resume_continue, conn, 1, + 1, silc_buffer_data(idp), + silc_buffer_len(idp)); + silc_client_command_send(client, conn, SILC_COMMAND_USERS, + silc_client_resume_continue, conn, 1, + 1, silc_buffer_data(idp), + silc_buffer_len(idp)); + silc_client_command_send(client, conn, SILC_COMMAND_TOPIC, + silc_client_resume_continue, conn, 1, + 1, silc_buffer_data(idp), + silc_buffer_len(idp)); + silc_buffer_free(idp); + } + + return SILC_FSM_WAIT; +} + +/* Resuming completed */ + +SILC_FSM_STATE(silc_client_st_resume_completed) +{ + SilcClientConnection conn = fsm_context; + SilcClient client = conn->client; + SilcClientResumeSession resume = state_context; + SilcIDCacheEntry entry; + SilcChannelEntry channel; + SilcList channels; + + if (conn->internal->disconnected) { + /** Disconnected */ + silc_fsm_next(fsm, silc_client_st_resume_error); + return SILC_FSM_CONTINUE; } -#endif /* 0 */ + + if (resume->channel_count > 0) { + resume->channel_count--; + if (resume->channel_count) + return SILC_FSM_WAIT; + } + + SILC_LOG_DEBUG(("Resuming completed")); + + /* Issue IDENTIFY command for itself to get resolved hostname + correctly from server. */ + silc_client_command_send(client, conn, SILC_COMMAND_IDENTIFY, + silc_client_command_called_dummy, NULL, + 1, 5, silc_buffer_data(conn->internal->local_idp), + silc_buffer_len(conn->internal->local_idp)); + + /* Issue INFO command to fetch the real server name and server + information and other stuff. */ + silc_client_command_send(client, conn, SILC_COMMAND_INFO, + silc_client_command_called_dummy, NULL, + 1, 2, silc_buffer_data(conn->internal->remote_idp), + silc_buffer_len(conn->internal->remote_idp)); + + /* Call connection callback. We have now resumed to SILC network. */ + conn->callback(client, conn, SILC_CLIENT_CONN_SUCCESS_RESUME, 0, NULL, + conn->callback_context); + + /* Call UMODE command reply. */ + if (conn->local_entry->mode) + silc_client_resume_command_callback(client, conn, SILC_COMMAND_UMODE, + conn->local_entry->mode); + + /* Call NICK command reply. */ + silc_client_resume_command_callback(client, conn, SILC_COMMAND_NICK, + conn->local_entry, + conn->local_entry->nickname, + &conn->local_entry->id); + + /* Call JOIN command replies for all joined channel */ + if (silc_idcache_get_all(conn->internal->channel_cache, &channels)) { + silc_list_start(channels); + while ((entry = silc_list_get(channels))) { + SilcHashTableList htl; + const char *cipher, *hmac; + + channel = entry->context; + cipher = (channel->internal.send_key ? + silc_cipher_get_name(channel->internal.send_key) : NULL); + hmac = (channel->internal.hmac ? + silc_hmac_get_name(channel->internal.hmac) : NULL); + silc_hash_table_list(channel->user_list, &htl); + silc_client_resume_command_callback(client, conn, SILC_COMMAND_JOIN, + channel->channel_name, channel, + channel->mode, &htl, channel->topic, + cipher, hmac, channel->founder_key, + channel->channel_pubkeys, + channel->user_limit); + silc_hash_table_list_reset(&htl); + } + } + + conn->internal->registering = FALSE; + silc_schedule_task_del_by_all(conn->internal->schedule, 0, + silc_client_connect_timeout, conn); + silc_free(resume->nickname); + silc_free(resume); + silc_async_free(conn->internal->cop); + conn->internal->cop = NULL; return SILC_FSM_FINISH; } +/* Error resuming to network */ + SILC_FSM_STATE(silc_client_st_resume_error) { - /* XXX */ - /* Close connection */ + SilcClientConnection conn = fsm_context; + SilcClientResumeSession resume = state_context; + + if (conn->internal->disconnected) { + if (resume) { + silc_free(resume->nickname); + silc_free(resume); + } + return SILC_FSM_FINISH; + } + + SILC_LOG_DEBUG(("Error resuming to network")); + + /* Signal to close connection */ + conn->internal->status = SILC_CLIENT_CONN_ERROR; + if (!conn->internal->disconnected) { + conn->internal->disconnected = TRUE; + SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event); + } + + silc_schedule_task_del_by_all(conn->internal->schedule, 0, + silc_client_connect_timeout, conn); + + if (resume) { + silc_free(resume->nickname); + silc_free(resume); + } return SILC_FSM_FINISH; } @@ -341,11 +668,14 @@ SilcBuffer silc_client_get_detach_data(SilcClient client, SilcBuffer detach; SilcHashTableList htl; SilcChannelUser chu; + unsigned char id[64]; + SilcUInt32 id_len; int ret, ch_count; SILC_LOG_DEBUG(("Creating detachment data")); ch_count = silc_hash_table_count(conn->local_entry->channels); + silc_id_id2str(conn->local_id, SILC_ID_CLIENT, id, sizeof(id), &id_len); /* Save the nickname, Client ID and user mode in SILC network */ detach = silc_buffer_alloc(0); @@ -357,12 +687,8 @@ SilcBuffer silc_client_get_detach_data(SilcClient client, SILC_STR_UI_SHORT(strlen(conn->local_entry->nickname)), SILC_STR_DATA(conn->local_entry->nickname, strlen(conn->local_entry->nickname)), - SILC_STR_UI_SHORT(silc_buffer_len(conn->internal-> - local_idp)), - SILC_STR_DATA(silc_buffer_data(conn->internal-> - local_idp), - silc_buffer_len(conn->internal-> - local_idp)), + SILC_STR_UI_SHORT(id_len), + SILC_STR_DATA(id, id_len), SILC_STR_UI_INT(conn->local_entry->mode), SILC_STR_UI_INT(ch_count), SILC_STR_END); @@ -388,7 +714,6 @@ SilcBuffer silc_client_get_detach_data(SilcClient client, SILC_STR_DATA(chid, chid_len), SILC_STR_UI_INT(chu->channel->mode), SILC_STR_END); - silc_free(chid); } silc_hash_table_list_reset(&htl);