/*      $NetBSD: ctl_srvr.c,v 1.1.1.2 2012/09/09 16:08:01 christos Exp $        */

/*
* 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.
*/

#if !defined(lint) && !defined(SABER)
static const char rcsid[] = "Id: ctl_srvr.c,v 1.10 2008/11/14 02:36:51 marka Exp ";
#endif /* not lint */

/* Extern. */

#include "port_before.h"

#include <sys/param.h>
#include <sys/file.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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif

#include <isc/assertions.h>
#include <isc/ctl.h>
#include <isc/eventlib.h>
#include <isc/list.h>
#include <isc/logging.h>
#include <isc/memcluster.h>

#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 */