/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2015 Joyent, Inc.
 */

/*
 * varpd door server logic
 */

#include <door.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stropts.h>
#include <stdlib.h>
#include <strings.h>
#include <priv.h>
#include <libvarpd_impl.h>

typedef int (libvarpd_door_f)(varpd_impl_t *, varpd_client_arg_t *, ucred_t *);

static boolean_t
libvarpd_door_privileged(ucred_t *credp)
{
	const priv_set_t *ps;

	ps = ucred_getprivset(credp, PRIV_EFFECTIVE);
	if (ps == NULL)
		return (B_FALSE);

	return (priv_ismember(ps, PRIV_SYS_NET_CONFIG));
}

/* ARGSUSED */
static int
libvarpd_door_f_create(varpd_impl_t *vip, varpd_client_arg_t *vcap,
    ucred_t *credp)
{
	int ret;
	varpd_instance_handle_t *ihdl;
	varpd_client_create_arg_t *vccap = &vcap->vca_un.vca_create;

	vccap->vcca_plugin[LIBVARPD_PROP_NAMELEN-1] = '\0';
	ret = libvarpd_instance_create((varpd_handle_t *)vip,
	    vccap->vcca_linkid, vccap->vcca_plugin, &ihdl);
	if (ret == 0)
		vccap->vcca_id = libvarpd_instance_id(ihdl);

	return (ret);
}

/* ARGSUSED */
static int
libvarpd_door_f_activate(varpd_impl_t *vip, varpd_client_arg_t *vcap,
    ucred_t *credp)
{
	varpd_instance_handle_t *ihp;
	varpd_client_instance_arg_t *vciap = &vcap->vca_un.vca_instance;

	ihp = libvarpd_instance_lookup((varpd_handle_t *)vip, vciap->vcia_id);
	if (ihp == NULL)
		return (ENOENT);
	return (libvarpd_instance_activate(ihp));
}

/* ARGSUSED */
static int
libvarpd_door_f_destroy(varpd_impl_t *vip, varpd_client_arg_t *vcap,
    ucred_t *credp)
{
	varpd_instance_handle_t *ihp;
	varpd_client_instance_arg_t *vciap = &vcap->vca_un.vca_instance;

	ihp = libvarpd_instance_lookup((varpd_handle_t *)vip, vciap->vcia_id);
	if (ihp == NULL)
		return (ENOENT);
	libvarpd_instance_destroy(ihp);
	return (0);
}

/* ARGSUSED */
static int
libvarpd_door_f_nprops(varpd_impl_t *vip, varpd_client_arg_t *vcap,
    ucred_t *credp)
{
	varpd_instance_handle_t *ihp;
	varpd_client_nprops_arg_t *vcnap = &vcap->vca_un.vca_nprops;

	ihp = libvarpd_instance_lookup((varpd_handle_t *)vip, vcnap->vcna_id);
	if (ihp == NULL)
		return (ENOENT);

	return (libvarpd_prop_nprops(ihp, &vcnap->vcna_nprops));
}

/* ARGSUSED */
static int
libvarpd_door_f_propinfo(varpd_impl_t *vip, varpd_client_arg_t *vcap,
    ucred_t *credp)
{
	int ret;
	varpd_instance_handle_t *ihp;
	varpd_prop_handle_t *phdl;
	varpd_client_propinfo_arg_t *vcfap = &vcap->vca_un.vca_info;

	ihp = libvarpd_instance_lookup((varpd_handle_t *)vip, vcfap->vcfa_id);
	if (ihp == NULL)
		return (ENOENT);
	ret = libvarpd_prop_handle_alloc((varpd_handle_t *)vip, ihp, &phdl);
	if (ret != 0)
		return (ret);

	if (vcfap->vcfa_propid != UINT_MAX) {
		ret = libvarpd_prop_info_fill(phdl, vcfap->vcfa_propid);
		if (ret != 0) {
			libvarpd_prop_handle_free(phdl);
			return (ret);
		}
	} else {
		uint_t i, nprop;
		const char *name;

		vcfap->vcfa_name[LIBVARPD_PROP_NAMELEN-1] = '\0';
		ret = libvarpd_prop_nprops(ihp, &nprop);
		if (ret != 0) {
			libvarpd_prop_handle_free(phdl);
			return (ret);
		}
		for (i = 0; i < nprop; i++) {
			ret = libvarpd_prop_info_fill(phdl, i);
			if (ret != 0) {
				libvarpd_prop_handle_free(phdl);
				return (ret);
			}
			ret = libvarpd_prop_info(phdl, &name, NULL, NULL, NULL,
			    NULL, NULL);
			if (ret != 0) {
				libvarpd_prop_handle_free(phdl);
				return (ret);
			}
			if (strcmp(vcfap->vcfa_name, name) == 0)
				break;
		}

		if (i == nprop) {
			libvarpd_prop_handle_free(phdl);
			return (ENOENT);
		}
		vcfap->vcfa_propid = i;
	}
	libvarpd_prop_door_convert(phdl, vcfap);
	libvarpd_prop_handle_free(phdl);
	return (0);
}

/* ARGSUSED */
static int
libvarpd_door_f_getprop(varpd_impl_t *vip, varpd_client_arg_t *vcap,
    ucred_t *credp)
{
	int ret;
	uint32_t size;
	varpd_instance_handle_t *ihp;
	varpd_prop_handle_t *phdl;
	varpd_client_prop_arg_t *vcpap = &vcap->vca_un.vca_prop;

	ihp = libvarpd_instance_lookup((varpd_handle_t *)vip, vcpap->vcpa_id);
	if (ihp == NULL)
		return (ENOENT);
	ret = libvarpd_prop_handle_alloc((varpd_handle_t *)vip, ihp, &phdl);
	if (ret != 0)
		return (ret);

	ret = libvarpd_prop_info_fill(phdl, vcpap->vcpa_propid);
	if (ret != 0) {
		libvarpd_prop_handle_free(phdl);
		return (ret);
	}

	ret = libvarpd_prop_get(phdl, vcpap->vcpa_buf, &size);
	if (ret == 0)
		vcpap->vcpa_bufsize = size;
	libvarpd_prop_handle_free(phdl);
	return (0);
}

/* ARGSUSED */
static int
libvarpd_door_f_setprop(varpd_impl_t *vip, varpd_client_arg_t *vcap,
    ucred_t *credp)
{
	int ret;
	varpd_instance_handle_t *ihp;
	varpd_prop_handle_t *phdl;
	varpd_client_prop_arg_t *vcpap = &vcap->vca_un.vca_prop;

	ihp = libvarpd_instance_lookup((varpd_handle_t *)vip, vcpap->vcpa_id);
	if (ihp == NULL)
		return (ENOENT);
	ret = libvarpd_prop_handle_alloc((varpd_handle_t *)vip, ihp, &phdl);
	if (ret != 0)
		return (ret);

	ret = libvarpd_prop_info_fill(phdl, vcpap->vcpa_propid);
	if (ret != 0) {
		libvarpd_prop_handle_free(phdl);
		return (ret);
	}

	ret = libvarpd_prop_set(phdl, vcpap->vcpa_buf, vcpap->vcpa_bufsize);
	libvarpd_prop_handle_free(phdl);
	return (ret);
}

/* ARGSUSED */
static int
libvarpd_door_f_lookup(varpd_impl_t *vip, varpd_client_arg_t *vcap,
    ucred_t *credp)
{
	varpd_instance_t *inst;
	varpd_client_lookup_arg_t *vclap = &vcap->vca_un.vca_lookup;

	inst = libvarpd_instance_lookup_by_dlid(vip, vclap->vcla_linkid);
	if (inst == NULL)
		return (ENOENT);

	vclap->vcla_id = inst->vri_id;
	return (0);
}

/* ARGSUSED */
static int
libvarpd_door_f_target(varpd_impl_t *vip, varpd_client_arg_t *vcap,
    ucred_t *credp)
{
	varpd_instance_handle_t *ihp;
	varpd_instance_t *inst;
	varpd_client_target_mode_arg_t *vtmap = &vcap->vca_un.vca_mode;

	ihp = libvarpd_instance_lookup((varpd_handle_t *)vip, vtmap->vtma_id);
	if (ihp == NULL)
		return (ENOENT);
	inst = (varpd_instance_t *)ihp;
	vtmap->vtma_dest = inst->vri_dest;
	vtmap->vtma_mode = inst->vri_mode;
	return (0);
}

static int
libvarpd_door_f_flush(varpd_impl_t *vip, varpd_client_arg_t *vcap,
    ucred_t *credp)
{
	varpd_instance_handle_t *ihp;
	varpd_client_target_cache_arg_t *vtcap = &vcap->vca_un.vca_cache;

	if (libvarpd_door_privileged(credp) == B_FALSE)
		return (EPERM);

	ihp = libvarpd_instance_lookup((varpd_handle_t *)vip, vtcap->vtca_id);
	if (ihp == NULL)
		return (ENOENT);
	return (libvarpd_overlay_cache_flush((varpd_instance_t *)ihp));
}

static int
libvarpd_door_f_delete(varpd_impl_t *vip, varpd_client_arg_t *vcap,
    ucred_t *credp)
{
	varpd_instance_handle_t *ihp;
	varpd_client_target_cache_arg_t *vtcap = &vcap->vca_un.vca_cache;

	if (libvarpd_door_privileged(credp) == B_FALSE)
		return (EPERM);

	ihp = libvarpd_instance_lookup((varpd_handle_t *)vip, vtcap->vtca_id);
	if (ihp == NULL)
		return (ENOENT);
	return (libvarpd_overlay_cache_delete((varpd_instance_t *)ihp,
	    vtcap->vtca_key));
}

/* ARGSUSED */
static int
libvarpd_door_f_get(varpd_impl_t *vip, varpd_client_arg_t *vcap,
    ucred_t *credp)
{
	varpd_instance_handle_t *ihp;
	varpd_client_target_cache_arg_t *vtcap = &vcap->vca_un.vca_cache;

	ihp = libvarpd_instance_lookup((varpd_handle_t *)vip, vtcap->vtca_id);
	if (ihp == NULL)
		return (ENOENT);
	return (libvarpd_overlay_cache_get((varpd_instance_t *)ihp,
	    vtcap->vtca_key, &vtcap->vtca_entry));
}

static int
libvarpd_door_f_set(varpd_impl_t *vip, varpd_client_arg_t *vcap,
    ucred_t *credp)
{
	varpd_instance_handle_t *ihp;
	varpd_client_target_cache_arg_t *vtcap = &vcap->vca_un.vca_cache;

	if (libvarpd_door_privileged(credp) == B_FALSE)
		return (EPERM);

	ihp = libvarpd_instance_lookup((varpd_handle_t *)vip, vtcap->vtca_id);
	if (ihp == NULL)
		return (ENOENT);

	return (libvarpd_overlay_cache_set((varpd_instance_t *)ihp,
	    vtcap->vtca_key, &vtcap->vtca_entry));
}

/* ARGSUSED */
static int
libvarpd_door_f_walk(varpd_impl_t *vip, varpd_client_arg_t *vcap,
    ucred_t *credp)
{
	varpd_instance_handle_t *ihp;
	varpd_client_target_walk_arg_t *vctwp = &vcap->vca_un.vca_walk;

	ihp = libvarpd_instance_lookup((varpd_handle_t *)vip, vctwp->vtcw_id);
	if (ihp == NULL)
		return (ENOENT);

	return (libvarpd_overlay_cache_walk_fill((varpd_instance_t *)ihp,
	    &vctwp->vtcw_marker, &vctwp->vtcw_count, vctwp->vtcw_ents));
}

static libvarpd_door_f *libvarpd_door_table[] = {
	libvarpd_door_f_create,
	libvarpd_door_f_activate,
	libvarpd_door_f_destroy,
	libvarpd_door_f_nprops,
	libvarpd_door_f_propinfo,
	libvarpd_door_f_getprop,
	libvarpd_door_f_setprop,
	libvarpd_door_f_lookup,
	libvarpd_door_f_target,
	libvarpd_door_f_flush,
	libvarpd_door_f_delete,
	libvarpd_door_f_get,
	libvarpd_door_f_set,
	libvarpd_door_f_walk
};

/* ARGSUSED */
static void
libvarpd_door_server(void *cookie, char *argp, size_t argsz, door_desc_t *dp,
    uint_t ndesc)
{
	int ret;
	varpd_client_eresp_t err;
	ucred_t *credp = NULL;
	varpd_impl_t *vip = cookie;
	varpd_client_arg_t *vcap = (varpd_client_arg_t *)argp;

	err.vce_command = VARPD_CLIENT_INVALID;
	if (argsz < sizeof (varpd_client_arg_t)) {
		err.vce_errno = EINVAL;
		goto errout;
	}

	if ((ret = door_ucred(&credp)) != 0) {
		err.vce_errno = ret;
		goto errout;
	}

	if (vcap->vca_command == VARPD_CLIENT_INVALID ||
	    vcap->vca_command >= VARPD_CLIENT_MAX) {
		err.vce_errno = EINVAL;
		goto errout;
	}

	vcap->vca_errno = 0;
	ret = libvarpd_door_table[vcap->vca_command - 1](vip, vcap, credp);
	if (ret != 0)
		vcap->vca_errno = ret;

	ucred_free(credp);
	(void) door_return(argp, argsz, NULL, 0);
	return;

errout:
	ucred_free(credp);
	(void) door_return((char *)&err, sizeof (err), NULL, 0);
}

int
libvarpd_door_server_create(varpd_handle_t *vhp, const char *path)
{
	int fd, ret;
	varpd_impl_t *vip = (varpd_impl_t *)vhp;

	mutex_enter(&vip->vdi_lock);
	if (vip->vdi_doorfd >= 0) {
		mutex_exit(&vip->vdi_lock);
		return (EEXIST);
	}

	vip->vdi_doorfd = door_create(libvarpd_door_server, vip,
	    DOOR_REFUSE_DESC | DOOR_NO_CANCEL);
	if (vip->vdi_doorfd == -1) {
		mutex_exit(&vip->vdi_lock);
		return (errno);
	}

	if ((fd = open(path, O_CREAT | O_RDWR, 0666)) == -1) {
		ret = errno;
		if (door_revoke(vip->vdi_doorfd) != 0)
			libvarpd_panic("failed to revoke door: %d",
			    errno);
		mutex_exit(&vip->vdi_lock);
		return (errno);
	}

	if (fchown(fd, UID_NETADM, GID_NETADM) != 0) {
		ret = errno;
		if (door_revoke(vip->vdi_doorfd) != 0)
			libvarpd_panic("failed to revoke door: %d",
			    errno);
		mutex_exit(&vip->vdi_lock);
		return (ret);
	}

	if (close(fd) != 0)
		libvarpd_panic("failed to close door fd %d: %d",
		    fd, errno);
	(void) fdetach(path);
	if (fattach(vip->vdi_doorfd, path) != 0) {
		ret = errno;
		if (door_revoke(vip->vdi_doorfd) != 0)
			libvarpd_panic("failed to revoke door: %d",
			    errno);
		mutex_exit(&vip->vdi_lock);
		return (ret);
	}

	mutex_exit(&vip->vdi_lock);
	return (0);
}

void
libvarpd_door_server_destroy(varpd_handle_t *vhp)
{
	varpd_impl_t *vip = (varpd_impl_t *)vhp;

	mutex_enter(&vip->vdi_lock);
	if (vip->vdi_doorfd != 0) {
		if (door_revoke(vip->vdi_doorfd) != 0)
			libvarpd_panic("failed to revoke door: %d",
			    errno);
		vip->vdi_doorfd = -1;
	}
	mutex_exit(&vip->vdi_lock);
}