/*
 * Copyright (C) 2013-2015 Kay Sievers
 * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
 * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org>
 * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com>
 * Copyright (C) 2013-2015 Linux Foundation
 * Copyright (C) 2014-2015 Djalal Harouni
 *
 * kdbus is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the
 * Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 */

#include <linux/fs.h>
#include <linux/hashtable.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/random.h>
#include <linux/sched.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/uio.h>

#include "bus.h"
#include "notify.h"
#include "connection.h"
#include "domain.h"
#include "endpoint.h"
#include "handle.h"
#include "item.h"
#include "match.h"
#include "message.h"
#include "metadata.h"
#include "names.h"
#include "policy.h"
#include "util.h"

static void kdbus_bus_free(struct kdbus_node *node)
{
	struct kdbus_bus *bus = container_of(node, struct kdbus_bus, node);

	WARN_ON(!list_empty(&bus->monitors_list));
	WARN_ON(!hash_empty(bus->conn_hash));

	kdbus_notify_free(bus);

	kdbus_user_unref(bus->creator);
	kdbus_name_registry_free(bus->name_registry);
	kdbus_domain_unref(bus->domain);
	kdbus_policy_db_clear(&bus->policy_db);
	kdbus_meta_proc_unref(bus->creator_meta);
	kfree(bus);
}

static void kdbus_bus_release(struct kdbus_node *node, bool was_active)
{
	struct kdbus_bus *bus = container_of(node, struct kdbus_bus, node);

	if (was_active)
		atomic_dec(&bus->creator->buses);
}

static struct kdbus_bus *kdbus_bus_new(struct kdbus_domain *domain,
				       const char *name,
				       struct kdbus_bloom_parameter *bloom,
				       const u64 *pattach_owner,
				       const u64 *pattach_recv,
				       u64 flags, kuid_t uid, kgid_t gid)
{
	struct kdbus_bus *b;
	u64 attach_owner;
	u64 attach_recv;
	int ret;

	if (bloom->size < 8 || bloom->size > KDBUS_BUS_BLOOM_MAX_SIZE ||
	    !KDBUS_IS_ALIGNED8(bloom->size) || bloom->n_hash < 1)
		return ERR_PTR(-EINVAL);

	ret = kdbus_sanitize_attach_flags(pattach_recv ? *pattach_recv : 0,
					  &attach_recv);
	if (ret < 0)
		return ERR_PTR(ret);

	ret = kdbus_sanitize_attach_flags(pattach_owner ? *pattach_owner : 0,
					  &attach_owner);
	if (ret < 0)
		return ERR_PTR(ret);

	ret = kdbus_verify_uid_prefix(name, domain->user_namespace, uid);
	if (ret < 0)
		return ERR_PTR(ret);

	b = kzalloc(sizeof(*b), GFP_KERNEL);
	if (!b)
		return ERR_PTR(-ENOMEM);

	kdbus_node_init(&b->node, KDBUS_NODE_BUS);

	b->node.free_cb = kdbus_bus_free;
	b->node.release_cb = kdbus_bus_release;
	b->node.uid = uid;
	b->node.gid = gid;
	b->node.mode = S_IRUSR | S_IXUSR;

	if (flags & (KDBUS_MAKE_ACCESS_GROUP | KDBUS_MAKE_ACCESS_WORLD))
		b->node.mode |= S_IRGRP | S_IXGRP;
	if (flags & KDBUS_MAKE_ACCESS_WORLD)
		b->node.mode |= S_IROTH | S_IXOTH;

	b->id = atomic64_inc_return(&domain->last_id);
	b->bus_flags = flags;
	b->attach_flags_req = attach_recv;
	b->attach_flags_owner = attach_owner;
	generate_random_uuid(b->id128);
	b->bloom = *bloom;
	b->domain = kdbus_domain_ref(domain);

	kdbus_policy_db_init(&b->policy_db);

	init_rwsem(&b->conn_rwlock);
	hash_init(b->conn_hash);
	INIT_LIST_HEAD(&b->monitors_list);

	INIT_LIST_HEAD(&b->notify_list);
	spin_lock_init(&b->notify_lock);
	mutex_init(&b->notify_flush_lock);

	ret = kdbus_node_link(&b->node, &domain->node, name);
	if (ret < 0)
		goto exit_unref;

	/* cache the metadata/credentials of the creator */
	b->creator_meta = kdbus_meta_proc_new();
	if (IS_ERR(b->creator_meta)) {
		ret = PTR_ERR(b->creator_meta);
		b->creator_meta = NULL;
		goto exit_unref;
	}

	ret = kdbus_meta_proc_collect(b->creator_meta,
				      KDBUS_ATTACH_CREDS |
				      KDBUS_ATTACH_PIDS |
				      KDBUS_ATTACH_AUXGROUPS |
				      KDBUS_ATTACH_TID_COMM |
				      KDBUS_ATTACH_PID_COMM |
				      KDBUS_ATTACH_EXE |
				      KDBUS_ATTACH_CMDLINE |
				      KDBUS_ATTACH_CGROUP |
				      KDBUS_ATTACH_CAPS |
				      KDBUS_ATTACH_SECLABEL |
				      KDBUS_ATTACH_AUDIT);
	if (ret < 0)
		goto exit_unref;

	b->name_registry = kdbus_name_registry_new();
	if (IS_ERR(b->name_registry)) {
		ret = PTR_ERR(b->name_registry);
		b->name_registry = NULL;
		goto exit_unref;
	}

	/*
	 * Bus-limits of the creator are accounted on its real UID, just like
	 * all other per-user limits.
	 */
	b->creator = kdbus_user_lookup(domain, current_uid());
	if (IS_ERR(b->creator)) {
		ret = PTR_ERR(b->creator);
		b->creator = NULL;
		goto exit_unref;
	}

	return b;

exit_unref:
	kdbus_node_deactivate(&b->node);
	kdbus_node_unref(&b->node);
	return ERR_PTR(ret);
}

/**
 * kdbus_bus_ref() - increase the reference counter of a kdbus_bus
 * @bus:		The bus to reference
 *
 * Every user of a bus, except for its creator, must add a reference to the
 * kdbus_bus using this function.
 *
 * Return: the bus itself
 */
struct kdbus_bus *kdbus_bus_ref(struct kdbus_bus *bus)
{
	if (bus)
		kdbus_node_ref(&bus->node);
	return bus;
}

/**
 * kdbus_bus_unref() - decrease the reference counter of a kdbus_bus
 * @bus:		The bus to unref
 *
 * Release a reference. If the reference count drops to 0, the bus will be
 * freed.
 *
 * Return: NULL
 */
struct kdbus_bus *kdbus_bus_unref(struct kdbus_bus *bus)
{
	if (bus)
		kdbus_node_unref(&bus->node);
	return NULL;
}

/**
 * kdbus_bus_find_conn_by_id() - find a connection with a given id
 * @bus:		The bus to look for the connection
 * @id:			The 64-bit connection id
 *
 * Looks up a connection with a given id. The returned connection
 * is ref'ed, and needs to be unref'ed by the user. Returns NULL if
 * the connection can't be found.
 */
struct kdbus_conn *kdbus_bus_find_conn_by_id(struct kdbus_bus *bus, u64 id)
{
	struct kdbus_conn *conn, *found = NULL;

	down_read(&bus->conn_rwlock);
	hash_for_each_possible(bus->conn_hash, conn, hentry, id)
		if (conn->id == id) {
			found = kdbus_conn_ref(conn);
			break;
		}
	up_read(&bus->conn_rwlock);

	return found;
}

/**
 * kdbus_bus_broadcast() - send a message to all subscribed connections
 * @bus:	The bus the connections are connected to
 * @conn_src:	The source connection, may be %NULL for kernel notifications
 * @kmsg:	The message to send.
 *
 * Send @kmsg to all connections that are currently active on the bus.
 * Connections must still have matches installed in order to let the message
 * pass.
 *
 * The caller must hold the name-registry lock of @bus.
 */
void kdbus_bus_broadcast(struct kdbus_bus *bus,
			 struct kdbus_conn *conn_src,
			 struct kdbus_kmsg *kmsg)
{
	struct kdbus_conn *conn_dst;
	unsigned int i;
	int ret;

	lockdep_assert_held(&bus->name_registry->rwlock);

	/*
	 * Make sure broadcast are queued on monitors before we send it out to
	 * anyone else. Otherwise, connections might react to broadcasts before
	 * the monitor gets the broadcast queued. In the worst case, the
	 * monitor sees a reaction to the broadcast before the broadcast itself.
	 * We don't give ordering guarantees across connections (and monitors
	 * can re-construct order via sequence numbers), but we should at least
	 * try to avoid re-ordering for monitors.
	 */
	kdbus_bus_eavesdrop(bus, conn_src, kmsg);

	down_read(&bus->conn_rwlock);
	hash_for_each(bus->conn_hash, i, conn_dst, hentry) {
		if (conn_dst->id == kmsg->msg.src_id)
			continue;
		if (!kdbus_conn_is_ordinary(conn_dst))
			continue;

		/*
		 * Check if there is a match for the kmsg object in
		 * the destination connection match db
		 */
		if (!kdbus_match_db_match_kmsg(conn_dst->match_db, conn_src,
					       kmsg))
			continue;

		if (conn_src) {
			/*
			 * Anyone can send broadcasts, as they have no
			 * destination. But a receiver needs TALK access to
			 * the sender in order to receive broadcasts.
			 */
			if (!kdbus_conn_policy_talk(conn_dst, NULL, conn_src))
				continue;

			ret = kdbus_kmsg_collect_metadata(kmsg, conn_src,
							  conn_dst);
			if (ret < 0) {
				kdbus_conn_lost_message(conn_dst);
				continue;
			}
		} else {
			/*
			 * Check if there is a policy db that prevents the
			 * destination connection from receiving this kernel
			 * notification
			 */
			if (!kdbus_conn_policy_see_notification(conn_dst, NULL,
								kmsg))
				continue;
		}

		ret = kdbus_conn_entry_insert(conn_src, conn_dst, kmsg, NULL);
		if (ret < 0)
			kdbus_conn_lost_message(conn_dst);
	}
	up_read(&bus->conn_rwlock);
}

/**
 * kdbus_bus_eavesdrop() - send a message to all subscribed monitors
 * @bus:	The bus the monitors are connected to
 * @conn_src:	The source connection, may be %NULL for kernel notifications
 * @kmsg:	The message to send.
 *
 * Send @kmsg to all monitors that are currently active on the bus. Monitors
 * must still have matches installed in order to let the message pass.
 *
 * The caller must hold the name-registry lock of @bus.
 */
void kdbus_bus_eavesdrop(struct kdbus_bus *bus,
			 struct kdbus_conn *conn_src,
			 struct kdbus_kmsg *kmsg)
{
	struct kdbus_conn *conn_dst;
	int ret;

	/*
	 * Monitor connections get all messages; ignore possible errors
	 * when sending messages to monitor connections.
	 */

	lockdep_assert_held(&bus->name_registry->rwlock);

	down_read(&bus->conn_rwlock);
	list_for_each_entry(conn_dst, &bus->monitors_list, monitor_entry) {
		if (conn_src) {
			ret = kdbus_kmsg_collect_metadata(kmsg, conn_src,
							  conn_dst);
			if (ret < 0) {
				kdbus_conn_lost_message(conn_dst);
				continue;
			}
		}

		ret = kdbus_conn_entry_insert(conn_src, conn_dst, kmsg, NULL);
		if (ret < 0)
			kdbus_conn_lost_message(conn_dst);
	}
	up_read(&bus->conn_rwlock);
}

/**
 * kdbus_cmd_bus_make() - handle KDBUS_CMD_BUS_MAKE
 * @domain:		domain to operate on
 * @argp:		command payload
 *
 * Return: NULL or newly created bus on success, ERR_PTR on failure.
 */
struct kdbus_bus *kdbus_cmd_bus_make(struct kdbus_domain *domain,
				     void __user *argp)
{
	struct kdbus_bus *bus = NULL;
	struct kdbus_cmd *cmd;
	struct kdbus_ep *ep = NULL;
	int ret;

	struct kdbus_arg argv[] = {
		{ .type = KDBUS_ITEM_NEGOTIATE },
		{ .type = KDBUS_ITEM_MAKE_NAME, .mandatory = true },
		{ .type = KDBUS_ITEM_BLOOM_PARAMETER, .mandatory = true },
		{ .type = KDBUS_ITEM_ATTACH_FLAGS_SEND },
		{ .type = KDBUS_ITEM_ATTACH_FLAGS_RECV },
	};
	struct kdbus_args args = {
		.allowed_flags = KDBUS_FLAG_NEGOTIATE |
				 KDBUS_MAKE_ACCESS_GROUP |
				 KDBUS_MAKE_ACCESS_WORLD,
		.argv = argv,
		.argc = ARRAY_SIZE(argv),
	};

	ret = kdbus_args_parse(&args, argp, &cmd);
	if (ret < 0)
		return ERR_PTR(ret);
	if (ret > 0)
		return NULL;

	bus = kdbus_bus_new(domain,
			    argv[1].item->str, &argv[2].item->bloom_parameter,
			    argv[3].item ? argv[3].item->data64 : NULL,
			    argv[4].item ? argv[4].item->data64 : NULL,
			    cmd->flags, current_euid(), current_egid());
	if (IS_ERR(bus)) {
		ret = PTR_ERR(bus);
		bus = NULL;
		goto exit;
	}

	if (atomic_inc_return(&bus->creator->buses) > KDBUS_USER_MAX_BUSES) {
		atomic_dec(&bus->creator->buses);
		ret = -EMFILE;
		goto exit;
	}

	if (!kdbus_node_activate(&bus->node)) {
		atomic_dec(&bus->creator->buses);
		ret = -ESHUTDOWN;
		goto exit;
	}

	ep = kdbus_ep_new(bus, "bus", cmd->flags, bus->node.uid, bus->node.gid,
			  false);
	if (IS_ERR(ep)) {
		ret = PTR_ERR(ep);
		ep = NULL;
		goto exit;
	}

	if (!kdbus_node_activate(&ep->node)) {
		ret = -ESHUTDOWN;
		goto exit;
	}

	/*
	 * Drop our own reference, effectively causing the endpoint to be
	 * deactivated and released when the parent bus is.
	 */
	ep = kdbus_ep_unref(ep);

exit:
	ret = kdbus_args_clear(&args, ret);
	if (ret < 0) {
		if (ep) {
			kdbus_node_deactivate(&ep->node);
			kdbus_ep_unref(ep);
		}
		if (bus) {
			kdbus_node_deactivate(&bus->node);
			kdbus_bus_unref(bus);
		}
		return ERR_PTR(ret);
	}
	return bus;
}

/**
 * kdbus_cmd_bus_creator_info() - handle KDBUS_CMD_BUS_CREATOR_INFO
 * @conn:		connection to operate on
 * @argp:		command payload
 *
 * Return: >=0 on success, negative error code on failure.
 */
int kdbus_cmd_bus_creator_info(struct kdbus_conn *conn, void __user *argp)
{
	struct kdbus_cmd_info *cmd;
	struct kdbus_bus *bus = conn->ep->bus;
	struct kdbus_pool_slice *slice = NULL;
	struct kdbus_item_header item_hdr;
	struct kdbus_info info = {};
	size_t meta_size, name_len;
	struct kvec kvec[5];
	u64 hdr_size = 0;
	u64 attach_flags;
	size_t cnt = 0;
	int ret;

	struct kdbus_arg argv[] = {
		{ .type = KDBUS_ITEM_NEGOTIATE },
	};
	struct kdbus_args args = {
		.allowed_flags = KDBUS_FLAG_NEGOTIATE,
		.argv = argv,
		.argc = ARRAY_SIZE(argv),
	};

	ret = kdbus_args_parse(&args, argp, &cmd);
	if (ret != 0)
		return ret;

	ret = kdbus_sanitize_attach_flags(cmd->attach_flags, &attach_flags);
	if (ret < 0)
		goto exit;

	attach_flags &= bus->attach_flags_owner;

	ret = kdbus_meta_export_prepare(bus->creator_meta, NULL,
					&attach_flags, &meta_size);
	if (ret < 0)
		goto exit;

	name_len = strlen(bus->node.name) + 1;
	info.id = bus->id;
	info.flags = bus->bus_flags;
	item_hdr.type = KDBUS_ITEM_MAKE_NAME;
	item_hdr.size = KDBUS_ITEM_HEADER_SIZE + name_len;

	kdbus_kvec_set(&kvec[cnt++], &info, sizeof(info), &hdr_size);
	kdbus_kvec_set(&kvec[cnt++], &item_hdr, sizeof(item_hdr), &hdr_size);
	kdbus_kvec_set(&kvec[cnt++], bus->node.name, name_len, &hdr_size);
	cnt += !!kdbus_kvec_pad(&kvec[cnt], &hdr_size);

	slice = kdbus_pool_slice_alloc(conn->pool, hdr_size + meta_size, false);
	if (IS_ERR(slice)) {
		ret = PTR_ERR(slice);
		slice = NULL;
		goto exit;
	}

	ret = kdbus_meta_export(bus->creator_meta, NULL, attach_flags,
				slice, hdr_size, &meta_size);
	if (ret < 0)
		goto exit;

	info.size = hdr_size + meta_size;

	ret = kdbus_pool_slice_copy_kvec(slice, 0, kvec, cnt, hdr_size);
	if (ret < 0)
		goto exit;

	kdbus_pool_slice_publish(slice, &cmd->offset, &cmd->info_size);

	if (kdbus_member_set_user(&cmd->offset, argp, typeof(*cmd), offset) ||
	    kdbus_member_set_user(&cmd->info_size, argp,
				  typeof(*cmd), info_size))
		ret = -EFAULT;

exit:
	kdbus_pool_slice_release(slice);

	return kdbus_args_clear(&args, ret);
}
