/*
 * Policy routines for the CUPS scheduler.
 *
 * Copyright © 2020-2024 by OpenPrinting.
 * Copyright © 2007-2011, 2014 by Apple Inc.
 * Copyright © 1997-2006 by Easy Software Products, all rights reserved.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more
 * information.
 */

/*
 * Include necessary headers...
 */

#include "cupsd.h"
#include <pwd.h>


/*
 * Local functions...
 */

static int	compare_ops(cupsd_location_t *a, cupsd_location_t *b);
static int	compare_policies(cupsd_policy_t *a, cupsd_policy_t *b);
static void	free_policy(cupsd_policy_t *p);
static int	hash_op(cupsd_location_t *op);


/*
 * 'cupsdAddPolicy()' - Add a policy to the system.
 */

cupsd_policy_t *			/* O - Policy */
cupsdAddPolicy(const char *policy)	/* I - Name of policy */
{
  cupsd_policy_t	*temp;		/* Pointer to policy */


  if (!policy)
    return (NULL);

  if (!Policies)
    Policies = cupsArrayNew3((cups_array_func_t)compare_policies, NULL,
			     (cups_ahash_func_t)NULL, 0,
			     (cups_acopy_func_t)NULL,
			     (cups_afree_func_t)free_policy);

  if (!Policies)
    return (NULL);

  if ((temp = calloc(1, sizeof(cupsd_policy_t))) != NULL)
  {
    cupsdSetString(&temp->name, policy);
    cupsArrayAdd(Policies, temp);
  }

  return (temp);
}


/*
 * 'cupsdAddPolicyOp()' - Add an operation to a policy.
 */

cupsd_location_t *			/* O - New policy operation */
cupsdAddPolicyOp(cupsd_policy_t   *p,	/* I - Policy */
                 cupsd_location_t *po,	/* I - Policy operation to copy */
                 ipp_op_t         op)	/* I - IPP operation code */
{
  cupsd_location_t	*temp;		/* New policy operation */


  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdAddPolicyOp(p=%p, po=%p, op=%x(%s))",
                  p, po, op, ippOpString(op));

  if (!p)
    return (NULL);

  if (!p->ops)
    p->ops = cupsArrayNew3((cups_array_func_t)compare_ops, NULL,
                           (cups_ahash_func_t)hash_op, 128,
			   (cups_acopy_func_t)NULL,
			   (cups_afree_func_t)cupsdFreeLocation);

  if (!p->ops)
    return (NULL);

  if ((temp = cupsdCopyLocation(po)) != NULL)
  {
    temp->op    = op;
    temp->limit = CUPSD_AUTH_LIMIT_IPP;

    cupsArrayAdd(p->ops, temp);
  }

  return (temp);
}


/*
 * 'cupsdCheckPolicy()' - Check the IPP operation and username against a policy.
 */

http_status_t				/* I - 1 if OK, 0 otherwise */
cupsdCheckPolicy(cupsd_policy_t *p,	/* I - Policy */
                 cupsd_client_t *con,	/* I - Client connection */
	         const char     *owner)	/* I - Owner of object */
{
  cupsd_location_t	*po;		/* Current policy operation */


 /*
  * Range check...
  */

  if (!p || !con)
  {
    cupsdLogMessage(CUPSD_LOG_CRIT, "cupsdCheckPolicy: p=%p, con=%p.", p, con);

    return (HTTP_STATUS_SERVER_ERROR);
  }

 /*
  * Find a match for the operation...
  */

  if ((po = cupsdFindPolicyOp(p, con->request->request.op.operation_id)) == NULL)
  {
    cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCheckPolicy: No matching operation, returning 0.");
    return (HTTP_STATUS_OK);
  }

  con->best = po;

 /*
  * Return the status of the check...
  */

  return (cupsdIsAuthorized(con, owner));
}


/*
 * 'cupsdDeleteAllPolicies()' - Delete all policies in memory.
 */

void
cupsdDeleteAllPolicies(void)
{
  cupsd_printer_t	*printer;	/* Current printer */


  if (!Policies)
    return;

 /*
  * First clear the policy pointers for all printers...
  */

  for (printer = (cupsd_printer_t *)cupsArrayFirst(Printers);
       printer;
       printer = (cupsd_printer_t *)cupsArrayNext(Printers))
    printer->op_policy_ptr = NULL;

  DefaultPolicyPtr = NULL;

 /*
  * Then free all of the policies...
  */

  cupsArrayDelete(Policies);

  Policies = NULL;
}


/*
 * 'cupsdFindPolicy()' - Find a named policy.
 */

cupsd_policy_t *			/* O - Policy */
cupsdFindPolicy(const char *policy)	/* I - Name of policy */
{
  cupsd_policy_t	key;		/* Search key */


 /*
  * Range check...
  */

  if (!policy)
    return (NULL);

 /*
  * Look it up...
  */

  key.name = (char *)policy;
  return ((cupsd_policy_t *)cupsArrayFind(Policies, &key));
}


/*
 * 'cupsdFindPolicyOp()' - Find a policy operation.
 */

cupsd_location_t *			/* O - Policy operation */
cupsdFindPolicyOp(cupsd_policy_t *p,	/* I - Policy */
                  ipp_op_t       op)	/* I - IPP operation */
{
  cupsd_location_t	key,		/* Search key... */
			*po;		/* Current policy operation */


  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdFindPolicyOp(p=%p, op=%x(%s))",
                  p, op, ippOpString(op));

 /*
  * Range check...
  */

  if (!p)
    return (NULL);

 /*
  * Check the operation against the available policies...
  */

  key.op = op;
  if ((po = (cupsd_location_t *)cupsArrayFind(p->ops, &key)) != NULL)
  {
    cupsdLogMessage(CUPSD_LOG_DEBUG2,
		    "cupsdFindPolicyOp: Found exact match...");
    return (po);
  }

  key.op = IPP_ANY_OPERATION;
  if ((po = (cupsd_location_t *)cupsArrayFind(p->ops, &key)) != NULL)
  {
    cupsdLogMessage(CUPSD_LOG_DEBUG2,
		    "cupsdFindPolicyOp: Found wildcard match...");
    return (po);
  }

  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdFindPolicyOp: No match found.");

  return (NULL);
}


/*
 * 'cupsdGetPrivateAttrs()' - Get the private attributes for the current
 *                            request.
 */

cups_array_t *				/* O - Array or NULL for no restrictions */
cupsdGetPrivateAttrs(
    cupsd_policy_t  *policy,		/* I - Policy */
    cupsd_client_t  *con,		/* I - Client connection */
    cupsd_printer_t *printer,		/* I - Printer, if any */
    const char      *owner)		/* I - Owner of object */
{
  char		*name;			/* Current name in access list */
  cups_array_t	*access_ptr,		/* Access array */
		*attrs_ptr;		/* Attributes array */
  const char	*username;		/* Username associated with request */
  ipp_attribute_t *attr;		/* Attribute from request */
  struct passwd	*pw;			/* User info */


#ifdef DEBUG
  cupsdLogMessage(CUPSD_LOG_DEBUG2,
                  "cupsdGetPrivateAttrs(policy=%p(%s), con=%p(%d), "
		  "printer=%p(%s), owner=\"%s\")", policy, policy ? policy->name : "", con,
		  con->number, printer, printer ? printer->name : "", owner);
#endif /* DEBUG */

  if (!policy)
  {
    cupsdLogMessage(CUPSD_LOG_CRIT, "cupsdGetPrivateAttrs: policy=%p, con=%p, printer=%p, owner=\"%s\", DefaultPolicyPtr=%p: This should never happen, please report a bug.", policy, con, printer, owner, DefaultPolicyPtr);
    policy = DefaultPolicyPtr;
  }

 /*
  * Get the access and attributes lists that correspond to the request...
  */

#ifdef DEBUG
  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdGetPrivateAttrs: %s",
                  ippOpString(con->request->request.op.operation_id));
#endif /* DEBUG */

  switch (con->request->request.op.operation_id)
  {
    case IPP_GET_SUBSCRIPTIONS :
    case IPP_GET_SUBSCRIPTION_ATTRIBUTES :
    case IPP_GET_NOTIFICATIONS :
        access_ptr = policy->sub_access;
	attrs_ptr  = policy->sub_attrs;
	break;

    default :
        access_ptr = policy->job_access;
	attrs_ptr  = policy->job_attrs;
        break;
  }

 /*
  * If none of the attributes are private, return NULL now...
  */

  if ((name = (char *)cupsArrayFirst(attrs_ptr)) != NULL &&
      !_cups_strcasecmp(name, "none"))
  {
#ifdef DEBUG
    cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdGetPrivateAttrs: Returning NULL.");
#endif /* DEBUG */

    return (NULL);
  }
  else if (name && !_cups_strcasecmp(name, "all"))
  {
    return (attrs_ptr);
  }

 /*
  * Otherwise check the user against the access list...
  */

  if (con->username[0])
    username = con->username;
  else if ((attr = ippFindAttribute(con->request, "requesting-user-name",
                                    IPP_TAG_NAME)) != NULL)
    username = attr->values[0].string.text;
  else
    username = "anonymous";

  if (username[0])
  {
    pw = getpwnam(username);
    endpwent();
  }
  else
    pw = NULL;

#ifdef DEBUG
  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdGetPrivateAttrs: username=\"%s\"",
                  username);
#endif /* DEBUG */

 /*
  * Otherwise check the user against the access list...
  */

  for (name = (char *)cupsArrayFirst(access_ptr);
       name;
       name = (char *)cupsArrayNext(access_ptr))
  {
#ifdef DEBUG
    cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdGetPrivateAttrs: name=%s", name);
#endif /* DEBUG */

    if (printer && !_cups_strcasecmp(name, "@ACL"))
    {
      char	*acl;			/* Current ACL user/group */

      for (acl = (char *)cupsArrayFirst(printer->users);
	   acl;
	   acl = (char *)cupsArrayNext(printer->users))
      {
	if (acl[0] == '@')
	{
	 /*
	  * Check group membership...
	  */

	  if (cupsdCheckGroup(username, pw, acl + 1))
	    break;
	}
	else if (acl[0] == '#')
	{
	 /*
	  * Check UUID...
	  */

	  if (cupsdCheckGroup(username, pw, acl))
	    break;
	}
	else if (!_cups_strcasecmp(username, acl))
	  break;
      }
    }
    else if (owner && !_cups_strcasecmp(name, "@OWNER") &&
             !_cups_strcasecmp(username, owner))
    {
#ifdef DEBUG
      cupsdLogMessage(CUPSD_LOG_DEBUG2,
		      "cupsdGetPrivateAttrs: Returning NULL.");
#endif /* DEBUG */

      return (NULL);
    }
    else if (!_cups_strcasecmp(name, "@SYSTEM"))
    {
      int i;				/* Looping var */

      for (i = 0; i < NumSystemGroups; i ++)
	if (cupsdCheckGroup(username, pw, SystemGroups[i]))
	{
#ifdef DEBUG
	  cupsdLogMessage(CUPSD_LOG_DEBUG2,
	                  "cupsdGetPrivateAttrs: Returning NULL.");
#endif /* DEBUG */

	  return (NULL);
	}
    }
    else if (name[0] == '@')
    {
      if (cupsdCheckGroup(username, pw, name + 1))
      {
#ifdef DEBUG
        cupsdLogMessage(CUPSD_LOG_DEBUG2,
	                "cupsdGetPrivateAttrs: Returning NULL.");
#endif /* DEBUG */

	return (NULL);
      }
    }
    else if (!_cups_strcasecmp(username, name))
    {
#ifdef DEBUG
      cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdGetPrivateAttrs: Returning NULL.");
#endif /* DEBUG */

      return (NULL);
    }
  }

 /*
  * No direct access, so return private attributes list...
  */

#ifdef DEBUG
  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdGetPrivateAttrs: Returning list.");
#endif /* DEBUG */

  return (attrs_ptr);
}


/*
 * 'compare_ops()' - Compare two operations.
 */

static int				/* O - Result of comparison */
compare_ops(cupsd_location_t *a,	/* I - First operation */
            cupsd_location_t *b)	/* I - Second operation */
{
  return (a->op - b->op);
}


/*
 * 'compare_policies()' - Compare two policies.
 */

static int				/* O - Result of comparison */
compare_policies(cupsd_policy_t *a,	/* I - First policy */
                 cupsd_policy_t *b)	/* I - Second policy */
{
  return (_cups_strcasecmp(a->name, b->name));
}


/*
 * 'free_policy()' - Free the memory used by a policy.
 */

static void
free_policy(cupsd_policy_t *p)		/* I - Policy to free */
{
  cupsArrayDelete(p->job_access);
  cupsArrayDelete(p->job_attrs);
  cupsArrayDelete(p->sub_access);
  cupsArrayDelete(p->sub_attrs);
  cupsArrayDelete(p->ops);
  cupsdClearString(&p->name);
  free(p);
}


/*
 * 'hash_op()' - Generate a lookup hash for the operation.
 */

static int				/* O - Hash value */
hash_op(cupsd_location_t *op)		/* I - Operation */
{
  return (((op->op >> 6) & 0x40) | (op->op & 0x3f));
}
