31,6 → 31,8 |
#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> |
66,28 → 68,42 |
/* 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_INTERFACE_1 "-i" |
#define PARAM_INTERFACE_2 "--interface" |
#define PARAM_PORT_1 "-p" |
#define PARAM_PORT_2 "--port" |
#define PARAM_CONFIG_1 "-c" |
#define PARAM_CONFIG_2 "--config" |
#define PARAM_VERSION "--version" |
#define PARAM_INTERFACE "--interface" |
#define PARAM_PORT "--port" |
#define PARAM_CONFIG "--config" |
#define PARAM_PID_FILE "--pid" |
#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; |
}; |
|
static struct config global_cfg; |
static int daemonized = 0; /* we are already a daemon */ |
volatile static int reconfig_needed = 0; /* we have to stop socket listening and reread config */ |
|
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 int get_revision(void); |
static void check_return(int res); |
|
static uint min(uint a, uint b) |
{ |
134,10 → 150,12 |
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)); |
if(errno == EINTR) { /* EINTR might happen on accept(), */ |
if(reconfig_needed) return; |
} |
else { |
syslog(LOG_ERR, "cannot get connection, %s", strerror(errno)); |
} |
continue; |
} |
|
183,6 → 201,22 |
printf("Then run from any other host:\necho -n \"some password\" | nc 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); |
196,18 → 230,33 |
|
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"); |
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; |
} |
|
/* return: 0 - no error, continue; 1 - no error, but exit; 2 - user error, exit; 3 - unexpected error, exit */ |
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; |
int res_cmd_line; |
int res_config_file; |
struct config cmd_cfg; |
struct config file_cfg; |
|
214,32 → 263,100 |
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') { |
fprintf(stderr, "Password is not set\n"); |
return RESULT_ERROR; |
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; |
|
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 */ |
strncpy(cfg->config_file, cmd_cfg.config_file, sizeof(cfg->config_file)); |
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(cmd_cfg.interface.s_addr) |
cfg->interface = cmd_cfg.interface; |
else if(file_cfg.interface.s_addr) |
cfg->interface = file_cfg.interface; |
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(cmd_cfg.port > 0) |
cfg->port = cmd_cfg.port; |
else if(file_cfg.port > 0) |
cfg->port = file_cfg.port; |
else |
cfg->port = DEFAULT_PORT; |
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; |
} |
275,7 → 392,7 |
memset(cfg, 0, sizeof(struct config)); |
|
for(i = 1; i < argc; i++) { |
if(0 == strcmp(argv[i], PARAM_INTERFACE_1) || 0 == strcmp(argv[i], PARAM_INTERFACE_2)) { |
if(0 == strcmp(argv[i], PARAM_INTERFACE)) { |
if(cfg->interface.s_addr) |
return print_cmd_error(argc, argv, "Interface is already set", NULL); |
|
288,7 → 405,7 |
return print_cmd_error(argc, argv, "Interface expected", NULL); |
} |
} |
else if(0 == strcmp(argv[i], PARAM_PORT_1) || 0 == strcmp(argv[i], PARAM_PORT_2)) { |
else if(0 == strcmp(argv[i], PARAM_PORT)) { |
if(cfg->port) |
return print_cmd_error(argc, argv, "Port is already set", NULL); |
|
303,21 → 420,35 |
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)) { |
else if(0 == strcmp(argv[i], PARAM_CONFIG)) { |
if(cfg->config_file[0] != '\0') |
return print_cmd_error(argc, argv, "Config file is already set", NULL); |
return print_cmd_error(argc, argv, "Config file name is already set", NULL); |
|
if(++i < argc) { |
strncpy(cfg->config_file, argv[i], MAX_CONFIG_LINE); |
if(cfg->config_file[MAX_CONFIG_LINE - 1] != '\0') |
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 expected", NULL); |
return print_cmd_error(argc, argv, "Config file name expected", NULL); |
} |
} |
else if(0 == strcmp(argv[i], PARAM_VERSION_1) || 0 == strcmp(argv[i], PARAM_VERSION_2)) { |
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_VERSION)) { |
print_version(); |
return RESULT_EXIT; |
} |
360,18 → 491,35 |
return RESULT_OK; |
} |
|
static int extract_quoted(const char *config_name, const int count, char **subline, char *string, uint len) |
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]) |
return print_config_error(config_name, count, "Open quot expected", NULL); |
(*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(cur_len < len && *subline[0] != '\0' && *subline[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)++; |
379,9 → 527,11 |
} |
|
string[0] = '\0'; |
if('"' != *subline[0]) |
return print_config_error(config_name, count, "Close quot expected", NULL); |
(*subline)++; |
if(quot) { |
if('"' != *subline[0]) |
return print_config_error(config_name, count, "Close quot expected", NULL); |
(*subline)++; |
} |
|
return RESULT_OK; |
} |
400,10 → 550,8 |
memset(cfg, 0, sizeof(struct config)); |
|
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; |
} |
if(!config_file) |
return print_error("Can not open config file", strerror(errno)); |
|
count = 0; |
while(fgets(buf, sizeof(buf), config_file)) { |
450,23 → 598,30 |
if((res = validate_equal_sign(config_name, count, line, |
sizeof(CONFIG_PASSWORD), &subline)) != RESULT_OK) return res; |
|
if((res = extract_quoted(config_name, count, &subline, |
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)) { |
fprintf(stderr, "Config file %s reading failed\n", config_name); |
print_error("Config file reading failed", strerror(errno)); |
} |
|
if(fclose(config_file) != 0) { |
fprintf(stderr, "Can not close config file %s: %s\n", config_name, strerror(errno)); |
return RESULT_UNEXPECTED; |
} |
if(fclose(config_file) != 0) |
return print_error("Can not close config file", strerror(errno)); |
|
return RESULT_OK; |
} |
485,6 → 640,40 |
return r; |
} |
|
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; |
499,6 → 688,7 |
fprintf(stderr, "Cannot close socket: %s\n", strerror(errno)); |
return RESULT_UNEXPECTED; /* an unexpected error */ |
} |
|
return RESULT_EXIT; /* we are the parent */ |
} |
else { |
515,30 → 705,104 |
return RESULT_OK; |
} |
|
static void reconfig_signal(void) |
{ |
syslog(LOG_INFO, "reconfig"); |
reconfig_needed = 1; |
} |
|
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 quit_signal(void) |
{ |
delete_pid_file(); |
syslog(LOG_WARNING, "quit"); |
exit(0); |
} |
|
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; |
struct config cfg; |
int soc; |
int reinit_needed; |
|
/* 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 */ |
} |
check_return(read_config(&global_cfg, argc, argv)); |
|
/* try to listen the port */ |
if((soc = establish(&cfg)) < 0) { |
fprintf(stderr, "Cannot listen to port %i\n", cfg.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 */ |
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 */ |
} |
check_return(create_child(soc)); |
|
/* continue as first child */ |
if(setsid() == -1) { |
547,22 → 811,42 |
} |
|
/* 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 */ |
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; |
|
/* continue as final child, from now do not use console for error output */ |
chdir("/"); |
umask(0); |
/* 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; |
|
syslog(LOG_INFO, "listen on %d", cfg.port); |
listen_socket(&cfg, soc); |
for(;;) { |
syslog(LOG_INFO, "listen on %d", global_cfg.port); |
listen_socket(&global_cfg, soc); |
|
return EXIT_OK; /* unreachedable */ |
if(reconfig_needed) { |
reconfig_needed = 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); |
} |
} |
} |
} |
|
return EXIT_OK; /* unreacheable */ |
} |
|