/*

  server.c

  Author: Pekka Riikonen <priikone@silcnet.org>

  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;
}
