/* * Copyright (C) 2004-2006, 2008 Internet Systems Consortium, Inc. ("ISC") * Copyright (C) 1998-2003 Internet Software Consortium. * * Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL ISC 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. */ #include "port_before.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_MEMORY_H #include #endif #include #include #include #include #include #include #include "ctl_p.h" #include "port_after.h" #ifdef SPRINTF_CHAR # define SPRINTF(x) strlen(sprintf/**/x) #else # define SPRINTF(x) ((size_t)sprintf x) #endif /* Macros. */ #define lastverb_p(verb) (verb->name == NULL || verb->func == NULL) #define address_expr ctl_sa_ntop((struct sockaddr *)&sess->sa, \ tmp, sizeof tmp, ctx->logger) /* Types. */ enum state { available = 0, initializing, writing, reading, reading_data, processing, idling, quitting, closing }; union sa_un { struct sockaddr_in in; #ifndef NO_SOCKADDR_UN struct sockaddr_un un; #endif }; struct ctl_sess { LINK(struct ctl_sess) link; struct ctl_sctx * ctx; enum state state; int sock; union sa_un sa; evFileID rdID; evStreamID wrID; evTimerID rdtiID; evTimerID wrtiID; struct ctl_buf inbuf; struct ctl_buf outbuf; const struct ctl_verb * verb; u_int helpcode; const void * respctx; u_int respflags; ctl_srvrdone donefunc; void * uap; void * csctx; }; struct ctl_sctx { evContext ev; void * uctx; u_int unkncode; u_int timeoutcode; const struct ctl_verb * verbs; const struct ctl_verb * connverb; int sock; int max_sess; int cur_sess; struct timespec timeout; ctl_logfunc logger; evConnID acID; LIST(struct ctl_sess) sess; }; /* Forward. */ static void ctl_accept(evContext, void *, int, const void *, int, const void *, int); static void ctl_close(struct ctl_sess *); static void ctl_new_state(struct ctl_sess *, enum state, const char *); static void ctl_start_read(struct ctl_sess *); static void ctl_stop_read(struct ctl_sess *); static void ctl_readable(evContext, void *, int, int); static void ctl_rdtimeout(evContext, void *, struct timespec, struct timespec); static void ctl_wrtimeout(evContext, void *, struct timespec, struct timespec); static void ctl_docommand(struct ctl_sess *); static void ctl_writedone(evContext, void *, int, int); static void ctl_morehelp(struct ctl_sctx *, struct ctl_sess *, const struct ctl_verb *, const char *, u_int, const void *, void *); static void ctl_signal_done(struct ctl_sctx *, struct ctl_sess *); /* Private data. */ static const char * state_names[] = { "available", "initializing", "writing", "reading", "reading_data", "processing", "idling", "quitting", "closing" }; static const char space[] = " "; static const struct ctl_verb fakehelpverb = { "fakehelp", ctl_morehelp , NULL }; /* Public. */ /*% * void * ctl_server() * create, condition, and start a listener on the control port. */ struct ctl_sctx * ctl_server(evContext lev, const struct sockaddr *sap, size_t sap_len, const struct ctl_verb *verbs, u_int unkncode, u_int timeoutcode, u_int timeout, int backlog, int max_sess, ctl_logfunc logger, void *uctx) { static const char me[] = "ctl_server"; static const int on = 1; const struct ctl_verb *connverb; struct ctl_sctx *ctx; int save_errno; if (logger == NULL) logger = ctl_logger; for (connverb = verbs; connverb->name != NULL && connverb->func != NULL; connverb++) if (connverb->name[0] == '\0') break; if (connverb->func == NULL) { (*logger)(ctl_error, "%s: no connection verb found", me); return (NULL); } ctx = memget(sizeof *ctx); if (ctx == NULL) { (*logger)(ctl_error, "%s: getmem: %s", me, strerror(errno)); return (NULL); } ctx->ev = lev; ctx->uctx = uctx; ctx->unkncode = unkncode; ctx->timeoutcode = timeoutcode; ctx->verbs = verbs; ctx->timeout = evConsTime(timeout, 0); ctx->logger = logger; ctx->connverb = connverb; ctx->max_sess = max_sess; ctx->cur_sess = 0; INIT_LIST(ctx->sess); ctx->sock = socket(sap->sa_family, SOCK_STREAM, PF_UNSPEC); if (ctx->sock > evHighestFD(ctx->ev)) { ctx->sock = -1; errno = ENOTSOCK; } if (ctx->sock < 0) { save_errno = errno; (*ctx->logger)(ctl_error, "%s: socket: %s", me, strerror(errno)); memput(ctx, sizeof *ctx); errno = save_errno; return (NULL); } if (ctx->sock > evHighestFD(lev)) { close(ctx->sock); (*ctx->logger)(ctl_error, "%s: file descriptor > evHighestFD"); errno = ENFILE; memput(ctx, sizeof *ctx); return (NULL); } #ifdef NO_UNIX_REUSEADDR if (sap->sa_family != AF_UNIX) #endif if (setsockopt(ctx->sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof on) != 0) { (*ctx->logger)(ctl_warning, "%s: setsockopt(REUSEADDR): %s", me, strerror(errno)); } if (bind(ctx->sock, sap, sap_len) < 0) { char tmp[MAX_NTOP]; save_errno = errno; (*ctx->logger)(ctl_error, "%s: bind: %s: %s", me, ctl_sa_ntop((const struct sockaddr *)sap, tmp, sizeof tmp, ctx->logger), strerror(save_errno)); close(ctx->sock); memput(ctx, sizeof *ctx); errno = save_errno; return (NULL); } if (fcntl(ctx->sock, F_SETFD, 1) < 0) { (*ctx->logger)(ctl_warning, "%s: fcntl: %s", me, strerror(errno)); } if (evListen(lev, ctx->sock, backlog, ctl_accept, ctx, &ctx->acID) < 0) { save_errno = errno; (*ctx->logger)(ctl_error, "%s: evListen(fd %d): %s", me, ctx->sock, strerror(errno)); close(ctx->sock); memput(ctx, sizeof *ctx); errno = save_errno; return (NULL); } (*ctx->logger)(ctl_debug, "%s: new ctx %p, sock %d", me, ctx, ctx->sock); return (ctx); } /*% * void * ctl_endserver(ctx) * if the control listener is open, close it. clean out all eventlib * stuff. close all active sessions. */ void ctl_endserver(struct ctl_sctx *ctx) { static const char me[] = "ctl_endserver"; struct ctl_sess *this, *next; (*ctx->logger)(ctl_debug, "%s: ctx %p, sock %d, acID %p, sess %p", me, ctx, ctx->sock, ctx->acID.opaque, ctx->sess); if (ctx->acID.opaque != NULL) { (void)evCancelConn(ctx->ev, ctx->acID); ctx->acID.opaque = NULL; } if (ctx->sock != -1) { (void) close(ctx->sock); ctx->sock = -1; } for (this = HEAD(ctx->sess); this != NULL; this = next) { next = NEXT(this, link); ctl_close(this); } memput(ctx, sizeof *ctx); } /*% * If body is non-NULL then it we add a "." line after it. * Caller must have escaped lines with leading ".". */ void ctl_response(struct ctl_sess *sess, u_int code, const char *text, u_int flags, const void *respctx, ctl_srvrdone donefunc, void *uap, const char *body, size_t bodylen) { static const char me[] = "ctl_response"; struct iovec iov[3], *iovp = iov; struct ctl_sctx *ctx = sess->ctx; char tmp[MAX_NTOP], *pc; int n; REQUIRE(sess->state == initializing || sess->state == processing || sess->state == reading_data || sess->state == writing); REQUIRE(sess->wrtiID.opaque == NULL); REQUIRE(sess->wrID.opaque == NULL); ctl_new_state(sess, writing, me); sess->donefunc = donefunc; sess->uap = uap; if (!allocated_p(sess->outbuf) && ctl_bufget(&sess->outbuf, ctx->logger) < 0) { (*ctx->logger)(ctl_error, "%s: %s: cant get an output buffer", me, address_expr); goto untimely; } if (sizeof "000-\r\n" + strlen(text) > (size_t)MAX_LINELEN) { (*ctx->logger)(ctl_error, "%s: %s: output buffer ovf, closing", me, address_expr); goto untimely; } sess->outbuf.used = SPRINTF((sess->outbuf.text, "%03d%c%s\r\n", code, (flags & CTL_MORE) != 0 ? '-' : ' ', text)); for (pc = sess->outbuf.text, n = 0; n < (int)sess->outbuf.used-2; pc++, n++) if (!isascii((unsigned char)*pc) || !isprint((unsigned char)*pc)) *pc = '\040'; *iovp++ = evConsIovec(sess->outbuf.text, sess->outbuf.used); if (body != NULL) { char *tmp; DE_CONST(body, tmp); *iovp++ = evConsIovec(tmp, bodylen); DE_CONST(".\r\n", tmp); *iovp++ = evConsIovec(tmp, 3); } (*ctx->logger)(ctl_debug, "%s: [%d] %s", me, sess->outbuf.used, sess->outbuf.text); if (evWrite(ctx->ev, sess->sock, iov, iovp - iov, ctl_writedone, sess, &sess->wrID) < 0) { (*ctx->logger)(ctl_error, "%s: %s: evWrite: %s", me, address_expr, strerror(errno)); goto untimely; } if (evSetIdleTimer(ctx->ev, ctl_wrtimeout, sess, ctx->timeout, &sess->wrtiID) < 0) { (*ctx->logger)(ctl_error, "%s: %s: evSetIdleTimer: %s", me, address_expr, strerror(errno)); goto untimely; } if (evTimeRW(ctx->ev, sess->wrID, sess->wrtiID) < 0) { (*ctx->logger)(ctl_error, "%s: %s: evTimeRW: %s", me, address_expr, strerror(errno)); untimely: ctl_signal_done(ctx, sess); ctl_close(sess); return; } sess->respctx = respctx; sess->respflags = flags; } void ctl_sendhelp(struct ctl_sess *sess, u_int code) { static const char me[] = "ctl_sendhelp"; struct ctl_sctx *ctx = sess->ctx; sess->helpcode = code; sess->verb = &fakehelpverb; ctl_morehelp(ctx, sess, NULL, me, CTL_MORE, (const void *)ctx->verbs, NULL); } void * ctl_getcsctx(struct ctl_sess *sess) { return (sess->csctx); } void * ctl_setcsctx(struct ctl_sess *sess, void *csctx) { void *old = sess->csctx; sess->csctx = csctx; return (old); } /* Private functions. */ static void ctl_accept(evContext lev, void *uap, int fd, const void *lav, int lalen, const void *rav, int ralen) { static const char me[] = "ctl_accept"; struct ctl_sctx *ctx = uap; struct ctl_sess *sess = NULL; char tmp[MAX_NTOP]; UNUSED(lev); UNUSED(lalen); UNUSED(ralen); if (fd < 0) { (*ctx->logger)(ctl_error, "%s: accept: %s", me, strerror(errno)); return; } if (ctx->cur_sess == ctx->max_sess) { (*ctx->logger)(ctl_error, "%s: %s: too many control sessions", me, ctl_sa_ntop((const struct sockaddr *)rav, tmp, sizeof tmp, ctx->logger)); (void) close(fd); return; } sess = memget(sizeof *sess); if (sess == NULL) { (*ctx->logger)(ctl_error, "%s: memget: %s", me, strerror(errno)); (void) close(fd); return; } if (fcntl(fd, F_SETFD, 1) < 0) { (*ctx->logger)(ctl_warning, "%s: fcntl: %s", me, strerror(errno)); } ctx->cur_sess++; INIT_LINK(sess, link); APPEND(ctx->sess, sess, link); sess->ctx = ctx; sess->sock = fd; sess->wrID.opaque = NULL; sess->rdID.opaque = NULL; sess->wrtiID.opaque = NULL; sess->rdtiID.opaque = NULL; sess->respctx = NULL; sess->csctx = NULL; if (((const struct sockaddr *)rav)->sa_family == AF_UNIX) ctl_sa_copy((const struct sockaddr *)lav, (struct sockaddr *)&sess->sa); else ctl_sa_copy((const struct sockaddr *)rav, (struct sockaddr *)&sess->sa); sess->donefunc = NULL; buffer_init(sess->inbuf); buffer_init(sess->outbuf); sess->state = available; ctl_new_state(sess, initializing, me); sess->verb = ctx->connverb; (*ctx->logger)(ctl_debug, "%s: %s: accepting (fd %d)", me, address_expr, sess->sock); (*ctx->connverb->func)(ctx, sess, ctx->connverb, "", 0, (const struct sockaddr *)rav, ctx->uctx); } static void ctl_new_state(struct ctl_sess *sess, enum state new_state, const char *reason) { static const char me[] = "ctl_new_state"; struct ctl_sctx *ctx = sess->ctx; char tmp[MAX_NTOP]; (*ctx->logger)(ctl_debug, "%s: %s: %s -> %s (%s)", me, address_expr, state_names[sess->state], state_names[new_state], reason); sess->state = new_state; } static void ctl_close(struct ctl_sess *sess) { static const char me[] = "ctl_close"; struct ctl_sctx *ctx = sess->ctx; char tmp[MAX_NTOP]; REQUIRE(sess->state == initializing || sess->state == writing || sess->state == reading || sess->state == processing || sess->state == reading_data || sess->state == idling); REQUIRE(sess->sock != -1); if (sess->state == reading || sess->state == reading_data) ctl_stop_read(sess); else if (sess->state == writing) { if (sess->wrID.opaque != NULL) { (void) evCancelRW(ctx->ev, sess->wrID); sess->wrID.opaque = NULL; } if (sess->wrtiID.opaque != NULL) { (void) evClearIdleTimer(ctx->ev, sess->wrtiID); sess->wrtiID.opaque = NULL; } } ctl_new_state(sess, closing, me); (void) close(sess->sock); if (allocated_p(sess->inbuf)) ctl_bufput(&sess->inbuf); if (allocated_p(sess->outbuf)) ctl_bufput(&sess->outbuf); (*ctx->logger)(ctl_debug, "%s: %s: closed (fd %d)", me, address_expr, sess->sock); UNLINK(ctx->sess, sess, link); memput(sess, sizeof *sess); ctx->cur_sess--; } static void ctl_start_read(struct ctl_sess *sess) { static const char me[] = "ctl_start_read"; struct ctl_sctx *ctx = sess->ctx; char tmp[MAX_NTOP]; REQUIRE(sess->state == initializing || sess->state == writing || sess->state == processing || sess->state == idling); REQUIRE(sess->rdtiID.opaque == NULL); REQUIRE(sess->rdID.opaque == NULL); sess->inbuf.used = 0; if (evSetIdleTimer(ctx->ev, ctl_rdtimeout, sess, ctx->timeout, &sess->rdtiID) < 0) { (*ctx->logger)(ctl_error, "%s: %s: evSetIdleTimer: %s", me, address_expr, strerror(errno)); ctl_close(sess); return; } if (evSelectFD(ctx->ev, sess->sock, EV_READ, ctl_readable, sess, &sess->rdID) < 0) { (*ctx->logger)(ctl_error, "%s: %s: evSelectFD: %s", me, address_expr, strerror(errno)); return; } ctl_new_state(sess, reading, me); } static void ctl_stop_read(struct ctl_sess *sess) { static const char me[] = "ctl_stop_read"; struct ctl_sctx *ctx = sess->ctx; REQUIRE(sess->state == reading || sess->state == reading_data); REQUIRE(sess->rdID.opaque != NULL); (void) evDeselectFD(ctx->ev, sess->rdID); sess->rdID.opaque = NULL; if (sess->rdtiID.opaque != NULL) { (void) evClearIdleTimer(ctx->ev, sess->rdtiID); sess->rdtiID.opaque = NULL; } ctl_new_state(sess, idling, me); } static void ctl_readable(evContext lev, void *uap, int fd, int evmask) { static const char me[] = "ctl_readable"; struct ctl_sess *sess = uap; struct ctl_sctx *ctx; char *eos, tmp[MAX_NTOP]; ssize_t n; REQUIRE(sess != NULL); REQUIRE(fd >= 0); REQUIRE(evmask == EV_READ); REQUIRE(sess->state == reading || sess->state == reading_data); ctx = sess->ctx; evTouchIdleTimer(lev, sess->rdtiID); if (!allocated_p(sess->inbuf) && ctl_bufget(&sess->inbuf, ctx->logger) < 0) { (*ctx->logger)(ctl_error, "%s: %s: cant get an input buffer", me, address_expr); ctl_close(sess); return; } n = read(sess->sock, sess->inbuf.text + sess->inbuf.used, MAX_LINELEN - sess->inbuf.used); if (n <= 0) { (*ctx->logger)(ctl_debug, "%s: %s: read: %s", me, address_expr, (n == 0) ? "Unexpected EOF" : strerror(errno)); ctl_close(sess); return; } sess->inbuf.used += n; eos = memchr(sess->inbuf.text, '\n', sess->inbuf.used); if (eos != NULL && eos != sess->inbuf.text && eos[-1] == '\r') { eos[-1] = '\0'; if ((sess->respflags & CTL_DATA) != 0) { INSIST(sess->verb != NULL); (*sess->verb->func)(sess->ctx, sess, sess->verb, sess->inbuf.text, CTL_DATA, sess->respctx, sess->ctx->uctx); } else { ctl_stop_read(sess); ctl_docommand(sess); } sess->inbuf.used -= ((eos - sess->inbuf.text) + 1); if (sess->inbuf.used == 0U) ctl_bufput(&sess->inbuf); else memmove(sess->inbuf.text, eos + 1, sess->inbuf.used); return; } if (sess->inbuf.used == (size_t)MAX_LINELEN) { (*ctx->logger)(ctl_error, "%s: %s: line too long, closing", me, address_expr); ctl_close(sess); } } static void ctl_wrtimeout(evContext lev, void *uap, struct timespec due, struct timespec itv) { static const char me[] = "ctl_wrtimeout"; struct ctl_sess *sess = uap; struct ctl_sctx *ctx = sess->ctx; char tmp[MAX_NTOP]; UNUSED(lev); UNUSED(due); UNUSED(itv); REQUIRE(sess->state == writing); sess->wrtiID.opaque = NULL; (*ctx->logger)(ctl_warning, "%s: %s: write timeout, closing", me, address_expr); if (sess->wrID.opaque != NULL) { (void) evCancelRW(ctx->ev, sess->wrID); sess->wrID.opaque = NULL; } ctl_signal_done(ctx, sess); ctl_new_state(sess, processing, me); ctl_close(sess); } static void ctl_rdtimeout(evContext lev, void *uap, struct timespec due, struct timespec itv) { static const char me[] = "ctl_rdtimeout"; struct ctl_sess *sess = uap; struct ctl_sctx *ctx = sess->ctx; char tmp[MAX_NTOP]; UNUSED(lev); UNUSED(due); UNUSED(itv); REQUIRE(sess->state == reading); sess->rdtiID.opaque = NULL; (*ctx->logger)(ctl_warning, "%s: %s: timeout, closing", me, address_expr); if (sess->state == reading || sess->state == reading_data) ctl_stop_read(sess); ctl_signal_done(ctx, sess); ctl_new_state(sess, processing, me); ctl_response(sess, ctx->timeoutcode, "Timeout.", CTL_EXIT, NULL, NULL, NULL, NULL, 0); } static void ctl_docommand(struct ctl_sess *sess) { static const char me[] = "ctl_docommand"; char *name, *rest, tmp[MAX_NTOP]; struct ctl_sctx *ctx = sess->ctx; const struct ctl_verb *verb; REQUIRE(allocated_p(sess->inbuf)); (*ctx->logger)(ctl_debug, "%s: %s: \"%s\" [%u]", me, address_expr, sess->inbuf.text, (u_int)sess->inbuf.used); ctl_new_state(sess, processing, me); name = sess->inbuf.text + strspn(sess->inbuf.text, space); rest = name + strcspn(name, space); if (*rest != '\0') { *rest++ = '\0'; rest += strspn(rest, space); } for (verb = ctx->verbs; verb != NULL && verb->name != NULL && verb->func != NULL; verb++) if (verb->name[0] != '\0' && strcasecmp(name, verb->name) == 0) break; if (verb != NULL && verb->name != NULL && verb->func != NULL) { sess->verb = verb; (*verb->func)(ctx, sess, verb, rest, 0, NULL, ctx->uctx); } else { char buf[1100]; if (sizeof "Unrecognized command \"\" (args \"\")" + strlen(name) + strlen(rest) > sizeof buf) strcpy(buf, "Unrecognized command (buf ovf)"); else sprintf(buf, "Unrecognized command \"%s\" (args \"%s\")", name, rest); ctl_response(sess, ctx->unkncode, buf, 0, NULL, NULL, NULL, NULL, 0); } } static void ctl_writedone(evContext lev, void *uap, int fd, int bytes) { static const char me[] = "ctl_writedone"; struct ctl_sess *sess = uap; struct ctl_sctx *ctx = sess->ctx; char tmp[MAX_NTOP]; int save_errno = errno; UNUSED(lev); UNUSED(uap); REQUIRE(sess->state == writing); REQUIRE(fd == sess->sock); REQUIRE(sess->wrtiID.opaque != NULL); sess->wrID.opaque = NULL; (void) evClearIdleTimer(ctx->ev, sess->wrtiID); sess->wrtiID.opaque = NULL; if (bytes < 0) { (*ctx->logger)(ctl_error, "%s: %s: %s", me, address_expr, strerror(save_errno)); ctl_close(sess); return; } INSIST(allocated_p(sess->outbuf)); ctl_bufput(&sess->outbuf); if ((sess->respflags & CTL_EXIT) != 0) { ctl_signal_done(ctx, sess); ctl_close(sess); return; } else if ((sess->respflags & CTL_MORE) != 0) { INSIST(sess->verb != NULL); (*sess->verb->func)(sess->ctx, sess, sess->verb, "", CTL_MORE, sess->respctx, sess->ctx->uctx); } else { ctl_signal_done(ctx, sess); ctl_start_read(sess); } } static void ctl_morehelp(struct ctl_sctx *ctx, struct ctl_sess *sess, const struct ctl_verb *verb, const char *text, u_int respflags, const void *respctx, void *uctx) { const struct ctl_verb *this = respctx, *next = this + 1; UNUSED(ctx); UNUSED(verb); UNUSED(text); UNUSED(uctx); REQUIRE(!lastverb_p(this)); REQUIRE((respflags & CTL_MORE) != 0); if (lastverb_p(next)) respflags &= ~CTL_MORE; ctl_response(sess, sess->helpcode, this->help, respflags, next, NULL, NULL, NULL, 0); } static void ctl_signal_done(struct ctl_sctx *ctx, struct ctl_sess *sess) { if (sess->donefunc != NULL) { (*sess->donefunc)(ctx, sess, sess->uap); sess->donefunc = NULL; } } /*! \file */