1148 lines
27 KiB
C
1148 lines
27 KiB
C
#if !defined(lint) && !defined(SABER)
|
|
static const char rcsid[] = "$Id: ns_ctl.c,v 8.47 2002/06/24 07:11:07 marka Exp $";
|
|
#endif /* not lint */
|
|
|
|
/*
|
|
* Copyright (c) 1997-2000 by Internet Software Consortium.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
|
|
* ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
|
|
* CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
|
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
|
|
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
|
|
* ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
/* Extern. */
|
|
|
|
#include "port_before.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <arpa/nameser.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <resolv.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
|
|
#include <isc/eventlib.h>
|
|
#include <isc/logging.h>
|
|
#include <isc/memcluster.h>
|
|
|
|
#include "port_after.h"
|
|
|
|
#include "named.h"
|
|
|
|
/* Defs. */
|
|
|
|
#define CONTROL_FOUND 0x0001 /* for mark and sweep. */
|
|
#define MAX_STR_LEN 500
|
|
|
|
struct control {
|
|
LINK(struct control) link;
|
|
enum { t_dead, t_inet, t_unix } type;
|
|
struct ctl_sctx *sctx;
|
|
u_int flags;
|
|
union {
|
|
struct {
|
|
struct sockaddr_in in;
|
|
ip_match_list allow;
|
|
} v_inet;
|
|
#ifndef NO_SOCKADDR_UN
|
|
struct {
|
|
struct sockaddr_un un;
|
|
mode_t mode;
|
|
uid_t owner;
|
|
gid_t group;
|
|
} v_unix;
|
|
#endif
|
|
} var;
|
|
};
|
|
|
|
/* Forward. */
|
|
|
|
static struct ctl_sctx *mksrvr(control, const struct sockaddr *, size_t);
|
|
static control new_control(void);
|
|
static void free_control(controls *, control);
|
|
static void free_controls(controls *);
|
|
static int match_control(control, control);
|
|
static control find_control(controls, control);
|
|
static void propagate_changes(const control, control);
|
|
static void install(control);
|
|
static void install_inet(control);
|
|
static void install_unix(control);
|
|
static void logger(enum ctl_severity, const char *fmt, ...)
|
|
ISC_FORMAT_PRINTF(2,3);
|
|
static void verb_connect(struct ctl_sctx *, struct ctl_sess *,
|
|
const struct ctl_verb *,
|
|
const char *, u_int, const void *, void *);
|
|
static void verb_getpid(struct ctl_sctx *, struct ctl_sess *,
|
|
const struct ctl_verb *,
|
|
const char *, u_int, const void *, void *);
|
|
static void getpid_closure(struct ctl_sctx *, struct ctl_sess *,
|
|
void *);
|
|
static void verb_status(struct ctl_sctx *, struct ctl_sess *,
|
|
const struct ctl_verb *,
|
|
const char *, u_int, const void *, void *);
|
|
static void status_closure(struct ctl_sctx *, struct ctl_sess *,
|
|
void *);
|
|
static void verb_stop(struct ctl_sctx *, struct ctl_sess *,
|
|
const struct ctl_verb *,
|
|
const char *, u_int, const void *, void *);
|
|
static void verb_exec(struct ctl_sctx *, struct ctl_sess *,
|
|
const struct ctl_verb *,
|
|
const char *, u_int, const void *, void *);
|
|
static void exec_closure(struct ctl_sctx *, struct ctl_sess *,
|
|
void *);
|
|
static void verb_reload(struct ctl_sctx *, struct ctl_sess *,
|
|
const struct ctl_verb *,
|
|
const char *, u_int, const void *, void *);
|
|
static void verb_reconfig(struct ctl_sctx *, struct ctl_sess *,
|
|
const struct ctl_verb *,
|
|
const char *, u_int, const void *, void *);
|
|
static void verb_dumpdb(struct ctl_sctx *, struct ctl_sess *,
|
|
const struct ctl_verb *,
|
|
const char *, u_int, const void *, void *);
|
|
static void verb_stats(struct ctl_sctx *, struct ctl_sess *,
|
|
const struct ctl_verb *,
|
|
const char *, u_int, const void *, void *);
|
|
static void verb_trace(struct ctl_sctx *, struct ctl_sess *,
|
|
const struct ctl_verb *,
|
|
const char *, u_int, const void *, void *);
|
|
static void trace_closure(struct ctl_sctx *, struct ctl_sess *,
|
|
void *);
|
|
static void verb_notrace(struct ctl_sctx *, struct ctl_sess *,
|
|
const struct ctl_verb *,
|
|
const char *, u_int, const void *, void *);
|
|
static void verb_querylog(struct ctl_sctx *, struct ctl_sess *,
|
|
const struct ctl_verb *,
|
|
const char *, u_int, const void *, void *);
|
|
static void verb_help(struct ctl_sctx *, struct ctl_sess *,
|
|
const struct ctl_verb *,
|
|
const char *, u_int, const void *, void *);
|
|
static void verb_quit(struct ctl_sctx *, struct ctl_sess *,
|
|
const struct ctl_verb *,
|
|
const char *, u_int, const void *, void *);
|
|
static void verb_args(struct ctl_sctx *, struct ctl_sess *,
|
|
const struct ctl_verb *,
|
|
const char *, u_int, const void *, void *);
|
|
|
|
/* Private data. */
|
|
|
|
static controls server_controls;
|
|
|
|
static struct ctl_verb verbs[] = {
|
|
{ "", verb_connect, ""},
|
|
{ "getpid", verb_getpid, "getpid"},
|
|
{ "status", verb_status, "status"},
|
|
{ "stop", verb_stop, "stop"},
|
|
{ "exec", verb_exec, "exec"},
|
|
{ "reload", verb_reload, "reload [zone] ..."},
|
|
{ "reconfig", verb_reconfig, "reconfig [-noexpired] (just sees new/gone zones)"},
|
|
{ "dumpdb", verb_dumpdb, "dumpdb"},
|
|
{ "stats", verb_stats, "stats [clear]"},
|
|
{ "trace", verb_trace, "trace [level]"},
|
|
{ "notrace", verb_notrace, "notrace"},
|
|
{ "querylog", verb_querylog, "querylog"},
|
|
{ "qrylog", verb_querylog, "qrylog"},
|
|
{ "help", verb_help, "help"},
|
|
{ "quit", verb_quit, "quit"},
|
|
{ "args", verb_args, "args"},
|
|
{ NULL, NULL, NULL}
|
|
};
|
|
|
|
/* Public functions. */
|
|
|
|
void
|
|
ns_ctl_initialize(void) {
|
|
INIT_LIST(server_controls);
|
|
}
|
|
|
|
void
|
|
ns_ctl_shutdown(void) {
|
|
if (!EMPTY(server_controls))
|
|
free_controls(&server_controls);
|
|
}
|
|
|
|
void
|
|
ns_ctl_defaults(controls *list) {
|
|
#ifdef NO_SOCKADDR_UN
|
|
struct in_addr saddr;
|
|
ip_match_list iml;
|
|
ip_match_element ime;
|
|
|
|
/*
|
|
* If the operating system does not support local domain sockets,
|
|
* connect with ndc on 127.0.0.1, port 101, and only allow
|
|
* connections from 127.0.0.1.
|
|
*/
|
|
saddr.s_addr = htonl (INADDR_LOOPBACK);
|
|
iml = new_ip_match_list();
|
|
ime = new_ip_match_pattern(saddr, 32);
|
|
add_to_ip_match_list(iml, ime);
|
|
|
|
ns_ctl_add(list, ns_ctl_new_inet(saddr, htons (101), iml));
|
|
#else
|
|
#ifdef NEED_SECURE_DIRECTORY
|
|
ns_ctl_add(list, ns_ctl_new_unix(_PATH_NDCSOCK, 0700, 0, 0));
|
|
#else
|
|
ns_ctl_add(list, ns_ctl_new_unix(_PATH_NDCSOCK, 0600, 0, 0));
|
|
#endif
|
|
#endif /*NO_SOCKADDR_UN*/
|
|
}
|
|
|
|
void
|
|
ns_ctl_add(controls *list, control new) {
|
|
if (!find_control(*list, new))
|
|
APPEND(*list, new, link);
|
|
}
|
|
|
|
control
|
|
ns_ctl_new_inet(struct in_addr saddr, u_int sport, ip_match_list allow) {
|
|
control new = new_control();
|
|
|
|
INIT_LINK(new, link);
|
|
new->type = t_inet;
|
|
memset(&new->var.v_inet.in, 0, sizeof new->var.v_inet.in);
|
|
new->var.v_inet.in.sin_family = AF_INET;
|
|
new->var.v_inet.in.sin_addr = saddr;
|
|
new->var.v_inet.in.sin_port = sport;
|
|
new->var.v_inet.allow = allow;
|
|
return (new);
|
|
}
|
|
|
|
#ifndef NO_SOCKADDR_UN
|
|
control
|
|
ns_ctl_new_unix(const char *path, mode_t mode, uid_t owner, gid_t group) {
|
|
control new = new_control();
|
|
|
|
INIT_LINK(new, link);
|
|
new->type = t_unix;
|
|
memset(&new->var.v_unix.un, 0, sizeof new->var.v_unix.un);
|
|
new->var.v_unix.un.sun_family = AF_UNIX;
|
|
strncpy(new->var.v_unix.un.sun_path, path,
|
|
sizeof new->var.v_unix.un.sun_path - 1);
|
|
new->var.v_unix.mode = mode;
|
|
new->var.v_unix.owner = owner;
|
|
new->var.v_unix.group = group;
|
|
return (new);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
ns_ctl_install(controls *new) {
|
|
control ctl, old, next;
|
|
|
|
/* Find all the controls which aren't new or deleted. */
|
|
for (ctl = HEAD(server_controls); ctl != NULL; ctl = NEXT(ctl, link))
|
|
ctl->flags &= ~CONTROL_FOUND;
|
|
for (ctl = HEAD(*new); ctl != NULL; ctl = next) {
|
|
next = NEXT(ctl, link);
|
|
old = find_control(server_controls, ctl);
|
|
if (old != NULL) {
|
|
old->flags |= CONTROL_FOUND;
|
|
propagate_changes(ctl, old);
|
|
if (old->sctx == NULL)
|
|
free_control(&server_controls, old);
|
|
free_control(new, ctl);
|
|
}
|
|
}
|
|
|
|
/* Destroy any old controls which weren't found. */
|
|
for (ctl = HEAD(server_controls); ctl != NULL; ctl = next) {
|
|
next = NEXT(ctl, link);
|
|
if ((ctl->flags & CONTROL_FOUND) == 0)
|
|
free_control(&server_controls, ctl);
|
|
}
|
|
|
|
/* Add any new controls which were found. */
|
|
for (ctl = HEAD(*new); ctl != NULL; ctl = next) {
|
|
next = NEXT(ctl, link);
|
|
UNLINK(*new, ctl, link);
|
|
APPEND(server_controls, ctl, link);
|
|
install(ctl);
|
|
if (ctl->sctx == NULL)
|
|
free_control(&server_controls, ctl);
|
|
}
|
|
}
|
|
|
|
/* Private functions. */
|
|
|
|
static struct ctl_sctx *
|
|
mksrvr(control ctl, const struct sockaddr *sa, size_t salen) {
|
|
return (ctl_server(ev, sa, salen, verbs, 500, 222,
|
|
600, 5, 10, logger, ctl));
|
|
}
|
|
|
|
static control
|
|
new_control(void) {
|
|
control new = memget(sizeof *new);
|
|
|
|
if (new == NULL)
|
|
panic("memget failed in new_control()", NULL);
|
|
new->type = t_dead;
|
|
new->sctx = NULL;
|
|
return (new);
|
|
}
|
|
|
|
static void
|
|
free_control(controls *list, control this) {
|
|
int was_live = 0;
|
|
struct stat sb;
|
|
|
|
if (this->sctx != NULL) {
|
|
ctl_endserver(this->sctx);
|
|
this->sctx = NULL;
|
|
was_live = 1;
|
|
}
|
|
switch (this->type) {
|
|
case t_inet:
|
|
if (this->var.v_inet.allow != NULL) {
|
|
free_ip_match_list(this->var.v_inet.allow);
|
|
this->var.v_inet.allow = NULL;
|
|
}
|
|
break;
|
|
#ifndef NO_SOCKADDR_UN
|
|
case t_unix:
|
|
/* XXX Race condition. */
|
|
if (was_live &&
|
|
stat(this->var.v_unix.un.sun_path, &sb) == 0 &&
|
|
(S_ISSOCK(sb.st_mode) || S_ISFIFO(sb.st_mode))) {
|
|
/* XXX Race condition. */
|
|
unlink(this->var.v_unix.un.sun_path);
|
|
}
|
|
break;
|
|
#endif
|
|
default:
|
|
panic("impossible type in free_control", NULL);
|
|
/* NOTREACHED */
|
|
}
|
|
UNLINK(*list, this, link);
|
|
memput(this, sizeof *this);
|
|
}
|
|
|
|
static void
|
|
free_controls(controls *list) {
|
|
control ctl, next;
|
|
|
|
for (ctl = HEAD(*list); ctl != NULL; ctl = next) {
|
|
next = NEXT(ctl, link);
|
|
free_control(list, ctl);
|
|
}
|
|
INIT_LIST(*list);
|
|
}
|
|
|
|
static int
|
|
match_control(control l, control r) {
|
|
int match = 1;
|
|
|
|
if (l->type != r->type)
|
|
match = 0;
|
|
else
|
|
switch (l->type) {
|
|
case t_inet:
|
|
if (l->var.v_inet.in.sin_family !=
|
|
r->var.v_inet.in.sin_family ||
|
|
l->var.v_inet.in.sin_port !=
|
|
r->var.v_inet.in.sin_port ||
|
|
l->var.v_inet.in.sin_addr.s_addr !=
|
|
r->var.v_inet.in.sin_addr.s_addr)
|
|
match = 0;
|
|
break;
|
|
#ifndef NO_SOCKADDR_UN
|
|
case t_unix:
|
|
if (l->var.v_unix.un.sun_family !=
|
|
r->var.v_unix.un.sun_family ||
|
|
strcmp(l->var.v_unix.un.sun_path,
|
|
r->var.v_unix.un.sun_path) != 0)
|
|
match = 0;
|
|
break;
|
|
#endif
|
|
default:
|
|
panic("impossible type in match_control", NULL);
|
|
/* NOTREACHED */
|
|
}
|
|
ns_debug(ns_log_config, 20, "match_control(): %d", match);
|
|
return (match);
|
|
}
|
|
|
|
static control
|
|
find_control(controls list, control new) {
|
|
control ctl;
|
|
|
|
for (ctl = HEAD(list); ctl != NULL; ctl = NEXT(ctl, link))
|
|
if (match_control(ctl, new))
|
|
return (ctl);
|
|
return (NULL);
|
|
}
|
|
|
|
static void
|
|
propagate_changes(const control diff, control base) {
|
|
int need_install = 0;
|
|
|
|
switch (base->type) {
|
|
case t_inet:
|
|
if (base->var.v_inet.allow != NULL)
|
|
free_ip_match_list(base->var.v_inet.allow);
|
|
base->var.v_inet.allow = diff->var.v_inet.allow;
|
|
diff->var.v_inet.allow = NULL;
|
|
need_install++;
|
|
break;
|
|
#ifndef NO_SOCKADDR_UN
|
|
case t_unix:
|
|
if (base->var.v_unix.mode != diff->var.v_unix.mode) {
|
|
base->var.v_unix.mode = diff->var.v_unix.mode;
|
|
need_install++;
|
|
}
|
|
if (base->var.v_unix.owner != diff->var.v_unix.owner) {
|
|
base->var.v_unix.owner = diff->var.v_unix.owner;
|
|
need_install++;
|
|
}
|
|
if (base->var.v_unix.group != diff->var.v_unix.group) {
|
|
base->var.v_unix.group = diff->var.v_unix.group;
|
|
need_install++;
|
|
}
|
|
break;
|
|
#endif
|
|
default:
|
|
panic("impossible type in ns_ctl::propagate_changes", NULL);
|
|
/* NOTREACHED */
|
|
}
|
|
if (need_install)
|
|
install(base);
|
|
}
|
|
|
|
static void
|
|
install(control ctl) {
|
|
switch (ctl->type) {
|
|
case t_inet:
|
|
install_inet(ctl);
|
|
break;
|
|
#ifndef NO_SOCKADDR_UN
|
|
case t_unix:
|
|
install_unix(ctl);
|
|
break;
|
|
#endif
|
|
default:
|
|
panic("impossible type in ns_ctl::install", NULL);
|
|
/* NOTREACHED */
|
|
}
|
|
}
|
|
|
|
static void
|
|
install_inet(control ctl) {
|
|
if (ctl->sctx == NULL) {
|
|
ctl->sctx = mksrvr(ctl,
|
|
(struct sockaddr *)&ctl->var.v_inet.in,
|
|
sizeof ctl->var.v_inet.in);
|
|
}
|
|
}
|
|
|
|
#ifndef NO_SOCKADDR_UN
|
|
/*
|
|
* Unattach an old unix domain socket if it exists.
|
|
*/
|
|
static void
|
|
unattach(control ctl) {
|
|
int s;
|
|
struct stat sb;
|
|
|
|
s = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (s < 0) {
|
|
ns_warning(ns_log_config,
|
|
"unix control \"%s\" socket failed: %s",
|
|
ctl->var.v_unix.un.sun_path,
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
|
|
if (stat(ctl->var.v_unix.un.sun_path, &sb) < 0) {
|
|
switch (errno) {
|
|
case ENOENT: /* We exited cleanly last time */
|
|
break;
|
|
default:
|
|
ns_warning(ns_log_config,
|
|
"unix control \"%s\" stat failed: %s",
|
|
ctl->var.v_unix.un.sun_path,
|
|
strerror(errno));
|
|
break;
|
|
}
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(S_ISSOCK(sb.st_mode) || S_ISFIFO(sb.st_mode))) {
|
|
ns_warning(ns_log_config, "unix control \"%s\" not socket",
|
|
ctl->var.v_unix.un.sun_path);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (connect(s, (struct sockaddr *)&ctl->var.v_unix.un,
|
|
sizeof ctl->var.v_unix.un) < 0) {
|
|
switch (errno) {
|
|
case ECONNREFUSED:
|
|
case ECONNRESET:
|
|
if (unlink(ctl->var.v_unix.un.sun_path) < 0)
|
|
ns_warning(ns_log_config,
|
|
"unix control \"%s\" unlink failed: %s",
|
|
ctl->var.v_unix.un.sun_path,
|
|
strerror(errno));
|
|
break;
|
|
default:
|
|
ns_warning(ns_log_config,
|
|
"unix control \"%s\" connect failed: %s",
|
|
ctl->var.v_unix.un.sun_path,
|
|
strerror(errno));
|
|
break;
|
|
}
|
|
}
|
|
cleanup:
|
|
close(s);
|
|
}
|
|
|
|
static void
|
|
install_unix(control ctl) {
|
|
char *path;
|
|
#ifdef NEED_SECURE_DIRECTORY
|
|
char *slash;
|
|
|
|
path = savestr(ctl->var.v_unix.un.sun_path, 1);
|
|
|
|
slash = strrchr(path, '/');
|
|
if (slash != NULL) {
|
|
if (slash != path)
|
|
*slash = '\0';
|
|
else {
|
|
(void)freestr(path);
|
|
path = savestr("/", 1);
|
|
}
|
|
} else {
|
|
(void)freestr(path);
|
|
path = savestr(".", 1);
|
|
}
|
|
if (mkdir(path, ctl->var.v_unix.mode) < 0) {
|
|
if (errno != EEXIST) {
|
|
ns_warning(ns_log_config,
|
|
"unix control \"%s\" mkdir failed: %s",
|
|
path, strerror(errno));
|
|
}
|
|
}
|
|
#else
|
|
path = ctl->var.v_unix.un.sun_path;
|
|
#endif
|
|
|
|
if (ctl->sctx == NULL) {
|
|
unattach(ctl);
|
|
ctl->sctx = mksrvr(ctl,
|
|
(struct sockaddr *)&ctl->var.v_unix.un,
|
|
sizeof ctl->var.v_unix.un);
|
|
}
|
|
if (ctl->sctx != NULL) {
|
|
/* XXX Race condition. */
|
|
if (chmod(path, ctl->var.v_unix.mode) < 0) {
|
|
ns_warning(ns_log_config, "chmod(\"%s\", 0%03o): %s",
|
|
ctl->var.v_unix.un.sun_path,
|
|
ctl->var.v_unix.mode,
|
|
strerror(errno));
|
|
}
|
|
if (chown(path, ctl->var.v_unix.owner,
|
|
ctl->var.v_unix.group) < 0) {
|
|
ns_warning(ns_log_config, "chown(\"%s\", %d, %d): %s",
|
|
ctl->var.v_unix.un.sun_path,
|
|
ctl->var.v_unix.owner,
|
|
ctl->var.v_unix.group,
|
|
strerror(errno));
|
|
}
|
|
}
|
|
#ifdef NEED_SECURE_DIRECTORY
|
|
(void)freestr(path);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
logger(enum ctl_severity ctlsev, const char *format, ...) {
|
|
va_list args;
|
|
int logsev;
|
|
|
|
switch (ctlsev) {
|
|
case ctl_debug: logsev = log_debug(5); break;
|
|
case ctl_warning: logsev = log_warning; break;
|
|
case ctl_error: logsev = log_error; break;
|
|
default: logsev = 0;
|
|
panic("invalid ctlsev in logger", NULL);
|
|
}
|
|
if (!log_ctx_valid)
|
|
return;
|
|
va_start(args, format);
|
|
log_vwrite(log_ctx, ns_log_control, logsev, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
static void
|
|
verb_connect(struct ctl_sctx *ctl, struct ctl_sess *sess,
|
|
const struct ctl_verb *verb, const char *rest,
|
|
u_int respflags, const void *respctx, void *uctx)
|
|
{
|
|
const struct sockaddr *sa = (const struct sockaddr *)respctx;
|
|
control nsctl = (control)uctx;
|
|
|
|
UNUSED(ctl);
|
|
UNUSED(verb);
|
|
UNUSED(rest);
|
|
UNUSED(respflags);
|
|
|
|
if (sa->sa_family == AF_INET) {
|
|
const struct sockaddr_in *in = (const struct sockaddr_in *)sa;
|
|
const ip_match_list acl = nsctl->var.v_inet.allow;
|
|
|
|
if (!ip_address_allowed(acl, in->sin_addr)) {
|
|
ctl_response(sess, 502, "Permission denied.",
|
|
CTL_EXIT, NULL, NULL, NULL, NULL, 0);
|
|
return;
|
|
}
|
|
}
|
|
ctl_response(sess, 220, server_options->version, 0, NULL, NULL, NULL,
|
|
NULL, 0);
|
|
}
|
|
|
|
static void
|
|
verb_getpid(struct ctl_sctx *ctl, struct ctl_sess *sess,
|
|
const struct ctl_verb *verb, const char *rest,
|
|
u_int respflags, const void *respctx, void *uctx)
|
|
{
|
|
char *msg = memget(MAX_STR_LEN);
|
|
|
|
UNUSED(ctl);
|
|
UNUSED(verb);
|
|
UNUSED(rest);
|
|
UNUSED(respflags);
|
|
UNUSED(respctx);
|
|
UNUSED(uctx);
|
|
|
|
if (msg == NULL) {
|
|
ctl_response(sess, 503, "(out of memory)", 0,
|
|
NULL, NULL, NULL, NULL, 0);
|
|
return;
|
|
}
|
|
sprintf(msg, "my pid is <%ld>", (long)getpid());
|
|
ctl_response(sess, 250, msg, 0, NULL, getpid_closure, msg, NULL, 0);
|
|
}
|
|
|
|
static void
|
|
getpid_closure(struct ctl_sctx *sctx, struct ctl_sess *sess, void *uap) {
|
|
char *msg = uap;
|
|
|
|
UNUSED(sctx);
|
|
UNUSED(sess);
|
|
|
|
memput(msg, MAX_STR_LEN);
|
|
}
|
|
|
|
enum state {
|
|
e_version = 0,
|
|
e_config,
|
|
e_nzones,
|
|
e_debug,
|
|
e_xfersrun,
|
|
e_xfersdfr,
|
|
e_qserials,
|
|
e_qrylog,
|
|
e_priming,
|
|
e_finito
|
|
};
|
|
|
|
struct pvt_status {
|
|
enum state state;
|
|
char text[MAX_STR_LEN];
|
|
};
|
|
|
|
static void
|
|
verb_status(struct ctl_sctx *ctl, struct ctl_sess *sess,
|
|
const struct ctl_verb *verb, const char *rest,
|
|
u_int respflags, const void *respctx, void *uctx)
|
|
{
|
|
struct pvt_status *pvt = ctl_getcsctx(sess);
|
|
|
|
UNUSED(ctl);
|
|
UNUSED(verb);
|
|
UNUSED(rest);
|
|
UNUSED(respflags);
|
|
UNUSED(respctx);
|
|
UNUSED(uctx);
|
|
|
|
if (pvt == NULL) {
|
|
pvt = memget(sizeof *pvt);
|
|
if (pvt == NULL) {
|
|
ctl_response(sess, 505, "(out of memory)",
|
|
0, NULL, NULL, NULL, NULL, 0);
|
|
return;
|
|
}
|
|
pvt->state = (enum state)0;
|
|
(void)ctl_setcsctx(sess, pvt);
|
|
}
|
|
switch (pvt->state++) {
|
|
case e_version:
|
|
strncpy(pvt->text, Version, sizeof pvt->text);
|
|
pvt->text[sizeof pvt->text - 1] = '\0';
|
|
break;
|
|
case e_config:
|
|
sprintf(pvt->text, "config (%s) last loaded at age: %24s",
|
|
conffile, ctime(&confmtime));
|
|
break;
|
|
case e_nzones:
|
|
sprintf(pvt->text, "number of zones allocated: %d", nzones);
|
|
break;
|
|
case e_debug:
|
|
sprintf(pvt->text, "debug level: %d", debug);
|
|
break;
|
|
case e_xfersrun:
|
|
sprintf(pvt->text, "xfers running: %d", xfers_running);
|
|
break;
|
|
case e_xfersdfr:
|
|
sprintf(pvt->text, "xfers deferred: %d", xfers_deferred);
|
|
break;
|
|
case e_qserials:
|
|
sprintf(pvt->text, "soa queries in progress: %d",
|
|
qserials_running);
|
|
break;
|
|
case e_qrylog:
|
|
sprintf(pvt->text, "query logging is %s",
|
|
qrylog ? "ON" : "OFF");
|
|
break;
|
|
case e_priming:
|
|
if (priming)
|
|
sprintf(pvt->text, "server is initialising itself");
|
|
else
|
|
sprintf(pvt->text, "server is up and running");
|
|
break;
|
|
case e_finito:
|
|
return;
|
|
}
|
|
ctl_response(sess, 250, pvt->text,
|
|
(pvt->state == e_finito) ? 0 : CTL_MORE,
|
|
NULL, status_closure, NULL, NULL, 0);
|
|
}
|
|
|
|
static void
|
|
status_closure(struct ctl_sctx *sctx, struct ctl_sess *sess, void *uap) {
|
|
struct pvt_status *pvt = ctl_getcsctx(sess);
|
|
|
|
UNUSED(sctx);
|
|
UNUSED(uap);
|
|
|
|
memput(pvt, sizeof *pvt);
|
|
ctl_setcsctx(sess, NULL);
|
|
}
|
|
|
|
static void
|
|
verb_stop(struct ctl_sctx *ctl, struct ctl_sess *sess,
|
|
const struct ctl_verb *verb, const char *rest,
|
|
u_int respflags, const void *respctx, void *uctx)
|
|
{
|
|
UNUSED(ctl);
|
|
UNUSED(verb);
|
|
UNUSED(rest);
|
|
UNUSED(respflags);
|
|
UNUSED(respctx);
|
|
UNUSED(uctx);
|
|
|
|
ns_need(main_need_exit);
|
|
ctl_response(sess, 250, "Shutdown initiated.", 0, NULL, NULL, NULL,
|
|
NULL, 0);
|
|
}
|
|
|
|
static void
|
|
verb_exec(struct ctl_sctx *ctl, struct ctl_sess *sess,
|
|
const struct ctl_verb *verb, const char *rest,
|
|
u_int respflags, const void *respctx, void *uctx)
|
|
{
|
|
struct stat sb;
|
|
|
|
UNUSED(ctl);
|
|
UNUSED(verb);
|
|
UNUSED(respflags);
|
|
UNUSED(respctx);
|
|
UNUSED(uctx);
|
|
|
|
if (rest != NULL && *rest != '\0') {
|
|
if (stat(rest, &sb) < 0) {
|
|
ctl_response(sess, 503, strerror(errno),
|
|
0, NULL, NULL, NULL, NULL, 0);
|
|
return;
|
|
}
|
|
saved_argv[0] = savestr(rest, 1); /* Never strfreed. */
|
|
}
|
|
|
|
if (stat(saved_argv[0], &sb) < 0) {
|
|
const char *save = strerror(errno);
|
|
|
|
ns_warning(ns_log_default, "can't exec, %s: %s",
|
|
saved_argv[0], save);
|
|
ctl_response(sess, 502, save, 0, NULL, NULL, NULL,
|
|
NULL, 0);
|
|
} else if (user_name != NULL || group_name != NULL) {
|
|
ctl_response(sess, 502,
|
|
"can't exec as user or group was specified",
|
|
0, NULL, NULL, NULL, NULL, 0);
|
|
} else {
|
|
ctl_response(sess, 250, "Restart initiated.", 0, NULL,
|
|
exec_closure, NULL, NULL, 0);
|
|
}
|
|
}
|
|
|
|
static void
|
|
exec_closure(struct ctl_sctx *sctx, struct ctl_sess *sess, void *uap) {
|
|
|
|
UNUSED(sctx);
|
|
UNUSED(sess);
|
|
UNUSED(uap);
|
|
ns_need(main_need_restart);
|
|
}
|
|
|
|
static void
|
|
verb_reload(struct ctl_sctx *ctl, struct ctl_sess *sess,
|
|
const struct ctl_verb *verb, const char *rest,
|
|
u_int respflags, const void *respctx, void *uctx)
|
|
{
|
|
static const char spaces[] = " \t";
|
|
struct zoneinfo *zp;
|
|
char *tmp = NULL, *x;
|
|
const char *cl;
|
|
const char *msg;
|
|
int class, code, success;
|
|
|
|
UNUSED(ctl);
|
|
UNUSED(verb);
|
|
UNUSED(respflags);
|
|
UNUSED(respctx);
|
|
UNUSED(uctx);
|
|
|
|
/* If there are no args, this is a classic reload of the config. */
|
|
if (rest == NULL || *rest == '\0') {
|
|
ns_need(main_need_reload);
|
|
code = 250;
|
|
msg = "Reload initiated.";
|
|
goto respond;
|
|
}
|
|
|
|
/* Look for optional zclass argument. Default is "in". */
|
|
tmp = savestr(rest, 1);
|
|
x = tmp + strcspn(tmp, spaces);
|
|
if (*x != '\0') {
|
|
*x++ = '\0';
|
|
x += strspn(x, spaces);
|
|
}
|
|
cl = (x == NULL || *x == '\0') ? "in" : x;
|
|
class = res_nametoclass(cl, &success);
|
|
if (!success) {
|
|
code = 507;
|
|
msg = "unrecognized class";
|
|
goto respond;
|
|
}
|
|
|
|
/* Look for the zone, and do the right thing to it. */
|
|
zp = find_zone(tmp, class);
|
|
if (zp == NULL) {
|
|
code = 506;
|
|
msg = "Zone not found.";
|
|
goto respond;
|
|
}
|
|
switch (zp->z_type) {
|
|
case z_master:
|
|
ns_stopxfrs(zp);
|
|
/*FALLTHROUGH*/
|
|
case z_hint:
|
|
block_signals();
|
|
code = 251;
|
|
msg = deferred_reload_unsafe(zp);
|
|
unblock_signals();
|
|
break;
|
|
case z_slave:
|
|
case z_stub:
|
|
ns_stopxfrs(zp);
|
|
if (zonefile_changed_p(zp))
|
|
zp->z_serial = 0; /* force xfer */
|
|
addxfer(zp);
|
|
code = 251;
|
|
msg = "Slave transfer queued.";
|
|
goto respond;
|
|
case z_forward:
|
|
case z_cache:
|
|
default:
|
|
msg = "Non reloadable zone.";
|
|
code = 507;
|
|
break;
|
|
}
|
|
|
|
respond:
|
|
ctl_response(sess, code, msg, 0, NULL, NULL, NULL, NULL, 0);
|
|
if (tmp != NULL)
|
|
(void)freestr(tmp);
|
|
}
|
|
|
|
static void
|
|
verb_reconfig(struct ctl_sctx *ctl, struct ctl_sess *sess,
|
|
const struct ctl_verb *verb, const char *rest,
|
|
u_int respflags, const void *respctx, void *uctx)
|
|
{
|
|
UNUSED(ctl);
|
|
UNUSED(verb);
|
|
UNUSED(respflags);
|
|
UNUSED(respctx);
|
|
UNUSED(uctx);
|
|
|
|
if (strcmp(rest, "-noexpired") != 0)
|
|
ns_need(main_need_reconfig);
|
|
else
|
|
ns_need(main_need_noexpired);
|
|
ctl_response(sess, 250, "Reconfig initiated.",
|
|
0, NULL, NULL, NULL, NULL, 0);
|
|
}
|
|
|
|
static void
|
|
verb_dumpdb(struct ctl_sctx *ctl, struct ctl_sess *sess,
|
|
const struct ctl_verb *verb, const char *rest,
|
|
u_int respflags, const void *respctx, void *uctx)
|
|
{
|
|
UNUSED(ctl);
|
|
UNUSED(verb);
|
|
UNUSED(rest);
|
|
UNUSED(respflags);
|
|
UNUSED(respctx);
|
|
UNUSED(uctx);
|
|
|
|
ns_need(main_need_dump);
|
|
ctl_response(sess, 250, "Database dump initiated.", 0, NULL,
|
|
NULL, NULL, NULL, 0);
|
|
}
|
|
|
|
static void
|
|
verb_stats(struct ctl_sctx *ctl, struct ctl_sess *sess,
|
|
const struct ctl_verb *verb, const char *rest,
|
|
u_int respflags, const void *respctx, void *uctx)
|
|
{
|
|
UNUSED(ctl);
|
|
UNUSED(verb);
|
|
UNUSED(respflags);
|
|
UNUSED(respctx);
|
|
UNUSED(uctx);
|
|
|
|
if (rest != NULL && strcmp(rest, "clear") == 0) {
|
|
ns_need(main_need_statsdumpandclear);
|
|
ctl_response(sess, 250, "Statistics dump and clear initiated.",
|
|
0, NULL, NULL, NULL, NULL, 0);
|
|
} else {
|
|
ns_need(main_need_statsdump);
|
|
ctl_response(sess, 250, "Statistics dump initiated.",
|
|
0, NULL, NULL, NULL, NULL, 0);
|
|
}
|
|
}
|
|
|
|
static void
|
|
verb_trace(struct ctl_sctx *ctl, struct ctl_sess *sess,
|
|
const struct ctl_verb *verb, const char *rest,
|
|
u_int respflags, const void *respctx, void *uctx)
|
|
{
|
|
int i = atoi(rest);
|
|
char *msg = memget(MAX_STR_LEN);
|
|
|
|
UNUSED(ctl);
|
|
UNUSED(verb);
|
|
UNUSED(respflags);
|
|
UNUSED(respctx);
|
|
UNUSED(uctx);
|
|
|
|
if (msg == NULL) {
|
|
ctl_response(sess, 503, "(out of memory)", 0,
|
|
NULL, NULL, NULL, NULL, 0);
|
|
return;
|
|
}
|
|
if (isdigit(*(const unsigned char *)rest) && i >= 0)
|
|
desired_debug = i;
|
|
else
|
|
desired_debug++;
|
|
ns_need(main_need_debug);
|
|
if (desired_debug == 0)
|
|
sprintf(msg, "Debugging turned off.");
|
|
else
|
|
sprintf(msg, "Debug level: %d", desired_debug);
|
|
ctl_response(sess, 250, msg, 0, NULL, trace_closure, msg, NULL, 0);
|
|
}
|
|
|
|
static void
|
|
trace_closure(struct ctl_sctx *sctx, struct ctl_sess *sess, void *uap) {
|
|
char *msg = uap;
|
|
|
|
UNUSED(sctx);
|
|
UNUSED(sess);
|
|
|
|
memput(msg, MAX_STR_LEN);
|
|
}
|
|
|
|
static void
|
|
verb_notrace(struct ctl_sctx *ctl, struct ctl_sess *sess,
|
|
const struct ctl_verb *verb, const char *rest,
|
|
u_int respflags, const void *respctx, void *uctx)
|
|
{
|
|
UNUSED(ctl);
|
|
UNUSED(verb);
|
|
UNUSED(rest);
|
|
UNUSED(respflags);
|
|
UNUSED(respctx);
|
|
UNUSED(uctx);
|
|
|
|
desired_debug = 0;
|
|
ns_need(main_need_debug);
|
|
ctl_response(sess, 250, "Debugging turned off.",
|
|
0, NULL, NULL, NULL, NULL, 0);
|
|
}
|
|
|
|
static void
|
|
verb_querylog(struct ctl_sctx *ctl, struct ctl_sess *sess,
|
|
const struct ctl_verb *verb, const char *rest,
|
|
u_int respflags, const void *respctx, void *uctx)
|
|
{
|
|
static const char on[] = "Query logging is now on.",
|
|
off[] = "Query logging is now off.";
|
|
|
|
UNUSED(ctl);
|
|
UNUSED(verb);
|
|
UNUSED(rest);
|
|
UNUSED(respflags);
|
|
UNUSED(respctx);
|
|
UNUSED(uctx);
|
|
|
|
toggle_qrylog();
|
|
ctl_response(sess, 250, qrylog ? on : off,
|
|
0, NULL, NULL, NULL, NULL, 0);
|
|
}
|
|
|
|
static void
|
|
verb_help(struct ctl_sctx *ctl, struct ctl_sess *sess,
|
|
const struct ctl_verb *verb, const char *rest,
|
|
u_int respflags, const void *respctx, void *uctx)
|
|
{
|
|
UNUSED(ctl);
|
|
UNUSED(verb);
|
|
UNUSED(rest);
|
|
UNUSED(respflags);
|
|
UNUSED(respctx);
|
|
UNUSED(uctx);
|
|
|
|
ctl_sendhelp(sess, 214);
|
|
}
|
|
|
|
static void
|
|
verb_quit(struct ctl_sctx *ctl, struct ctl_sess *sess,
|
|
const struct ctl_verb *verb, const char *rest,
|
|
u_int respflags, const void *respctx, void *uctx)
|
|
{
|
|
UNUSED(ctl);
|
|
UNUSED(verb);
|
|
UNUSED(rest);
|
|
UNUSED(respflags);
|
|
UNUSED(respctx);
|
|
UNUSED(uctx);
|
|
|
|
ctl_response(sess, 221, "End of control session.", CTL_EXIT, NULL,
|
|
NULL, NULL, NULL, 0);
|
|
}
|
|
|
|
static char hex[] = "0123456789abcdef";
|
|
|
|
struct pvt_args {
|
|
int argc;
|
|
char text[MAX_STR_LEN];
|
|
};
|
|
|
|
static void
|
|
args_closure(struct ctl_sctx *sctx, struct ctl_sess *sess, void *uap) {
|
|
struct pvt_args *pvt = ctl_getcsctx(sess);
|
|
|
|
UNUSED(sctx);
|
|
UNUSED(uap);
|
|
|
|
memput(pvt, sizeof *pvt);
|
|
ctl_setcsctx(sess, NULL);
|
|
}
|
|
|
|
static void
|
|
verb_args(struct ctl_sctx *ctl, struct ctl_sess *sess,
|
|
const struct ctl_verb *verb, const char *rest,
|
|
u_int respflags, const void *respctx, void *uctx)
|
|
{
|
|
struct pvt_args *pvt = ctl_getcsctx(sess);
|
|
char *cp, *tp;
|
|
|
|
UNUSED(ctl);
|
|
UNUSED(verb);
|
|
UNUSED(rest);
|
|
UNUSED(respflags);
|
|
UNUSED(respctx);
|
|
UNUSED(uctx);
|
|
|
|
if (pvt == NULL) {
|
|
unsigned int i = 0;
|
|
pvt = memget(sizeof *pvt);
|
|
if (pvt == NULL) {
|
|
ctl_response(sess, 505, "(out of memory)",
|
|
0, NULL, NULL, NULL, NULL, 0);
|
|
return;
|
|
}
|
|
pvt->argc = 0;
|
|
ctl_setcsctx(sess, pvt);
|
|
|
|
/* Send the arguement count. */
|
|
while (saved_argv[i] != NULL)
|
|
i++;
|
|
sprintf(pvt->text, "%u", i);
|
|
ctl_response(sess, 250, pvt->text, CTL_MORE,
|
|
NULL, args_closure, NULL, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Percent escape arguement.
|
|
*/
|
|
cp = saved_argv[pvt->argc++];
|
|
tp = pvt->text;
|
|
while (cp && *cp != NULL)
|
|
if (*cp == '%' || *cp == ' ' ||
|
|
!isprint((unsigned char)*cp)) {
|
|
if (tp >= pvt->text + sizeof(pvt->text) - 4)
|
|
break;
|
|
*tp++ = '%';
|
|
*tp++ = hex[(*cp>>4)&0xf];
|
|
*tp++ = hex[(*cp++)&0xf];
|
|
} else {
|
|
if (tp >= pvt->text + sizeof(pvt->text) - 2)
|
|
break;
|
|
*tp++ = *cp++;
|
|
}
|
|
*tp = '\0';
|
|
|
|
ctl_response(sess, 250, pvt->text,
|
|
saved_argv[pvt->argc] == NULL ? 0 : CTL_MORE,
|
|
NULL, args_closure, NULL, NULL, 0);
|
|
}
|