/* server_st_command.c Author: Pekka Riikonen Copyright (C) 1997 - 2005 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 "silcserver.h" #include "server_internal.h" /************************** Types and definitions ***************************/ #define SILC_SERVER_COMMAND_CHECK(min, max) \ do { \ SilcUInt32 _argc; \ \ SILC_LOG_DEBUG(("Start")); \ \ _argc = silc_argument_get_arg_num(args); \ if (_argc < min) { \ SILC_LOG_DEBUG(("Not enough parameters in command")); \ silc_server_command_send_status_reply(cmd, \ silc_command_get(cmd->payload), \ SILC_STATUS_ERR_NOT_ENOUGH_PARAMS, \ 0); \ silc_server_command_free(cmd); \ return SILC_FSM_FINISH; \ } \ if (_argc > max) { \ SILC_LOG_DEBUG(("Too many parameters in command")); \ silc_server_command_send_status_reply(cmd, \ silc_command_get(cmd->payload), \ SILC_STATUS_ERR_TOO_MANY_PARAMS, \ 0); \ silc_server_command_free(cmd); \ return SILC_FSM_FINISH; \ } \ } while(0) /************************ Static utility functions **************************/ /* Sends simple status message as command reply packet */ static void silc_server_command_send_status_reply(SilcServerCommand cmd, SilcCommand command, SilcStatus status, SilcStatus error) { SilcBuffer buffer; /* Statistics */ cmd->thread->server->stat.commands_sent++; SILC_LOG_DEBUG(("Sending command status %d", status)); buffer = silc_command_reply_payload_encode_va(command, status, error, silc_command_get_ident(cmd->payload), 0); silc_packet_send(cmd->packet->stream, SILC_PACKET_COMMAND_REPLY, 0, buffer->data, silc_buffer_len(buffer)); silc_buffer_free(buffer); } /* Sends command status reply with one extra argument. The argument type must be sent as argument. */ static void silc_server_command_send_status_data(SilcServerCommand cmd, SilcCommand command, SilcStatus status, SilcStatus error, SilcUInt32 arg_type, const unsigned char *arg, SilcUInt32 arg_len) { SilcBuffer buffer; /* Statistics */ cmd->thread->server->stat.commands_sent++; SILC_LOG_DEBUG(("Sending command status %d", status)); buffer = silc_command_reply_payload_encode_va(command, status, 0, silc_command_get_ident(cmd->payload), 1, arg_type, arg, arg_len); silc_packet_send(cmd->packet->stream, SILC_PACKET_COMMAND_REPLY, 0, buffer->data, silc_buffer_len(buffer)); silc_buffer_free(buffer); } static void silc_server_command_send_status_data2(SilcServerCommand cmd, SilcCommand command, SilcStatus status, SilcStatus error, SilcUInt32 arg_type1, const unsigned char *arg1, SilcUInt32 arg_len1, SilcUInt32 arg_type2, const unsigned char *arg2, SilcUInt32 arg_len2) { SilcBuffer buffer; /* Statistics */ cmd->thread->server->stat.commands_sent++; SILC_LOG_DEBUG(("Sending command status %d", status)); buffer = silc_command_reply_payload_encode_va(command, status, 0, silc_command_get_ident(cmd->payload), 2, arg_type1, arg1, arg_len1, arg_type2, arg2, arg_len2); silc_packet_send(cmd->packet->stream, SILC_PACKET_COMMAND_REPLY, 0, buffer->data, silc_buffer_len(buffer)); silc_buffer_free(buffer); } void silc_server_command_pending_free(SilcServerThread thread, SilcServerPending pending); /**************************** Utility functions *****************************/ /* Gets command context from freelist or allocates a new one. */ SilcServerCommand silc_server_command_alloc(SilcServerThread thread) { SilcServerCommand cmd; silc_mutex_lock(thread->server->lock); /* Get command context from freelist or allocate new one. */ cmd = silc_list_get(thread->server->command_pool); if (!cmd) { silc_mutex_unlock(thread->server->lock); cmd = silc_calloc(1, sizeof(*cmd)); if (!cmd) return NULL; SILC_LOG_DEBUG(("Allocating command context %p", cmd)); cmd->thread = thread; return cmd; } SILC_LOG_DEBUG(("Get command context %p", cmd)); /* Delete from freelist */ silc_list_del(thread->server->command_pool, cmd); cmd->thread = thread; silc_mutex_unlock(thread->server->lock); return cmd; } /* Puts the command context back to freelist */ void silc_server_command_free(SilcServerCommand cmd) { SilcServerThread thread = cmd->thread; silc_mutex_lock(thread->server->lock); /* Check for double free */ #if defined(SILC_DEBUG) assert(cmd->packet != NULL); #endif /* SILC_DEBUG */ if (cmd->packet) silc_packet_free(cmd->packet); cmd->packet = NULL; if (cmd->pending) silc_server_command_pending_free(thread, cmd->pending); /* Put the packet back to freelist */ silc_list_add(thread->server->command_pool, cmd); silc_mutex_unlock(thread->server->lock); } /* Returns pending context used to wait for a command reply. */ SilcServerPending silc_server_command_pending(SilcServerThread thread, SilcUInt16 cmd_ident) { SilcServerPending pending; silc_mutex_lock(thread->server->lock); /* Check if pending already */ if (silc_hash_table_find(thread->server->pending_commands, SILC_32_TO_PTR(cmd_ident), NULL, (void **)&pending)) { pending->refcnt++; silc_mutex_unlock(thread->server->lock); return pending; } pending = silc_calloc(1, sizeof(*pending)); if (!pending) { silc_mutex_unlock(thread->server->lock); return NULL; } silc_fsm_sema_init(&pending->wait_reply, &thread->fsm, 0); pending->refcnt = 1; pending->cmd_ident = cmd_ident; /* Add to pending commands hash table */ if (!silc_hash_table_add(thread->server->pending_commands, SILC_32_TO_PTR(cmd_ident), pending)) { silc_mutex_unlock(thread->server->lock); silc_free(pending); return NULL; } silc_mutex_unlock(thread->server->lock); return pending; } /* Free's the pending command context */ void silc_server_command_pending_free(SilcServerThread thread, SilcServerPending pending) { silc_mutex_lock(thread->server->lock); pending->refcnt--; if (pending->refcnt > 0) { silc_mutex_unlock(thread->server->lock); return; } /* If command reply context set, free it also */ if (pending->reply) { pending->reply->pending = NULL; silc_server_command_free(pending->reply); } /* Remove from pending commands */ silc_hash_table_del_by_context(thread->server->pending_commands, SILC_32_TO_PTR(pending->cmd_ident), pending); silc_free(pending); silc_mutex_unlock(thread->server->lock); } /* Returns pending command context for command identifier */ SilcServerPending silc_server_command_pending_get(SilcServerThread thread, SilcUInt16 cmd_ident) { SilcServerPending pending = NULL; silc_mutex_lock(thread->server->lock); silc_hash_table_find(thread->server->pending_commands, SILC_32_TO_PTR(cmd_ident), NULL, (void **)&pending); silc_mutex_unlock(thread->server->lock); return pending; } /* Signals pending command waiters. Used by command reply routines. */ void silc_server_command_pending_signal(SilcServerCommand cmd) { SilcServerThread thread = cmd->thread; SilcServerPending pending = cmd->pending; if (!pending) return; silc_mutex_lock(thread->server->lock); /* Signal */ pending->reply = cmd; SILC_FSM_SEMA_POST(&pending->wait_reply); /* Remove from pending */ silc_hash_table_del_by_context(thread->server->pending_commands, SILC_32_TO_PTR(pending->cmd_ident), pending); silc_mutex_unlock(thread->server->lock); } /**************************** Command received ******************************/ /* Received a COMMAND packet. We parse the packet and process the requested command. */ SILC_FSM_STATE(silc_server_st_packet_command) { SilcServerThread thread = fsm_context; SilcPacket packet = state_context; SilcEntryData data = silc_packet_get_context(packet->stream); SilcServerCommand cmd; SilcUInt32 timeout = 0; /* Allocate command context. */ cmd = silc_server_command_alloc(thread); if (!cmd) { silc_packet_free(packet); return SILC_FSM_FINISH; } cmd->packet = packet; /* Parse the command payload in the packet */ cmd->payload = silc_command_payload_parse(packet->buffer.data, silc_buffer_len(&packet->buffer)); if (!cmd->payload) { SILC_LOG_ERROR(("Bad command payload")); silc_server_command_free(cmd); return SILC_FSM_FINISH; } /* If client executes commands more frequently than once in 2 seconds, apply 0 - 2 seconds of timeout to prevent flooding. */ if (data->type == SILC_CONN_CLIENT) { SilcClientEntry client = (SilcClientEntry)data; if (client->last_command && (time(NULL) - client->last_command) < 2) { client->fast_command++; if (client->fast_command > 5) timeout = (client->fast_command < 3 ? 0 : 2 - (time(NULL) - client->last_command)); } else { if (client->fast_command - 2 <= 0) client->fast_command = 0; else client->fast_command -= 2; } } silc_fsm_set_state_context(fsm, cmd); SILC_LOG_DEBUG(("Processing %s command (%d timeout)", silc_get_command_name(silc_command_get(cmd->payload)), timeout)); /* Process command */ switch (silc_command_get(cmd->payload)) { case SILC_COMMAND_WHOIS: /** Command WHOIS */ silc_fsm_next_later(fsm, silc_server_st_command_whois, timeout, 0); break; case SILC_COMMAND_WHOWAS: /** Command WHOWAS */ silc_fsm_next_later(fsm, silc_server_st_command_whowas, timeout, 0); break; case SILC_COMMAND_IDENTIFY: /** Command IDENTIFY */ silc_fsm_next_later(fsm, silc_server_st_command_identify, timeout, 0); break; case SILC_COMMAND_NICK: /** Command NICK */ silc_fsm_next_later(fsm, silc_server_st_command_nick, timeout, 0); break; case SILC_COMMAND_LIST: /** Command LIST */ silc_fsm_next_later(fsm, silc_server_st_command_list, timeout, 0); break; case SILC_COMMAND_TOPIC: /** Command TOPIC */ silc_fsm_next_later(fsm, silc_server_st_command_topic, timeout, 0); break; case SILC_COMMAND_INVITE: /** Command INVITE */ silc_fsm_next_later(fsm, silc_server_st_command_invite, timeout, 0); break; case SILC_COMMAND_QUIT: /** Command QUIT */ silc_fsm_next_later(fsm, silc_server_st_command_quit, timeout, 0); break; case SILC_COMMAND_KILL: /** Command KILL */ silc_fsm_next_later(fsm, silc_server_st_command_kill, timeout, 0); break; case SILC_COMMAND_INFO: /** Command INFO */ silc_fsm_next_later(fsm, silc_server_st_command_info, timeout, 0); break; case SILC_COMMAND_STATS: /** Command STATS */ silc_fsm_next_later(fsm, silc_server_st_command_stats, timeout, 0); break; case SILC_COMMAND_PING: /** Command INFO */ silc_fsm_next_later(fsm, silc_server_st_command_ping, timeout, 0); break; case SILC_COMMAND_OPER: /** Command OPER */ silc_fsm_next_later(fsm, silc_server_st_command_oper, timeout, 0); break; case SILC_COMMAND_JOIN: /** Command JOIN */ silc_fsm_next_later(fsm, silc_server_st_command_join, timeout, 0); break; case SILC_COMMAND_MOTD: /** Command MOTD */ silc_fsm_next_later(fsm, silc_server_st_command_motd, timeout, 0); break; case SILC_COMMAND_UMODE: /** Command UMODE */ silc_fsm_next_later(fsm, silc_server_st_command_umode, timeout, 0); break; case SILC_COMMAND_CMODE: /** Command CMODE */ silc_fsm_next_later(fsm, silc_server_st_command_cmode, timeout, 0); break; case SILC_COMMAND_CUMODE: /** Command CUMODE */ silc_fsm_next_later(fsm, silc_server_st_command_cumode, timeout, 0); break; case SILC_COMMAND_KICK: /** Command KICK */ silc_fsm_next_later(fsm, silc_server_st_command_kick, timeout, 0); break; case SILC_COMMAND_BAN: /** Command BAN */ silc_fsm_next_later(fsm, silc_server_st_command_ban, timeout, 0); break; case SILC_COMMAND_DETACH: /** Command DETACH */ silc_fsm_next_later(fsm, silc_server_st_command_detach, timeout, 0); break; case SILC_COMMAND_WATCH: /** Command WATCH */ silc_fsm_next_later(fsm, silc_server_st_command_watch, timeout, 0); break; case SILC_COMMAND_SILCOPER: /** Command SILCOPER */ silc_fsm_next_later(fsm, silc_server_st_command_silcoper, timeout, 0); break; case SILC_COMMAND_LEAVE: /** Command LEAVE */ silc_fsm_next_later(fsm, silc_server_st_command_leave, timeout, 0); break; case SILC_COMMAND_USERS: /** Command USERS */ silc_fsm_next_later(fsm, silc_server_st_command_users, timeout, 0); break; case SILC_COMMAND_GETKEY: /** Command GETKEY */ silc_fsm_next_later(fsm, silc_server_st_command_getkey, timeout, 0); break; case SILC_COMMAND_SERVICE: /** Command SERVICE */ silc_fsm_next_later(fsm, silc_server_st_command_service, timeout, 0); break; default: SILC_LOG_DEBUG(("Unknown command %d", silc_command_get(cmd->payload))); silc_server_command_free(cmd); return SILC_FSM_FINISH; break; } /* Statistics */ thread->server->stat.commands_received++; return timeout ? SILC_FSM_WAIT : SILC_FSM_CONTINUE; } /********************************** NICK ************************************/ SILC_FSM_STATE(silc_server_st_command_nick) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /********************************** LIST ************************************/ SILC_FSM_STATE(silc_server_st_command_list) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /********************************** TOPIC ***********************************/ SILC_FSM_STATE(silc_server_st_command_topic) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /********************************* INVITE ***********************************/ SILC_FSM_STATE(silc_server_st_command_invite) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /********************************** QUIT ************************************/ SILC_FSM_STATE(silc_server_st_command_quit) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /********************************** KILL ************************************/ SILC_FSM_STATE(silc_server_st_command_kill) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /********************************** INFO ************************************/ /* Server side of command INFO. This sends information about us to the client. If client requested specific server we will send the command to that server. */ SILC_FSM_STATE(silc_server_st_command_info) { return SILC_FSM_FINISH; } /********************************** STATS ***********************************/ SILC_FSM_STATE(silc_server_st_command_stats) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /********************************** PING ************************************/ /* Server side of command PING. */ SILC_FSM_STATE(silc_server_st_command_ping) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); SilcUInt32 tmp_len; unsigned char *tmp; SilcServerID server_id; SILC_SERVER_COMMAND_CHECK(1, 1); /* Get Server ID */ tmp = silc_argument_get_arg_type(args, 1, &tmp_len); if (!tmp) { silc_server_command_send_status_reply(cmd, silc_command_get(cmd->payload), SILC_STATUS_ERR_NOT_ENOUGH_PARAMS, 0); goto out; } if (!silc_id_payload_parse_id(tmp, tmp_len, NULL, &server_id, sizeof(server_id))) { silc_server_command_send_status_data(cmd, silc_command_get(cmd->payload), SILC_STATUS_ERR_BAD_SERVER_ID, 0, 2, tmp, tmp_len); goto out; } if (SILC_ID_SERVER_COMPARE(&server_id, &thread->server->id)) { /* Send our reply */ silc_server_command_send_status_reply(cmd, silc_command_get(cmd->payload), SILC_STATUS_OK, 0); } else { silc_server_command_send_status_data(cmd, silc_command_get(cmd->payload), SILC_STATUS_ERR_NO_SUCH_SERVER_ID, 0, 2, tmp, tmp_len); } out: silc_server_command_free(cmd); return SILC_FSM_FINISH; } /*********************************** OPER ***********************************/ SILC_FSM_STATE(silc_server_st_command_oper) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /*********************************** JOIN ***********************************/ SILC_FSM_STATE(silc_server_st_command_join) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /*********************************** MOTD ***********************************/ SILC_FSM_STATE(silc_server_st_command_motd) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /*********************************** UMODE **********************************/ SILC_FSM_STATE(silc_server_st_command_umode) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /*********************************** CMODE **********************************/ SILC_FSM_STATE(silc_server_st_command_cmode) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /********************************** CUMODE **********************************/ SILC_FSM_STATE(silc_server_st_command_cumode) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /*********************************** KICK ***********************************/ SILC_FSM_STATE(silc_server_st_command_kick) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /*********************************** BAN ************************************/ SILC_FSM_STATE(silc_server_st_command_ban) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /********************************** DETACH **********************************/ SILC_FSM_STATE(silc_server_st_command_detach) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /********************************** WATCH ***********************************/ SILC_FSM_STATE(silc_server_st_command_watch) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /********************************* SILCOPER *********************************/ SILC_FSM_STATE(silc_server_st_command_silcoper) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /********************************** LEAVE ***********************************/ SILC_FSM_STATE(silc_server_st_command_leave) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /********************************** USERS ***********************************/ SILC_FSM_STATE(silc_server_st_command_users) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /********************************** GETKEY **********************************/ SILC_FSM_STATE(silc_server_st_command_getkey) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; } /********************************** SERVICE *********************************/ SILC_FSM_STATE(silc_server_st_command_service) { SilcServerThread thread = fsm_context; SilcServerCommand cmd = state_context; SilcArgumentPayload args = silc_command_get_args(cmd->payload); return SILC_FSM_FINISH; }