/*

  silcd.c

  Author: Pekka Riikonen <priikone@silcnet.org>

  Copyright (C) 1997 - 2014 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.

*/
/*
 * Created: Wed Mar 19 00:17:12 1997
 *
 * This is the main program for the SILC daemon. This parses command
 * line arguments and creates the server object.
 */
/* $Id$ */

#include "serverincludes.h"
#include "server_internal.h"
#include "silcversion.h"

/* For now, we'll have this one server context global for this module. */
static SilcServer silcd;

static void silc_usage(void);

#ifdef HAVE_GETOPT_LONG
/* Long command line options */
static struct option long_opts[] =
{
  { "config-file", 1, NULL, 'f' },
  { "passphrase", 1, NULL, 'p' },
  { "debug", 2, NULL, 'd' },
  { "debug-level", 1, NULL, 'D' },
  { "hexdump", 0, NULL, 'x' },
  { "help", 0, NULL, 'h' },
  { "foreground", 0, NULL, 'F' },
  { "version", 0, NULL,'V' },

  /* Key management options */
  { "create-key-pair", 1, NULL, 'C' },
  { "pkcs", 1, NULL, 10 },
  { "bits", 1, NULL, 11 },
  { "identifier", 1, NULL, 12 },

  { NULL, 0, NULL, 0 }
};
#endif /* HAVE_GETOPT_LONG */

/* Command line option variables */
static char *opt_keypath = NULL;
static char *opt_pkcs = "rsa";
static char *opt_identifier = NULL;
static int opt_bits = 4096;

/* Prints out the usage of silc client */

static void silc_usage(void)
{
  printf(""
"Usage: silcd [options]\n"
"\n"
"  Generic Options:\n"
"  -f  --config-file=FILE        Alternate configuration file\n"
"  -d  --debug=string            Enable debugging (Implies --foreground)\n"
"  -D  --debug-level=level       Enable debugging (Implies --foreground)\n"
"  -x  --hexdump                 Enable hexdumps (Implies --debug)\n"
"  -h  --help                    Display this message\n"
"  -F  --foreground              Dont fork\n"
"  -V  --version                 Display version\n"
"\n"
"  Key Management Options:\n"
"  -C, --create-key-pair=PATH    Create new public key pair\n"
"      --pkcs=PKCS               Set the PKCS of the public key pair\n"
"      --bits=VALUE              Set length of the public key pair\n"
"      --identifier=IDENTIFIER   Public key identifier\n"
"\n"
"      The public key identifier may be of the following format:\n"
"\n"
"      UN=<username>, HN=<hostname or IP>, RN=<real name>, E=<email>,\n"
"      O=<organization>, C=<country>\n"
"\n"
"      The UN and HN must be provided, the others are optional.  If the\n"
"      --identifier option is not used an identifier will be created for\n"
"      the public key automatically.\n"
"\n"
"      Example identifier: \"UN=foobar, HN=foo.bar.com, RN=Foo T. Bar, \n"
"                           E=foo@bar.com, C=FI\"\n"
"\n");
  exit(0);
}

/* Die if a *valid* pid file exists already */

static void silc_server_checkpid(SilcServer silcd)
{
  if (silcd->config->server_info->pid_file) {
    int oldpid;
    char *buf;
    SilcUInt32 buf_len;

    SILC_LOG_DEBUG(("Checking for another silcd running"));
    buf = silc_file_readfile(silcd->config->server_info->pid_file, &buf_len);
    if (!buf)
      return;
    oldpid = atoi(buf);
    silc_free(buf);
    if (oldpid <= 0)
      return;
    kill(oldpid, SIGCHLD); /* this signal does nothing, check if alive */
    if (errno != ESRCH) {
      fprintf(stderr, "\nI detected another daemon running with the "
	      "same pid file.\n");
      fprintf(stderr, "Please change the config file, or erase the %s\n",
	silcd->config->server_info->pid_file);
      exit(1);
    }
  }
}

/* Drop root privileges. If some system call fails, die. */

static void silc_server_drop_privs(SilcServer server)
{
  /* Are we executing silcd as root or a regular user? */
  if (geteuid()) {
    SILC_LOG_DEBUG(("Server started as user"));
  }
  else {
    struct passwd *pw;
    struct group *gr;
    char *user, *group;

    SILC_LOG_DEBUG(("Server started as root. Dropping privileges."));

    /* Get the values given for user and group in configuration file */
    user = server->config->server_info->user;
    group = server->config->server_info->group;

    if (!user || !group) {
      fprintf(stderr, "Error:"
       "\tSILC server must not be run as root.  For the security of your\n"
       "\tsystem it is strongly suggested that you run SILC under dedicated\n"
       "\tuser account.  Modify the ServerInfo configuration section to run\n"
       "\tthe server as non-root user.\n");
      exit(1);
    }

    /* Check whether the user/group does not begin with a number */
    if (isdigit(user[0]) || isdigit(group[0])) {
      SILC_LOG_DEBUG(("User and/or group starts with a number"));
      fprintf(stderr, "Invalid user and/or group information\n");
      fprintf(stderr, "Please assign them as names, not numbers\n");
      exit(1);
    }

    if (!(pw = getpwnam(user))) {
      fprintf(stderr, "Error: No such user %s found.\n", user);
      exit(1);
    }
    if (!(gr = getgrnam(group))) {
      fprintf(stderr, "Error: No such group %s found.\n", group);
      exit(1);
    }

    /* Check whether user and/or group is set to root. If yes, exit
       immediately. Otherwise, setgid and setuid server to user.group */
    if ((gr->gr_gid == 0) || (pw->pw_uid == 0)) {
      fprintf(stderr, "Error:"
       "\tSILC server must not be run as root.  For the security of your\n"
       "\tsystem it is strongly suggested that you run SILC under dedicated\n"
       "\tuser account.  Modify the ServerInfo configuration section to run\n"
       "\tthe server as non-root user.\n");
      exit(1);
    }

    SILC_LOG_DEBUG(("Changing to group %s (gid=%u)", group, gr->gr_gid));
    if (setgid(gr->gr_gid) != 0) {
      fprintf(stderr, "Error: Failed setgid() to %s (gid=%u). Exiting.\n",
	      group, gr->gr_gid);
      exit(1);
    }
#if defined HAVE_SETGROUPS && defined HAVE_INITGROUPS
    SILC_LOG_DEBUG(("Removing supplementary groups"));
    if (setgroups(0, NULL) != 0) {
      fprintf(stderr, "Error: Failed setgroups() to NULL. Exiting.\n");
      exit(1);
    }
    SILC_LOG_DEBUG(("Setting supplementary groups for user %s", user));
    if (initgroups(user, gr->gr_gid) != 0) {
      fprintf(stderr, "Error: Failed initgroups() for user %s (gid=%u). "
	      "Exiting.\n", user, gr->gr_gid);
      exit(1);
    }
#endif
    SILC_LOG_DEBUG(("Changing to user %s (uid=%u)", user, pw->pw_uid));
    if (setuid(pw->pw_uid) != 0) {
      fprintf(stderr, "Error: Failed to setuid() to %s (gid=%u). Exiting.\n",
              user, pw->pw_uid);
      exit(1);
    }
  }
}

/* Fork server to background */

static void silc_server_daemonise(SilcServer server)
{
  int i;

  SILC_LOG_DEBUG(("Forking SILC server to background"));

  if ((i = fork()) < 0) {
    fprintf(stderr, "Error: fork() failed: %s\n", strerror(errno));
    exit(1);
  }

  if (i) /* Kill the parent */
    exit(0);

  server->background = TRUE;
  setsid();

  /* XXX close stdin, stdout, stderr -- before this, check that all writes
     to stderr are changed to SILC_SERVER_LOG_ERROR() */
}

SILC_TASK_CALLBACK(got_hup)
{
  /* First, reset all log files (they might have been deleted) */
  silc_log_reset_all();

  /* Rehash the configuration file */
  silc_server_rehash(silcd);
}

SILC_TASK_CALLBACK(stop_server)
{
  silc_server_stop(silcd);
}

/* Dump server statistics into a file into /tmp directory */

SILC_TASK_CALLBACK(dump_stats)
{
  FILE *fdd;
  int fild;
  char filename[256];

  memset(filename, 0, sizeof(filename));
  snprintf(filename, sizeof(filename) - 1, "/tmp/silcd.%d.stats-XXXXXX", getpid());
  fild = mkstemp(filename);
  if (fild == -1)
    return;

  fdd = fdopen(fild, "w");
  if (fdd == NULL) {
    close(fild);
    unlink(filename);
    return;
  }

#define STAT_OUTPUT(fmt, stat) fprintf(fdd, fmt "\n", (int)stat);

  fprintf(fdd, "SILC Server %s Statistics\n\n", silcd->server_name);
  fprintf(fdd, "Local Stats:\n");
  STAT_OUTPUT("  My clients              : %d", silcd->stat.my_clients);
  STAT_OUTPUT("  My servers              : %d", silcd->stat.my_servers);
  STAT_OUTPUT("  My routers              : %d", silcd->stat.my_routers);
  STAT_OUTPUT("  My channels             : %d", silcd->stat.my_channels);
  STAT_OUTPUT("  My joined users         : %d", silcd->stat.my_chanclients);
  STAT_OUTPUT("  My aways                : %d", silcd->stat.my_aways);
  STAT_OUTPUT("  My detached clients     : %d", silcd->stat.my_detached);
  STAT_OUTPUT("  My server operators     : %d", silcd->stat.my_server_ops);
  STAT_OUTPUT("  My router operators     : %d", silcd->stat.my_router_ops);
  fprintf(fdd, "\nGlobal Stats:\n");
  STAT_OUTPUT("  Cell clients            : %d", silcd->stat.cell_clients);
  STAT_OUTPUT("  Cell servers            : %d", silcd->stat.cell_servers);
  STAT_OUTPUT("  Cell channels           : %d", silcd->stat.cell_channels);
  STAT_OUTPUT("  Cell joined users       : %d", silcd->stat.cell_chanclients);
  STAT_OUTPUT("  All clients             : %d", silcd->stat.clients);
  STAT_OUTPUT("  All servers             : %d", silcd->stat.servers);
  STAT_OUTPUT("  All routers             : %d", silcd->stat.routers);
  STAT_OUTPUT("  All channels            : %d", silcd->stat.channels);
  STAT_OUTPUT("  All joined users        : %d", silcd->stat.chanclients);
  STAT_OUTPUT("  All aways               : %d", silcd->stat.aways);
  STAT_OUTPUT("  All detached clients    : %d", silcd->stat.detached);
  STAT_OUTPUT("  All server operators    : %d", silcd->stat.server_ops);
  STAT_OUTPUT("  All router operators    : %d", silcd->stat.router_ops);
  fprintf(fdd, "\nGeneral Stats:\n");
  STAT_OUTPUT("  Connection attempts     : %d", silcd->stat.conn_attempts);
  STAT_OUTPUT("  Connection failures     : %d", silcd->stat.conn_failures);
  STAT_OUTPUT("  Authentication attempts : %d", silcd->stat.auth_attempts);
  STAT_OUTPUT("  Authentication failures : %d", silcd->stat.auth_failures);
  STAT_OUTPUT("  Packets sent            : %d", silcd->stat.packets_sent);
  STAT_OUTPUT("  Packets received        : %d", silcd->stat.packets_received);
  STAT_OUTPUT("  Commands sent           : %d", silcd->stat.commands_sent);
  STAT_OUTPUT("  Commands received       : %d", silcd->stat.commands_received);
  STAT_OUTPUT("  Connections             : %d", silcd->stat.conn_num);

#undef STAT_OUTPUT

  /* Dump internal flags */
  fprintf(fdd, "\nDumping internal flags\n");
  fprintf(fdd, "  server_type            : %d\n", silcd->server_type);
  fprintf(fdd, "  standalone             : %d\n", silcd->standalone);
  fprintf(fdd, "  listenning             : %d\n", silcd->listenning);
  fprintf(fdd, "  background             : %d\n", silcd->background);
  fprintf(fdd, "  backup_router          : %d\n", silcd->backup_router);
  fprintf(fdd, "  backup_primary         : %d\n", silcd->backup_primary);
  fprintf(fdd, "  backup_noswitch        : %d\n", silcd->backup_noswitch);
  fprintf(fdd, "  backup_closed          : %d\n", silcd->backup_closed);
  fprintf(fdd, "  wait_backup            : %d\n", silcd->wait_backup);
  if (silcd->router)
    fprintf(fdd, "  primary router         : %s\n",
      silcd->router->server_name ? silcd->router->server_name : "");

  /* Dump connections */
  {
    SilcPacketStream s;
    SilcDList conns = silc_packet_engine_get_streams(silcd->packet_engine);

    fprintf(fdd, "\nDumping connections\n");
    silc_dlist_start(conns);
    while ((s = silc_dlist_get(conns))) {
      const char *hostname, *ip;
      SilcUInt16 port;
      SilcSocket sock;
      SilcIDListData idata = silc_packet_get_context(s);
      if (!silc_socket_stream_get_info(silc_packet_stream_get_stream(s),
				       &sock, &hostname, &ip, &port))
	continue;
      fprintf(fdd, "  %d: host %s ip %s port %d type %d idata %p\n",
	      sock, hostname ? hostname : "N/A",
	      ip ? ip : "N/A", port, idata ? idata->conn_type : 0, idata);
    }
    silc_dlist_uninit(conns);
  }

  /* Dump lists */
  {
    SilcList list;
    SilcIDCacheEntry id_cache = NULL;
    SilcServerEntry server_entry;
    SilcClientEntry client_entry;
    SilcChannelEntry channel_entry;
    int c;

    fprintf(fdd, "\nDumping databases\n");

    if (silc_idcache_get_all(silcd->local_list->servers, &list)) {
      c = 1;
      fprintf(fdd, "\nServers in local-list:\n");
      silc_list_start(list);
      while ((id_cache = silc_list_get(list))) {
	  server_entry = (SilcServerEntry)id_cache->context;
	  fprintf(fdd, "  %d: name %s id %s status 0x%x idata %p\n", c,
		  server_entry->server_name ? server_entry->server_name :
		  "N/A", server_entry->id ?
		  silc_id_render(server_entry->id, SILC_ID_SERVER) : "N/A",
		  server_entry->data.status, server_entry);
	  c++;
      }
    }
    if (silc_idcache_get_all(silcd->global_list->servers, &list)) {
	fprintf(fdd, "\nServers in global-list:\n");
	c = 1;
        silc_list_start(list);
        while ((id_cache = silc_list_get(list))) {
	  server_entry = (SilcServerEntry)id_cache->context;
	  fprintf(fdd, "  %d: name %s id %s status 0x%x idata %p\n", c,
		  server_entry->server_name ? server_entry->server_name :
		  "N/A", server_entry->id ?
		  silc_id_render(server_entry->id, SILC_ID_SERVER) : "N/A",
		  server_entry->data.status, server_entry);
	  c++;
        }
    }
    if (silc_idcache_get_all(silcd->local_list->clients, &list)) {
	fprintf(fdd, "\nClients in local-list:\n");
	c = 1;
        silc_list_start(list);
        while ((id_cache = silc_list_get(list))) {
	  client_entry = (SilcClientEntry)id_cache->context;
	  server_entry = client_entry->router;
	  fprintf(fdd, "  %d: name %s id %s status 0x%x from %s idata %p\n", c,
		  client_entry->nickname ? client_entry->nickname :
		  (unsigned char *)"N/A", client_entry->id ?
		  silc_id_render(client_entry->id, SILC_ID_CLIENT) : "N/A",
		  client_entry->data.status, server_entry ?
		  server_entry->server_name ? server_entry->server_name :
		  "N/A" : "local", client_entry);
	  c++;
	}
    }
    if (silc_idcache_get_all(silcd->global_list->clients, &list)) {
	fprintf(fdd, "\nClients in global-list:\n");
	c = 1;
        silc_list_start(list);
        while ((id_cache = silc_list_get(list))) {
	  client_entry = (SilcClientEntry)id_cache->context;
	  server_entry = client_entry->router;
	  fprintf(fdd, "  %d: name %s id %s status 0x%x from %s idata %p\n", c,
		  client_entry->nickname ? client_entry->nickname :
		  (unsigned char *)"N/A", client_entry->id ?
		  silc_id_render(client_entry->id, SILC_ID_CLIENT) : "N/A",
		  client_entry->data.status, server_entry ?
		  server_entry->server_name ? server_entry->server_name :
		  "N/A" : "local", client_entry);
	  c++;
	}
    }
    if (silc_idcache_get_all(silcd->local_list->channels, &list)) {
	fprintf(fdd, "\nChannels in local-list:\n");
	c = 1;
        silc_list_start(list);
        while ((id_cache = silc_list_get(list))) {
	  channel_entry = (SilcChannelEntry)id_cache->context;
	  fprintf(fdd, "  %d: name %s id %s\n", c,
		  channel_entry->channel_name ? channel_entry->channel_name :
		  "N/A", channel_entry->id ?
		  silc_id_render(channel_entry->id, SILC_ID_CHANNEL) : "N/A");
	  c++;
	}
    }
    if (silc_idcache_get_all(silcd->global_list->channels, &list)) {
	fprintf(fdd, "\nChannels in global-list:\n");
	c = 1;
        silc_list_start(list);
        while ((id_cache = silc_list_get(list))) {
	  channel_entry = (SilcChannelEntry)id_cache->context;
	  fprintf(fdd, "  %d: name %s id %s\n", c,
		  channel_entry->channel_name ? channel_entry->channel_name :
		  "N/A", channel_entry->id ?
		  silc_id_render(channel_entry->id, SILC_ID_CHANNEL) : "N/A");
	  c++;
	}
    }
  }

  fflush(fdd);
  fclose(fdd);
}

#ifdef SILC_DEBUG

typedef struct {
  int level;
  const char *string;
} DebugLevel;

static DebugLevel debug_levels[] = {
  /* Very basic stuff from silcd/ */
  { 3, "silcd\\.c,server\\.c" },

  /* More stuff from silcd/ */
  { 7, "silcd\\.c,server\\.c,command\\.c,server_backup\\.c,packet_send\\.c" },

  /* All basic stuff from silcd/ */
  { 10, "silc_server_*,*silc_id_create_*,*idlist*,*skr*" },

  /* All from silcd/ */
  { 15, "*silcd*,*serverid*,silc_server_*,*idlist*,*skr*" },

  /* All from silcd/ and basic stuff from libs */
  { 20, "*silcd*,*serverid*,silc_server_*,*idlist*,*silcconauth*,*silcske*,"
    "*skr*" },

  /* All from silcd/ and more stuff from libs */
  { 25, "*silcd*,*serverid*,silc_server_*,*idlist*,*silcconauth*,"
    "*silcpacket*,*ske*,*silcrng*,*skr*" },

  /* All from silcd/ and even more stuff from libs */
  { 30, "*silcd*,*serverid*,silc_server_*,*idlist*,*silcconauth*,*skr*"
    "*silcpacket*,*ske*,*silcrng*,*command*,*channel*,*private*,*notify*" },

  /* All from silcd/ and even more stuff from libs + all from silccore */
  { 35, "*silcd*,*serverid*,silc_server_*,*idlist*,*silcconauth*,*skr*"
    "*silcpacket*,*ske*,*silcrng*,*command*,*channel*,*private*,*notify*"
    "*silcid*,*argument*" },

  /* All from silcd/, all from silccore, silccrypt and silcmath */
  { 40, "*silcd*,*serverid*,silc_server_*,*idlist*,*silcconauth*,*skr*"
    "*silcpacket*,*ske*,*silcrng*,*command*,*channel*,*private*,*notify*"
    "*silcid*,*argument*,*pkcs*,*hmac*,*hash*,*cipher*,silc_math*" },

  /* All from silcd/, all from silccore, silccrypt and silcmath + stuff
     from silcutil */
  { 45, "*silcd*,*serverid*,silc_server_*,*idlist*,*silcconauth*,*skr*"
    "*silcpacket*,*ske*,*silcrng*,*command*,*channel*,*private*,*notify*"
    "*silcid*,*argument*,*pkcs*,*hmac*,*hash*,*cipher*,silc_math*,*sim*"
    "*sockconn*" },

  /* All from silcd/, all from silccore, silccrypt and silcmath + more stuff
     from silcutil */
  { 50, "*silcd*,*serverid*,silc_server_*,*idlist*,*silcconauth*,*skr*"
    "*silcpacket*,*ske*,*silcrng*,*command*,*channel*,*private*,*notify*"
    "*silcid*,*argument*,*pkcs*,*hmac*,*hash*,*cipher*,silc_math*,*sim*"
    "*sockconn*,*net*" },

  /* All from silcd/, all from silccore, silccrypt and silcmath + more stuff
     from silcutil */
  { 55, "*silcd*,*serverid*,silc_server_*,*idlist*,*silcconauth*,*skr*"
    "*silcpacket*,*ske*,*silcrng*,*command*,*channel*,*private*,*notify*"
    "*silcid*,*argument*,*pkcs*,*hmac*,*hash*,*cipher*,silc_math*,*sim*"
    "*sockconn*,*net*,*log*,*config*" },

  /* All */
  { 90, "*" },

  { -1, NULL },
};

static void silc_get_debug_level(int level)
{
  int i;

  if (level < 0)
    return;

  for (i = 0; debug_levels[i].string; i++)
    if (level <= debug_levels[i].level) {
      silc_log_set_debug_string(debug_levels[i].string);
      break;
    }
}
#endif /* SILC_DEBUG */

/* This function should not be called directly but through the appropriate
   wrapper macro defined in server.h */

void silc_server_stderr(SilcLogType type, char *message)
{
  if (silcd->background) {
    char *p, *n = message;

    /* remove newlines if we are going to output it to a log file */
    for (p = n; *p; p++) {
      if (*p != '\n') {
	if (p != n)
	  *n = *p;
	n++;
      }
    }
    *n = 0;

    /* the message is freed inside the logging function */
    silc_log_output(type, message);
  }
  else {
    fprintf(stderr, "%s\n", message);
    silc_free(message);
  }
}

#ifdef SILC_DEBUG
#define NUM_DEBUGS 3000
static char debugs[NUM_DEBUGS][128];
static int cur_debug = 0;

SilcBool silc_server_debug_callback(char *file, char *function, int line,
				    char *message, void *context)
{
  SilcTimeStruct curtime;

  /* Save the message to ring buffer */
  silc_time_value(0, &curtime);
  silc_snprintf(debugs[cur_debug % NUM_DEBUGS], sizeof(debugs[0]),
		"%02d:%02d:%02d %s:%d: %s", curtime.hour,
		curtime.minute, curtime.second, function, line,
		message);
  cur_debug++;

  return FALSE;
}
#endif /* SILC_DEBUG */

int main(int argc, char **argv)
{
  int ret, opt, option_index;
  SilcBool foreground = FALSE;
  SilcBool opt_create_keypair = FALSE;
  char *silcd_config_file = NULL;
  struct sigaction sa;

#ifdef SILC_DEBUG
  silc_log_set_debug_callbacks(silc_server_debug_callback, NULL, NULL, NULL);
#endif /* SILC_DEBUG */

  /* Parse command line arguments */
  if (argc > 1) {
#ifdef HAVE_GETOPT_LONG
    while ((opt = getopt_long(argc, argv, "f:p:d:D:xhFVC:",
			      long_opts, &option_index)) != EOF) {
#else
    while ((opt = getopt(argc, argv, "f:p:d:D:xhFVC:")) != EOF) {
#endif /* HAVE_GETOPT_LONG */
      switch(opt) {
	case 'h':
	  silc_usage();
	  break;
	case 'V':
	  printf("SILCd Secure Internet Live Conferencing daemon, "
		 "version %s (base: SILC Toolkit %s)\n",
		 silc_dist_version, silc_version);
	  printf("(c) 1997 - 2007 Pekka Riikonen "
		 "<priikone@silcnet.org>\n");
	  exit(0);
	  break;
	case 'd':
#ifdef SILC_DEBUG
	  silc_log_debug(TRUE);
	  silc_log_quick(TRUE);
	  if (optarg)
	    silc_log_set_debug_string(optarg);
	  foreground = TRUE;	    /* implied */
#else
	  fprintf(stderr,
		  "Run-time debugging is not enabled. To enable it recompile\n"
		  "the server with --enable-debug configuration option.\n");
#endif
	  break;
	case 'D':
#ifdef SILC_DEBUG
	  silc_log_debug(TRUE);
	  silc_log_quick(TRUE);
	  if (optarg)
	    silc_get_debug_level(atoi(optarg));
	  foreground = TRUE;	    /* implied */
#else
	  fprintf(stderr,
		  "Run-time debugging is not enabled. To enable it recompile\n"
		  "the server with --enable-debug configuration option.\n");
#endif
	  break;
	case 'x':
#ifdef SILC_DEBUG
	  silc_log_debug(TRUE);
	  silc_log_debug_hexdump(TRUE);
	  silc_log_quick(TRUE);
	  foreground = TRUE; /* implied */
#else
	  fprintf(stderr,
		  "Run-time debugging is not enabled. To enable it recompile\n"
		  "the server with --enable-debug configuration option.\n");
#endif
	  break;
	case 'f':
	  silcd_config_file = strdup(optarg);
	  break;
	case 'F':
	  foreground = TRUE;
	  break;

	  /*
	   * Key management options
	   */
	case 'C':
	  opt_create_keypair = TRUE;
	  if (optarg)
	    opt_keypath = strdup(optarg);
	  break;
	case 10:
	  if (optarg)
	    opt_pkcs = strdup(optarg);
	  break;
	case 11:
	  if (optarg)
	    opt_bits = atoi(optarg);
	  break;
	case 12:
	  if (optarg)
	    opt_identifier = strdup(optarg);
	  break;

	default:
	  silc_usage();
	  break;
      }
    }
  }

  if (opt_create_keypair == TRUE) {
    /* Create new key pair and exit */
    char pubfile[256], prvfile[256];

    memset(pubfile, 0, sizeof(pubfile));
    memset(prvfile, 0, sizeof(prvfile));
    snprintf(pubfile, sizeof(pubfile) - 1, "%s/silcd.pub", opt_keypath);
    snprintf(prvfile, sizeof(prvfile) - 1, "%s/silcd.prv", opt_keypath);

    silc_cipher_register_default();
    silc_pkcs_register_default();
    silc_hash_register_default();
    silc_hmac_register_default();

    if (opt_bits < 4096)
      fprintf(stderr,
              "warning: You have specified key length under 4096 bits. It is "
	      "recommended to use at least 4096 bits.\n");

    if (!silc_create_key_pair(opt_pkcs, opt_bits, pubfile, prvfile,
			      opt_identifier, "", NULL, NULL, FALSE))
      exit(1);
    exit(0);
  }

  /* Default configuration file */
  if (!silcd_config_file)
    silcd_config_file = strdup(SILC_SERVER_CONFIG_FILE);

  /* Create SILC Server object */
  ret = silc_server_alloc(&silcd);
  if (ret == FALSE)
    goto fail;

  /* Register default crypto stuff since we are going to need them
     in the configuration file parsing phase */
  silc_cipher_register_default();
  silc_pkcs_register_default();
  silc_hash_register_default();
  silc_hmac_register_default();

  /* Read configuration files */
  silcd->config = silc_server_config_alloc(silcd_config_file, silcd);
  if (silcd->config == NULL)
    goto fail;
  silcd->config_file = silcd_config_file;

  /* Unregister the default crypto stuff so that configuration takes effect */
  silc_cipher_unregister_all();
  /* silc_pkcs_unregister_all();  MUST NOT do this anymore; SilcPublicKey
     parsed from config file references pointers so we cannot unregister */
  silc_hash_unregister_all();
  silc_hmac_unregister_all();

  /* Check for another silcd running */
  silc_server_checkpid(silcd);

  /* Initialize the server */
  if (silc_server_init(silcd) == FALSE)
    goto fail;

  /* Ignore some signals */
  sa.sa_handler = SIG_IGN;
  sa.sa_flags = 0;
  sigemptyset(&sa.sa_mask);
#if defined(SIGPIPE)
  sigaction(SIGPIPE, &sa, NULL);      /* Ignore broken pipes */
#endif /* SIGPIPE*/
#if defined(SIGXFSZ)
  sigaction(SIGXFSZ, &sa, NULL);      /* Ignore file limit exceeds */
#endif /* SIGXFSZ */
#if defined(SIGXCPU)
  sigaction(SIGXCPU, &sa, NULL);      /* Ignore CPU time limit exceeds */
#endif /* SIGXCPU */

  /* Handle specificly some other signals. */
  silc_schedule_task_add_signal(silcd->schedule, SIGHUP, got_hup, NULL);
  silc_schedule_task_add_signal(silcd->schedule, SIGTERM, stop_server, NULL);
  silc_schedule_task_add_signal(silcd->schedule, SIGINT, stop_server, NULL);
  silc_schedule_task_add_signal(silcd->schedule, SIGUSR1, dump_stats, NULL);

  if (!foreground) {
    /* Before running the server, fork to background. */
    silc_server_daemonise(silcd);

    /* If set, write pid to file */
    if (silcd->config->server_info->pid_file) {
      char buf[10], *pidfile = silcd->config->server_info->pid_file;
      unlink(pidfile);
      snprintf(buf, sizeof(buf) - 1, "%d\n", getpid());
      silc_file_writefile(pidfile, buf, strlen(buf));
    }

  }
  silc_server_drop_privs(silcd);

  /* Run the server. When this returns the server has been stopped
     and we will exit. */
  silc_server_run(silcd);

  /* Free server */
  silc_server_config_destroy(silcd->config);
  silc_server_free(silcd);

  /* Flush the logging system */
  silc_log_flush_all();

  silc_free(silcd_config_file);
  silc_free(opt_identifier);
  silc_free(opt_keypath);
  exit(0);

 fail:
  silc_free(silcd_config_file);
  silc_free(opt_identifier);
  silc_free(opt_keypath);
  exit(1);
}
