Subversion Repositories general

Rev

Rev 1158 | Go to most recent revision | Blame | Last modification | View Log | RSS feed

/******************************************************************
 * rebootd.
 *
 * A daemon which waits on specified TCP port for special string
 * (password) and reboots the computer.
 *
 * Command line: rebootd [--port PORT] [--config CONFIG_FILE]
 * Defaults are port 19 and config /etc/rebootd.conf.
 *
 * Config file looks like:
 * --------------------------
 * port=19
 * password="some password"
 * --------------------------
 *
 * Usage example (from any other host):
 * echo -n "some password" | nc host_to_rebot 19
 *
 * 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 <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 VERSION           "1.0"
#define REVISION          "$Rev: 1159 $"

#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_1   "-v"
#define PARAM_VERSION_2   "--version"
#define PARAM_PORT_1      "-p"
#define PARAM_PORT_2      "--port"
#define PARAM_CONFIG_1    "-c"
#define PARAM_CONFIG_2    "--config"
#define CONFIG_PORT       "port"
#define CONFIG_PASSWORD   "password"

struct config {
        ushort port;
        char   password[MAX_CONFIG_LINE];
};

static int parse_cmd_line(int argc, const char* argv[], long *port, char *config_name);
static int parse_config_file(const char *config_name, long *port, char *password);
static int get_revision(void);

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

static int establish(ushort port)
{
        int s;
        struct sockaddr_in sa;

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

        sa.sin_family      = AF_INET;
        sa.sin_port        = htons(port);
        sa.sin_addr.s_addr = INADDR_ANY;

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

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

        if(listen(s, 1) != 0) {
                close(s);
                return -1;
        }

        return s;
}

static void listen_socket(const struct config *cfg, int soc)
{
        int                t;
        char               cmd[CMDSIZE];
        char               buf[BUFSIZE];
        int                br;
        struct sockaddr_in sa;
        socklen_t          sa_len;
        int                cmdlen;

        for(;;) {
                sa_len = sizeof(sa);
                if((t = accept(soc, (struct sockaddr *)&sa, &sa_len)) < 0) {
                        if(errno == EINTR) /* EINTR might happen on accept(), */
                                continue;        /* try again */

                        syslog(LOG_ERR, "cannot get connection, %s", strerror(errno));
                        continue;
                }

                syslog(LOG_INFO, "connect from %s", inet_ntoa(sa.sin_addr));

                cmd[0] = '\0';
                cmdlen = 0;
                while((br = recv(t, buf, BUFSIZE, 0)) > 0) {
                        strncat(cmd, buf, min((uint)br, sizeof(cmd) - cmdlen - 1));
                        cmdlen += min((uint)br, sizeof(cmd) - cmdlen - 1);
                }
                sleep(1);
                close(t);

                syslog(LOG_INFO, "got command");
                
                if(0 == strncmp(cmd, cfg->password, sizeof(cmd))) {
                        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.%d\n", APP_NAME, VERSION, get_revision());
}

static void pring_usage(int argc, const char* argv[])
{
        print_version();
        printf("\nA daemon which waits on specified TCP port for special");
        printf(" string (password) and reboots the computer.\n\n");
        printf("Usage: %s [--port PORT] [--config CONFIG_FILE]\n", argv[0]);
        printf("   or: %s --version\n", argv[0]);
        printf("   or: %s --help\n\n", argv[0]);
        printf("Defaults are port 19 and config /etc/rebootd.conf.\n\n");
        printf("Config file looks like:\n--------------------------\n");
        printf("port=19\npassword=\"some password\"\n--------------------------\n\n");
        printf("Then run from any other host:\necho -n \"some password\" | nc host_to_rebot 19\n");
}

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)
{
        fprintf(stderr, "Error in %s, line %d: %s", config_name, line, msg);
        if(value) fprintf(stderr, " - %s", value);
        fprintf(stderr, "\n");

        return RESULT_ERROR;
}

/* return: 0 - no error, continue; 1 - no error, but exit; 2 - user error, exit; 3 - unexpected error, exit */
static int read_config(struct config *cfg, int argc, const char* argv[])
{
        int  res_cmd_line;
        int  res_config_file;
        long cmd_port;
        long file_port;
        char cmd_config[MAX_CONFIG_LINE];

        res_cmd_line = parse_cmd_line(argc, argv, &cmd_port, cmd_config);
        if(res_cmd_line != RESULT_OK) return res_cmd_line;

        res_config_file = parse_config_file(cmd_config[0] != '\0' ? cmd_config : DEFAULT_CFG_FILE,
                &file_port, cfg->password);
        if(res_config_file != RESULT_OK) return res_config_file;

        if(cfg->password[0] == '\0') {
                fprintf(stderr, "Password is not set\n");
                return RESULT_ERROR;
        }

        if(cmd_port > 0)
                cfg->port = (ushort)cmd_port;
        else if(file_port > 0)
                cfg->port = (ushort)file_port;
        else
                cfg->port = DEFAULT_PORT;

        return RESULT_OK;
}

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

        *port          = 0;
        config_name[0] = '\0';

        for(i = 1; i < argc; i++) {
                if(0 == strcmp(argv[i], PARAM_PORT_1) || 0 == strcmp(argv[i], PARAM_PORT_2)) {
                        if(*port > 0)
                                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);
                        }
                        else {
                                return print_cmd_error(argc, argv, "Port number expected", NULL);
                        }
                }
                else if(0 == strcmp(argv[i], PARAM_CONFIG_1) || 0 == strcmp(argv[i], PARAM_CONFIG_2)) {
                        if(config_name[0] != '\0')
                                return print_cmd_error(argc, argv, "Config file is already set", NULL);

                        if(++i < argc) {
                                strncpy(config_name, argv[i], MAX_CONFIG_LINE);
                                if(config_name[MAX_CONFIG_LINE - 1] != '\0')
                                        return print_cmd_error(argc, argv,
                                                "Config file name is too long", NULL);
                        }
                        else {
                                return print_cmd_error(argc, argv, "Config file expected", NULL);
                        }
                }
                else if(0 == strcmp(argv[i], PARAM_VERSION_1) || 0 == strcmp(argv[i], PARAM_VERSION_2)) {
                        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 parse_config_file(const char *config_name, long *port, char *password)
{
        FILE *config_file;
        char buf[MAX_CONFIG_LINE];
        char *line;
        char *subline;
        char *cur;
        int  count;
        uint len;

        config_file = fopen(config_name, "r");
        if(!config_file) {
                fprintf(stderr, "Can not open config file %s: %s\n", config_name, strerror(errno));
                return RESULT_ERROR;
        }

        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_PORT, min(sizeof(CONFIG_PORT) - 1, len)) == 0) {
                        subline = line + sizeof(CONFIG_PORT) - 1;
                        if('=' != subline[0] && !isspace(subline[0]))
                                return print_config_error(config_name, count, "Unknown config parameter", line);
                        while(subline[0] != '\0' && isspace(subline[0])) subline++;
                        if('=' != subline[0])
                                return print_config_error(config_name, count, "Equal sign expected", NULL);
                        subline++;
                        *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);
                        while(subline[0] != '\0' && isspace(subline[0])) subline++;
                        if('\0' != subline[0] && '#' != subline[0])
                                return print_config_error(config_name, count, "End of line expected", NULL);
                }
                else if(strncmp(line, CONFIG_PASSWORD, min(sizeof(CONFIG_PASSWORD) - 1, len)) == 0) {
                        subline = line + sizeof(CONFIG_PASSWORD) - 1;
                        if('=' != subline[0] && !isspace(subline[0]))
                                return print_config_error(config_name, count, "Unknown config parameter", line);
                        while(subline[0] != '\0' && isspace(subline[0])) subline++;
                        if('=' != subline[0])
                                return print_config_error(config_name, count, "Equal sign expected", NULL);
                        subline++;
                        while(subline[0] != '\0' && isspace(subline[0])) subline++;
                        if('"' != subline[0])
                                return print_config_error(config_name, count, "Open quot expected", NULL);
                        subline++;

                        cur = password;
                        while(subline[0] != '\0' && subline[0] != '"') {
                                cur[0] = subline[0];
                                cur++;
                                subline++;
                        }

                        cur[0] = '\0';
                        if('"' != subline[0])
                                return print_config_error(config_name, count, "Close quot expected", NULL);
                        subline++;
                        while(subline[0] != '\0' && isspace(subline[0])) subline++;
                        if('\0' != subline[0] && '#' != subline[0])
                                return print_config_error(config_name, count, "End of line expected", NULL);
                }
                else {
                        return print_config_error(config_name, count, "Unknown config parameter", line);
                }
        }

        if(fclose(config_file) != 0) {
                fprintf(stderr, "Can not close config file %s: %s\n", config_name, strerror(errno));
                return RESULT_UNEXPECTED;
        }

        return RESULT_OK;
}

static int get_revision(void)
{
        int        r;
        const char *begin;

        begin = REVISION;
        while(begin[0] != '\0' && begin[0] != ':') begin++;
        if(begin[0] == '\0') return 0;
        begin++;
        r = (int)strtol(begin, (char**)NULL, 10);

        return r;
}

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

int main(int argc, const char* argv[])
{
        int           soc;
        struct config cfg;

        /* get config */
        switch(read_config(&cfg, argc, argv)) {
                case RESULT_EXIT:       return EXIT_OK;           /* no error but exit */
                case RESULT_ERROR:      return EXIT_USER_ERROR;   /* user error */
                case RESULT_UNEXPECTED: return EXIT_UNEXPECTED;   /* unexpected error */
        }

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

        /* fork and release the console */
        switch(create_child(soc)) {
                case RESULT_EXIT:       return EXIT_OK;           /* no error but exit */
                case RESULT_ERROR:      return EXIT_USER_ERROR;   /* user error */
                case RESULT_UNEXPECTED: return EXIT_UNEXPECTED;   /* unexpected error */
        }

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

        /* fork the second time */
        switch(create_child(soc)) {
                case RESULT_EXIT:       return EXIT_OK;           /* no error but exit */
                case RESULT_ERROR:      return EXIT_USER_ERROR;   /* user error */
                case RESULT_UNEXPECTED: return EXIT_UNEXPECTED;   /* unexpected error */
        }

        /* continue as final child, from now do not use console for error output */
        chdir("/");
        umask(0);
        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;

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

        return EXIT_OK; /* unreachedable */
}