0,0 → 1,477 |
/****************************************************************** |
* rebootd. Copyleft 2005 Anatoli Klassen |
******************************************************************/ |
|
#include <stdlib.h> |
#include <stdio.h> |
#include <string.h> |
#include <errno.h> |
#include <syslog.h> |
#include <ctype.h> |
#include <sys/stat.h> |
#include <netinet/in.h> |
#include <arpa/inet.h> |
|
#include "common.h" |
#include "config.h" |
|
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 uint min(uint a, uint b) |
{ |
return (a < b) ? a : b; |
} |
|
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 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; |
} |
|
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; |
} |
|
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; |
} |
|