#if !defined(lint) && !defined(SABER) static const char rcsid[] = "$Id: ns_ctl.c,v 8.28 1999/10/13 16:39:04 vixie Exp $"; #endif /* not lint */ /* * Copyright (c) 1997-1999 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; struct { struct sockaddr_un un; mode_t mode; uid_t owner; gid_t group; } v_unix; } 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, ...); static void verb_connect(struct ctl_sctx *, struct ctl_sess *, const struct ctl_verb *, const char *, u_int, void *, void *); static void verb_getpid(struct ctl_sctx *, struct ctl_sess *, const struct ctl_verb *, const char *, u_int, 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, 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, void *, void *); static void verb_exec(struct ctl_sctx *, struct ctl_sess *, const struct ctl_verb *, const char *, u_int, void *, void *); static void verb_reload(struct ctl_sctx *, struct ctl_sess *, const struct ctl_verb *, const char *, u_int, void *, void *); static void verb_reconfig(struct ctl_sctx *, struct ctl_sess *, const struct ctl_verb *, const char *, u_int, void *, void *); static void verb_dumpdb(struct ctl_sctx *, struct ctl_sess *, const struct ctl_verb *, const char *, u_int, void *, void *); static void verb_stats(struct ctl_sctx *, struct ctl_sess *, const struct ctl_verb *, const char *, u_int, void *, void *); static void verb_trace(struct ctl_sctx *, struct ctl_sess *, const struct ctl_verb *, const char *, u_int, 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, void *, void *); static void verb_querylog(struct ctl_sctx *, struct ctl_sess *, const struct ctl_verb *, const char *, u_int, void *, void *); static void verb_help(struct ctl_sctx *, struct ctl_sess *, const struct ctl_verb *, const char *, u_int, void *, void *); static void verb_quit(struct ctl_sctx *, struct ctl_sess *, const struct ctl_verb *, const char *, u_int, 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 (just sees new/gone zones)"}, { "dumpdb", verb_dumpdb, "dumpdb"}, { "stats", verb_stats, "stats"}, { "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"}, { 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) { ns_ctl_add(list, ns_ctl_new_unix(_PATH_NDCSOCK, 0600, 0, 0)); } 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); } control ns_ctl_new_unix(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); } 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); 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; 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; 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; 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; 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; 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; 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; case t_unix: install_unix(ctl); break; 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); } } /* * 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) { 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(ctl->var.v_unix.un.sun_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(ctl->var.v_unix.un.sun_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)); } } } 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: 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, void *respctx, void *uctx) { const struct sockaddr *sa = (struct sockaddr *)respctx; control nsctl = (control)uctx; if (sa->sa_family == AF_INET) { const struct sockaddr_in *in = (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, void *respctx, void *uctx) { char *msg = memget(MAX_STR_LEN); 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; memput(msg, MAX_STR_LEN); } enum state { e_version = 0, e_nzones, e_debug, e_xfersrun, e_xfersdfr, e_qserials, e_qrylog, e_priming, e_loading, 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, void *respctx, void *uctx) { struct pvt_status *pvt = ctl_getcsctx(sess); 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 = e_version; (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_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: sprintf(pvt->text, "server is %s priming", priming ? "STILL" : "DONE"); break; case e_loading: sprintf(pvt->text, "server %s loading its configuration", loading ? "IS" : "IS NOT"); 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); 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, void *respctx, void *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, void *respctx, void *uctx) { struct stat sb; 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 { ns_need(main_need_restart); ctl_response(sess, 250, "Restart initiated.", 0, NULL, NULL, NULL, NULL, 0); } } static void verb_reload(struct ctl_sctx *ctl, struct ctl_sess *sess, const struct ctl_verb *verb, const char *rest, u_int respflags, void *respctx, void *uctx) { static const char spaces[] = " \t"; struct zoneinfo *zp; char *tmp = NULL, *x; const char *msg; int class, code, success; /* 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); } if (x == NULL || *x == '\0') x = "in"; class = sym_ston(__p_class_syms, x, &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); 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) 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, void *respctx, void *uctx) { ns_need(main_need_reconfig); 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, void *respctx, void *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, void *respctx, void *uctx) { 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, void *respctx, void *uctx) { int i = atoi(rest); char *msg = memget(MAX_STR_LEN); if (msg == NULL) { ctl_response(sess, 503, "(out of memory)", 0, NULL, NULL, NULL, NULL, 0); return; } if (i > 0) desired_debug = i; else desired_debug++; ns_need(main_need_debug); 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; 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, void *respctx, void *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, void *respctx, void *uctx) { static const char on[] = "Query logging is now on.", off[] = "Query logging is now off."; 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, void *respctx, void *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, void *respctx, void *uctx) { ctl_response(sess, 221, "End of control session.", CTL_EXIT, NULL, NULL, NULL, NULL, 0); }