0,0 → 1,470 |
/****************************************************************** |
* RebootDaemon. |
* |
* A daemon which waits on specified TCP port for special string |
* and reboots the computer. |
* |
* Command line: rebootdaemon [--port PORT] [--config CONFIG_FILE] |
* Defaults are port 19 and config /etc/rebootdaemon.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 "RebootDaemon" |
#define VERSION "1.0" |
#define REVISION "$Rev$" |
|
#define DEFAULT_PORT 19 |
#define DEFAULT_CFG_FILE "/etc/rebootdaemon.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 string 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/rebootdaemon.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 */ |
} |
|
Property changes: |
Added: svn:keywords |
+Rev |
\ No newline at end of property |