/* client_register.c Author: 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 the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "silc.h" #include "silcclient.h" #include "client_internal.h" /************************** 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 *******************************/ /* Received new ID packet from server during registering to SILC network */ 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) goto out; SILC_LOG_DEBUG(("New ID received from server")); if (!silc_id_payload_parse_id(silc_buffer_data(&packet->buffer), 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, nick, client->username, client->realname, &id.u.client_id, 0); if (!conn->local_entry) goto out; /* 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 remote ID */ if (packet->src_id_len) { 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->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) silc_fsm_continue_sync(&conn->internal->event_thread); out: /** Packet processed */ silc_packet_free(packet); return SILC_FSM_FINISH; } /************************ Register to SILC network **************************/ /* Register to network */ 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)), SILC_STR_DATA(client->username, strlen(client->username)), 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); return SILC_FSM_CONTINUE; } /** Wait for new ID */ conn->internal->registering = TRUE; silc_fsm_next_later(fsm, silc_client_st_register_complete, conn->internal->retry_timer, 0); return SILC_FSM_WAIT; } /* Wait for NEW_ID packet to arrive */ SILC_FSM_STATE(silc_client_st_register_complete) { SilcClientConnection conn = fsm_context; SilcClient client = conn->client; 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, 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, 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->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; SILC_LOG_DEBUG(("Error registering 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); 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; } 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 */ } /* 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) { 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; }