/* silcd.c Author: Pekka Riikonen Copyright (C) 1997 - 2002 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; either version 2 of the License, or (at your option) any later version. 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); static char *silc_server_create_identifier(void); static int silc_server_create_key_pair(char *pkcs_name, int bits, char *path, char *identifier, SilcPublicKey *ret_pub_key, SilcPrivateKey *ret_prv_key); /* 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 } }; /* Command line option variables */ static char *opt_keypath = NULL; static char *opt_pkcs = "rsa"; static char *opt_identifier = NULL; static int opt_bits = 2048; /* 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=, HN=, RN=, E=,\n" " O=, C=\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() */ } static void signal_handler(int sig) { /* Mark the signal to be caller after this signal is over. */ silc_schedule_signal_call(silcd->schedule, sig); } 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) { /* Stop scheduler, the program will stop eventually after noticing that the scheduler is down. */ silc_schedule_stop(silcd->schedule); } /* Dump server statistics into a file into /tmp directory */ SILC_TASK_CALLBACK(dump_stats) { FILE *fdd; char filename[256]; memset(filename, 0, sizeof(filename)); snprintf(filename, sizeof(filename) - 1, "/tmp/silcd.%d.stats", getpid()); fdd = fopen(filename, "w+"); if (!fdd) 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); #undef STAT_OUTPUT fflush(fdd); fclose(fdd); } 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_*" }, /* All from silcd/ */ { 15, "*silcd*,*serverid*,silc_server_*,*idlist*" }, /* All from silcd/ and basic stuff from libs */ { 20, "*silcd*,*serverid*,silc_server_*,*idlist*,*silcauth*,*silcske*" }, /* All from silcd/ and more stuff from libs */ { 25, "*silcd*,*serverid*,silc_server_*,*idlist*,*silcauth*," "*silcpacket*,*ske*,*silcrng*" }, /* All from silcd/ and even more stuff from libs */ { 30, "*silcd*,*serverid*,silc_server_*,*idlist*,*silcauth*," "*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*,*silcauth*," "*silcpacket*,*ske*,*silcrng*,*command*,*channel*,*private*,*notify*" "*silcid*,*argument*" }, /* All from silcd/, all from silccore, silccrypt and silcmath */ { 40, "*silcd*,*serverid*,silc_server_*,*idlist*,*silcauth*," "*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*,*silcauth*," "*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*,*silcauth*," "*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*,*silcauth*," "*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; } } /* This function should not be called directly but thru the wrapper macro SILC_SERVER_LOG_STDERR() */ void silc_server_stderr(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; silc_log_output(SILC_LOG_ERROR, message); } else { fprintf(stderr, "%s\n", message); silc_free(message); } } int main(int argc, char **argv) { int ret, opt, option_index; bool foreground = FALSE; bool opt_create_keypair = FALSE; char *silcd_config_file = NULL; struct sigaction sa; /* Parse command line arguments */ if (argc > 1) { while ((opt = getopt_long(argc, argv, "f:p:d:D:xhFVC:", long_opts, &option_index)) != EOF) { 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 - 2002 Pekka Riikonen " "\n"); exit(0); break; case 'd': #ifdef SILC_DEBUG silc_debug = TRUE; if (optarg) silc_log_set_debug_string(optarg); foreground = TRUE; /* implied */ silc_log_quick = 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_debug = TRUE; if (optarg) silc_get_debug_level(atoi(optarg)); foreground = TRUE; /* implied */ silc_log_quick = 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_debug_hexdump = TRUE; silc_debug = TRUE; /* implied */ foreground = TRUE; /* implied */ silc_log_quick = 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 */ silc_cipher_register_default(); silc_pkcs_register_default(); silc_hash_register_default(); silc_hmac_register_default(); silc_server_create_key_pair(opt_pkcs, opt_bits, opt_keypath, opt_identifier, NULL, NULL); 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; /* Read configuration files */ silcd->config = silc_server_config_alloc(silcd_config_file); if (silcd->config == NULL) goto fail; silcd->config_file = silcd_config_file; /* Check for another silcd running */ silc_server_checkpid(silcd); /* Initialize the server */ if (silc_server_init(silcd) == FALSE) goto fail; /* Ignore SIGPIPE */ sa.sa_handler = SIG_IGN; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sigaction(SIGPIPE, &sa, NULL); sa.sa_handler = signal_handler; sigaction(SIGHUP, &sa, NULL); sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); sigaction(SIGUSR1, &sa, NULL); silc_schedule_signal_register(silcd->schedule, SIGHUP, got_hup, NULL); silc_schedule_signal_register(silcd->schedule, SIGTERM, stop_server, NULL); silc_schedule_signal_register(silcd->schedule, SIGINT, stop_server, NULL); silc_schedule_signal_register(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)); } } /* Drop root if we are not in debug mode, so you don't need to bother about file writing permissions and so on */ if (!silc_debug) silc_server_drop_privs(silcd); /* Run the server. When this returns the server has been stopped and we will exit. */ silc_server_run(silcd); /* Stop the server and free it. */ silc_server_stop(silcd); 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); } /* Returns identifier string for public key generation. */ static char *silc_server_create_identifier(void) { char *username = NULL, *realname = NULL; char hostname[256], email[256]; /* Get realname */ realname = silc_get_real_name(); /* Get hostname */ memset(hostname, 0, sizeof(hostname)); gethostname(hostname, sizeof(hostname)); /* Get username (mandatory) */ username = silc_get_username(); if (!username) return NULL; /* Create default email address, whether it is right or not */ snprintf(email, sizeof(email), "%s@%s", username, hostname); return silc_pkcs_encode_identifier(username, hostname, realname, email, NULL, NULL); } /* Creates new public key and private key pair. This is used only when user wants to create new key pair from command line. */ static int silc_server_create_key_pair(char *pkcs_name, int bits, char *path, char *identifier, SilcPublicKey *ret_pub_key, SilcPrivateKey *ret_prv_key) { SilcPKCS pkcs; SilcPublicKey pub_key; SilcPrivateKey prv_key; SilcRng rng; unsigned char *key; SilcUInt32 key_len; char pkfile[256], prvfile[256]; if (!pkcs_name || !path) return FALSE; if (!silc_pkcs_is_supported(pkcs_name)) { fprintf(stderr, "Unsupported PKCS `%s'", pkcs_name); return FALSE; } if (!bits) bits = 2048; if (!identifier) identifier = silc_server_create_identifier(); rng = silc_rng_alloc(); silc_rng_init(rng); silc_rng_global_init(rng); snprintf(pkfile, sizeof(pkfile) - 1, "%s%s", path, SILC_SERVER_PUBLIC_KEY_NAME); snprintf(prvfile, sizeof(prvfile) - 1, "%s%s", path, SILC_SERVER_PRIVATE_KEY_NAME); /* Generate keys */ silc_pkcs_alloc(pkcs_name, &pkcs); silc_pkcs_generate_key(pkcs, bits, rng); /* Save public key into file */ key = silc_pkcs_get_public_key(pkcs, &key_len); pub_key = silc_pkcs_public_key_alloc(silc_pkcs_get_name(pkcs), identifier, key, key_len); silc_pkcs_save_public_key(pkfile, pub_key, SILC_PKCS_FILE_PEM); if (ret_pub_key) *ret_pub_key = pub_key; else silc_pkcs_public_key_free(pub_key); memset(key, 0, sizeof(key_len)); silc_free(key); /* Save private key into file */ key = silc_pkcs_get_private_key(pkcs, &key_len); prv_key = silc_pkcs_private_key_alloc(silc_pkcs_get_name(pkcs), key, key_len); silc_pkcs_save_private_key(prvfile, prv_key, NULL, SILC_PKCS_FILE_BIN); if (ret_prv_key) *ret_prv_key = prv_key; else silc_pkcs_private_key_free(prv_key); printf("Public key has been saved into `%s'\n", pkfile); printf("Private key has been saved into `%s'\n", prvfile); memset(key, 0, sizeof(key_len)); silc_free(key); silc_rng_free(rng); silc_pkcs_free(pkcs); return TRUE; }