/*
 * RSBAC REG decision module for RBAC model
 *
 * Author and (c) 1999-2009 Amon Ott <ao@rsbac.org>
 * Last change: 20/Jan/2009
 */

/* general stuff */
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/string.h>
/* for (un)lock_kernel() */
#include <linux/sched.h>
#include <linux/smp.h>
#include <linux/smp_lock.h>
/* for file access */
#include <linux/fs.h>
#include <linux/seq_file.h>
#include <asm/uaccess.h>
/* rsbac */
#include <rsbac/types.h>
#include <rsbac/reg.h>
#include <rsbac/adf.h>
#include <rsbac/aci.h>
#include <rsbac/lists.h>
#include <rsbac/getname.h>
#include <rsbac/error.h>
#include <rsbac/proc_fs.h>
#include <rsbac/request_groups.h>

#include "rbac.h"

#define RBAC_ROLE_USER \
	{ \
		.parent = 0, \
		.name = "General User", \
		.fd_rights = ((RSBAC_READ_WRITE_REQUEST_VECTOR | RSBAC_EXECUTE_REQUEST_VECTOR) & RSBAC_FD_REQUEST_VECTOR) \
	}
#define RBAC_ROLE_SECOFF \
	{ \
		.parent = 0, \
		.name = "Security Officer", \
		.fd_rights = ((RSBAC_READ_WRITE_REQUEST_VECTOR | RSBAC_EXECUTE_REQUEST_VECTOR | RSBAC_SECURITY_REQUEST_VECTOR) & RSBAC_FD_REQUEST_VECTOR) \
	}
#define RBAC_ROLE_SYSADM \
	{ \
		.parent = 0, \
		.name = "System Admin", \
		.fd_rights = ((RSBAC_READ_WRITE_REQUEST_VECTOR | RSBAC_EXECUTE_REQUEST_VECTOR | RSBAC_SYSTEM_REQUEST_VECTOR) & RSBAC_FD_REQUEST_VECTOR) \
	}


static u_long nr_request_calls = 0;
static u_long nr_set_attr_calls = 0;
static u_long nr_need_overwrite_calls = 0;
static u_long nr_write_calls = 0;
static u_long nr_system_calls = 0;

MODULE_AUTHOR("Amon Ott");
MODULE_DESCRIPTION("RSBAC REG RBAC module");
MODULE_LICENSE("GPL");

static u_int listkey = 769764297;
static long handle = 783497290;

#define SYSLISTROLESHANDLE 98239829;
static long sys_list_roles_handle = 0;
#define SYSGETROLEHANDLE 78400429;
static long sys_get_role_handle = 0;
#define SYSSETROLEHANDLE 183487242;
static long sys_set_role_handle = 0;
#define SYSDELROLEHANDLE 198754082;
static long sys_del_role_handle = 0;

/* Filenames for persistent data in /rsbac.dat dir of ROOT_DEV (max 7 chars) */
#define ROLEFILENAME "rbac_r"
#define USERFILENAME "rbac_u"

#define PROC_NAME "rbac"

/* Version number for on disk data structures */
#define ROLE_LIST_VERSION 1
#define USER_LIST_VERSION 1

static rsbac_list_handle_t role_list_handle = NULL;
static rsbac_list_handle_t user_list_handle = NULL;

/* Helper functions */

static void detach_lists (void)
{
      if(role_list_handle && rsbac_list_detach(&role_list_handle, listkey))
        rsbac_printk(KERN_WARNING "RSBAC REG RBAC module: Unregistering list failed - beware!\n");
      if(user_list_handle && rsbac_list_lol_detach(&user_list_handle, listkey))
        rsbac_printk(KERN_WARNING "RSBAC REG RBAC module: Unregistering user list failed - beware!\n");
}

static void unregister_syscalls (void)
{
  if (sys_list_roles_handle > 0)
    rsbac_reg_unregister_syscall (sys_list_roles_handle);
  if (sys_get_role_handle > 0)
    rsbac_reg_unregister_syscall (sys_get_role_handle);
  if (sys_set_role_handle > 0)
    rsbac_reg_unregister_syscall (sys_set_role_handle);
  if (sys_del_role_handle > 0)
    rsbac_reg_unregister_syscall (sys_del_role_handle);
}

static void cleanup (void)
{
  #if defined(CONFIG_RSBAC_PROC)
  remove_proc_entry(PROC_NAME, proc_rsbac_root_p);
  #endif 
  rsbac_reg_unregister(handle);
  unregister_syscalls();
  detach_lists();
}

/* PROC functions */

#if defined(CONFIG_RSBAC_PROC)
static struct proc_dir_entry * rbac_proc_p;

static int
rbac_proc_show(struct seq_file *m, void *v)
{
  union rsbac_target_id_t       rsbac_target_id;
  union rsbac_attribute_value_t rsbac_attribute_value;

  if (!rsbac_is_initialized())
    return -ENOSYS;

  rsbac_target_id.scd = ST_rsbac;
  rsbac_attribute_value.dummy = 0;
  if (!rsbac_adf_request(R_GET_STATUS_DATA,
                         task_pid(current),
                         T_SCD,
                         rsbac_target_id,
                         A_none,
                         rsbac_attribute_value))
    {
      return -EPERM;
    }
  seq_puts(m, "RSBAC REG RBAC module\n---------------------\n");
  seq_printf(m, "%lu calls to request function.\n",
                 nr_request_calls);
  seq_printf(m, "%lu calls to set_attr function.\n",
                 nr_set_attr_calls);
  seq_printf(m, "%lu calls to need_overwrite function.\n",
                 nr_need_overwrite_calls);
  seq_printf(m, "%lu calls to write function.\n",
                 nr_write_calls);
  seq_printf(m, "%lu RBAC system calls\n",
                 nr_system_calls);
  seq_printf(m, "%li roles defined.\n",
                 rsbac_list_count(role_list_handle));
  seq_printf(m, "%li users have %li roles defined.\n",
                 rsbac_list_lol_count(user_list_handle),
                 rsbac_list_lol_all_subcount(user_list_handle));
  return 0;
}

static ssize_t rbac_proc_open(struct inode *inode, struct file *file)
{
	return single_open(file, rbac_proc_show, NULL);
}

static const struct file_operations rbac_proc_fops = {
       .owner          = THIS_MODULE,
       .open           = rbac_proc_open,
       .read           = seq_read,
       .llseek         = seq_lseek,
       .release        = single_release,
};

#endif /* CONFIG_RSBAC_PROC */

/**** Decision Functions ****/

static rsbac_boolean_t check_fd_right(__u32 role, rsbac_request_vector_t request_vector, u_int inherit)
  {
    struct rbac_role_entry_t entry;

    if (rsbac_list_get_data(role_list_handle, &role, &entry))
      return FALSE;

    if (entry.fd_rights & request_vector)
      return TRUE;

    if (   (entry.parent > 0)
        && (inherit > 0)
       )
      return check_fd_right(entry.parent, request_vector, inherit - 1);

    return FALSE;
  }

static  int request_func  ( enum  rsbac_adf_request_t     request,
                                  rsbac_pid_t             owner_pid,
                            enum  rsbac_target_t          target,
                            union rsbac_target_id_t       tid,
                            enum  rsbac_attribute_t       attr,
                            union rsbac_attribute_value_t attr_val,
                            rsbac_uid_t                   owner)
  {
    __u32 user;
    __u32 * role_array;
    enum rsbac_adf_req_ret_t result = DO_NOT_CARE;
    int count;

    nr_request_calls++;
    switch (target)
      {
        case T_FILE:
          result = NOT_GRANTED;
          user = RSBAC_UID_NUM(owner);
          count = rsbac_list_lol_get_all_subdesc(user_list_handle, &user, (void **) &role_array);
          if (count > 0)
            {
              u_int i;

              for (i=0; i<count; i++)
                {
                  if (check_fd_right(role_array[i], RSBAC_REQUEST_VECTOR(request), MAXINHERIT))
                    {
                      result = GRANTED;
                      break;
                    }
                }
              rsbac_vfree(role_array);
            }
          break;

        default:
          break;
      }
    return result;
  }

static  int set_attr_func ( enum  rsbac_adf_request_t     request,
                                  rsbac_pid_t             owner_pid,
                            enum  rsbac_target_t          target,
                            union rsbac_target_id_t       tid,
                            enum  rsbac_target_t          new_target,
                            union rsbac_target_id_t       new_tid,
                            enum  rsbac_attribute_t       attr,
                            union rsbac_attribute_value_t attr_val,
                            rsbac_uid_t                   owner)
  {
    nr_set_attr_calls++;
    return 0;
  }

static rsbac_boolean_t need_overwrite_func (struct dentry * dentry_p)
  {
    nr_need_overwrite_calls++;
    return FALSE;
  }

static int write_func(rsbac_boolean_t need_lock)
  {
    nr_write_calls++;
    return 0;
  }

static int sys_list_roles (void * arg)
  {
    __u32 * role_array;
    struct rbac_sys_list_roles_arg k_arg;
    int err;
    int tmperr;

    nr_system_calls++;
    if (!arg)
      return -RSBAC_EINVALIDPOINTER;
    err = rsbac_get_user((u_char *) &k_arg, (u_char *) arg, sizeof(k_arg) );
    if (err)
      return err;

    if (!k_arg.maxnum)
      return rsbac_list_count(role_list_handle);

    err = rsbac_list_get_all_desc(role_list_handle, (void **) &role_array);
    if (err <= 0)
      return err;

    err = rsbac_min(err, k_arg.maxnum);

    tmperr = rsbac_put_user((u_char *) role_array, (u_char *) k_arg.role_array, sizeof(*role_array) * err);
    if (tmperr)
      err = tmperr;

    rsbac_vfree (role_array);
    return err;
  }

static int sys_get_role (void * arg)
  {
    struct rbac_sys_get_role_arg k_arg;
    struct rbac_role_entry_t k_entry;
    int err;

    nr_system_calls++;
    if (!arg)
      return -RSBAC_EINVALIDPOINTER;
    err = rsbac_get_user((u_char *) &k_arg, (u_char *) arg, sizeof(k_arg) );
    if (err)
      return err;

    err = rsbac_list_get_data(role_list_handle, &k_arg.role, &k_entry);
    if (!err)
      err = rsbac_put_user((u_char *) &k_entry, (u_char *) k_arg.entry_p, sizeof(k_entry));
    return err;
  }

static int sys_set_role (void * arg)
  {
    struct rbac_sys_set_role_arg k_arg;
    int err;

    nr_system_calls++;
    if (!arg)
      return -RSBAC_EINVALIDPOINTER;
    err = rsbac_get_user((u_char *) &k_arg, (u_char *) arg, sizeof(k_arg) );
    if (err)
      return err;

    err = rsbac_list_add(role_list_handle, &k_arg.role, &k_arg.entry);
    return err;
  }

static int sys_del_role (void * arg)
  {
    int err;
    __u32 k_role;

    nr_system_calls++;
    if (!arg)
      return -RSBAC_EINVALIDPOINTER;
    err = rsbac_get_user((u_char *) &k_role, (u_char *) arg, sizeof(k_role) );
    if (err)
      return err;

    err = rsbac_list_remove(role_list_handle, &k_role);
    if(err)
      return err;
    err = rsbac_list_lol_subremove_from_all(user_list_handle, &k_role);
    return err;
  }

/**** Init ****/

int init_module(void)
{
  struct rsbac_reg_entry_t entry;
  struct rsbac_reg_syscall_entry_t syscall_entry;
  struct rsbac_list_info_t list_info;
  struct rsbac_list_lol_info_t list_lol_info;

  rsbac_printk(KERN_INFO "RSBAC REG RBAC module: Initializing.\n");

  /* clearing registration entries */
  memset(&entry, 0, sizeof(entry));
  memset(&syscall_entry, 0, sizeof(syscall_entry));

  /* Register role list */
  list_info.version = ROLE_LIST_VERSION;
  list_info.key = listkey;
  list_info.desc_size = sizeof(__u32);
  list_info.data_size = sizeof(struct rbac_role_entry_t);
  list_info.max_age = 0;
  if(rsbac_list_register_hashed(RSBAC_LIST_VERSION,
                         &role_list_handle,
                         &list_info,
                         RSBAC_LIST_BACKUP | RSBAC_LIST_AUTO_HASH_RESIZE,
                         NULL,
                         NULL,
                         NULL,
                         ROLEFILENAME,
                         0,
                         1,
                         rsbac_list_hash_u32,
                         NULL))
    {
      rsbac_printk(KERN_WARNING "RSBAC REG RBAC module: Registering role list failed. Unloading.\n");
      return -ENOEXEC;
    }
  /* Create default roles, if list is empty */
  if (!rsbac_list_count(role_list_handle)) {
    __u32 id;
    struct rbac_role_entry_t user_entry = RBAC_ROLE_USER;
    struct rbac_role_entry_t secoff_entry = RBAC_ROLE_SECOFF;
    struct rbac_role_entry_t sysadm_entry = RBAC_ROLE_SYSADM;

    rsbac_printk(KERN_INFO "RSBAC REG RBAC module: role list is empty, generating default roles\n");
    id = 0;
    if (rsbac_list_add(role_list_handle, &id, &user_entry)) {
      rsbac_printk(KERN_WARNING "RSBAC REG RBAC module: adding role 0 failed\n");
    }
    id = 1;
    if (rsbac_list_add(role_list_handle, &id, &secoff_entry)) {
      rsbac_printk(KERN_WARNING "RSBAC REG RBAC module: adding role 1 failed\n");
    }
    id = 2;
    if (rsbac_list_add(role_list_handle, &id, &sysadm_entry)) {
      rsbac_printk(KERN_WARNING "RSBAC REG RBAC module: adding role 2 failed\n");
    }
  }
  /* Register user list of lists */
  list_lol_info.version = ROLE_LIST_VERSION;
  list_lol_info.key = listkey;
  list_lol_info.desc_size = sizeof(__u32); /* uid */
  list_lol_info.data_size = 0;
  list_lol_info.subdesc_size = sizeof(__u32); /* role */
  list_lol_info.subdata_size = 0;
  list_lol_info.max_age = 0;
  if(rsbac_list_lol_register_hashed(RSBAC_LIST_VERSION,
                         &user_list_handle,
                         &list_lol_info,
                         RSBAC_LIST_BACKUP | RSBAC_LIST_AUTO_HASH_RESIZE | RSBAC_LIST_DEF_DATA,
                         NULL, NULL, /* compare / subcompare */
                         NULL, NULL, /* get_conv / get_subconv */
                         NULL, NULL, /* def_data / def_subdata */
                         USERFILENAME,
                         0,
                         1,
                         rsbac_list_hash_u32,
                         NULL))
    {
      rsbac_printk(KERN_WARNING "RSBAC REG RBAC module: Registering user list failed. Unloading.\n");
      cleanup();
      return -ENOEXEC;
    }
  if (!rsbac_list_lol_count(user_list_handle)) {
    __u32 user;
    __u32 role;

   rsbac_printk(KERN_INFO "RSBAC REG RBAC module: user list is empty, generating default users\n");
   user = 0;
   role = 2;
   if (rsbac_list_lol_subadd(user_list_handle, &user, &role, NULL))
      rsbac_printk(KERN_WARNING "RSBAC REG RBAC module: adding user 0 with role 2 failed\n");
   user = 400;
   role = 1;
   if (rsbac_list_lol_subadd(user_list_handle, &user, &role, NULL))
      rsbac_printk(KERN_WARNING "RSBAC REG RBAC module: adding user 400 with role 1 failed\n");
  }

  rsbac_printk(KERN_INFO "RSBAC REG RBAC module: List Version: %u, Name: %s, Handle: %p, Key: %u\n",
         RSBAC_LIST_VERSION, ROLEFILENAME, role_list_handle, listkey);

  /* Register to ADF */
  strcpy(entry.name, "RSBAC REG RBAC module");
  rsbac_printk(KERN_INFO "RSBAC REG RBAC module: REG Version: %u, Name: %s, Handle: %li\n",
         RSBAC_REG_VERSION, entry.name, handle);

  entry.handle = handle;
  entry.request_func = request_func;
  entry.set_attr_func = set_attr_func;
  entry.need_overwrite_func = need_overwrite_func;
  entry.write_func = write_func;
  entry.switch_on = TRUE;

  rsbac_printk(KERN_INFO "RSBAC REG RBAC module: Registering to ADF.\n");
  if(rsbac_reg_register(RSBAC_REG_VERSION, entry) < 0)
    {
      rsbac_printk(KERN_WARNING "RSBAC REG RBAC module: Registering failed. Unloading.\n");
      cleanup();
      return -ENOEXEC;
    }

//  rsbac_printk(KERN_INFO "RSBAC REG RBAC module: REG Version: %u, Name: %s, Dispatcher Handle: %li\n",
//         RSBAC_REG_VERSION, syscall_entry.name, RBAC_sys_list_roles);
  rsbac_printk(KERN_INFO "RSBAC REG RBAC module: Registering syscall.\n");
  strcpy(syscall_entry.name, RBAC_sys_list_roles_name);
  syscall_entry.registration_handle = SYSLISTROLESHANDLE;
  syscall_entry.dispatcher_handle = RBAC_sys_list_roles;
  syscall_entry.syscall_func = sys_list_roles;
  sys_list_roles_handle = rsbac_reg_register_syscall(RSBAC_REG_VERSION, syscall_entry);
  if(sys_list_roles_handle < 0)
    {
      rsbac_printk(KERN_WARNING "RSBAC REG RBAC module: Registering syscall list_roles failed. Unloading.\n");
      cleanup();
      return -ENOEXEC;
    }
  strcpy(syscall_entry.name, RBAC_sys_get_role_name);
  syscall_entry.registration_handle = SYSGETROLEHANDLE;
  syscall_entry.dispatcher_handle = RBAC_sys_get_role;
  syscall_entry.syscall_func = sys_get_role;
  sys_get_role_handle = rsbac_reg_register_syscall(RSBAC_REG_VERSION, syscall_entry);
  if(sys_get_role_handle < 0)
    {
      rsbac_printk(KERN_WARNING "RSBAC REG RBAC module: Registering syscall get_role failed. Unloading.\n");
      cleanup();
      return -ENOEXEC;
    }
  strcpy(syscall_entry.name, RBAC_sys_set_role_name);
  syscall_entry.registration_handle = SYSSETROLEHANDLE;
  syscall_entry.dispatcher_handle = RBAC_sys_set_role;
  syscall_entry.syscall_func = sys_set_role;
  sys_set_role_handle = rsbac_reg_register_syscall(RSBAC_REG_VERSION, syscall_entry);
  if(sys_set_role_handle < 0)
    {
      rsbac_printk(KERN_WARNING "RSBAC REG RBAC module: Registering syscall set_role failed. Unloading.\n");
      cleanup();
      return -ENOEXEC;
    }
  strcpy(syscall_entry.name, RBAC_sys_del_role_name);
  syscall_entry.registration_handle = SYSDELROLEHANDLE;
  syscall_entry.dispatcher_handle = RBAC_sys_del_role;
  syscall_entry.syscall_func = sys_del_role;
  sys_del_role_handle = rsbac_reg_register_syscall(RSBAC_REG_VERSION, syscall_entry);
  if(sys_del_role_handle < 0)
    {
      rsbac_printk(KERN_WARNING "RSBAC REG RBAC module: Registering syscall del_role failed. Unloading.\n");
      cleanup();
      return -ENOEXEC;
    }

  #if defined(CONFIG_RSBAC_PROC)
  rbac_proc_p = proc_create(PROC_NAME, S_IFREG | S_IRUGO, proc_rsbac_root_p, &rbac_proc_fops);
  if(!rbac_proc_p)
    {
      rsbac_printk(KERN_WARNING "RSBAC REG RBAC module: Not loaded due to failed proc entry registering.\n");
      cleanup();
      return -ENOEXEC;
    }
  #endif 

  rsbac_printk(KERN_INFO "RSBAC REG RBAC module: Loaded.\n");

  return 0;
}

void cleanup_module(void)
{
  rsbac_printk(KERN_INFO "RSBAC REG RBAC module: Unregistering.\n");
  cleanup();
  rsbac_printk(KERN_INFO "RSBAC REG RBAC module: Unloaded.\n");
}
