/*-
 * 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/cdefs.h>
#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)
{

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

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

	while (service_have_connections()) {
		if (!service_poll_dispatch())
			_exit(1);
	}

	_exit(0);
}
