/************************************************* */
/* Rule Set Based Access Control                   */
/* Implementation of the Access Control Decision   */
/* Facility (ADF) - Functional Control             */
/* File: rsbac/adf/fc/main.c                       */
/*                                                 */
/* Author and (c) 1999,2000: Amon Ott              */
/*                                                 */
/* Last modified: 14/Apr/2000                      */
/************************************************* */

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

#ifndef NULL
#define NULL ((void *) 0)
#endif

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

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

static enum rsbac_adf_req_ret_t
         check_role_fc(   enum  rsbac_target_t          target,
                          union rsbac_target_id_t       tid,
                                rsbac_uid_t             owner)
  {
    int                           err;
    union rsbac_target_id_t       i_tid;
    union rsbac_attribute_value_t i_attr_val1;
    union rsbac_attribute_value_t i_attr_val2;

    /* test object's object_category */
    if ((err=rsbac_get_attr(target,
                            tid,
                            A_object_category,
                            &i_attr_val1,
                            TRUE)))
      {
        printk(KERN_WARNING "check_role_fc(): rsbac_get_attr() returned error %i!\n",err);
        return(NOT_GRANTED);
      }
    /* Access to category general is always granted */
    if (i_attr_val1.object_category == OC_general)
      return(GRANTED);

    /* test owner's system_role */
    i_tid.user = owner;
    if ((err=rsbac_get_attr(T_USER,
                       i_tid,
                       A_system_role,
                       &i_attr_val2,
                       TRUE)))
      {
        printk(KERN_WARNING "check_role_fc(): rsbac_get_attr() returned error %i!\n",err);
        return(NOT_GRANTED);
      }

    /* Access is granted, if object_category and system_role fit */

    if (    (   (i_attr_val1.object_category == OC_system)
             && (i_attr_val2.system_role == SR_administrator)
            )
         || (   (i_attr_val1.object_category == OC_security)
             && (i_attr_val2.system_role == SR_security_officer)
            )
       )
      return(GRANTED);
    else
      {
/*        printk(KERN_DEBUG "check_role_fc(): role is %i, obj_cat is %i -> NOT_GRANTED!\n",
               i_attr_val2.system_role, i_attr_val1.object_category); */
        return(NOT_GRANTED);
      }
  }

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

enum rsbac_adf_req_ret_t
   rsbac_adf_request_fc  (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)
  {
    int                           err;
    enum  rsbac_adf_req_ret_t result = DO_NOT_CARE;
    union rsbac_target_id_t       i_tid;
    union rsbac_attribute_value_t i_attr_val1;
#ifdef CONFIG_RSBAC_FC_ROLE_PROT
    union rsbac_attribute_value_t i_attr_val2;
#endif

    switch (request)
      {
        case R_ADD_TO_KERNEL:
            switch(target)
              {
                case T_FILE:
                case T_NONE:
                  /* test owner's system_role */
                  i_tid.user = owner;
                  if ((err=rsbac_get_attr(T_USER,
                                     i_tid,
                                     A_system_role,
                                     &i_attr_val1,
                                     TRUE)))
                    {
                      printk(KERN_WARNING "rsbac_adf_request_fc(): rsbac_get_attr() returned error %i!\n",err);
                      return(NOT_GRANTED);
                    }
                  /* only administrators may add to kernel */
                  if (i_attr_val1.system_role != SR_administrator)
                    return(NOT_GRANTED);

                  /* test access to file */
                  if (target == T_NONE)
                    return(GRANTED);
                  else
                    return(check_role_fc(target,tid,owner));

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


        case R_ALTER:
            /* only for IPC */
            switch(target)
              {
                case T_IPC:
                  return(check_role_fc(target,tid,owner));

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

        case R_APPEND_OPEN:
        case R_READ_WRITE_OPEN:
            switch(target)
              {
                case T_FILE:
                case T_DEV:
                case T_IPC:
                  return(check_role_fc(target,tid,owner));

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

        case R_CHANGE_GROUP:
            switch(target)
              {
                case T_FILE:
                case T_DIR:
                case T_IPC:
                  return(check_role_fc(target,tid,owner));

                case T_PROCESS:
                case T_USER:
                  return(DO_NOT_CARE);
                /* all other cases are undefined */
                default: return(DO_NOT_CARE);
              }

        case R_CHANGE_OWNER:
            switch(target)
              {
                case T_FILE:
                case T_DIR:
                case T_IPC:
                  return(check_role_fc(target,tid,owner));
                  
                case T_PROCESS:
                  #ifdef CONFIG_RSBAC_FC_ROLE_PROT
                  if(attr != A_owner)
                    return(UNDEFINED);
                  /* Administrator or secoff? */
                  i_tid.user = owner;
                  if (rsbac_get_attr(T_USER,
                                     i_tid,
                                     A_system_role,
                                     &i_attr_val1,
                                     TRUE))
                    {
                      printk(KERN_WARNING
                             "rsbac_adf_request_fc(): rsbac_get_attr() returned error!\n");
                      return(NOT_GRANTED);
                    }
                  /* if general user, then grant */
                  if (i_attr_val1.system_role == SR_user)
                    return(GRANTED);
                  /* get target user's role */
                  i_tid.user = attr_val.owner;
                  if (rsbac_get_attr(T_USER,
                                     i_tid,
                                     A_system_role,
                                     &i_attr_val2,
                                     TRUE))
                    {
                      printk(KERN_WARNING
                             "rsbac_adf_request_fc(): rsbac_get_attr() returned error!\n");
                      return(NOT_GRANTED);
                    }
                  /* target is no general user and has different role -> deny */
                  if (   (i_attr_val2.system_role != SR_user)
                      && (i_attr_val1.system_role != i_attr_val2.system_role)
                     )
                    return(NOT_GRANTED);
                  else
                    return(GRANTED);
                  #endif

                  /* fall through... */
                case T_NONE:
                  return(DO_NOT_CARE);
                /* all other cases are undefined */
                default:
                  return(DO_NOT_CARE);
              }

        case R_CHDIR:
        case R_READ:
        case R_SEARCH:
        /* Moving to other dir only */
        case R_WRITE:
            switch(target)
              {
                case T_DIR: 
                  return(check_role_fc(target,tid,owner));

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

        case R_CLONE:
        case R_TRACE:
        case R_SEND_SIGNAL:
        case R_TERMINATE:
            if (target == T_PROCESS)
              return(DO_NOT_CARE);
            else
              return(DO_NOT_CARE);

        /* Creating dir or (pseudo) file IN target dir! */
        case R_CREATE:
            switch(target)
              {
                case T_DIR: 
                  return(check_role_fc(target,tid,owner));
                /* IPC is always granted */
                case T_IPC:
                  return(GRANTED);

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

        case R_DELETE:
            switch(target)
              {
                case T_FILE:
                case T_DIR:
                case T_IPC:
                  return(check_role_fc(target,tid,owner));

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

        case R_EXECUTE:
            switch(target)
              {
                case T_FILE:
                  return(check_role_fc(target,tid,owner));

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

        case R_GET_PERMISSIONS_DATA:
            switch(target)
              {
                case T_FILE:
                case T_DIR:
                case T_IPC:
                case T_SCD:
                  return(DO_NOT_CARE);
                default:
                  return(DO_NOT_CARE);
               };

        case R_GET_STATUS_DATA:
            switch(target)
              {
                case T_FILE:
                case T_DIR:
                case T_IPC:
                  return(DO_NOT_CARE);
                case T_SCD:
                  /* target rsbaclog? only for secoff */
                  if (tid.scd != ST_rsbaclog)
                    return(GRANTED);
                  /* Secoff? */
                  i_tid.user = owner;
                  if ((err=rsbac_get_attr(T_USER,
                                     i_tid,
                                     A_system_role,
                                     &i_attr_val1,
                                     TRUE)))
                    {
                      printk(KERN_WARNING
                             "rsbac_adf_request_fc(): rsbac_get_attr() returned error %i!\n",err);
                      return(NOT_GRANTED);
                    }
                  /* grant only for secoff */
                  if (i_attr_val1.system_role == SR_security_officer)
                    return(GRANTED);
                  else
                    return(NOT_GRANTED);
                default:
                  return(DO_NOT_CARE);
               };

        case R_LINK_HARD:
        case R_TRUNCATE:
            switch(target)
              {
                case T_FILE:
                  return(check_role_fc(target,tid,owner));

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

        case R_MODIFY_ACCESS_DATA:
        case R_RENAME:
            switch(target)
              {
                case T_FILE:
                case T_DIR:
                  return(check_role_fc(target,tid,owner));

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

        case R_MODIFY_ATTRIBUTE:
            switch(attr)
              { /* owner must be changed by other request to prevent inconsistency */
                case A_owner:
                  return(NOT_GRANTED);
                case A_system_role:
                case A_object_category:
                case A_log_array_low:
                case A_log_array_high:
                case A_log_program_based:
                case A_log_user_based:
                #ifdef CONFIG_RSBAC_FC_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 ((err=rsbac_get_attr(T_USER,
                                     i_tid,
                                     A_system_role,
                                     &i_attr_val1,
                                     TRUE)))
                    {
                      printk(KERN_WARNING
                             "rsbac_adf_request_fc(): rsbac_get_attr() returned error %i!\n",err);
                      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_MODIFY_PERMISSIONS_DATA:
            switch(target)
              {
                case T_FILE:
                case T_DIR:
                case T_IPC:
                  return(check_role_fc(target,tid,owner));
                  
                case T_SCD:
                  #ifdef CONFIG_RSBAC_USER_MOD_IOPERM
                  if(tid.scd == ST_ioports)
                    return GRANTED;
                  #endif
                  /* Security Officer? */
                  i_tid.user = owner;
                  if ((err=rsbac_get_attr(T_USER,
                                     i_tid,
                                     A_system_role,
                                     &i_attr_val1,
                                     TRUE)))
                    {
                      printk(KERN_WARNING
                             "rsbac_adf_request_fc(): rsbac_get_attr() returned error %i!\n",err);
                      return(NOT_GRANTED);
                    }
                  /* if sec_officer, then grant */
                  if (i_attr_val1.system_role == SR_security_officer)
                    return(GRANTED);
                  /* For booting: if administrator and ioports, then grant */
                  if (   (i_attr_val1.system_role == SR_administrator)
                      && (tid.scd == ST_ioports) )
                    return(GRANTED);
                  else
                    return(NOT_GRANTED);
                  
                /* all other cases are undefined */
                default: return(DO_NOT_CARE);
              }

        case R_MODIFY_SYSTEM_DATA:
            switch(target)
              {
                case T_SCD:
                  /* target rlimit? no problem, but needed -> grant */
                  if (tid.scd == ST_rlimit)
                    return(GRANTED);
                  /* Administrator? */
                  i_tid.user = owner;
                  if ((err=rsbac_get_attr(T_USER,
                                     i_tid,
                                     A_system_role,
                                     &i_attr_val1,
                                     TRUE)))
                    {
                      printk(KERN_WARNING
                             "rsbac_adf_request_fc(): rsbac_get_attr() returned error %i!\n",err);
                      return(NOT_GRANTED);
                    }
                  /* if rsbaclog: grant only for secoff */
                  if(tid.scd == ST_rsbaclog)
                    {
                      if (i_attr_val1.system_role == SR_security_officer)
                        return(GRANTED);
                      else
                        return(NOT_GRANTED);
                    }
                  /* if administrator, then grant all other types */
                  if (i_attr_val1.system_role == SR_administrator)
                    return(GRANTED);
                  else
                    return(NOT_GRANTED);
                  
                /* all other cases are undefined */
                default: return(DO_NOT_CARE);
              }

        case R_MOUNT:
        case R_UMOUNT:
            switch(target)
              {
                case T_DIR:
                case T_DEV:
                  /* test owner's system_role */
                  i_tid.user = owner;
                  if ((err=rsbac_get_attr(T_USER,
                                     i_tid,
                                     A_system_role,
                                     &i_attr_val1,
                                     TRUE)))
                    {
                      printk(KERN_WARNING
                             "rsbac_adf_request_fc(): rsbac_get_attr() returned error %i!\n",
                             err);
                      return(NOT_GRANTED);
                    }
                  /* only administrators may umount */
                  if (i_attr_val1.system_role != SR_administrator)
                    return(NOT_GRANTED);

                  /* test access to dir */
                  return(check_role_fc(target,tid,owner));

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

        case R_READ_ATTRIBUTE:
            switch(attr)
              {
                case A_owner:
                case A_system_role:
                case A_object_category:
                case A_log_array_low:
                case A_log_array_high:
                case A_log_program_based:
                case A_log_user_based:
                #ifdef CONFIG_RSBAC_FC_AUTH_PROT
                case A_auth_may_setuid:
                case A_auth_may_set_cap:
                #endif
                  /* Security Officer? */
                  i_tid.user = owner;
                  if ((err=rsbac_get_attr(T_USER,
                                     i_tid,
                                     A_system_role,
                                     &i_attr_val1,
                                     TRUE)))
                    {
                      printk(KERN_WARNING
                             "rsbac_adf_request_fc(): rsbac_get_attr() returned error %i!\n",err);
                      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_OPEN:
            switch(target)
              {
                case T_FILE:
                case T_DIR:
                case T_DEV:
                case T_IPC:
                  return(check_role_fc(target,tid,owner));

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

        case R_REMOVE_FROM_KERNEL:
        case R_SHUTDOWN:
            switch(target)
              {
                case T_NONE:
                  /* test owner's system_role */
                  i_tid.user = owner;
                  if ((err=rsbac_get_attr(T_USER,
                                     i_tid,
                                     A_system_role,
                                     &i_attr_val1,
                                     TRUE)))
                    {
                      printk(KERN_WARNING "rsbac_adf_request_fc(): rsbac_get_attr() returned error %i!\n",err);
                      return(NOT_GRANTED);
                    }
                  /* only administrators may shutdown or remove from kernel */
                  if (i_attr_val1.system_role != SR_administrator)
                    return(NOT_GRANTED);

                  /* grant */
                  return(GRANTED);

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

        case R_SWITCH_LOG:
            switch(target)
              {
                case T_NONE:
                  /* test owner's system_role */
                  i_tid.user = owner;
                  if ((err=rsbac_get_attr(T_USER,
                                     i_tid,
                                     A_system_role,
                                     &i_attr_val1,
                                     TRUE)))
                    {
                      printk(KERN_WARNING "rsbac_adf_request_fc(): rsbac_get_attr() returned error %i!\n",err);
                      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 undefined */
                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 != FC)
                     #ifdef CONFIG_RSBAC_FC_AUTH_PROT
                     && (attr_val.switch_target != AUTH)
                     #endif
                    )
                    return(DO_NOT_CARE);
                  /* test owner's system_role */
                  i_tid.user = owner;
                  if ((err=rsbac_get_attr(T_USER,
                                     i_tid,
                                     A_system_role,
                                     &i_attr_val1,
                                     TRUE)))
                    {
                      printk(KERN_WARNING "rsbac_adf_request_fc(): rsbac_get_attr() returned error %i!\n",err);
                      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 undefined */
                default: return(DO_NOT_CARE);
              }

        case R_WRITE_OPEN:
            switch(target)
              {
                case T_FILE:
                case T_DEV:
                case T_IPC:
                  return(check_role_fc(target,tid,owner));

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

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

    return(result);
  }; /* end of rsbac_adf_request_fc() */


/*****************************************************************************/
/* 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. Because of this, the write boundary is not adjusted - there  */
/* is no user-level writing anyway...                                        */
/* 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_fc(
                      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)
  {
    int                           err;
    union rsbac_target_id_t       i_tid;
    union rsbac_attribute_value_t i_attr_val1;
    union rsbac_attribute_value_t i_attr_val2;

    switch (request)
      {
        case R_CREATE:
            switch(target)
              {
                /* Creating dir or (pseudo) file IN target dir! */
                case T_DIR:
                  /* Mode of created item is ignored! */
                  /* Inherit object_category from DIR - but only, if not */
                  /* inherited by default anyway. */
                  #ifdef CONFIG_RSBAC_DEF_INHERIT
                    i_attr_val1.object_category = OC_inherit;
                  #else
                    /* get target object_category */
                    if ((err=rsbac_get_attr(T_DIR,
                                            tid,
                                            A_object_category,
                                            &i_attr_val1,
                                            TRUE)))
                      {
                        printk(KERN_WARNING
                               "rsbac_adf_set_attr_fc(): rsbac_get_attr() returned error %i!\n",err);
                        return(NOT_GRANTED);
                      }
                  #endif
                  /* And set it for new item in all cases */
                  if ((err=rsbac_set_attr(new_target,
                                     new_tid,
                                     A_object_category,
                                     i_attr_val1)))
                    {
                      printk(KERN_WARNING "rsbac_adf_set_attr_fc(): rsbac_set_attr() returned error %i!\n",err);
                      return(-RSBAC_EWRITEFAILED);
                    }
                  return(0);

                case T_IPC: 
                  /* Get system_role of process owner... */
                  i_tid.user = owner;
                  if ((err=rsbac_get_attr(T_USER,
                                     i_tid,
                                     A_system_role,
                                     &i_attr_val1,
                                     TRUE)))
                    {
                      printk(KERN_WARNING
                             "rsbac_adf_set_attr_fc(): rsbac_get_attr() returned error %i!\n",err);
                      return(-RSBAC_EREADFAILED);
                    }
                  /* Derive object_category for this ipc item from owner's */
                  /* system_role */
                  switch (i_attr_val1.system_role)
                    {
                      case SR_user:
                        i_attr_val2.object_category = OC_general;
                        break;
                      case SR_security_officer:
                        i_attr_val2.object_category = OC_security;
                        break;
                      case SR_administrator:
                        i_attr_val2.object_category = OC_system;
                        break;
                      default:
                        printk(KERN_WARNING
                               "rsbac_adf_set_attr_fc(): invalid system_role %i!\n",
                               i_attr_val1.system_role);
                        i_attr_val2.object_category = OC_general;
                    }
                  /* set object_category for new IPC item */
                  if ((err=rsbac_set_attr(T_IPC,
                                     tid,
                                     A_object_category,
                                     i_attr_val2)))
                    {
                      printk(KERN_WARNING "rsbac_adf_set_attr_fc(): rsbac_set_attr() returned error %i!\n",err);
                      return(-RSBAC_EWRITEFAILED);
                    }
                  return(0);
                  
                /* all other cases are undefined */
                default:
                  return(0);
              }


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

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

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