Subversion Repositories general

Rev

Rev 1173 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/******************************************************************
 * rebootd.
 *
 * A daemon which waits on specified UDP port for special string
 * (password) and reboots the computer.
 *
 * Run "./rebootd --help" to see options and parameters.
 *
 * Copyleft 2005 Anatoli Klassen
 *
 ******************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <netdb.h>
#include <syslog.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/reboot.h>
#include <netinet/in.h>
#include <arpa/inet.h>

/* name version number */
#define APP_NAME          "rebootd"

#define DEFAULT_PORT      19
#define DEFAULT_CFG_FILE  "/etc/rebootd.conf"
#define CMDSIZE           4096
#define BUFSIZE           4096
#define MAX_CONFIG_LINE   4096
#define PORT_MIN          1
#define PORT_MAX          65535

/* return values of functions */
#define RESULT_OK         0
#define RESULT_EXIT       1
#define RESULT_ERROR      2
#define RESULT_UNEXPECTED 3

/* return values for the whole program */
#define EXIT_OK           0
#define EXIT_USER_ERROR   1
#define EXIT_UNEXPECTED   2

/* command line and config file parameters */
#define PARAM_HELP_1      "-h"
#define PARAM_HELP_2      "--help"
#define PARAM_VERSION     "--version"
#define PARAM_INTERFACE   "--interface"
#define PARAM_PORT        "--port"
#define PARAM_CONFIG      "--config"
#define PARAM_PID_FILE    "--pid"
#define PARAM_DEBUG       "--debug"
#define CONFIG_INTERFACE  "interface"
#define CONFIG_PORT       "port"
#define CONFIG_PASSWORD   "password"
#define CONFIG_PID_FILE   "pid"

/* priority of defferent sources of options */
#define CFG_PRIO_CMD      1
#define CFG_PRIO_CONFIG   2
#define CFG_PRIO_DEFAULT  3

struct config {
        char           config_file[FILENAME_MAX];
        char           config_file_prio;
        struct in_addr interface;
        char           interface_prio;
        ushort         port;
        char           port_prio;
        char           password[MAX_CONFIG_LINE];
        char           password_prio;
        char           pid_file[FILENAME_MAX];
        char           pid_file_prio;
        char           debug_mode;
};
  
static struct config global_cfg;
static int           daemonized        = 0; /* we are already a daemon */
volatile static int  reconfig_required = 0; /* we have to stop socket listening and reread config */
volatile static int  quit_required     = 0; /* we have to stop socket listening and exit */

static int parse_cmd_line(struct config *cfg, int argc, const char* argv[]);
static int parse_config_file(struct config *cfg, const char *config_name);
static void check_return(int res);

static uint min(uint a, uint b)
{
        return (a < b) ? a : b;
}

static int establish(const struct config *cfg)
{
        int s;
        struct sockaddr_in sa;

        memset(&sa, 0, sizeof(struct sockaddr_in));

        sa.sin_family      = AF_INET;
        sa.sin_port        = htons(cfg->port);
        sa.sin_addr.s_addr = (cfg->interface.s_addr ? cfg->interface.s_addr : INADDR_ANY);

        if((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
                return -1;

        if(bind(s, (struct sockaddr *)&sa, sizeof(struct sockaddr_in)) < 0) {
                close(s);
                return -1;
        }

        return s;
}

static void listen_socket(const struct config *cfg, int soc)
{
        char                    buf[BUFSIZE];
        int                     br;
        struct sockaddr_storage ss;
        socklen_t               ss_size;

        for(;;) {
                ss_size = sizeof(ss);
                br = recvfrom(soc, buf, sizeof(buf)-1, 0, (struct sockaddr *)&ss, &ss_size);

                if(br < 0) {
                        if(errno == EINTR) {
                                if(reconfig_required || quit_required) break;
                        }
                        else {
                                syslog(LOG_INFO, "cannot receive: %s", strerror(errno));
                        }

                        continue;
                }

                buf[br] = '\0';
                sleep(1);
                syslog(LOG_INFO, "got command");
                
                if(0 == strncmp(buf, cfg->password, sizeof(buf))) {
                        if(cfg->debug_mode) {
                                syslog(LOG_INFO, "REBOOT, debug mode");
                        }
                        else {
                                syslog(LOG_EMERG, "REBOOT");
                                sleep(5);
                                if(reboot(RB_AUTOBOOT) < 0) {
                                        syslog(LOG_ERR, "cannot reboot, %s", strerror(errno));
                                }
                        }
                }
        }
}

static void print_version(void)
{
        printf("%s %s.%s.%s\n", APP_NAME, VER_MAJOR, VER_MINOR, VER_REVISION);
}

static void pring_usage(int argc, const char* argv[])
{
        print_version();
        printf("\nA daemon which waits on specified UDP port for special");
        printf(" string (password) and reboots the computer.\n\n");
        printf("Usage: %s [--debug] [--interface IP] [--port PORT]\n", argv[0]);
        printf("       [--config CONFIG_FILE] [--pid FILE]\n");
        printf("   or: %s --version\n", argv[0]);
        printf("   or: %s --help\n\n", argv[0]);
        printf("Defaults are port 19, all interfaces, config /etc/rebootd.conf and no PID file.\n");
        printf("In debug mode do not reboot, just write to log.\n\n");
        printf("Config file looks like:\n--------------------------\n");
        printf("interface=192.168.0.1\nport=19\npassword=\"some password\"\n");
        printf("pid=/var/run/rebootd.pid\n--------------------------\n\n");
        printf("Then run from any other host:\necho -n \"some password\" | nc -uo host_to_rebot 19\n");
}

static int print_error(const char *msg, const char *value)
{
        if(daemonized) {
                syslog(LOG_ERR, "%s", msg);
                if(value) syslog(LOG_ERR, " - %s\n", value);
                syslog(LOG_ERR, "\n");
        }
        else {
                fprintf(stderr, "%s", msg);
                if(value) fprintf(stderr, " - %s\n", value);
                fprintf(stderr, "\n");
        }

        return RESULT_ERROR;
}

static int print_cmd_error(int argc, const char* argv[], const char *msg, const char *value)
{
        fprintf(stderr, "%s", msg);
        if(value) fprintf(stderr, " - %s\n", value);
        fprintf(stderr, "\n");

        pring_usage(argc, argv);

        return RESULT_ERROR;
}

static int print_config_error(const char *config_name, int line, const char *msg, const char *value)
{
        if(daemonized) {
                syslog(LOG_ERR, "Error in %s, line %d: %s", config_name, line, msg);
                if(value) syslog(LOG_ERR, " - %s", value);
                syslog(LOG_ERR, "\n");
        }
        else {
                fprintf(stderr, "Error in %s, line %d: %s", config_name, line, msg);
                if(value) fprintf(stderr, " - %s", value);
                fprintf(stderr, "\n");
        }

        return RESULT_ERROR;
}

static int check_abs_path(const char *file_name)
{
        if(file_name && file_name[0] != '\0' && file_name[0] != '/') {
                return print_error("File must have an absolute path", file_name);
        }

        return RESULT_OK;
}

static int read_config(struct config *cfg, int argc, const char* argv[])
{
        int           res_cmd_line;
        int           res_config_file;
        struct config cmd_cfg;
        struct config file_cfg;

        memset(cfg, 0, sizeof(struct config));
        res_cmd_line = parse_cmd_line(&cmd_cfg, argc, argv);
        if(res_cmd_line != RESULT_OK) return res_cmd_line;
        if(check_abs_path(cmd_cfg.config_file) != RESULT_OK) return RESULT_ERROR;

        res_config_file = parse_config_file(&file_cfg,
                cmd_cfg.config_file[0] != '\0' ? cmd_cfg.config_file : DEFAULT_CFG_FILE);
        if(res_config_file != RESULT_OK) return res_config_file;

        if(file_cfg.password[0] == '\0')
                return print_error("Password is not set", NULL);

        /* save parsed values to general config */
        if(cmd_cfg.config_file[0] != '\0') {
                strncpy(cfg->config_file, cmd_cfg.config_file, sizeof(cfg->config_file));
                cfg->config_file_prio = CFG_PRIO_CMD;
        }
        else {
                strncpy(cfg->config_file, DEFAULT_CFG_FILE, sizeof(cfg->config_file));
                cfg->config_file_prio = CFG_PRIO_DEFAULT;
        }

        if(cmd_cfg.interface.s_addr) {
                cfg->interface      = cmd_cfg.interface;
                cfg->interface_prio = CFG_PRIO_CMD;
        }
        else if(file_cfg.interface.s_addr) {
                cfg->interface      = file_cfg.interface;
                cfg->interface_prio = CFG_PRIO_CONFIG;
        }
        else {
                cfg->interface_prio = CFG_PRIO_DEFAULT;
        }

        if(cmd_cfg.port) {
                cfg->port      = cmd_cfg.port;
                cfg->port_prio = CFG_PRIO_CMD;
        }
        else if(file_cfg.port) {
                cfg->port      = file_cfg.port;
                cfg->port_prio = CFG_PRIO_CONFIG;
        }
        else {
                cfg->port      = DEFAULT_PORT;
                cfg->port_prio = CFG_PRIO_DEFAULT;
        }

        if(cmd_cfg.pid_file[0] != '\0') {
                strncpy(cfg->pid_file, cmd_cfg.pid_file, sizeof(cfg->pid_file));
                cfg->pid_file_prio = CFG_PRIO_CMD;
        }
        else if(file_cfg.pid_file[0] != '\0') {
                strncpy(cfg->pid_file, file_cfg.pid_file, sizeof(cfg->pid_file));
                cfg->pid_file_prio = CFG_PRIO_CONFIG;
        }
        else {
                cfg->pid_file_prio = CFG_PRIO_DEFAULT;
        }
        if(check_abs_path(cfg->config_file) != RESULT_OK) return RESULT_ERROR;

        strncpy(cfg->password, file_cfg.password, sizeof(cfg->password));
        cfg->password_prio = CFG_PRIO_CONFIG;

        cfg->debug_mode = cmd_cfg.debug_mode;

        return RESULT_OK;
}

static int reread_config(struct config *cfg, int *reinit_needed)
{
        int           res_config_file;
        struct config file_cfg;

        *reinit_needed = 0;

        res_config_file = parse_config_file(&file_cfg, cfg->config_file);
        if(res_config_file != RESULT_OK) return res_config_file;

        if(file_cfg.password[0] == '\0')
                return print_error("Password is not set", NULL);

        /* save parsed values to general config */
        if(cfg->interface_prio >= CFG_PRIO_CONFIG && cfg->interface.s_addr != file_cfg.interface.s_addr)
        {
                *reinit_needed      = 1;
                cfg->interface      = file_cfg.interface;
                cfg->interface_prio = CFG_PRIO_CONFIG;
        }

        if(cfg->port_prio >= CFG_PRIO_CONFIG && cfg->port != file_cfg.port && file_cfg.port) {
                *reinit_needed = 1;
                cfg->port      = file_cfg.port;
                cfg->port_prio = CFG_PRIO_CONFIG;
        }

        if(check_abs_path(cfg->config_file) != RESULT_OK) return RESULT_ERROR;

        strncpy(cfg->password, file_cfg.password, sizeof(cfg->password));
        cfg->password_prio = CFG_PRIO_CONFIG;

        return RESULT_OK;
}

static int parse_interface(const char *s, char **end, struct in_addr *ip)
{
        char       buf[MAX_CONFIG_LINE];
        int        count;
        const char *c;

        c     = s;
        count = 0;
        while(c[0] != '\0' && !isspace(c[0])) c++, count++;
        if(count > MAX_CONFIG_LINE) return RESULT_ERROR;
        strncpy(buf, s, count);
        buf[count] = '\0';
        c++;

        if(end) *end = (char *)c;

        if(inet_aton(buf, ip) == 1)
                return RESULT_OK;
        else
                return RESULT_ERROR;
}

static int parse_cmd_line(struct config *cfg, int argc, const char* argv[])
{
        char *end;
        int  i;
        long port;

        memset(cfg, 0, sizeof(struct config));

        for(i = 1; i < argc; i++) {
                if(0 == strcmp(argv[i], PARAM_INTERFACE)) {
                        if(cfg->interface.s_addr)
                                return print_cmd_error(argc, argv, "Interface is already set", NULL);

                        if(++i < argc) {
                                if(parse_interface(argv[i], (char **)NULL, &cfg->interface) != RESULT_OK)
                                        return print_cmd_error(argc, argv, 
                                                "Cannot parse interface", argv[i]);
                        }
                        else {
                                return print_cmd_error(argc, argv, "Interface expected", NULL);
                        }
                }
                else if(0 == strcmp(argv[i], PARAM_PORT)) {
                        if(cfg->port)
                                return print_cmd_error(argc, argv, "Port is already set", NULL);

                        if(++i < argc) {
                                port = strtol(argv[i], &end, 10);
                                if(*end != '\0' || port < PORT_MIN || port > PORT_MAX)
                                        return print_cmd_error(argc, argv, 
                                                "Port number must be integer between 1 and 65535", NULL);
                                cfg->port = (ushort)port;
                        }
                        else {
                                return print_cmd_error(argc, argv, "Port number expected", NULL);
                        }
                }
                else if(0 == strcmp(argv[i], PARAM_CONFIG)) {
                        if(cfg->config_file[0] != '\0')
                                return print_cmd_error(argc, argv, "Config file name is already set", NULL);

                        if(++i < argc) {
                                strncpy(cfg->config_file, argv[i], FILENAME_MAX);
                                if(cfg->config_file[FILENAME_MAX - 1] != '\0')
                                        return print_cmd_error(argc, argv,
                                                "Config file name is too long", NULL);
                        }
                        else {
                                return print_cmd_error(argc, argv, "Config file name expected", NULL);
                        }
                }
                else if(0 == strcmp(argv[i], PARAM_PID_FILE)) {
                        if(cfg->pid_file[0] != '\0')
                                return print_cmd_error(argc, argv, "PID file name is already set", NULL);

                        if(++i < argc) {
                                strncpy(cfg->pid_file, argv[i], FILENAME_MAX);
                                if(cfg->pid_file[FILENAME_MAX - 1] != '\0')
                                        return print_cmd_error(argc, argv,
                                                "PID file name is too long", NULL);
                        }
                        else {
                                return print_cmd_error(argc, argv, "PID file name expected", NULL);
                        }
                }
                else if(0 == strcmp(argv[i], PARAM_DEBUG)) {
                        cfg->debug_mode = 1;
                }
                else if(0 == strcmp(argv[i], PARAM_VERSION)) {
                        print_version();
                        return RESULT_EXIT;
                }
                else if(0 == strcmp(argv[i], PARAM_HELP_1) || 0 == strcmp(argv[i], PARAM_HELP_2)) {
                        pring_usage(argc, argv);
                        return RESULT_EXIT;
                }
                else {
                        return print_cmd_error(argc, argv, "Unknown parameter", argv[i]);
                }
        }

        return RESULT_OK;
}

static int validate_equal_sign(const char *config_name, const int count, const char *line,
        uint name_len, char **subline)
{
        const char *c;

        c = line + name_len - 1;
        if('=' != c[0] && !isspace(c[0]))
                return print_config_error(config_name, count, "Unknown config parameter", line);
        while(c[0] != '\0' && isspace(c[0])) c++;
        if('=' != c[0])
                return print_config_error(config_name, count, "Equal sign expected", NULL);
        c++;

        if(subline) *subline = (char *)c;

        return RESULT_OK;
}

static int validate_eol(const char *config_name, const int count, const char *line)
{
        while(line[0] != '\0' && isspace(line[0])) line++;
        if('\0' != line[0] && '#' != line[0])
                return print_config_error(config_name, count, "End of line expected", NULL);

        return RESULT_OK;
}

static int extract_string_value(const char *config_name, const int count, char **subline, int must_quot,
        char *string, uint len)
{
        uint cur_len;
        int  quot;

        while(*subline[0] != '\0' && isspace(*subline[0])) (*subline)++;
        if('"' == *subline[0]) {
                quot = 1;
                (*subline)++;
        }
        else {
                if(must_quot) {
                        return print_config_error(config_name, count, "Open quot expected", NULL);
                }
                else {
                        quot = 0;
                        /* skip spaces if not quoted */
                        while(*subline[0] != '\0' && isspace(*subline[0])) (*subline)++;
                }
        }

        cur_len = 0;
        while(*subline[0] != '\0')
        {
                if(cur_len >= len) return print_config_error(config_name, count, "Value too long", NULL);
                if(quot && *subline[0] == '"') break;
                if(!quot && isspace(*subline[0])) break;

                string[0] = *subline[0];
                string++;
                (*subline)++;
                cur_len++;
        }

        string[0] = '\0';
        if(quot) {
                if('"' != *subline[0])
                        return print_config_error(config_name, count, "Close quot expected", NULL);
                (*subline)++;
        }

        return RESULT_OK;
}

static int parse_config_file(struct config *cfg, const char *config_name)
{
        FILE *config_file;
        char buf[MAX_CONFIG_LINE];
        char *line;
        char *subline;
        int  count;
        uint len;
        long port;
        int  res;

        memset(cfg, 0, sizeof(struct config));

        config_file = fopen(config_name, "r");
        if(!config_file)
                return print_error("Can not open config file", strerror(errno));

        count = 0;
        while(fgets(buf, sizeof(buf), config_file)) {
                count++;
                line = buf;

                /* skip end spaces */
                len = strlen(line);
                if(len == MAX_CONFIG_LINE-1)
                        return print_config_error(config_name, count, "Line is too long", NULL);
                while(len && isspace(line[len-1])) --len;
                if(!len) continue;
                line[len] = '\0';

                /* skip begin spaces */
                while(line[0] != '\0' && isspace(line[0])) line++;

                if('#' == line[0]) { /* skip comment lines */
                        continue;
                }
                else if(strncmp(line, CONFIG_INTERFACE, min(sizeof(CONFIG_INTERFACE) - 1, len)) == 0) {
                        if((res = validate_equal_sign(config_name, count, line,
                                sizeof(CONFIG_INTERFACE), &subline)) != RESULT_OK) return res;

                        if(parse_interface(subline, &subline, &cfg->interface) != RESULT_OK)
                                return print_config_error(config_name, count,
                                        "Cannot parse interface", NULL);

                        if((res = validate_eol(config_name, count, subline)) != RESULT_OK) return res;
                }
                else if(strncmp(line, CONFIG_PORT, min(sizeof(CONFIG_PORT) - 1, len)) == 0) {
                        if((res = validate_equal_sign(config_name, count, line, sizeof(CONFIG_PORT),
                                &subline)) != RESULT_OK) return res;

                        port = strtol(subline, &subline, 10);
                        if(port < PORT_MIN || port > PORT_MAX)
                                return print_config_error(config_name, count, 
                                        "Port number must be integer between 1 and 65535", NULL);

                        if((res = validate_eol(config_name, count, subline)) != RESULT_OK) return res;
                        cfg->port = (ushort)port;
                }
                else if(strncmp(line, CONFIG_PASSWORD, min(sizeof(CONFIG_PASSWORD) - 1, len)) == 0) {
                        if((res = validate_equal_sign(config_name, count, line,
                                sizeof(CONFIG_PASSWORD), &subline)) != RESULT_OK) return res;

                        if((res = extract_string_value(config_name, count, &subline, 1,
                                cfg->password, sizeof(cfg->password))) != RESULT_OK) return res;

                        if((res = validate_eol(config_name, count, subline)) != RESULT_OK) return res;
                }
                else if(strncmp(line, CONFIG_PID_FILE, min(sizeof(CONFIG_PID_FILE) - 1, len)) == 0) {
                        if((res = validate_equal_sign(config_name, count, line,
                                sizeof(CONFIG_PID_FILE), &subline)) != RESULT_OK) return res;

                        if((res = extract_string_value(config_name, count, &subline, 0,
                                cfg->pid_file, sizeof(cfg->pid_file))) != RESULT_OK) return res;

                        if((res = validate_eol(config_name, count, subline)) != RESULT_OK) return res;
                }
                else {
                        return print_config_error(config_name, count, "Unknown config parameter", line);
                }
        }
        if(ferror(config_file)) {
                print_error("Config file reading failed", strerror(errno));
        }

        if(fclose(config_file) != 0)
                return print_error("Can not close config file", strerror(errno));

        return RESULT_OK;
}

static int save_pid(const char *pid_file, const pid_t pid)
{
        int  fd;
        FILE *file;

        if(!pid_file || pid_file[0] == '\0') return RESULT_OK;
        
        unlink(pid_file);

        fd = open(pid_file, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
        if(fd < 0) {
                syslog(LOG_ERR, "Can open PID file %s for write: %s\n", pid_file, strerror(errno));
                return RESULT_ERROR;
        }

        file = fdopen(fd, "w");
        if(!file) {
                syslog(LOG_ERR, "Can open PID file %s for write: %s\n", pid_file, strerror(errno));
                return RESULT_ERROR;
        }

        if(fprintf(file, "%d\n", pid) < 0) {
                syslog(LOG_ERR, "Can write PID to file %s: %s\n", pid_file, strerror(errno));
                return RESULT_ERROR;
        }

        if(fclose(file) != 0) {
                syslog(LOG_ERR, "Can not close PID file %s: %s\n", pid_file, strerror(errno));
                return RESULT_UNEXPECTED;
        }

        return RESULT_OK;
}

static int create_child(int soc)
{
        pid_t child;

        child = fork();
        if(child == -1) {
                fprintf(stderr, "Cannot fork: %s\n", strerror(errno));
                return RESULT_UNEXPECTED; /* an unexpected error */
        }
        else if(child > 0) {
                if(close(soc) != 0) {
                        fprintf(stderr, "Cannot close socket: %s\n", strerror(errno));
                        return RESULT_UNEXPECTED; /* an unexpected error */
                }

                return RESULT_EXIT; /* we are the parent */
        }
        else {
                return RESULT_OK; /* we are the child */
        }
}

static int close_descr(int d)
{
        if(close(d) != 0) {
                syslog(LOG_ERR, "Cannot close descriptor %d: %s\n", d, strerror(errno));
                return RESULT_UNEXPECTED;
        }
        return RESULT_OK;
}

static void delete_pid_file(void)
{
        if(global_cfg.pid_file && global_cfg.pid_file[0] != '\0') {
                if(unlink(global_cfg.pid_file) != 0) {
                        syslog(LOG_ERR, "Cannot delete PID file %s: %s\n",
                                global_cfg.pid_file, strerror(errno));
                }
        }
}

static void reconfig_signal(void)
{
        syslog(LOG_INFO, "reconfig");
        reconfig_required = 1;
}

static void quit_signal(void)
{
        syslog(LOG_WARNING, "quit");
        quit_required = 1;
}

static void signal_handler(int sig)
{
        int saved_errno = errno;

        switch(sig) {
                case SIGHUP:
                        reconfig_signal();
                        break;

                case SIGINT:
                case SIGTERM:
                case SIGQUIT:
                        quit_signal();
                        break;
        }

        errno = saved_errno;
}

static int init_signal_handler(int sig)
{
        struct sigaction sa;

        sa.sa_flags   = 0;
        sa.sa_handler = signal_handler;
        sigemptyset(&sa.sa_mask);

        if(sigaction(sig, &sa, (struct sigaction *)NULL) != 0) {
                syslog(LOG_ERR, "Cannot register handler for signal %d: %s\n", sig, strerror(errno));
                return RESULT_UNEXPECTED;
        }

        return RESULT_OK;
}

static int init_signals(void)
{
        if(init_signal_handler(SIGHUP)  != RESULT_OK) return RESULT_UNEXPECTED;
        if(init_signal_handler(SIGINT)  != RESULT_OK) return RESULT_UNEXPECTED;
        if(init_signal_handler(SIGTERM) != RESULT_OK) return RESULT_UNEXPECTED;
        if(init_signal_handler(SIGQUIT) != RESULT_OK) return RESULT_UNEXPECTED;

        return RESULT_OK;
}

static void check_return(int res)
{
        if(daemonized && res != RESULT_OK) {
                delete_pid_file();
                syslog(LOG_WARNING, "quit");
        }

        switch(res) {
                case RESULT_EXIT:       exit(EXIT_OK);         return;  /* no error but exit */
                case RESULT_ERROR:      exit(EXIT_USER_ERROR); return;  /* user error */
                case RESULT_UNEXPECTED: exit(EXIT_UNEXPECTED); return;  /* unexpected error */
        }
}

int main(int argc, const char* argv[])
{
        int soc;
        int reinit_needed;

        /* get config */
        check_return(read_config(&global_cfg, argc, argv));

        /* try to listen the port */
        if((soc = establish(&global_cfg)) < 0) {
                fprintf(stderr, "Cannot listen to port %i\n", global_cfg.port);
                return EXIT_USER_ERROR;
        }

        /* fork and release the console */
        check_return(create_child(soc));

        /* continue as first child */
        if(setsid() == -1) {
                fprintf(stderr, "Cannot create session: %s\n", strerror(errno));
                return EXIT_UNEXPECTED;
        }

        /* fork the second time */
        check_return(create_child(soc));

        /* continue as final child */
        if(init_signals() != RESULT_OK) return EXIT_UNEXPECTED;

        if(chdir("/") < 0) {
                fprintf(stderr, "Cannot chdir to /: %s\n", strerror(errno));
                return EXIT_UNEXPECTED;
        }
        umask(0);
        check_return(save_pid(global_cfg.pid_file, getpid()));
        daemonized = 1;

        /* from now do not use console for error output */
        if(close_descr(0) != RESULT_OK) return EXIT_UNEXPECTED;
        if(close_descr(1) != RESULT_OK) return EXIT_UNEXPECTED;
        if(close_descr(2) != RESULT_OK) return EXIT_UNEXPECTED;

        for(;;) {
                syslog(LOG_INFO, "listen on %d", global_cfg.port);
                listen_socket(&global_cfg, soc);

                if(quit_required) {
                        break;
                }
                else if(reconfig_required) {
                        reconfig_required = 0;
                        check_return(reread_config(&global_cfg, &reinit_needed));
                        if(reinit_needed) {
                                close(soc);

                                if((soc = establish(&global_cfg)) < 0) {
                                        syslog(LOG_ERR, "Cannot listen to port %i\n", global_cfg.port);
                                        check_return(RESULT_ERROR);
                                }
                        }
                }
        }

        delete_pid_file();
        return EXIT_OK;
}