X-Git-Url: http://git.silcnet.org/gitweb/?p=silc.git;a=blobdiff_plain;f=lib%2Fsilcclient%2Fclient_register.c;h=efbeaa24d9cae4534d01d65ad53e221b9ec83fb7;hp=98a86f513f228519c0b0d14be21231f09a862a9b;hb=805fddcf6431e784f9f77114782a90c9d12f9cbe;hpb=dba72cd444469151d0def91ec67361c68ee78022 diff --git a/lib/silcclient/client_register.c b/lib/silcclient/client_register.c index 98a86f51..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 @@ -23,9 +23,71 @@ /************************** Types and definitions ***************************/ +/* Resume session context */ +typedef struct { + SilcClient client; + SilcClientConnection conn; + SilcBufferStruct detach; + SilcBuffer auth; + char *nickname; + unsigned char *id; + SilcUInt32 id_len; + SilcUInt32 channel_count; +} *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 *******************************/ @@ -36,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) @@ -47,37 +110,45 @@ 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->nickname ? - client->nickname : - 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->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); + conn->internal->local_idp = silc_buffer_copy(&packet->buffer); /* Save remote ID */ if (packet->src_id_len) { - conn->remote_idp = silc_id_payload_encode_data(packet->src_id, - packet->src_id_len, - packet->src_id_type); - if (!conn->remote_idp) + conn->internal->remote_idp = + silc_id_payload_encode_data(packet->src_id, + packet->src_id_len, + packet->src_id_type); + if (!conn->internal->remote_idp) goto out; - silc_id_payload_parse_id(silc_buffer_data(conn->remote_idp), - silc_buffer_len(conn->remote_idp), + silc_id_payload_parse_id(silc_buffer_data(conn->internal->remote_idp), + silc_buffer_len(conn->internal->remote_idp), &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) @@ -98,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)), @@ -109,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); @@ -117,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; } @@ -128,77 +208,518 @@ 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, - 1, 5, silc_buffer_data(conn->local_idp), - silc_buffer_len(conn->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 (client->nickname && - !silc_utf8_strcasecmp(client->nickname, client->username)) - silc_client_command_send(client, conn, SILC_COMMAND_NICK, NULL, NULL, - 1, 1, client->nickname, strlen(client->nickname)); + 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)); + + /* 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_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, - 1, 2, silc_buffer_data(conn->remote_idp), - silc_buffer_len(conn->remote_idp)); + 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 are now inside SILC network. */ conn->callback(client, conn, SILC_CLIENT_CONN_SUCCESS, 0, NULL, - conn->context); + 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->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 */ SILC_FSM_STATE(silc_client_st_resume) { + SilcClientConnection conn = fsm_context; + SilcClient client = conn->client; + SilcClientResumeSession resume; + unsigned char *id; + SilcUInt16 id_len; + SilcClientID client_id; + int ret; + + SILC_LOG_DEBUG(("Resuming detached session")); + + resume = silc_calloc(1, sizeof(*resume)); + if (!resume) { + /** Out of memory */ + silc_fsm_next(fsm, silc_client_st_resume_error); + return SILC_FSM_CONTINUE; + } + silc_fsm_set_state_context(fsm, resume); + + silc_buffer_set(&resume->detach, conn->internal->params.detach_data, + conn->internal->params.detach_data_len); + SILC_LOG_HEXDUMP(("Detach data"), silc_buffer_data(&resume->detach), + silc_buffer_len(&resume->detach)); + + /* Take the old client ID from the detachment data */ + ret = silc_buffer_unformat(&resume->detach, + SILC_STR_ADVANCE, + SILC_STR_UI16_NSTRING_ALLOC(&resume->nickname, + NULL), + SILC_STR_UI16_NSTRING(&id, &id_len), + SILC_STR_UI_INT(NULL), + SILC_STR_UI_INT(&resume->channel_count), + 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; + } - return SILC_FSM_FINISH; + 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 */ + 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 */ } -SILC_FSM_STATE(silc_client_st_resume_new_id) +/* 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(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_channels, 15, 0); + return SILC_FSM_WAIT; +} + +/* Resolve the old session information, user mode and joined channels. */ + +SILC_FSM_STATE(silc_client_st_resume_resolve_channels) +{ + 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 */ + conn->internal->registering = FALSE; + silc_fsm_next(fsm, silc_client_st_resume_error); + 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); + + /* 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; + SilcBuffer idp; + SilcChannelID channel_id; + char *name; + + 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; + } + + 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; } + +/* Generates the session detachment data. This data can be used later + to resume back to the server. */ + +SilcBuffer silc_client_get_detach_data(SilcClient client, + SilcClientConnection conn) +{ + 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); + if (!detach) + return NULL; + ret = + silc_buffer_format(detach, + SILC_STR_ADVANCE, + 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(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); + if (ret < 0) { + silc_buffer_free(detach); + return NULL; + } + + /* Save all joined channels */ + silc_hash_table_list(conn->local_entry->channels, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chu)) { + unsigned char chid[32]; + SilcUInt32 chid_len; + + silc_id_id2str(&chu->channel->id, SILC_ID_CHANNEL, chid, sizeof(chid), + &chid_len); + silc_buffer_format(detach, + SILC_STR_ADVANCE, + SILC_STR_UI_SHORT(strlen(chu->channel->channel_name)), + SILC_STR_DATA(chu->channel->channel_name, + strlen(chu->channel->channel_name)), + SILC_STR_UI_SHORT(chid_len), + SILC_STR_DATA(chid, chid_len), + SILC_STR_UI_INT(chu->channel->mode), + SILC_STR_END); + } + silc_hash_table_list_reset(&htl); + + silc_buffer_start(detach); + SILC_LOG_HEXDUMP(("Detach data"), silc_buffer_data(detach), + silc_buffer_len(detach)); + + return detach; +}