/* This file is part of NSsync
Copyright (C) 2011-2017 Sergey Poznyakoff
NSsync is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
NSsync is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with NSsync. If not, see . */
#include "nssync.h"
#include
#include
#include "runcap.h"
#include
#include
#include
#include
int lint_mode;
int dry_run_mode;
int preprocess_only;
int debug_level;
char *config_file = SYSCONFDIR "/nssync.conf";
char *slave_status_file;
char *pidfile;
char *cli_pidfile;
int force;
char *tempdir;
char *reload_command = "/usr/sbin/rndc reload";
struct json_value *error_list;
struct json_value *changed_zones;
int check_ns;
int server_mode;
struct grecs_sockaddr *server_addr;
unsigned periodic_timeout = 3600;
unsigned delay_timeout = 30;
int syslog_facility = LOG_DAEMON;
char *syslog_tag;
int foreground;
int syslog_option;
#include "cmdline.h"
void
start_syslog(void)
{
grecs_log_to_stderr = 0;
openlog(program_name, LOG_PID, syslog_facility);
}
void
vdiag(int prio, const char *fmt, va_list ap)
{
if (grecs_log_to_stderr) {
fprintf(stderr, "%s: ", program_name);
vfprintf(stderr, fmt, ap);
fputc('\n', stderr);
} else {
vsyslog(prio, fmt, ap);
}
}
void
verror(const char *fmt, va_list ap)
{
vdiag(LOG_ERR, fmt, ap);
}
void
error(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
verror(fmt, ap);
va_end(ap);
}
void
info_printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vdiag(LOG_INFO, fmt, ap);
va_end(ap);
}
void
debug_printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vdiag(LOG_DEBUG, fmt, ap);
va_end(ap);
}
void
dlz_error(char const *zone, char const *fmt, ...)
{
struct json_value *obj;
char *str = NULL;
size_t sz = 0;
va_list ap;
va_start(ap, fmt);
grecs_vasprintf(&str, &sz, fmt, ap);
va_end(ap);
obj = json_new_object();
json_object_set(obj, "message", json_new_string(str));
free(str);
if (zone)
json_object_set(obj, "zone", json_new_string(zone));
json_array_append(error_list, obj);
}
void
dlz_success(char const *zone)
{
json_array_append(changed_zones, json_new_string(zone));
}
/* Create the directory DIR, eventually creating all intermediate directories
starting from DIR + BASELEN. */
int
create_hierarchy(char *dir, size_t baselen)
{
int rc;
struct stat st;
char *p;
if (stat(dir, &st) == 0) {
if (!S_ISDIR(st.st_mode)) {
error("component %s is not a directory", dir);
return 1;
}
return 0;
} else if (errno != ENOENT) {
error("cannot stat file %s: %s", dir, strerror(errno));
return 1;
}
p = strrchr(dir, '/');
if (p) {
if (p - dir + 1 < baselen) {
error("base directory %s does not exist", dir);
return 1;
}
*p = 0;
}
rc = create_hierarchy(dir, baselen);
if (rc == 0) {
if (p)
*p = '/';
if (mkdir(dir, 0744)) {
error("cannot create directory %s: %s",
dir, strerror(errno));
rc = 1;
}
}
return rc;
}
static int
trycreate(const char *file_name)
{
int rc = 1;
char *dir_name = grecs_strdup(file_name);
char *p = strrchr(dir_name, '/');
if (p) {
size_t len = strlen(bind_working_dir);
if (strncmp(dir_name, bind_working_dir, len) == 0) {
*p = 0;
rc = create_hierarchy(dir_name, len);
}
}
free(dir_name);
return rc;
}
int
copy_file(const char *file_name, FILE *infile, const char *dst_file)
{
int out_fd;
struct stat st;
int rc;
char *buf;
size_t bufsize;
size_t fsize;
if (dry_run_mode)
return 0;
if (fstat(fileno(infile), &st)) {
error("cannot stat source file %s: %s",
file_name, strerror(errno));
return 1;
}
out_fd = open(dst_file, O_WRONLY|O_TRUNC|O_CREAT, 0640);
if (out_fd == -1) {
if (errno == ENOENT) {
if (trycreate(dst_file))
return 1;
out_fd = open(dst_file, O_WRONLY|O_TRUNC|O_CREAT,
0640);
}
if (out_fd == -1) {
error("cannot create destination file %s: %s",
dst_file, strerror(errno));
return 1;
}
}
buf = NULL;
fsize = st.st_size;
for (bufsize = fsize; bufsize > 0 && (buf = malloc (bufsize)) == NULL;
bufsize /= 2)
;
if (bufsize == 0)
grecs_alloc_die ();
rc = 0;
fseeko(infile, 0, SEEK_SET);
while (fsize > 0) {
size_t rest;
size_t rdbytes;
rest = fsize > bufsize ? bufsize : fsize;
rdbytes = fread(buf, 1, rest, infile);
if (rdbytes == -1) {
error("unexpected error reading %s: %s",
file_name, strerror(errno));
rc = 1;
break;
}
rest = write(out_fd, buf, rdbytes);
if (rest == -1) {
error("unexpected error writing to %s: %s",
dst_file, strerror(errno));
rc = 1;
break;
} else if (rest != rdbytes) {
error("short write on %s", dst_file);
rc = 1;
}
fsize -= rdbytes;
}
free(buf);
close(out_fd);
if (rc)
unlink(dst_file);
return rc;
}
/* Move FILE to DST_FILE. If they reside on different devices, use copy_file
+ unlink. */
int
move_file(const char *file, const char *dst_file)
{
int rc = 0;
rc = rename(file, dst_file);
if (rc && errno == ENOENT) {
if (trycreate(dst_file))
return 1;
rc = rename(file, dst_file);
}
if (rc) {
if (errno == EXDEV) {
FILE *fp = fopen(file, "r");
if (!fp) {
error("cannot open %s for reading: %s",
file, strerror(errno));
return 1;
}
rc = copy_file (file, fp, dst_file);
fclose(fp);
if (rc) {
error("cannot copy %s to %s: %s",
file, dst_file, strerror(errno));
rc = 1;
} else if (unlink(file)) {
error("cannot unlink %s: %s",
file, strerror(errno));
}
} else {
error("cannot move %s to %s: %s",
file, dst_file, strerror(errno));
rc = 1;
}
}
return rc;
}
/* Return 0 if files are the same, 1 if they differ and -1 if in trouble
*/
static int
filecmp(FILE *fp1, FILE *fp2)
{
int c1, c2;
while (1) {
c1 = fgetc(fp1);
c2 = fgetc(fp2);
if (c1 == c2) {
if (c1 == EOF)
break;
continue;
}
return 1;
}
return 0;
}
int
compare(const char *file1, const char *file2)
{
int rc;
FILE *fp1, *fp2;
struct stat st1, st2;
if (access(file2, F_OK) && errno == ENOENT) {
debug(2,("destination file %s does not exist", file2));
return 1;
}
if (stat(file1, &st1) == 0 && stat(file2, &st2) == 0) {
if (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino)
return 0;
else if (st1.st_size != st2.st_size)
return 1;
}
fp1 = fopen(file1, "r");
if (!fp1) {
error("can't open file \"%s\": %s", file1, strerror(errno));
return -1;
}
fp2 = fopen(file2, "r");
if (!fp2) {
error("can't open file \"%s\": %s", file2, strerror(errno));
return -1;
}
rc = filecmp(fp1, fp2);
fclose(fp1);
fclose(fp2);
return rc;
}
static void
synchronize(struct nssync *sp)
{
mode_t um;
size_t size = 0;
debug(1, ("processing %s", sp->tag));
if (grecs_asprintf(&sp->temp_file_name, &size,
"%s/%s.tmp", tempdir, sp->tag))
grecs_alloc_die();
um = umask(077);
sp->fp = fopen(sp->temp_file_name, "w+");
umask(um);
if (!sp->fp) {
error("cannot open %s: %s", sp->temp_file_name,
strerror(errno));
return;
}
format_zones(sp);
if (!dry_run_mode)
unlink(sp->temp_file_name);
/* Cleanup */
free(sp->temp_file_name);
sp->temp_file_name = NULL;
}
int
check_slave_status(void)
{
FILE *fp;
char *saved_file = NULL;
char *saved_off;
char *buf = NULL;
size_t size = 0;
char *sql_file, *sql_off;
if (!slave_status_file)
return 0;
if (sql_get_slave_status(&sql_file, &sql_off)) {
unlink(slave_status_file);
return 0;
}
fp = fopen(slave_status_file, "r");
if (fp) {
if (grecs_getline(&buf, &size, fp) > 0) {
buf[strlen(buf)-1] = 0;
saved_file = buf;
saved_off = strchr(saved_file, ' ');
if (saved_off)
*saved_off++ = 0;
}
fclose(fp);
}
if (!force && saved_file && saved_off &&
strcmp(sql_file, saved_file) == 0 &&
strcmp(sql_off, saved_off) == 0) {
debug(1, ("slave status hasn't changed: nothing to do"));
return 1;
}
fp = fopen(slave_status_file, "w");
if (fp) {
fprintf(fp, "%s %s\n", sql_file, sql_off);
fclose(fp);
}
free(sql_file);
free(sql_off);
return 0;
}
void
remove_pidfile()
{
if (pidfile && unlink(pidfile))
error("cannot unlink pidfile `%s': %s",
pidfile, strerror(errno));
}
void
create_pidfile(void)
{
FILE *fp;
if (!pidfile)
return;
fp = fopen(pidfile, "w");
if (!fp) {
error("cannot create pidfile `%s': %s",
pidfile, strerror(errno));
exit(EX_TEMPFAIL);
}
fprintf(fp, "%lu", (unsigned long) getpid());
fclose(fp);
}
/* Check whether pidfile NAME exists and if so, whether its PID is still
active. Exit if it is. */
void
check_pidfile()
{
unsigned long pid;
FILE *fp;
if (!pidfile)
return;
fp = fopen(pidfile, "r");
if (fp) {
if (fscanf(fp, "%lu", &pid) != 1) {
error("cannot get pid from pidfile `%s'", pidfile);
} else {
if (kill(pid, 0) == 0) {
error("%s appears to run with pid %lu. "
"If it does not, remove `%s' and retry.",
program_name, pid, pidfile);
exit(EX_TEMPFAIL);
}
}
fclose(fp);
if (unlink(pidfile)) {
error("cannot unlink pidfile `%s': %s",
pidfile, strerror(errno));
exit(EX_NOPERM);
}
} else if (errno != ENOENT) {
error("cannot open pidfile `%s': %s",
pidfile, strerror(errno));
exit(EX_TEMPFAIL);
}
}
struct linemon_closure
{
char const *prefix;
};
static void
linemon(const char *ptr, size_t len, void *data)
{
struct linemon_closure *clos = data;
if (ptr[len-1] != '\n')
error("[%s]: %*.*s\\",
clos->prefix, (int)len, (int)len, ptr);
else
error("[%s]: %*.*s",
clos->prefix, (int)(len - 1), (int)(len - 1), ptr);
}
int
dns_reload(void)
{
int c;
struct runcap rc;
char *argv[4];
struct linemon_closure closure[2];
debug(1,("about to run %s", reload_command));
if (dry_run_mode)
return 0;
argv[0] = "/bin/sh";
argv[1] = "-c";
argv[2] = reload_command;
argv[3] = NULL;
closure[0].prefix = "STDOUT";
rc.rc_cap[RUNCAP_STDOUT].sc_linemon = linemon;
rc.rc_cap[RUNCAP_STDOUT].sc_monarg = &closure[0];
closure[1].prefix = "STDERR";
rc.rc_cap[RUNCAP_STDERR].sc_linemon = linemon;
rc.rc_cap[RUNCAP_STDERR].sc_monarg = &closure[1];
rc.rc_timeout = 10; /* FIXME */
rc.rc_argv = argv;
c = runcap(&rc,
RCF_STDOUT_LINEMON | RCF_STDERR_LINEMON | RCF_TIMEOUT);
if (c) {
error("can't run \"%s\": %s", reload_command, strerror(errno));
} else if (WIFEXITED(rc.rc_status)) {
int status = WEXITSTATUS(rc.rc_status);
if (status) {
error("command \"%s\" returned %d",
reload_command, status);
c = 1;
}
} else if (WIFSIGNALED(rc.rc_status)) {
error("command \"%s\" terminated on signal %d",
reload_command, WTERMSIG(rc.rc_status));
c = 1;
} else if (WIFSTOPPED(rc.rc_status)) {
error("command \"%s\" got stopped", reload_command);
c = 1;
} else {
error("command \"%s\" terminated with unrecognized status %d",
reload_command, rc.rc_status);
c = 1;
}
runcap_free(&rc);
return c;
}
int
nssync(struct json_value **ret_json)
{
struct grecs_list_entry *ep;
int res = 1;
changed_zones = json_new_array();
error_list = json_new_array();
if (check_slave_status())
res = 0;
else {
sql_connect();
for (ep = synclist->head; ep; ep = ep->next)
synchronize(ep->data);
if (json_array_size(error_list) == 0) {
for (ep = synclist->head; ep; ep = ep->next)
flush_zone_list(ep->data);
filetab_clear();
if (json_array_size(error_list) == 0) {
if (json_array_size(changed_zones) > 0) {
res = dns_reload();
if (res)
dlz_error(NULL,
"reload failed");
} else
res = 0;
}
}
}
if (ret_json) {
struct timeval tv;
struct timezone tz;
struct json_value *json;
json = json_new_object();
json_object_set(json, "success", json_new_bool(!res));
if (json_array_size(changed_zones) > 0)
json_object_set(json, "zones", changed_zones);
if (json_array_size(error_list) > 0)
json_object_set(json, "errors", error_list);
gettimeofday(&tv, &tz);
tv.tv_sec += tz.tz_minuteswest * 60;
json_object_set(json, "timestamp", json_new_number(tv.tv_sec));
*ret_json = json;
} else {
json_value_free(error_list);
json_value_free(changed_zones);
}
error_list = NULL;
changed_zones = NULL;
return res;
}
/* Signal handing */
void
sigfatal(int sig)
{
remove_pidfile();
_exit(0);
}
void
sigign(int sig)
{
}
void
set_signal(int sig, void (*sighandler)(int))
{
struct sigaction act;
act.sa_handler = sighandler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_RESTART;
sigaction(sig, &act, NULL);
}
void
set_signals(int *sigv, void (*sighandler)(int))
{
int i;
for (i = 0; sigv[i]; i++)
set_signal(sigv[i], sighandler);
}
int fatal_signals[] = {
SIGHUP,
SIGINT,
SIGQUIT,
SIGTERM,
0
};
int
single_run(void)
{
if (syslog_option)
start_syslog();
runas();
debug(1,("start up"));
create_pidfile();
if (nssync(NULL)) {
error("exiting due to errors");
return EX_UNAVAILABLE;
}
return 0;
}
int
main(int argc, char **argv)
{
int rc;
config_init();
parse_options(argc, argv);
if (preprocess_only)
exit(grecs_preproc_run(config_file, grecs_preprocessor) ?
EX_CONFIG : 0);
config_parse();
if (cli_pidfile)
pidfile = cli_pidfile;
check_pidfile();
if (check_ns)
get_host_addresses();
if (server_mode) {
set_signals(fatal_signals, sigign);
rc = nssync_server();
} else {
set_signals(fatal_signals, sigfatal);
rc = single_run();
}
debug(1,("shut down"));
remove_pidfile();
return rc;
}