root/lib/libcasper/libcasper/libcasper_service.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2012 The FreeBSD Foundation
 * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org>
 * Copyright (c) 2017 Robert N. M. Watson
 * All rights reserved.
 *
 * This software was developed by Pawel Jakub Dawidek under sponsorship from
 * the FreeBSD Foundation.
 *
 * This software was developed by SRI International and the University of
 * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237)
 * ("CTSRD"), as part of the DARPA CRASH research programme.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/nv.h>

#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "libcasper_impl.h"
#include "zygote.h"

struct casper_service {
        struct service                  *cs_service;
        TAILQ_ENTRY(casper_service)      cs_next;
};

static TAILQ_HEAD(, casper_service) casper_services =
    TAILQ_HEAD_INITIALIZER(casper_services);

#define CORE_CASPER_NAME                "core.casper"
#define CSERVICE_IS_CORE(service)       \
        (strcmp(service_name(service->cs_service), CORE_CASPER_NAME) == 0)

static struct casper_service *
service_find(const char *name)
{
        struct casper_service *casserv;

        TAILQ_FOREACH(casserv, &casper_services, cs_next) {
                if (strcmp(service_name(casserv->cs_service), name) == 0)
                        break;
        }
        return (casserv);
}

struct casper_service *
service_register(const char *name, service_limit_func_t *limitfunc,
   service_command_func_t *commandfunc, uint64_t flags)
{
        struct casper_service *casserv;

        if (commandfunc == NULL)
                return (NULL);
        if (name == NULL || name[0] == '\0')
                return (NULL);
        if (service_find(name) != NULL)
                return (NULL);

        casserv = malloc(sizeof(*casserv));
        if (casserv == NULL)
                return (NULL);

        casserv->cs_service = service_alloc(name, limitfunc, commandfunc,
            flags);
        if (casserv->cs_service == NULL) {
                free(casserv);
                return (NULL);
        }
        TAILQ_INSERT_TAIL(&casper_services, casserv, cs_next);

        return (casserv);
}

static bool
casper_allowed_service(const nvlist_t *limits, const char *service)
{

        if (limits == NULL)
                return (true);

        if (nvlist_exists_null(limits, service))
                return (true);

        return (false);
}

static int
casper_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
{
        const char *name;
        int type;
        void *cookie;

        cookie = NULL;
        while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
                if (type != NV_TYPE_NULL)
                        return (EINVAL);
                if (!casper_allowed_service(oldlimits, name))
                        return (ENOTCAPABLE);
        }

        return (0);
}

void
service_execute(int chanfd)
{
        struct casper_service *casserv;
        struct service *service;
        const char *servname;
        nvlist_t *nvl;
        int procfd;

        nvl = nvlist_recv(chanfd, 0);
        if (nvl == NULL)
                _exit(1);
        if (!nvlist_exists_string(nvl, "service"))
                _exit(1);
        servname = nvlist_get_string(nvl, "service");
        casserv = service_find(servname);
        if (casserv == NULL)
                _exit(1);
        service = casserv->cs_service;
        procfd = nvlist_take_descriptor(nvl, "procfd");
        nvlist_destroy(nvl);

        service_start(service, chanfd, procfd);
        /* Not reached. */
        _exit(1);
}

static int
casper_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
    nvlist_t *nvlout)
{
        struct casper_service *casserv;
        const char *servname;
        nvlist_t *nvl;
        int chanfd, procfd, error;

        if (strcmp(cmd, "open") != 0)
                return (EINVAL);
        if (!nvlist_exists_string(nvlin, "service"))
                return (EINVAL);

        servname = nvlist_get_string(nvlin, "service");
        casserv = service_find(servname);
        if (casserv == NULL)
                return (ENOENT);

        if (!casper_allowed_service(limits, servname))
                return (ENOTCAPABLE);

        if (zygote_clone_service_execute(&chanfd, &procfd) == -1)
                return (errno);

        nvl = nvlist_create(0);
        nvlist_add_string(nvl, "service", servname);
        nvlist_move_descriptor(nvl, "procfd", procfd);
        if (nvlist_send(chanfd, nvl) == -1) {
                error = errno;
                nvlist_destroy(nvl);
                close(chanfd);
                return (error);
        }
        nvlist_destroy(nvl);

        nvlist_move_descriptor(nvlout, "chanfd", chanfd);
        nvlist_add_number(nvlout, "chanflags",
            service_get_channel_flags(casserv->cs_service));

        return (0);
}

static void
service_register_core(int fd)
{
        struct casper_service *casserv;
        struct service_connection *sconn;

        casserv = service_register(CORE_CASPER_NAME, casper_limit,
            casper_command, 0);
        sconn = service_connection_add(casserv->cs_service, fd, NULL);
        if (sconn == NULL) {
                close(fd);
                abort();
        }
}

void
casper_main_loop(int fd)
{
        fd_set fds;
        struct casper_service *casserv;
        struct service_connection *sconn, *sconntmp;
        int sock, maxfd, ret;

        if (zygote_init() < 0)
                _exit(1);

        /*
         * Register core services.
         */
        service_register_core(fd);

        for (;;) {
                FD_ZERO(&fds);
                FD_SET(fd, &fds);
                maxfd = -1;
                TAILQ_FOREACH(casserv, &casper_services, cs_next) {
                        /* We handle only core services. */
                        if (!CSERVICE_IS_CORE(casserv))
                                continue;
                        for (sconn = service_connection_first(casserv->cs_service);
                            sconn != NULL;
                            sconn = service_connection_next(sconn)) {
                                sock = service_connection_get_sock(sconn);
                                FD_SET(sock, &fds);
                                maxfd = sock > maxfd ? sock : maxfd;
                        }
                }
                if (maxfd == -1) {
                        /* Nothing to do. */
                        _exit(0);
                }
                maxfd++;


                assert(maxfd <= (int)FD_SETSIZE);
                ret = select(maxfd, &fds, NULL, NULL, NULL);
                assert(ret == -1 || ret > 0);   /* select() cannot timeout */
                if (ret == -1) {
                        if (errno == EINTR)
                                continue;
                        _exit(1);
                }

                TAILQ_FOREACH(casserv, &casper_services, cs_next) {
                        /* We handle only core services. */
                        if (!CSERVICE_IS_CORE(casserv))
                                continue;
                        for (sconn = service_connection_first(casserv->cs_service);
                            sconn != NULL; sconn = sconntmp) {
                                /*
                                 * Prepare for connection to be removed from
                                 * the list on failure.
                                 */
                                sconntmp = service_connection_next(sconn);
                                sock = service_connection_get_sock(sconn);
                                if (FD_ISSET(sock, &fds)) {
                                        service_message(casserv->cs_service,
                                            sconn);
                                }
                        }
                }
        }
}