/**************************************************** */
/* Rule Set Based Access Control                      */
/* Implementation of the Access Control Decision      */
/* Facility (ADF) - Linux Capabilities (CAP)          */
/* File: rsbac/adf/cap/main.c                         */
/*                                                    */
/* Author and (c) 1999-2001: Amon Ott <ao@rsbac.org>  */
/*                                                    */
/* Last modified: 03/Sep/2001                         */
/**************************************************** */

#include <linux/string.h>
#include <rsbac/types.h>
#include <rsbac/aci.h>
#include <rsbac/adf_main.h>
#include <rsbac/error.h>
#include <rsbac/helpers.h>
#include <rsbac/getname.h>
#include <rsbac/debug.h>

/************************************************* */
/*           Global Variables                      */
/************************************************* */

/************************************************* */
/*          Internal Help functions                */
/************************************************* */

/************************************************* */
/*          Externally visible functions           */
/************************************************* */

enum rsbac_adf_req_ret_t
   rsbac_adf_request_cap (enum  rsbac_adf_request_t     request,
                                rsbac_pid_t             caller_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)
  {
    union rsbac_target_id_t       i_tid;
    union rsbac_attribute_value_t i_attr_val1;

    switch (request)
      {
        case R_MODIFY_ATTRIBUTE:
            switch(attr)
              {
                case A_system_role:
                case A_cap_role:
                case A_min_caps:
                case A_max_caps:
                #ifdef CONFIG_RSBAC_CAP_AUTH_PROT
                case A_auth_may_setuid:
                case A_auth_may_set_cap:
                case A_auth_add_f_cap:
                case A_auth_remove_f_cap:
                #endif
                /* All attributes (remove target!) */
                case A_none:
                  /* Security Officer? */
                  i_tid.user = owner;
                  if (rsbac_get_attr(CAP,
                                     T_USER,
                                     i_tid,
                                     A_cap_role,
                                     &i_attr_val1,
                                     TRUE))
                    {
                      printk(KERN_WARNING
                             "rsbac_adf_request_cap(): rsbac_get_attr() returned error!\n");
                      return(NOT_GRANTED);
                    }
                  /* if sec_officer, then grant */
                  if (i_attr_val1.system_role == SR_security_officer)
                    return(GRANTED);
                  else
                    return(NOT_GRANTED);

                default:
                  return(DO_NOT_CARE);
              }

        case R_READ_ATTRIBUTE:
            switch(attr)
              {
                case A_system_role:
                case A_cap_role:
                case A_min_caps:
                case A_max_caps:
                /* All attributes (remove target!) */
                case A_none:
                  /* Security Officer or Admin? */
                  i_tid.user = owner;
                  if (rsbac_get_attr(CAP,
                                     T_USER,
                                     i_tid,
                                     A_cap_role,
                                     &i_attr_val1,
                                     TRUE))
                    {
                      printk(KERN_WARNING
                             "rsbac_adf_request_cap(): rsbac_get_attr() returned error!\n");
                      return(NOT_GRANTED);
                    }
                  /* if sec_officer, then grant */
                  if(   (i_attr_val1.system_role == SR_security_officer)
                     || (i_attr_val1.system_role == SR_administrator)
                    )
                    return(GRANTED);
                  else
                    return(NOT_GRANTED);

                default:
                  return(DO_NOT_CARE);
              }

        case R_SWITCH_LOG:
            switch(target)
              {
                case T_NONE:
                  /* test owner's cap_role */
                  i_tid.user = owner;
                  if (rsbac_get_attr(CAP,
                                     T_USER,
                                     i_tid,
                                     A_cap_role,
                                     &i_attr_val1,
                                     TRUE))
                    {
                      printk(KERN_WARNING "rsbac_adf_request_cap(): rsbac_get_attr() returned error!\n");
                      return(NOT_GRANTED);
                    }
                  /* security officer? -> grant  */
                  if (i_attr_val1.system_role == SR_security_officer)
                    return(GRANTED);
                  else
                    return(NOT_GRANTED);

                /* all other cases are unknown */
                default: return(DO_NOT_CARE);
              }

        case R_SWITCH_MODULE:
            switch(target)
              {
                case T_NONE:
                  /* we need the switch_target */
                  if(attr != A_switch_target)
                    return(UNDEFINED);
                  /* do not care for other modules */
                  if(   (attr_val.switch_target != CAP)
                     #ifdef CONFIG_RSBAC_CAP_AUTH_PROT
                     && (attr_val.switch_target != AUTH)
                     #endif
                     #ifdef CONFIG_RSBAC_SOFTMODE
                     && (attr_val.switch_target != SOFTMODE)
                     #endif
                    )
                    return(DO_NOT_CARE);
                  /* test owner's cap_role */
                  i_tid.user = owner;
                  if (rsbac_get_attr(CAP,
                                     T_USER,
                                     i_tid,
                                     A_cap_role,
                                     &i_attr_val1,
                                     TRUE))
                    {
                      printk(KERN_WARNING "rsbac_adf_request_cap(): rsbac_get_attr() returned error!\n");
                      return(NOT_GRANTED);
                    }
                  /* security officer? -> grant  */
                  if (i_attr_val1.system_role == SR_security_officer)
                    return(GRANTED);
                  else
                    return(NOT_GRANTED);

                /* all other cases are unknown */
                default: return(DO_NOT_CARE);
              }


/*********************/
        default: return DO_NOT_CARE;
      }

    return(DO_NOT_CARE);
  }; /* end of rsbac_adf_request_cap() */


/*****************************************************************************/
/* If the request returned granted and the operation is performed,           */
/* the following function can be called by the AEF to get all aci set        */
/* correctly. For write accesses that are performed fully within the kernel, */
/* this is usually not done to prevent extra calls, including R_CLOSE for    */
/* cleaning up.                                                              */
/* The second instance of target specification is the new target, if one has */
/* been created, otherwise its values are ignored.                           */
/* On success, 0 is returned, and an error from rsbac/error.h otherwise.     */

int  rsbac_adf_set_attr_cap(
                      enum  rsbac_adf_request_t     request,
                            rsbac_pid_t             caller_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)
  {
    union rsbac_target_id_t       i_tid;
    union rsbac_attribute_value_t i_attr_val1;

    switch (request)
      {
        case R_CHANGE_OWNER:
            switch(target)
              {
                case T_PROCESS:
                  if(attr != A_owner)
                    return(-RSBAC_EINVALIDATTR);
                  /* Adjust Linux caps */
                  i_tid.user = attr_val.owner;
                  #ifdef CONFIG_RSBAC_SOFTMODE
                  if(!rsbac_softmode)
                  #endif
                    {
                      if (rsbac_get_attr(CAP,
                                         T_USER,
                                         i_tid,
                                         A_max_caps,
                                         &i_attr_val1,
                                         FALSE))
                        {
                          printk(KERN_WARNING
                                 "rsbac_adf_request(): rsbac_get_attr() for max_caps returned error!\n");
                        }
                      else
                        {
                          extern spinlock_t task_capability_lock;

                          /* set caps for process */
                          spin_lock(&task_capability_lock);
                          current->cap_permitted &= i_attr_val1.max_caps;
                          current->cap_effective &= i_attr_val1.max_caps;
                          current->cap_inheritable &= i_attr_val1.max_caps;
                          spin_unlock(&task_capability_lock);
                        }
                    }
                  if (rsbac_get_attr(CAP,
                                     T_USER,
                                     i_tid,
                                     A_min_caps,
                                     &i_attr_val1,
                                     FALSE))
                    {
                      printk(KERN_WARNING
                             "rsbac_adf_request(): rsbac_get_attr() for min_caps returned error!\n");
                    }
                  else
                    {
                      extern spinlock_t task_capability_lock;

                      /* set caps for process */
                      spin_lock(&task_capability_lock);
                      current->cap_permitted |= i_attr_val1.min_caps;
                      current->cap_effective |= i_attr_val1.min_caps;
                      current->cap_inheritable |= i_attr_val1.min_caps;
                      spin_unlock(&task_capability_lock);
                    }
                  return 0;

                /* all other cases are unknown */
                default:
                  return(0);
              }
            break;

        case R_EXECUTE:
            switch(target)
              {
                case T_FILE:
                  /* Adjust Linux caps - first user, then program based */
                  /* User must be redone, because caps are cleared by Linux kernel */
                  i_tid.user = owner;
                  #ifdef CONFIG_RSBAC_SOFTMODE
                  if(!rsbac_softmode)
                  #endif
                    {
                      if (rsbac_get_attr(CAP,
                                         T_USER,
                                         i_tid,
                                         A_max_caps,
                                         &i_attr_val1,
                                         FALSE))
                        {
                          printk(KERN_WARNING
                                 "rsbac_adf_request(): rsbac_get_attr() for max_caps returned error!\n");
                        }
                      else
                        {
                          extern spinlock_t task_capability_lock;

                          /* set caps for process */
                          spin_lock(&task_capability_lock);
                          current->cap_permitted &= i_attr_val1.max_caps;
                          current->cap_effective &= i_attr_val1.max_caps;
                          current->cap_inheritable &= i_attr_val1.max_caps;
                          spin_unlock(&task_capability_lock);
                        }
                    }
                  if (rsbac_get_attr(CAP,
                                     T_USER,
                                     i_tid,
                                     A_min_caps,
                                     &i_attr_val1,
                                     FALSE))
                    {
                      printk(KERN_WARNING
                             "rsbac_adf_request(): rsbac_get_attr() for min_caps returned error!\n");
                    }
                  else
                    {
                      extern spinlock_t task_capability_lock;

                      /* set caps for process */
                      spin_lock(&task_capability_lock);
                      current->cap_permitted |= i_attr_val1.min_caps;
                      current->cap_effective |= i_attr_val1.min_caps;
                      current->cap_inheritable |= i_attr_val1.min_caps;
                      spin_unlock(&task_capability_lock);
                    }
                  #ifdef CONFIG_RSBAC_SOFTMODE
                  if(!rsbac_softmode)
                  #endif
                    {
                      if (rsbac_get_attr(CAP,
                                         target,
                                         tid,
                                         A_max_caps,
                                         &i_attr_val1,
                                         FALSE))
                        {
                          printk(KERN_WARNING
                                 "rsbac_adf_request(): rsbac_get_attr() for max_caps returned error!\n");
                        }
                      else
                        {
                          extern spinlock_t task_capability_lock;

                          /* set caps for process */
                          spin_lock(&task_capability_lock);
                          current->cap_permitted &= i_attr_val1.max_caps;
                          current->cap_effective &= i_attr_val1.max_caps;
                          current->cap_inheritable &= i_attr_val1.max_caps;
                          spin_unlock(&task_capability_lock);
                        }
                    }
                  if (rsbac_get_attr(CAP,
                                     target,
                                     tid,
                                     A_min_caps,
                                     &i_attr_val1,
                                     FALSE))
                    {
                      printk(KERN_WARNING
                             "rsbac_adf_request(): rsbac_get_attr() for min_caps returned error!\n");
                    }
                  else
                    {
                      extern spinlock_t task_capability_lock;

                      /* set caps for process */
                      spin_lock(&task_capability_lock);
                      current->cap_permitted |= i_attr_val1.min_caps;
                      current->cap_effective |= i_attr_val1.min_caps;
                      current->cap_inheritable |= i_attr_val1.min_caps;
                      spin_unlock(&task_capability_lock);
                    }
                  return 0;

                /* all other cases are unknown */
                default:
                  return(0);
              }
            break;

/*********************/
        default: return(0);
      }

    return(0);
  }; /* end of rsbac_adf_set_attr_cap() */

/* end of rsbac/adf/cap/main.c */
