root/usr/src/lib/libresolv2/common/isc/ctl_clnt.c
/*
 * Copyright (C) 2004, 2005, 2007, 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 <sys/param.h>
#include <sys/file.h>
#include <sys/socket.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>
#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/memcluster.h>

#include "ctl_p.h"

#include "port_after.h"

/* Constants. */


/* Macros. */

#define donefunc_p(ctx) ((ctx).donefunc != NULL)
#define arpacode_p(line) (isdigit((unsigned char)(line[0])) && \
                          isdigit((unsigned char)(line[1])) && \
                          isdigit((unsigned char)(line[2])))
#define arpacont_p(line) (line[3] == '-')
#define arpadone_p(line) (line[3] == ' ' || line[3] == '\t' || \
                          line[3] == '\r' || line[3] == '\0')

/* Types. */

enum state {
        initializing = 0, connecting, connected, destroyed
};

struct ctl_tran {
        LINK(struct ctl_tran)   link;
        LINK(struct ctl_tran)   wlink;
        struct ctl_cctx *       ctx;
        struct ctl_buf          outbuf;
        ctl_clntdone            donefunc;
        void *                  uap;
};

struct ctl_cctx {
        enum state              state;
        evContext               ev;
        int                     sock;
        ctl_logfunc             logger;
        ctl_clntdone            donefunc;
        void *                  uap;
        evConnID                coID;
        evTimerID               tiID;
        evFileID                rdID;
        evStreamID              wrID;
        struct ctl_buf          inbuf;
        struct timespec         timeout;
        LIST(struct ctl_tran)   tran;
        LIST(struct ctl_tran)   wtran;
};

/* Forward. */

static struct ctl_tran *new_tran(struct ctl_cctx *, ctl_clntdone, void *, int);
static void             start_write(struct ctl_cctx *);
static void             destroy(struct ctl_cctx *, int);
static void             error(struct ctl_cctx *);
static void             new_state(struct ctl_cctx *, enum state);
static void             conn_done(evContext, void *, int,
                                  const void *, int,
                                  const void *, int);
static void             write_done(evContext, void *, int, int);
static void             start_read(struct ctl_cctx *);
static void             stop_read(struct ctl_cctx *);
static void             readable(evContext, void *, int, int);
static void             start_timer(struct ctl_cctx *);
static void             stop_timer(struct ctl_cctx *);
static void             touch_timer(struct ctl_cctx *);
static void             timer(evContext, void *,
                              struct timespec, struct timespec);

#ifndef HAVE_MEMCHR
static void *
memchr(const void *b, int c, size_t len) {
        const unsigned char *p = b;
        size_t i;

        for (i = 0; i < len; i++, p++)
                if (*p == (unsigned char)c)
                        return ((void *)p);
        return (NULL);
}
#endif

/* Private data. */

static const char * const state_names[] = {
        "initializing", "connecting", "connected", "destroyed"
};

/* Public. */

/*%
 * void
 * ctl_client()
 *      create, condition, and connect to a listener on the control port.
 */
struct ctl_cctx *
ctl_client(evContext lev, const struct sockaddr *cap, size_t cap_len,
           const struct sockaddr *sap, size_t sap_len,
           ctl_clntdone donefunc, void *uap,
           u_int timeout, ctl_logfunc logger)
{
        static const char me[] = "ctl_client";
        static const int on = 1;
        struct ctl_cctx *ctx;
        struct sockaddr *captmp;

        if (logger == NULL)
                logger = ctl_logger;
        ctx = memget(sizeof *ctx);
        if (ctx == NULL) {
                (*logger)(ctl_error, "%s: getmem: %s", me, strerror(errno));
                goto fatal;
        }
        ctx->state = initializing;
        ctx->ev = lev;
        ctx->logger = logger;
        ctx->timeout = evConsTime(timeout, 0);
        ctx->donefunc = donefunc;
        ctx->uap = uap;
        ctx->coID.opaque = NULL;
        ctx->tiID.opaque = NULL;
        ctx->rdID.opaque = NULL;
        ctx->wrID.opaque = NULL;
        buffer_init(ctx->inbuf);
        INIT_LIST(ctx->tran);
        INIT_LIST(ctx->wtran);
        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) {
                (*ctx->logger)(ctl_error, "%s: socket: %s",
                               me, strerror(errno));
                goto fatal;
        }
        if (cap != NULL) {
                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));
                }
                DE_CONST(cap, captmp);
                if (bind(ctx->sock, captmp, cap_len) < 0) {
                        (*ctx->logger)(ctl_error, "%s: bind: %s", me,
                                       strerror(errno));
                        goto fatal;
                }
        }
        if (evConnect(lev, ctx->sock, (const struct sockaddr *)sap, sap_len,
                      conn_done, ctx, &ctx->coID) < 0) {
                (*ctx->logger)(ctl_error, "%s: evConnect(fd %d): %s",
                               me, ctx->sock, strerror(errno));
 fatal:
                if (ctx != NULL) {
                        if (ctx->sock >= 0)
                                close(ctx->sock);
                        memput(ctx, sizeof *ctx);
                }
                return (NULL);
        }
        new_state(ctx, connecting);
        return (ctx);
}

/*%
 * void
 * ctl_endclient(ctx)
 *      close a client and release all of its resources.
 */
void
ctl_endclient(struct ctl_cctx *ctx) {
        if (ctx->state != destroyed)
                destroy(ctx, 0);
        memput(ctx, sizeof *ctx);
}

/*%
 * int
 * ctl_command(ctx, cmd, len, donefunc, uap)
 *      Queue a transaction, which will begin with sending cmd
 *      and complete by calling donefunc with the answer.
 */
int
ctl_command(struct ctl_cctx *ctx, const char *cmd, size_t len,
            ctl_clntdone donefunc, void *uap)
{
        struct ctl_tran *tran;
        char *pc;
        unsigned int n;

        switch (ctx->state) {
        case destroyed:
                errno = ENOTCONN;
                return (-1);
        case connecting:
        case connected:
                break;
        default:
                abort();
        }
        if (len >= (size_t)MAX_LINELEN) {
                errno = EMSGSIZE;
                return (-1);
        }
        tran = new_tran(ctx, donefunc, uap, 1);
        if (tran == NULL)
                return (-1);
        if (ctl_bufget(&tran->outbuf, ctx->logger) < 0)
                return (-1);
        memcpy(tran->outbuf.text, cmd, len);
        tran->outbuf.used = len;
        for (pc = tran->outbuf.text, n = 0; n < tran->outbuf.used; pc++, n++)
                if (!isascii((unsigned char)*pc) ||
                    !isprint((unsigned char)*pc))
                        *pc = '\040';
        start_write(ctx);
        return (0);
}

/* Private. */

static struct ctl_tran *
new_tran(struct ctl_cctx *ctx, ctl_clntdone donefunc, void *uap, int w) {
        struct ctl_tran *new = memget(sizeof *new);

        if (new == NULL)
                return (NULL);
        new->ctx = ctx;
        buffer_init(new->outbuf);
        new->donefunc = donefunc;
        new->uap = uap;
        INIT_LINK(new, link);
        INIT_LINK(new, wlink);
        APPEND(ctx->tran, new, link);
        if (w)
                APPEND(ctx->wtran, new, wlink);
        return (new);
}

static void
start_write(struct ctl_cctx *ctx) {
        static const char me[] = "isc/ctl_clnt::start_write";
        struct ctl_tran *tran;
        struct iovec iov[2], *iovp = iov;
        char * tmp;

        REQUIRE(ctx->state == connecting || ctx->state == connected);
        /* If there is a write in progress, don't try to write more yet. */
        if (ctx->wrID.opaque != NULL)
                return;
        /* If there are no trans, make sure timer is off, and we're done. */
        if (EMPTY(ctx->wtran)) {
                if (ctx->tiID.opaque != NULL)
                        stop_timer(ctx);
                return;
        }
        /* Pull it off the head of the write queue. */
        tran = HEAD(ctx->wtran);
        UNLINK(ctx->wtran, tran, wlink);
        /* Since there are some trans, make sure timer is successfully "on". */
        if (ctx->tiID.opaque != NULL)
                touch_timer(ctx);
        else
                start_timer(ctx);
        if (ctx->state == destroyed)
                return;
        /* Marshall a newline-terminated message and clock it out. */
        *iovp++ = evConsIovec(tran->outbuf.text, tran->outbuf.used);
        DE_CONST("\r\n", tmp);
        *iovp++ = evConsIovec(tmp, 2);
        if (evWrite(ctx->ev, ctx->sock, iov, iovp - iov,
                    write_done, tran, &ctx->wrID) < 0) {
                (*ctx->logger)(ctl_error, "%s: evWrite: %s", me,
                               strerror(errno));
                error(ctx);
                return;
        }
        if (evTimeRW(ctx->ev, ctx->wrID, ctx->tiID) < 0) {
                (*ctx->logger)(ctl_error, "%s: evTimeRW: %s", me,
                               strerror(errno));
                error(ctx);
                return;
        }
}

static void
destroy(struct ctl_cctx *ctx, int notify) {
        struct ctl_tran *this, *next;

        if (ctx->sock != -1) {
                (void) close(ctx->sock);
                ctx->sock = -1;
        }
        switch (ctx->state) {
        case connecting:
                REQUIRE(ctx->wrID.opaque == NULL);
                REQUIRE(EMPTY(ctx->tran));
                /*
                 * This test is nec'y since destroy() can be called from
                 * start_read() while the state is still "connecting".
                 */
                if (ctx->coID.opaque != NULL) {
                        (void)evCancelConn(ctx->ev, ctx->coID);
                        ctx->coID.opaque = NULL;
                }
                break;
        case connected:
                REQUIRE(ctx->coID.opaque == NULL);
                if (ctx->wrID.opaque != NULL) {
                        (void)evCancelRW(ctx->ev, ctx->wrID);
                        ctx->wrID.opaque = NULL;
                }
                if (ctx->rdID.opaque != NULL)
                        stop_read(ctx);
                break;
        case destroyed:
                break;
        default:
                abort();
        }
        if (allocated_p(ctx->inbuf))
                ctl_bufput(&ctx->inbuf);
        for (this = HEAD(ctx->tran); this != NULL; this = next) {
                next = NEXT(this, link);
                if (allocated_p(this->outbuf))
                        ctl_bufput(&this->outbuf);
                if (notify && this->donefunc != NULL)
                        (*this->donefunc)(ctx, this->uap, NULL, 0);
                memput(this, sizeof *this);
        }
        if (ctx->tiID.opaque != NULL)
                stop_timer(ctx);
        new_state(ctx, destroyed);
}

static void
error(struct ctl_cctx *ctx) {
        REQUIRE(ctx->state != destroyed);
        destroy(ctx, 1);
}

static void
new_state(struct ctl_cctx *ctx, enum state new_state) {
        static const char me[] = "isc/ctl_clnt::new_state";

        (*ctx->logger)(ctl_debug, "%s: %s -> %s", me,
                       state_names[ctx->state], state_names[new_state]);
        ctx->state = new_state;
}

static void
conn_done(evContext ev, void *uap, int fd,
          const void *la, int lalen,
          const void *ra, int ralen)
{
        static const char me[] = "isc/ctl_clnt::conn_done";
        struct ctl_cctx *ctx = uap;
        struct ctl_tran *tran;

        UNUSED(ev);
        UNUSED(la);
        UNUSED(lalen);
        UNUSED(ra);
        UNUSED(ralen);

        ctx->coID.opaque = NULL;
        if (fd < 0) {
                (*ctx->logger)(ctl_error, "%s: evConnect: %s", me,
                               strerror(errno));
                error(ctx);
                return;
        }
        new_state(ctx, connected);
        tran = new_tran(ctx, ctx->donefunc, ctx->uap, 0);
        if (tran == NULL) {
                (*ctx->logger)(ctl_error, "%s: new_tran failed: %s", me,
                               strerror(errno));
                error(ctx);
                return;
        }
        start_read(ctx);
        if (ctx->state == destroyed) {
                (*ctx->logger)(ctl_error, "%s: start_read failed: %s",
                               me, strerror(errno));
                error(ctx);
                return;
        }
}

static void
write_done(evContext lev, void *uap, int fd, int bytes) {
        struct ctl_tran *tran = (struct ctl_tran *)uap;
        struct ctl_cctx *ctx = tran->ctx;

        UNUSED(lev);
        UNUSED(fd);

        ctx->wrID.opaque = NULL;
        if (ctx->tiID.opaque != NULL)
                touch_timer(ctx);
        ctl_bufput(&tran->outbuf);
        start_write(ctx);
        if (bytes < 0)
                destroy(ctx, 1);
        else
                start_read(ctx);
}

static void
start_read(struct ctl_cctx *ctx) {
        static const char me[] = "isc/ctl_clnt::start_read";

        REQUIRE(ctx->state == connecting || ctx->state == connected);
        REQUIRE(ctx->rdID.opaque == NULL);
        if (evSelectFD(ctx->ev, ctx->sock, EV_READ, readable, ctx,
                       &ctx->rdID) < 0)
        {
                (*ctx->logger)(ctl_error, "%s: evSelect(fd %d): %s", me,
                               ctx->sock, strerror(errno));
                error(ctx);
                return;
        }
}

static void
stop_read(struct ctl_cctx *ctx) {
        REQUIRE(ctx->coID.opaque == NULL);
        REQUIRE(ctx->rdID.opaque != NULL);
        (void)evDeselectFD(ctx->ev, ctx->rdID);
        ctx->rdID.opaque = NULL;
}

static void
readable(evContext ev, void *uap, int fd, int evmask) {
        static const char me[] = "isc/ctl_clnt::readable";
        struct ctl_cctx *ctx = uap;
        struct ctl_tran *tran;
        ssize_t n;
        char *eos;

        UNUSED(ev);

        REQUIRE(ctx != NULL);
        REQUIRE(fd >= 0);
        REQUIRE(evmask == EV_READ);
        REQUIRE(ctx->state == connected);
        REQUIRE(!EMPTY(ctx->tran));
        tran = HEAD(ctx->tran);
        if (!allocated_p(ctx->inbuf) &&
            ctl_bufget(&ctx->inbuf, ctx->logger) < 0) {
                (*ctx->logger)(ctl_error, "%s: can't get an input buffer", me);
                error(ctx);
                return;
        }
        n = read(ctx->sock, ctx->inbuf.text + ctx->inbuf.used,
                 MAX_LINELEN - ctx->inbuf.used);
        if (n <= 0) {
                (*ctx->logger)(ctl_warning, "%s: read: %s", me,
                               (n == 0) ? "Unexpected EOF" : strerror(errno));
                error(ctx);
                return;
        }
        if (ctx->tiID.opaque != NULL)
                touch_timer(ctx);
        ctx->inbuf.used += n;
        (*ctx->logger)(ctl_debug, "%s: read %d, used %d", me,
                       n, ctx->inbuf.used);
 again:
        eos = memchr(ctx->inbuf.text, '\n', ctx->inbuf.used);
        if (eos != NULL && eos != ctx->inbuf.text && eos[-1] == '\r') {
                int done = 0;

                eos[-1] = '\0';
                if (!arpacode_p(ctx->inbuf.text)) {
                        /* XXX Doesn't FTP do this sometimes? Is it legal? */
                        (*ctx->logger)(ctl_error, "%s: no arpa code (%s)", me,
                                       ctx->inbuf.text);
                        error(ctx);
                        return;
                }
                if (arpadone_p(ctx->inbuf.text))
                        done = 1;
                else if (arpacont_p(ctx->inbuf.text))
                        done = 0;
                else {
                        /* XXX Doesn't FTP do this sometimes? Is it legal? */
                        (*ctx->logger)(ctl_error, "%s: no arpa flag (%s)", me,
                                       ctx->inbuf.text);
                        error(ctx);
                        return;
                }
                (*tran->donefunc)(ctx, tran->uap, ctx->inbuf.text,
                                  (done ? 0 : CTL_MORE));
                ctx->inbuf.used -= ((eos - ctx->inbuf.text) + 1);
                if (ctx->inbuf.used == 0U)
                        ctl_bufput(&ctx->inbuf);
                else
                        memmove(ctx->inbuf.text, eos + 1, ctx->inbuf.used);
                if (done) {
                        UNLINK(ctx->tran, tran, link);
                        memput(tran, sizeof *tran);
                        stop_read(ctx);
                        start_write(ctx);
                        return;
                }
                if (allocated_p(ctx->inbuf))
                        goto again;
                return;
        }
        if (ctx->inbuf.used == (size_t)MAX_LINELEN) {
                (*ctx->logger)(ctl_error, "%s: line too long (%-10s...)", me,
                               ctx->inbuf.text);
                error(ctx);
        }
}

/* Timer related stuff. */

static void
start_timer(struct ctl_cctx *ctx) {
        static const char me[] = "isc/ctl_clnt::start_timer";

        REQUIRE(ctx->tiID.opaque == NULL);
        if (evSetIdleTimer(ctx->ev, timer, ctx, ctx->timeout, &ctx->tiID) < 0){
                (*ctx->logger)(ctl_error, "%s: evSetIdleTimer: %s", me,
                               strerror(errno));
                error(ctx);
                return;
        }
}

static void
stop_timer(struct ctl_cctx *ctx) {
        static const char me[] = "isc/ctl_clnt::stop_timer";

        REQUIRE(ctx->tiID.opaque != NULL);
        if (evClearIdleTimer(ctx->ev, ctx->tiID) < 0) {
                (*ctx->logger)(ctl_error, "%s: evClearIdleTimer: %s", me,
                               strerror(errno));
                error(ctx);
                return;
        }
        ctx->tiID.opaque = NULL;
}

static void
touch_timer(struct ctl_cctx *ctx) {
        REQUIRE(ctx->tiID.opaque != NULL);

        evTouchIdleTimer(ctx->ev, ctx->tiID);
}

static void
timer(evContext ev, void *uap, struct timespec due, struct timespec itv) {
        static const char me[] = "isc/ctl_clnt::timer";
        struct ctl_cctx *ctx = uap;

        UNUSED(ev);
        UNUSED(due);
        UNUSED(itv);

        ctx->tiID.opaque = NULL;
        (*ctx->logger)(ctl_error, "%s: timeout after %u seconds while %s", me,
                       ctx->timeout.tv_sec, state_names[ctx->state]);
        error(ctx);
}

/*! \file */