#include "defs.h"
static void usage(const char *av0);
static void dotest(const char *target);
static void add_server(const char *target);
static void process_config_file(const char *path);
static pid_t check_pid(void);
static void set_pid(const char *av0);
static void sigint_handler(int signo __unused);
static struct server_info **servers;
static int nservers;
static int maxservers;
int daemon_opt = 1;
int debug_opt = 0;
int debug_level = -1;
int quickset_opt = 0;
int no_update_opt = 0;
int min_sleep_opt = 5;
int nom_sleep_opt = 300;
int max_sleep_opt = 1800;
int family = PF_UNSPEC;
double insane_deviation = 0.5;
const char *config_opt;
const char *pid_opt = "/var/run/dntpd.pid";
int
main(int ac, char **av)
{
int test_opt = 0;
pid_t pid;
int ch;
int i;
srandomdev();
while ((ch = getopt(ac, av, "46df:i:l:np:qstFL:QST:")) != -1) {
switch(ch) {
case '4':
family = PF_INET;
break;
case '6':
family = PF_INET6;
break;
case 'd':
debug_opt = 1;
daemon_opt = 0;
if (debug_level < 0)
debug_level = 99;
if (config_opt == NULL)
config_opt = "/dev/null";
break;
case 'p':
pid_opt = optarg;
break;
case 'f':
config_opt = optarg;
break;
case 'i':
insane_deviation = strtod(optarg, NULL);
break;
case 'l':
debug_level = strtol(optarg, NULL, 0);
break;
case 'n':
no_update_opt = 1;
break;
case 'q':
debug_level = 0;
break;
case 's':
quickset_opt = 1;
break;
case 'S':
quickset_opt = 0;
break;
case 't':
test_opt = 1;
debug_opt = 1;
daemon_opt = 0;
if (debug_level < 0)
debug_level = 99;
if (config_opt == NULL)
config_opt = "/dev/null";
break;
case 'F':
daemon_opt = 0;
break;
case 'L':
max_sleep_opt = strtol(optarg, NULL, 0);
break;
case 'T':
nom_sleep_opt = strtol(optarg, NULL, 0);
if (nom_sleep_opt < 1) {
fprintf(stderr, "Warning: nominal poll interval too small, "
"limiting to 1 second\n");
nom_sleep_opt = 1;
}
if (nom_sleep_opt > 24 * 60 * 60) {
fprintf(stderr, "Warning: nominal poll interval too large, "
"limiting to 24 hours\n");
nom_sleep_opt = 24 * 60 * 60;
}
if (min_sleep_opt > nom_sleep_opt)
min_sleep_opt = nom_sleep_opt;
if (max_sleep_opt < nom_sleep_opt * 5)
max_sleep_opt = nom_sleep_opt * 5;
break;
case 'Q':
if ((pid = check_pid()) != 0) {
fprintf(stderr, "%s: killing old daemon\n", av[0]);
kill(pid, SIGINT);
usleep(100000);
if (check_pid())
sleep(1);
if (check_pid())
sleep(9);
if (check_pid()) {
fprintf(stderr, "%s: Unable to kill running daemon.\n", av[0]);
} else {
fprintf(stderr, "%s: Running daemon has been terminated.\n", av[0]);
}
} else {
fprintf(stderr, "%s: There is no daemon running to kill.\n", av[0]);
}
exit(0);
break;
case 'h':
default:
usage(av[0]);
}
}
if (min_sleep_opt > max_sleep_opt)
min_sleep_opt = max_sleep_opt;
if (nom_sleep_opt > max_sleep_opt)
nom_sleep_opt = max_sleep_opt;
if (config_opt == NULL) {
if (optind != ac)
config_opt = "/dev/null";
else
config_opt = "/etc/dntpd.conf";
}
if (debug_level < 0)
debug_level = 1;
process_config_file(config_opt);
if (debug_opt == 0)
openlog("dntpd", LOG_CONS|LOG_PID, LOG_DAEMON);
if (test_opt) {
if (optind != ac - 1)
usage(av[0]);
dotest(av[optind]);
}
for (i = optind; i < ac; ++i) {
add_server(av[i]);
}
if (nservers == 0) {
usage(av[0]);
}
if (daemon_opt) {
if ((pid = check_pid()) != 0) {
logerrstr("%s: NOTE: killing old daemon and starting a new one",
av[0]);
kill(pid, SIGINT);
usleep(100000);
if (check_pid())
sleep(1);
if (check_pid())
sleep(9);
if (check_pid()) {
logerrstr("%s: Unable to kill running daemon, exiting", av[0]);
exit(1);
}
}
daemon(0, 0);
} else if (check_pid() != 0) {
logerrstr("%s: A background dntpd is running, you must kill it first",
av[0]);
exit(1);
}
if (debug_opt == 0) {
log_stderr = 0;
set_pid(av[0]);
signal(SIGINT, sigint_handler);
logdebug(0, "dntpd version %s started\n", DNTPD_VERSION);
}
sysntp_clear_alternative_corrections();
client_init();
client_check_duplicate_ips(servers, nservers);
client_main(servers, nservers);
return(0);
}
static
void
usage(const char *av0)
{
fprintf(stderr, "%s [-dnqstFSQ] [-f config_file] [-l log_level] [-T poll_interval] [-L poll_limit] [additional_targets]\n", av0);
fprintf(stderr,
"\t-d\tDebugging mode, implies -F, -l 99, and logs to stderr\n"
"\t-f file\tSpecify the config file (/etc/dntpd.conf)\n"
"\t-l int\tSet log level (0-4), default 1\n"
"\t-n\tNo-update mode. No offset or frequency corrections are made\n"
"\t-q\tQuiet-mode, same as -L 0\n"
"\t-s\tSet the time immediately on startup\n"
"\t-t\tTest mode, implies -F, -l 99, -n, logs to stderr\n"
"\t-F\tRun in foreground (log still goes to syslog)\n"
"\t-L int\tMaximum polling interval\n"
"\t-S\tDo not set the time immediately on startup\n"
"\t-T int\tNominal polling interval\n"
"\t-Q\tTerminate any running background daemon\n"
"\n"
"\t\tNOTE: in debug and test modes -f must be specified if\n"
"\t\tyou want to use a config file.\n"
);
exit(1);
}
static
void
dotest(const char *target)
{
struct server_info info;
bzero(&info, sizeof(info));
info.sam = (struct sockaddr *)&info.sam_st;
info.fd = udp_socket(target, 123, info.sam, LOG_DNS_ERROR);
if (info.fd < 0) {
logerrstr("unable to create UDP socket for %s", target);
return;
}
info.target = strdup(target);
client_init();
fprintf(stderr,
"Will run %d-second polls until interrupted.\n", nom_sleep_opt);
for (;;) {
client_poll(&info, nom_sleep_opt, 1);
sleep(nom_sleep_opt);
}
}
static char *
myaddr2ascii(struct sockaddr *sa)
{
static char str[INET6_ADDRSTRLEN];
struct sockaddr_in *soin;
struct sockaddr_in6 *sin6;
switch (sa->sa_family) {
case AF_INET:
soin = (struct sockaddr_in *) sa;
inet_ntop(AF_INET, &soin->sin_addr, str, sizeof(str));
break;
case AF_INET6:
sin6 = (struct sockaddr_in6 *) sa;
inet_ntop(AF_INET6, &sin6->sin6_addr, str, sizeof(str));
break;
}
return (str);
}
static void
add_server(const char *target)
{
server_info_t info;
if (nservers == maxservers) {
maxservers += 16;
servers = realloc(servers, maxservers * sizeof(server_info_t));
assert(servers != NULL);
}
info = malloc(sizeof(struct server_info));
servers[nservers] = info;
bzero(info, sizeof(struct server_info));
info->sam = (struct sockaddr *)&info->sam_st;
info->target = strdup(target);
info->fd = -1;
info->server_state = -1;
++nservers;
}
void
disconnect_server(server_info_t info)
{
if (info->fd >= 0)
close(info->fd);
info->fd = -1;
if (info->ipstr) {
free(info->ipstr);
info->ipstr = NULL;
}
}
void
reconnect_server(server_info_t info)
{
const char *ipstr;
dns_error_policy_t policy;
policy = IGNORE_DNS_ERROR;
if (info->fd >= 0) {
close(info->fd);
policy = LOG_DNS_ERROR;
}
if (info->ipstr) {
free(info->ipstr);
info->ipstr = NULL;
}
info->sam = (struct sockaddr *)&info->sam_st;
info->fd = udp_socket(info->target, 123, info->sam, policy);
if (info->fd >= 0) {
ipstr = myaddr2ascii(info->sam);
info->ipstr = strdup(ipstr);
}
}
static void
process_config_file(const char *path)
{
const char *ws = " \t\r\n";
char buf[1024];
char *keyword;
char *data;
int line;
FILE *fi;
if ((fi = fopen(path, "r")) != NULL) {
line = 1;
while (fgets(buf, sizeof(buf), fi) != NULL) {
if (strchr(buf, '#'))
*strchr(buf, '#') = 0;
if ((keyword = strtok(buf, ws)) != NULL) {
data = strtok(NULL, ws);
if (strcmp(keyword, "server") == 0) {
if (data == NULL) {
logerr("%s:%d server missing host specification",
path, line);
} else {
add_server(data);
}
} else {
logerr("%s:%d unknown keyword %s", path, line, keyword);
}
}
++line;
}
fclose(fi);
} else {
logerr("Unable to open %s", path);
exit(1);
}
}
static
pid_t
check_pid(void)
{
char buf[32];
pid_t pid;
FILE *fi;
pid = 0;
if ((fi = fopen(pid_opt, "r")) != NULL) {
if (fgets(buf, sizeof(buf), fi) != NULL) {
pid = strtol(buf, NULL, 0);
if (kill(pid, 0) != 0)
pid = 0;
}
fclose(fi);
}
return(pid);
}
static
void
set_pid(const char *av0)
{
pid_t pid;
FILE *fo;
pid = getpid();
if ((fo = fopen(pid_opt, "w")) != NULL) {
fprintf(fo, "%d\n", (int)pid);
fclose(fo);
} else {
logerr("%s: Unable to create %s, continuing anyway.", av0, pid_opt);
}
}
static
void
sigint_handler(int signo __unused)
{
remove(pid_opt);
logdebug(0, "dntpd version %s stopped\n", DNTPD_VERSION);
exit(0);
}