/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/module.h>
#include <linux/types.h>
#include <linux/net.h>
#include <linux/socket.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#include <linux/gfp.h>
#include <linux/uaccess.h>
#include <linux/kernel.h>
#include <linux/msm_ipc.h>
#include <linux/rwsem.h>

#include <asm/uaccess.h>

#include <net/sock.h>
#include "ipc_router.h"
#include "msm_ipc_router_security.h"

#define IRSC_COMPLETION_TIMEOUT_MS 30000
#define SEC_RULES_HASH_SZ 32

#ifndef SIZE_MAX
#define SIZE_MAX ((size_t)-1)
#endif

struct security_rule {
	struct list_head list;
	uint32_t service_id;
	uint32_t instance_id;
	unsigned reserved;
	int num_group_info;
	gid_t *group_id;
};

static DECLARE_RWSEM(security_rules_lock_lha4);
static struct list_head security_rules[SEC_RULES_HASH_SZ];
static DECLARE_COMPLETION(irsc_completion);

/**
 * wait_for_irsc_completion() - Wait for IPC Router Security Configuration
 *                              (IRSC) to complete
 */
void wait_for_irsc_completion(void)
{
	unsigned long rem_jiffies;
	do {
		rem_jiffies = wait_for_completion_timeout(&irsc_completion,
				msecs_to_jiffies(IRSC_COMPLETION_TIMEOUT_MS));
		if (rem_jiffies)
			return;
		pr_err("%s: waiting for IPC Security Conf.\n", __func__);
	} while (1);
}

/**
 * signal_irsc_completion() - Signal the completion of IRSC
 */
void signal_irsc_completion(void)
{
	complete_all(&irsc_completion);
}

/**
 * check_permisions() - Check whether the process has permissions to
 *                      create an interface handle with IPC Router
 *
 * @return: true if the process has permissions, else false.
 */
int check_permissions(void)
{
	int rc = 0;
	if (!current_euid() || in_egroup_p(AID_NET_RAW))
		rc = 1;
	return rc;
}
EXPORT_SYMBOL(check_permissions);

/**
 * msm_ipc_config_sec_rules() - Add a security rule to the database
 * @arg: Pointer to the buffer containing the rule.
 *
 * @return: 0 if successfully added, < 0 for error.
 *
 * A security rule is defined using <Service_ID: Group_ID> tuple. The rule
 * implies that a user-space process in order to send a QMI message to
 * service Service_ID should belong to the Linux group Group_ID.
 */
int msm_ipc_config_sec_rules(void *arg)
{
	struct config_sec_rules_args sec_rules_arg;
	struct security_rule *rule, *temp_rule;
	int key;
	size_t group_info_sz;
	int ret;

	if (current_euid())
		return -EPERM;

	ret = copy_from_user(&sec_rules_arg, (void *)arg,
			     sizeof(sec_rules_arg));
	if (ret)
		return -EFAULT;

	if (sec_rules_arg.num_group_info <= 0)
		return -EINVAL;

	if (sec_rules_arg.num_group_info > (SIZE_MAX / sizeof(gid_t))) {
		pr_err("%s: Integer Overflow %d * %d\n", __func__,
			sizeof(gid_t), sec_rules_arg.num_group_info);
		return -EINVAL;
	}
	group_info_sz = sec_rules_arg.num_group_info * sizeof(gid_t);

	rule = kzalloc(sizeof(struct security_rule), GFP_KERNEL);
	if (!rule) {
		pr_err("%s: security_rule alloc failed\n", __func__);
		return -ENOMEM;
	}

	rule->group_id = kzalloc(group_info_sz, GFP_KERNEL);
	if (!rule->group_id) {
		pr_err("%s: group_id alloc failed\n", __func__);
		kfree(rule);
		return -ENOMEM;
	}

	rule->service_id = sec_rules_arg.service_id;
	rule->instance_id = sec_rules_arg.instance_id;
	rule->reserved = sec_rules_arg.reserved;
	rule->num_group_info = sec_rules_arg.num_group_info;
	ret = copy_from_user(rule->group_id,
			     ((void *)(arg + sizeof(sec_rules_arg))),
			     group_info_sz);
	if (ret) {
		kfree(rule->group_id);
		kfree(rule);
		return -EFAULT;
	}

	key = rule->service_id & (SEC_RULES_HASH_SZ - 1);
	down_write(&security_rules_lock_lha4);
	if (rule->service_id == ALL_SERVICE) {
		temp_rule = list_first_entry(&security_rules[key],
					     struct security_rule, list);
		list_del(&temp_rule->list);
		kfree(temp_rule->group_id);
		kfree(temp_rule);
	}
	list_add_tail(&rule->list, &security_rules[key]);
	up_write(&security_rules_lock_lha4);

	if (rule->service_id == ALL_SERVICE)
		msm_ipc_sync_default_sec_rule((void *)rule);
	else
		msm_ipc_sync_sec_rule(rule->service_id, rule->instance_id,
				      (void *)rule);

	return 0;
}
EXPORT_SYMBOL(msm_ipc_config_sec_rules);

/**
 * msm_ipc_add_default_rule() - Add default security rule
 *
 * @return: 0 on success, < 0 on error/
 *
 * This function is used to ensure the basic security, if there is no
 * security rule defined for a service. It can be overwritten by the
 * default security rule from user-space script.
 */
static int msm_ipc_add_default_rule(void)
{
	struct security_rule *rule;
	int key;

	rule = kzalloc(sizeof(struct security_rule), GFP_KERNEL);
	if (!rule) {
		pr_err("%s: security_rule alloc failed\n", __func__);
		return -ENOMEM;
	}

	rule->group_id = kzalloc(sizeof(int), GFP_KERNEL);
	if (!rule->group_id) {
		pr_err("%s: group_id alloc failed\n", __func__);
		kfree(rule);
		return -ENOMEM;
	}

	rule->service_id = ALL_SERVICE;
	rule->instance_id = ALL_INSTANCE;
	rule->num_group_info = 1;
	*(rule->group_id) = AID_NET_RAW;
	down_write(&security_rules_lock_lha4);
	key = (ALL_SERVICE & (SEC_RULES_HASH_SZ - 1));
	list_add_tail(&rule->list, &security_rules[key]);
	up_write(&security_rules_lock_lha4);
	return 0;
}

/**
 * msm_ipc_get_security_rule() - Get the security rule corresponding to a
 *                               service
 * @service_id: Service ID for which the rule has to be got.
 * @instance_id: Instance ID for which the rule has to be got.
 *
 * @return: Returns the rule info on success, NULL on error.
 *
 * This function is used when the service comes up and gets registered with
 * the IPC Router.
 */
void *msm_ipc_get_security_rule(uint32_t service_id, uint32_t instance_id)
{
	int key;
	struct security_rule *rule;

	key = (service_id & (SEC_RULES_HASH_SZ - 1));
	down_read(&security_rules_lock_lha4);
	/* Return the rule for a specific <service:instance>, if found. */
	list_for_each_entry(rule, &security_rules[key], list) {
		if ((rule->service_id == service_id) &&
		    (rule->instance_id == instance_id)) {
			up_read(&security_rules_lock_lha4);
			return (void *)rule;
		}
	}

	/* Return the rule for a specific service, if found. */
	list_for_each_entry(rule, &security_rules[key], list) {
		if ((rule->service_id == service_id) &&
		    (rule->instance_id == ALL_INSTANCE)) {
			up_read(&security_rules_lock_lha4);
			return (void *)rule;
		}
	}

	/* Return the default rule, if no rule defined for a service. */
	key = (ALL_SERVICE & (SEC_RULES_HASH_SZ - 1));
	list_for_each_entry(rule, &security_rules[key], list) {
		if ((rule->service_id == ALL_SERVICE) &&
		    (rule->instance_id == ALL_INSTANCE)) {
			up_read(&security_rules_lock_lha4);
			return (void *)rule;
		}
	}
	up_read(&security_rules_lock_lha4);
	return NULL;
}
EXPORT_SYMBOL(msm_ipc_get_security_rule);

/**
 * msm_ipc_check_send_permissions() - Check if the sendng process has
 *                                    permissions specified as per the rule
 * @data: Security rule to be checked.
 *
 * @return: true if the process has permissions, else false.
 *
 * This function is used to check if the current executing process has
 * permissions to send message to the remote entity. The security rule
 * corresponding to the remote entity is specified by "data" parameter
 */
int msm_ipc_check_send_permissions(void *data)
{
	int i;
	struct security_rule *rule = (struct security_rule *)data;

	/* Source/Sender is Root user */
	if (!current_euid())
		return 1;

	/* Destination has no rules defined, possibly a client. */
	if (!rule)
		return 1;

	for (i = 0; i < rule->num_group_info; i++) {
		if (in_egroup_p(rule->group_id[i]))
			return 1;
	}
	return 0;
}
EXPORT_SYMBOL(msm_ipc_check_send_permissions);

/**
 * msm_ipc_router_security_init() - Initialize the security rule database
 *
 * @return: 0 if successful, < 0 for error.
 */
int msm_ipc_router_security_init(void)
{
	int i;

	for (i = 0; i < SEC_RULES_HASH_SZ; i++)
		INIT_LIST_HEAD(&security_rules[i]);

	msm_ipc_add_default_rule();
	return 0;
}
EXPORT_SYMBOL(msm_ipc_router_security_init);
