/* server.c Author: Pekka Riikonen Copyright (C) 1997 - 2006 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. */ /* * This is the actual SILC server than handles everything relating to * servicing the SILC connections. This is also a SILC router as a router * is also normal server. */ /* $Id$ */ #include "silc.h" #include "silcserver.h" #include "server_internal.h" /************************** Types and definitions ***************************/ /************************ Static utility functions **************************/ /* Packet engine callback to receive a packet */ static SilcBool silc_server_packet_receive(SilcPacketEngine engine, SilcPacketStream stream, SilcPacket packet, void *callback_context, void *stream_context) { SilcServerThread thread = callback_context; SilcEntryData data = silc_packet_get_context(stream); /* Packets we do not handle */ switch (packet->type) { case SILC_PACKET_HEARTBEAT: case SILC_PACKET_SUCCESS: case SILC_PACKET_FAILURE: case SILC_PACKET_REJECT: case SILC_PACKET_KEY_EXCHANGE: case SILC_PACKET_KEY_EXCHANGE_1: case SILC_PACKET_KEY_EXCHANGE_2: case SILC_PACKET_REKEY: case SILC_PACKET_REKEY_DONE: case SILC_PACKET_CONNECTION_AUTH: case SILC_PACKET_CONNECTION_AUTH_REQUEST: return FALSE; break; } /* Only specific packets can come without source ID present. */ if ((!packet->src_id || data->registered == FALSE) && packet->type != SILC_PACKET_NEW_CLIENT && packet->type != SILC_PACKET_NEW_SERVER && packet->type != SILC_PACKET_DISCONNECT) return FALSE; /* NEW_CLIENT and NEW_SERVER are accepted only without source ID and for unregistered connection. */ if (packet->src_id && (packet->type == SILC_PACKET_NEW_CLIENT || packet->type == SILC_PACKET_NEW_SERVER) && data->registered == TRUE) return FALSE; /* Add the packet to packet queue */ silc_list_add(thread->packet_queue, packet); /* Signal thread that packet has arrived */ if (!thread->new_packet) { thread->new_packet = TRUE; SILC_FSM_EVENT_SIGNAL(&thread->wait_event); } return TRUE; } /* Packet engine callback to indicate end of stream */ static void silc_server_packet_eos(SilcPacketEngine engine, SilcPacketStream stream, void *callback_context, void *stream_context) { SILC_LOG_DEBUG(("End of stream received")); } /* Packet engine callback to indicate error */ static void silc_server_packet_error(SilcPacketEngine engine, SilcPacketStream stream, SilcPacketError error, void *callback_context, void *stream_context) { } /* Packet stream callbacks */ static SilcPacketCallbacks silc_server_stream_cbs = { silc_server_packet_receive, silc_server_packet_eos, silc_server_packet_error }; /* Server FSM destructor */ static void silc_server_destructor(SilcFSM fsm, void *fsm_context, void *destructor_context) { } /* Creates new server thread. Adds to server threads list automatically and starts the thread. Depending on server configuration the created thread is either FSM thread of real thread. */ static SilcServerThread silc_server_new_thread(SilcServer server) { SilcServerThread thread; SILC_LOG_DEBUG(("Creating new server thread")); thread = silc_calloc(1, sizeof(*thread)); if (!thread) return NULL; thread->server = server; silc_list_init(thread->new_conns, struct SilcServerAcceptStruct, next); /* Start packet engine */ thread->packet_engine = silc_packet_engine_start(server->rng, server->server_type == SILC_ROUTER, &silc_server_stream_cbs, thread); if (!thread->packet_engine) { silc_free(thread); return NULL; } /* Add to server */ silc_list_add(server->threads, thread); /* Start the thread */ silc_fsm_thread_init(&thread->thread, &server->fsm, thread, NULL, NULL, server->params->use_threads); silc_fsm_start(&thread->thread, silc_server_thread_st_start); /* Allocate data stack. Its allocation is allowed to fail so we don't check for it. */ thread->stack = silc_stack_alloc(0); return thread; } /* Network listener callback to accept new connections */ static void silc_server_accept_connection(SilcNetStatus status, SilcStream stream, void *context) { SilcServer server = context; SilcServerAccept ac; if (status != SILC_NET_OK) { SILC_LOG_ERROR(("Error %d accepting new connection", status)); return; } ac = silc_calloc(1, sizeof(*ac)); if (!ac) { silc_stream_destroy(stream); return; } ac->stream = stream; /* Add as new connection */ silc_list_add(server->new_conns, ac); /* Signal server of new connection */ if (!server->new_connection) { server->new_connection = TRUE; SILC_FSM_EVENT_SIGNAL(&server->wait_event); } } /* Packet thread destructor */ static void silc_server_thread_packet_dest(SilcFSM fsm, void *fsm_context, void *destructor_context) { silc_fsm_free(fsm); } /****************************** Server thread *******************************/ /* Thread's start function. This may be FSM thread or real system thread, depending on server configuration. */ SILC_FSM_STATE(silc_server_thread_st_start) { SilcServerThread thread = fsm_context; SILC_LOG_DEBUG(("New server thread started")); /*** Run thread's machine */ silc_fsm_init(&thread->fsm, thread, NULL, NULL, silc_fsm_get_schedule(fsm)); silc_fsm_event_init(&thread->wait_event, &thread->fsm, 0); silc_fsm_start_sync(&thread->fsm, silc_server_thread_st_run); /* Signal server that we are up */ SILC_FSM_EVENT_SIGNAL(&thread->server->thread_up); /* Wait here for this thread to finish */ return SILC_FSM_WAIT; } /* Thread's machine's main state where we wait for various events. */ SILC_FSM_STATE(silc_server_thread_st_run) { SilcServerThread thread = fsm_context; SILC_LOG_DEBUG(("Start")); /* Wait for events */ SILC_FSM_EVENT_WAIT(&thread->wait_event); /* Process events */ if (thread->new_packet) { /*** Packet received */ SilcPacket packet; SilcFSMThread t; SILC_LOG_DEBUG(("Processing incoming packets")); /* Each packet is processed in FSM thread */ silc_list_start(thread->packet_queue); while ((packet = silc_list_get(thread->packet_queue)) != SILC_LIST_END) { /* XXX shouldn't the fsm be &thread->fsm */ t = silc_fsm_thread_alloc(fsm, thread, silc_server_thread_packet_dest, NULL, FALSE); if (t) { silc_fsm_set_state_context(t, packet); silc_fsm_start_sync(t, silc_server_st_packet_received); } } /* Empty the queue */ silc_list_init(thread->packet_queue, struct SilcPacketStruct, next); thread->new_packet = FALSE; return SILC_FSM_CONTINUE; } silc_mutex_lock(thread->server->lock); if (thread->new_connection) { /*** Accept new connection */ SilcServerAccept ac; SILC_LOG_DEBUG(("Processing incoming connections")); /* Accept the new connection in own thread */ silc_list_start(thread->new_conns); while ((ac = silc_list_get(thread->new_conns)) != SILC_LIST_END) { ac->thread = thread; silc_fsm_thread_init(&ac->t, &thread->fsm, ac, silc_server_accept_connection_dest, NULL, FALSE); silc_fsm_start(&ac->t, silc_server_st_accept_connection); } /* Empty the list */ silc_list_init(thread->new_conns, struct SilcServerAcceptStruct, next); thread->new_connection = FALSE; silc_mutex_unlock(thread->server->lock); return SILC_FSM_CONTINUE; } /* NOT REACHED */ #if defined(SILC_DEBUG) assert(FALSE); #endif /* SILC_DEBUG */ return SILC_FSM_CONTINUE; } /*************************** Main server machine ****************************/ /* The server's main state where we wait for various events */ SILC_FSM_STATE(silc_server_st_run) { SilcServer server = fsm_context; SILC_LOG_DEBUG(("Start")); /* Wait for events */ SILC_FSM_EVENT_WAIT(&server->wait_event); /* Process events */ if (server->run_callback && server->running) { /* Call running callbcak back to application */ server->run_callback = FALSE; server->running(server, server->running_context); return SILC_FSM_CONTINUE; } if (server->new_connection) { /** New connection */ silc_fsm_next(fsm, silc_server_st_new_connection); return SILC_FSM_CONTINUE; } if (server->connect_router) { /** Connect to router(s) */ silc_fsm_next(fsm, silc_server_st_connect_router); return SILC_FSM_CONTINUE; } if (server->get_statistics) { /** Retrieve statistics */ silc_fsm_next(fsm, silc_server_st_get_stats); return SILC_FSM_CONTINUE; } if (server->reconfigure) { /** Reconfigure server */ silc_fsm_next(fsm, silc_server_st_reconfigure); return SILC_FSM_CONTINUE; } if (server->server_shutdown) { /** Shutdown server */ silc_fsm_next(fsm, silc_server_st_stop); return SILC_FSM_CONTINUE; } /* NOT REACHED */ #if defined(SILC_DEBUG) assert(FALSE); #endif /* SILC_DEBUG */ return SILC_FSM_CONTINUE; } /* New connection received */ SILC_FSM_STATE(silc_server_st_new_connection) { SilcServer server = fsm_context; SilcServerThread thread; SilcServerAccept ac; SILC_LOG_DEBUG(("Process new connections")); silc_list_start(server->new_conns); while ((ac = silc_list_get(server->new_conns)) != SILC_LIST_END) { /* Find thread where to put this connection */ silc_list_start(server->threads); while ((thread = silc_list_get(server->threads)) != SILC_LIST_END) { if (!server->params->use_threads) break; if (thread->num_conns < server->params->connections_per_thread) break; } if (!thread) { /** Create new thread */ thread = silc_server_new_thread(server); if (!thread) { silc_list_del(server->new_conns, ac); silc_stream_destroy(ac->stream); silc_free(ac); continue; } silc_fsm_next(fsm, silc_server_st_wait_new_thread); return SILC_FSM_CONTINUE; } silc_list_del(server->new_conns, ac); /* Give this connection to this thread */ silc_mutex_lock(server->lock); silc_list_add(thread->new_conns, ac); thread->num_conns++; SILC_LOG_DEBUG(("Signal thread for new connection")); /* Signal the thread for new connection */ if (!thread->new_connection) { thread->new_connection = TRUE; SILC_FSM_EVENT_SIGNAL(&thread->wait_event); } silc_mutex_unlock(server->lock); } server->new_connection = FALSE; /** Connections processed */ silc_fsm_next(fsm, silc_server_st_run); return SILC_FSM_CONTINUE; } /* Wait here until newly created thread is up */ SILC_FSM_STATE(silc_server_st_wait_new_thread) { SilcServer server = fsm_context; /* Wait here until new thread is up */ SILC_FSM_EVENT_WAIT(&server->thread_up); /** Process new connections */ silc_fsm_next(fsm, silc_server_st_new_connection); return SILC_FSM_CONTINUE; } /* Stops server */ SILC_FSM_STATE(silc_server_st_stop) { #if 0 SilcServer server = fsm_context; SILC_LOG_INFO(("SILC Server shutting down")); if (server->schedule) { int i; server->server_shutdown = TRUE; /* Close all connections */ for (i = 0; i < server->config->param.connections_max; i++) { if (!server->sockets[i]) continue; if (!SILC_IS_LISTENER(server->sockets[i])) { SilcSocketConnection sock = server->sockets[i]; SilcIDListData idata = sock->user_data; if (idata) idata->status &= ~SILC_IDLIST_STATUS_DISABLED; silc_schedule_task_del_by_context(server->schedule, server->sockets[i]); silc_schedule_task_del_by_fd(server->schedule, server->sockets[i]->sock); silc_server_disconnect_remote(server, server->sockets[i], SILC_STATUS_OK, "Server is shutting down"); if (server->sockets[i]) { if (sock->user_data) silc_server_free_sock_user_data(server, sock, "Server is shutting down"); silc_socket_free(sock); } } else { silc_socket_free(server->sockets[i]); server->sockets[i] = NULL; server->stat.conn_num--; } } /* We are not connected to network anymore */ server->standalone = TRUE; silc_schedule_stop(server->schedule); silc_schedule_uninit(server->schedule); server->schedule = NULL; silc_free(server->sockets); server->sockets = NULL; } silc_server_protocols_unregister(); #endif /* 0 */ /** Wait events */ silc_fsm_next(fsm, silc_server_st_run); return SILC_FSM_CONTINUE; } /* Reconfigure server */ SILC_FSM_STATE(silc_server_st_reconfigure) { SilcServer server = fsm_context; SILC_LOG_DEBUG(("Reconfiguring server")); /** Wait events */ server->reconfigure = FALSE; silc_fsm_next(fsm, silc_server_st_run); return SILC_FSM_CONTINUE; } /* Get statistics */ SILC_FSM_STATE(silc_server_st_get_stats) { SilcServer server = fsm_context; SILC_LOG_DEBUG(("Getting statistics")); /** Wait events */ server->get_statistics = FALSE; silc_fsm_next(fsm, silc_server_st_run); return SILC_FSM_CONTINUE; } /**************************** Public interface ******************************/ /* Allocates server context and returns it */ SilcServer silc_server_alloc(void *app_context, SilcServerParams params, SilcSchedule schedule) { SilcServer server; SilcServerParamInterface iface; SilcBool id_created = FALSE; SILC_LOG_DEBUG(("Allocating new server")); if (!schedule || !params) return NULL; server = silc_calloc(1, sizeof(*server)); if (!server) return NULL; server->app_context = app_context; server->schedule = schedule; server->params = params; server->server_type = SILC_SERVER; server->standalone = TRUE; #ifdef SILC_SIM server->sim = silc_dlist_init(); #endif #if defined(SILC_DEBUG) /* Set debugging on if configured */ if (params->debug_string) { silc_log_debug(TRUE); silc_log_set_debug_string(params->debug_string); } #endif /* SILC_DEBUG */ /* Allocate ID caches */ server->clients = silc_idcache_alloc(0, SILC_ID_CLIENT, silc_server_destructor_client, server); server->servers = silc_idcache_alloc(0, SILC_ID_SERVER, silc_server_destructor_server, server); server->channels = silc_idcache_alloc(0, SILC_ID_CHANNEL, silc_server_destructor_channel, server); if (!server->clients || !server->servers || !server->channels) { SILC_LOG_ERROR(("Could not allocate ID cache")); goto err; } /* Allocate key repository */ server->repository = silc_skr_alloc(schedule); if (!server->repository) { SILC_LOG_ERROR(("Could not allocate key repository")); goto err; } /* Allocate server lock */ if (!silc_mutex_alloc(&server->lock)) { SILC_LOG_DEBUG(("Could not allocate server lock")); goto err; } /* Init FSM */ silc_fsm_init(&server->fsm, server, silc_server_destructor, NULL, schedule); /* Init semaphore signallers */ silc_fsm_event_init(&server->wait_event, &server->fsm, 0); silc_fsm_event_init(&server->thread_up, &server->fsm, 0); /* Initialize lists */ silc_list_init(server->new_conns, struct SilcServerAcceptStruct, next); silc_list_init(server->command_pool, struct SilcServerCommandStruct, next); #if 0 /* Register all paramsured ciphers, PKCS and hash functions. */ if (!silc_server_params_register_ciphers(server)) silc_cipher_register_default(); if (!silc_server_params_register_pkcs(server)) silc_pkcs_register_default(); if (!silc_server_params_register_hashfuncs(server)) silc_hash_register_default(); if (!silc_server_params_register_hmacs(server)) silc_hmac_register_default(); #else silc_cipher_register_default(); silc_pkcs_register_default(); silc_hash_register_default(); silc_hmac_register_default(); #endif /* 0 */ /* Initialize random number generator for the server. */ server->rng = silc_rng_alloc(); if (!server->rng) { SILC_LOG_ERROR(("Could not allocate RNG")); goto err; } silc_rng_init(server->rng); silc_rng_global_init(server->rng); /* Initialize hash functions for server to use */ silc_hash_alloc("md5", &server->md5hash); silc_hash_alloc("sha1", &server->sha1hash); /* Steal public and private key from the params object */ server->public_key = server->params->server_info->public_key; server->private_key = server->params->server_info->private_key; server->params->server_info->public_key = NULL; server->params->server_info->private_key = NULL; /* Create network listener(s) */ server->listeners = silc_dlist_init(); if (!server->listeners) goto err; silc_list_start(params->server_info->interfaces); while ((iface = silc_list_get(params->server_info->interfaces)) != SILC_LIST_END) { if (!id_created) { /* Create a Server ID for the server */ if (!silc_server_create_server_id(server, iface->ip, iface->port, &server->id)) { SILC_LOG_ERROR(("Could not create Server ID")); goto err; } id_created = TRUE; } SilcNetServer listener = silc_net_create_server((const char **)&iface->ip, 1, iface->port, params->require_reverse_lookup, server->schedule, silc_server_accept_connection, server); if (!listener) { SILC_LOG_ERROR(("Could not bind %s on %d", iface->ip, iface->port)); goto err; } silc_dlist_add(server->listeners, listener); } /* First, register log files paramsuration for error output */ // silc_server_params_setlogfiles(server); /* Init watcher lists */ server->watcher_list = silc_hash_table_alloc(1, silc_hash_client_id_hash, NULL, silc_hash_data_compare, (void *)CLIENTID_HASH_LEN, NULL, NULL, TRUE); if (!server->watcher_list) goto err; #if 0 server->watcher_list_pk = silc_hash_table_alloc(1, silc_hash_public_key, NULL, silc_hash_public_key_compare, NULL, NULL, NULL, TRUE); if (!server->watcher_list_pk) goto err; #endif /* 0 */ server->server_name = server->params->server_info->server_name; server->params->server_info->server_name = NULL; #if 0 /* If server connections has been paramsured then we must be router as normal server cannot have server connections, only router connections. */ if (server->params->servers) { SilcServerParamsServer *ptr = server->params->servers; server->server_type = SILC_ROUTER; while (ptr) { if (ptr->backup_router) { server->server_type = SILC_BACKUP_ROUTER; server->backup_router = TRUE; server->id_entry->server_type = SILC_BACKUP_ROUTER; break; } ptr = ptr->next; } } #endif /* 0 */ if (server->server_type == SILC_ROUTER) server->stat.routers++; return server; err: return NULL; } /* Free's the SILC server context */ void silc_server_free(SilcServer server) { #if 0 SilcIDCacheList list; SilcIDCacheEntry cache; if (!server) return; #ifdef SILC_SIM { SilcSim sim; silc_dlist_start(server->sim); while ((sim = silc_dlist_get(server->sim)) != SILC_LIST_END) { silc_dlist_del(server->sim, sim); silc_sim_close(sim); silc_sim_free(sim); } silc_dlist_uninit(server->sim); } #endif silc_server_backup_free(server); silc_server_params_unref(&server->params_ref); if (server->rng) silc_rng_free(server->rng); if (server->pkcs) silc_pkcs_free(server->pkcs); if (server->public_key) silc_pkcs_public_key_free(server->public_key); if (server->private_key) silc_pkcs_private_key_free(server->private_key); if (server->pending_commands) silc_dlist_uninit(server->pending_commands); if (server->id_entry) silc_idlist_del_server(server->local_list, server->id_entry); /* Delete all channels */ list = NULL; if (silc_idcache_get_all(server->local_list->channels, &list) && silc_idcache_list_first(list, &cache)) { silc_idlist_del_channel(server->local_list, cache->context); while (silc_idcache_list_next(list, &cache)) silc_idlist_del_channel(server->local_list, cache->context); } if (list) silc_idcache_list_free(list); list = NULL; if (silc_idcache_get_all(server->global_list->channels, &list) && silc_idcache_list_first(list, &cache)) { silc_idlist_del_channel(server->global_list, cache->context); while (silc_idcache_list_next(list, &cache)) silc_idlist_del_channel(server->global_list, cache->context); } if (list) silc_idcache_list_free(list); if (server->pk_hash) silc_hash_table_free(server->pk_hash); /* Delete all clients */ list = NULL; if (silc_idcache_get_all(server->local_list->clients, &list) && silc_idcache_list_first(list, &cache)) { silc_idlist_del_client(server->local_list, cache->context); while (silc_idcache_list_next(list, &cache)) silc_idlist_del_client(server->local_list, cache->context); } if (list) silc_idcache_list_free(list); list = NULL; if (silc_idcache_get_all(server->global_list->clients, &list) && silc_idcache_list_first(list, &cache)) { silc_idlist_del_client(server->global_list, cache->context); while (silc_idcache_list_next(list, &cache)) silc_idlist_del_client(server->global_list, cache->context); } if (list) silc_idcache_list_free(list); /* Delete all servers */ list = NULL; if (silc_idcache_get_all(server->local_list->servers, &list) && silc_idcache_list_first(list, &cache)) { silc_idlist_del_server(server->local_list, cache->context); while (silc_idcache_list_next(list, &cache)) silc_idlist_del_server(server->local_list, cache->context); } if (list) silc_idcache_list_free(list); list = NULL; if (silc_idcache_get_all(server->global_list->servers, &list) && silc_idcache_list_first(list, &cache)) { silc_idlist_del_server(server->global_list, cache->context); while (silc_idcache_list_next(list, &cache)) silc_idlist_del_server(server->global_list, cache->context); } if (list) silc_idcache_list_free(list); #endif /* 0 */ silc_idcache_free(server->clients); silc_idcache_free(server->servers); silc_idcache_free(server->channels); silc_hash_table_free(server->watcher_list); silc_hash_table_free(server->watcher_list_pk); silc_hash_free(server->md5hash); silc_hash_free(server->sha1hash); silc_hmac_unregister_all(); silc_hash_unregister_all(); silc_cipher_unregister_all(); silc_pkcs_unregister_all(); } /* Starts the SILC server FSM machine and returns immediately. The scheduler must be run or be running already when this returns. */ void silc_server_run(SilcServer server, SilcServerRunning running, void *running_context) { SILC_LOG_INFO(("Starting SILC server")); server->starttime = time(NULL); server->running = running; server->running_context = running_context; /* Start the server */ silc_fsm_start_sync(&server->fsm, silc_server_st_run); /* Signal the application when we are running */ server->run_callback = TRUE; SILC_FSM_EVENT_SIGNAL(&server->wait_event); /* Signal to connect to router */ server->connect_router = TRUE; SILC_FSM_EVENT_SIGNAL(&server->wait_event); /* Start getting statistics from the network on normal server */ if (server->server_type != SILC_ROUTER) { server->get_statistics = TRUE; SILC_FSM_EVENT_SIGNAL(&server->wait_event); } } /* Stops the SILC server */ void silc_server_stop(SilcServer server, SilcServerStop stopped, void *stop_context) { SILC_LOG_INFO(("Stopping SILC server")); server->stopped = stopped; server->stop_context = stop_context; /* Signal that server is going down */ server->server_shutdown = TRUE; SILC_FSM_EVENT_SIGNAL(&server->wait_event); } /* Disconnects remote connection */ SilcBool silc_server_disconnect(SilcServer server, SilcPacketStream stream, SilcStatus error, const char *error_string) { return TRUE; }