summaryrefslogtreecommitdiffstats
path: root/lib/libaccess/acleval.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libaccess/acleval.cpp')
-rw-r--r--lib/libaccess/acleval.cpp556
1 files changed, 556 insertions, 0 deletions
diff --git a/lib/libaccess/acleval.cpp b/lib/libaccess/acleval.cpp
new file mode 100644
index 00000000..a2be1f7b
--- /dev/null
+++ b/lib/libaccess/acleval.cpp
@@ -0,0 +1,556 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/*
+ * Description (acleval.c)
+ *
+ * This module provides functions for evaluating Access Control List
+ * (ACL) structures in memory.
+ *
+ */
+
+#include "base/systems.h"
+#include "netsite.h"
+#include "libaccess/symbols.h"
+#include "libaccess/aclerror.h"
+#include "libaccess/acleval.h"
+#include <assert.h>
+
+/*
+ * Description (RLMEQUIV)
+ *
+ * Macro for realm comparison. Both realm pointers must be non-null.
+ * The realms are equivalent if the pointers are equal, or if the
+ * authentication methods and database names are the same. The
+ * prompt string is not considered.
+ */
+#define RLMEQUIV(rlm1, rlm2) (((rlm1) != 0) && ((rlm2) != 0) && \
+ (((rlm1) == (rlm2)) || \
+ (((rlm1)->rlm_ameth == (rlm2)->rlm_ameth) && \
+ ((rlm1)->rlm_dbname != 0) && \
+ ((rlm2)->rlm_dbname != 0) && \
+ !strcmp((rlm1)->rlm_dbname, \
+ (rlm2)->rlm_dbname))))
+
+int aclDNSLookup(DNSFilter_t * dnf, char * dnsspec, int fqdn, char **match)
+{
+ char * subdns; /* suffix of client DNS name */
+ void * table; /* hash table pointer */
+ Symbol_t * sym; /* DNS spec symbol pointer */
+ int rv; /* result value */
+
+ fqdn = (fqdn) ? 1 : 0;
+
+ if (match) *match = 0;
+
+ /* Handle null or empty filter */
+ if ((dnf == 0) || (dnf->dnf_hash == 0)) {
+
+ return ACL_NOMATCH;
+ }
+
+ /* Got the client's DNS name? */
+ if (!dnsspec || !*dnsspec) {
+ /* No, use special one */
+ dnsspec = "unknown";
+ }
+
+ /* Get hash table pointer */
+ table = dnf->dnf_hash;
+
+ /*
+ * Look up each possible suffix for the client domain name,
+ * starting with the entire string, and working toward the
+ * last component.
+ */
+
+ subdns = dnsspec;
+
+ while (subdns != 0) {
+
+ /* Look up the domain name suffix in the hash table */
+ rv = symTableFindSym(table, subdns, fqdn, (void **)&sym);
+ if (rv == 0) break;
+
+ /* Step to the next level */
+ if (subdns[0] == '.') subdns += 1;
+ subdns = strchr(subdns, '.');
+
+ /* If it was fully qualified, now it's not */
+ fqdn = 0;
+ }
+
+ /* One more possibility if nothing found yet... */
+ if (rv) {
+ rv = symTableFindSym(table, "*", 0, (void **)&sym);
+ }
+
+ if (rv == 0) {
+ if (match) *match = sym->sym_name;
+ rv = ACL_DNMATCH;
+ }
+ else rv = ACL_NOMATCH;
+
+ return rv;
+}
+
+int aclIPLookup(IPFilter_t * ipf, IPAddr_t ipaddr, void **match)
+{
+ IPLeaf_t * leaf; /* radix tree leaf pointer */
+ IPAddr_t bitmask; /* bit mask for current node */
+ IPNode_t * ipn; /* current internal node */
+ IPNode_t * lastipn; /* last internal node seen in search */
+ IPNode_t * mipn; /* ipn_masked subtree root pointer */
+
+ if (match) *match = 0;
+
+ /* Handle null or empty IP filter */
+ if ((ipf == 0) || (ipf->ipf_tree == 0)) goto fail;
+
+ lastipn = NULL;
+ ipn = ipf->ipf_tree;
+
+ /*
+ * The tree traversal first works down the tree, under the assumption
+ * that all of the bits in the given IP address may be significant.
+ * The internal nodes of the tree will cause particular bits of the
+ * IP address to be tested, and the ipn_clear or ipn_set link to
+ * a descendant followed accordingly. The internal nodes are arranged
+ * in such a way that high-order bits are tested before low-order bits.
+ * Usually some bits are skipped, as they are not needed to distinguish
+ * the entries in the tree.
+ *
+ * At the bottom of the tree, a leaf node may be found, or the last
+ * descendant link may be NULL. If a leaf node is found, it is
+ * tested for a match against the given IP address. If it doesn't
+ * match, or the link was NULL, backtracking begins, as described
+ * below.
+ *
+ * Backtracking follows the ipn_parent links back up the tree from
+ * the last internal node, looking for internal nodes with ipn_masked
+ * descendants. The subtrees attached to these links are traversed
+ * downward, as before, with the same processing at the bottom as
+ * the first downward traversal. Following the ipn_masked links is
+ * essentially examining the possibility that the IP address bit
+ * associated with the internal node may be masked out by the
+ * ipl_netmask in a leaf at the bottom of such a subtree. Since
+ * the ipn_masked links are examined from the bottom of the tree
+ * to the top, this looks at the low-order bits first.
+ */
+
+ while (ipn != NULL) {
+
+ /*
+ * Work down the tree testing bits in the IP address indicated
+ * by the internal nodes. Exit the loop when there are no more
+ * internal nodes.
+ */
+ while ((ipn != NULL) && (ipn->ipn_type == IPN_NODE)) {
+
+ /* Save pointer to internal node */
+ lastipn = ipn;
+
+ /* Get a mask for the bit this node tests */
+ bitmask = (IPAddr_t) 1<<ipn->ipn_bit;
+
+ /* Select link to follow for this IP address */
+ ipn = (bitmask & ipaddr) ? ipn->ipn_set : ipn->ipn_clear;
+ }
+
+ /* Did we end up with a non-NULL node pointer? */
+ if (ipn != NULL) {
+
+ /* It must be a leaf node */
+ assert(ipn->ipn_type == IPN_LEAF);
+ leaf = (IPLeaf_t *)ipn;
+
+ /* Is it a matching leaf? */
+ if (leaf->ipl_ipaddr == (ipaddr & leaf->ipl_netmask)) goto win;
+ }
+
+ /*
+ * Backtrack, starting at lastipn. Search each subtree
+ * emanating from an ipn_masked link. Step up the tree
+ * until the ipn_masked link of the node referenced by
+ * "ipf->ipf_tree" has been considered.
+ */
+
+ for (ipn = lastipn; ipn != NULL; ipn = ipn->ipn_parent) {
+
+ /*
+ * Look for a node with a non-NULL masked link, but don't
+ * go back to the node we just came from.
+ */
+
+ if ((ipn->ipn_masked != NULL) && (ipn->ipn_masked != lastipn)) {
+
+ /* Get the root of this subtree */
+ mipn = ipn->ipn_masked;
+
+ /* If this is an internal node, start downward traversal */
+ if (mipn->ipn_type == IPN_NODE) {
+ ipn = mipn;
+ break;
+ }
+
+ /* Otherwise it's a leaf */
+ assert(mipn->ipn_type == IPN_LEAF);
+ leaf = (IPLeaf_t *)mipn;
+
+ /* Is it a matching leaf? */
+ if (leaf->ipl_ipaddr == (ipaddr & leaf->ipl_netmask)) goto win;
+ }
+
+ /* Don't consider nodes above the given root */
+ if (ipn == ipf->ipf_tree) goto fail;
+
+ lastipn = ipn;
+ }
+ }
+
+ fail:
+ /* No matching entry found */
+ return ACL_NOMATCH;
+
+ win:
+ /* Found a match in leaf */
+ if (match) *match = (void *)leaf;
+
+ return ACL_IPMATCH;
+}
+
+int aclUserLookup(UidUser_t * uup, UserObj_t * uoptr)
+{
+ int gl1cnt; /* elements left in uup->uu_group list */
+ int gl2cnt; /* elements left in uoptr->uo_groups list */
+ USI_t * gl1ptr; /* pointer to next group in uup->uu_group */
+ USI_t * gl2ptr; /* pointer to next group in uoptr->uo_groups */
+
+ /* Try for a direct match on the user id */
+ if (usiPresent(&uup->uu_user, uoptr->uo_uid)) {
+ return ACL_USMATCH;
+ }
+
+ /*
+ * Now we want to see if there are any matches between the
+ * uup->uu_group group id list and the list of groups in the
+ * user object.
+ */
+
+ gl1cnt = UILCOUNT(&uup->uu_group);
+ gl1ptr = UILLIST(&uup->uu_group);
+ gl2cnt = UILCOUNT(&uoptr->uo_groups);
+ gl2ptr = UILLIST(&uoptr->uo_groups);
+
+ while ((gl1cnt > 0) && (gl2cnt > 0)) {
+
+ if (*gl1ptr == *gl2ptr) {
+ return ACL_GRMATCH;
+ }
+
+ if (*gl1ptr < *gl2ptr) {
+ ++gl1ptr;
+ --gl1cnt;
+ }
+ else {
+ ++gl2ptr;
+ --gl2cnt;
+ }
+ }
+
+ return ACL_NOMATCH;
+}
+
+/*
+ * Description (aclEvaluate)
+ *
+ * This function evaluates a given ACL against specified client
+ * information and a particular access right that is needed to
+ * service the client. It can optionally return the ACL directive
+ * number which allows or denies the client's access.
+ *
+ * Arguments:
+ *
+ * acl - pointer to ACL to evaluate
+ * arid - desired access right id value
+ * clauth - pointer to client authentication information
+ * padn - pointer to returned ACL directive number
+ * (may be null)
+ *
+ * Returns:
+ *
+ * A return value of zero indicates that the given ACL does not
+ * control the desired access right, or contains no directives which
+ * match the specified client. A positive return value contains a
+ * value of ACD_ALLOW, ACD_DENY, or ACD_AUTH, and may also have the
+ * ACD_ALWAYS bit flag set. The value indicates whether the client
+ * should be allowed or denied access, or whether authentication is
+ * needed. The ACD_ALWAYS flag indicates if the action should occur
+ * immediately, terminating any further ACL evaluation. An error
+ * is indicated by a negative error code (ACLERRxxxx - see aclerror.h).
+ */
+
+int aclEvaluate(ACL_t * acl, USI_t arid, ClAuth_t * clauth, int * padn)
+{
+ ACDirective_t * acd; /* current ACL directive pointer */
+ RightSpec_t * rsp; /* pointer to rights controlled by ACL */
+ ACClients_t * csp; /* pointer to clients specification */
+ HostSpec_t * hsp; /* pointer to host specification */
+ UserSpec_t * usp; /* pointer to user specification */
+ Realm_t * rlm = 0; /* current authentication realm pointer */
+ Realm_t * authrlm = 0; /* realm to be used for authentication */
+ int ndir; /* ACL directive number */
+ int rv; /* result value */
+ int decision = 0; /* current access control decision */
+ int result = 0; /* function return value */
+ int mdn = 0; /* matching directive number */
+
+ if (padn) *padn = 0;
+
+ /* Does this ACL control the desired access right? */
+
+ rsp = acl->acl_rights;
+ if ((rsp == 0) || !usiPresent(&rsp->rs_list, arid)) {
+
+ /* No, nothing to do */
+ return 0;
+ }
+
+ ndir = 0;
+
+ /* Loop on each ACL directive */
+ for (acd = acl->acl_dirf; acd != 0; acd = acd->acd_next) {
+
+ /* Bump directive number */
+ ++ndir;
+
+ /* Dispatch on directive action code */
+ switch (acd->acd_action) {
+
+ case ACD_ALLOW:
+ case ACD_DENY:
+
+ /* Loop to process list of client specifications */
+ for (csp = acd->acd_cl; csp != 0; csp = csp->cl_next) {
+
+ /* Is there a host list? */
+ hsp = csp->cl_host;
+ if (hsp != 0) {
+
+ /* An empty host list will not match */
+ rv = 0;
+
+ /* Yes, is there an IP address filter? */
+ if (hsp->hs_host.inh_ipf.ipf_tree != 0) {
+
+ /*
+ * Yes, see if the the client's IP address
+ * matches anything in the IP filter.
+ */
+ rv = aclIPLookup(&hsp->hs_host.inh_ipf,
+ clauth->cla_ipaddr, 0);
+ }
+
+ /* If no IP match, is there a DNS filter? */
+ if (!rv && (hsp->hs_host.inh_dnf.dnf_hash != 0)) {
+
+ /* Yes, try for a DNS match */
+ rv = aclDNSLookup(&hsp->hs_host.inh_dnf,
+ clauth->cla_dns, 1, 0);
+ }
+
+ /*
+ * Does the client match the host list? If not, skip
+ * to the next client specification.
+ */
+ if (!rv) continue;
+ }
+
+ /* Is there a user list? */
+ usp = csp->cl_user;
+ if (usp != 0) {
+
+ /* Yes, has the client user been authenticated yet? */
+ if ((clauth->cla_realm != 0) && (clauth->cla_uoptr != 0)) {
+
+ /*
+ * Yes, has the client user been authenticated in the
+ * realm associated with this user list?
+ */
+ if (RLMEQUIV(rlm, clauth->cla_realm)) {
+
+ /*
+ * Yes, does the user spec allow all
+ * authenticated users?
+ */
+ rv = (usp->us_flags & ACL_USALL) ? ACL_GRMATCH : 0;
+ if (!rv) {
+
+ /*
+ * No, need to check client user against list.
+ */
+ rv = aclUserLookup(&usp->us_user,
+ clauth->cla_uoptr);
+ }
+
+ /* Got a match yet? */
+ if (rv) {
+
+ /*
+ * Yes, update the the access control decision,
+ * clearing any pending authentication request
+ * flag.
+ */
+ authrlm = 0;
+ decision = acd->acd_action;
+
+ /* Copy the "always" flag to the result */
+ result = (acd->acd_flags & ACD_ALWAYS);
+ mdn = ndir;
+ }
+ }
+ else {
+
+ /*
+ * The client has been authenticated already,
+ * but not in the realm used by this directive.
+ * Since directives in a given ACL are not
+ * independent policy statements, it seems that
+ * the proper thing to do here is to reject
+ * this ACL in its entirity. This case is not
+ * an authentication failure per se, but rather
+ * an inability to evaluate this particular
+ * ACL directive which requires authentication.
+ */
+ return 0;
+ }
+ }
+ else {
+
+ /*
+ * The client user has not been authenticated in this
+ * realm yet, but could potentially be one of the
+ * users on this user list. This directive is
+ * therefore "potentially matching". The question
+ * is: would it change the current decision to allow
+ * or deny the client if the client user actually did
+ * match the user list?
+ */
+ if ((authrlm == 0) && (decision != acd->acd_action)) {
+
+ /*
+ * Yes, set the "request authentication" flag,
+ * along with ACD_ALWAYS if it is set in the
+ * directive.
+ */
+ authrlm = rlm;
+ decision = ACD_AUTH;
+ result = (acd->acd_flags & ACD_ALWAYS);
+ mdn = ndir;
+ }
+ }
+ }
+ else {
+
+ /*
+ * There is no user list. Therefore any user,
+ * authenticated or not, is considered a match.
+ * Update the decision, and clear the
+ * "authentication requested" flag.
+ */
+ authrlm = 0;
+ decision = acd->acd_action;
+ result = (acd->acd_flags & ACD_ALWAYS);
+ mdn = ndir;
+ }
+
+ /*
+ * If we hit a client specification that requires
+ * immediate action, exit the loop.
+ */
+ if (result & ACD_ALWAYS) break;
+ }
+ break;
+
+ case ACD_AUTH:
+
+ /* Got a pointer to a realm specification? */
+ if (acd->acd_auth.au_realm != 0) {
+
+ /* Yes, update the current realm pointer */
+ rlm = &acd->acd_auth.au_realm->rs_realm;
+
+ /* Has the client already successfully authenticated? */
+ if ((clauth->cla_realm == 0) || (clauth->cla_uoptr == 0)) {
+
+ /*
+ * No, if this is an "always" directive, override any
+ * previously selected realm and request authentication.
+ */
+ if ((acd->acd_flags & ACD_ALWAYS) != 0) {
+
+ /* Set decision to request authentication */
+ authrlm = rlm;
+ decision = ACD_AUTH;
+ result = ACD_ALWAYS;
+ mdn = ndir;
+ }
+ }
+ }
+ break;
+
+ case ACD_EXEC:
+
+ /* Conditionally terminate ACL evaluation */
+ switch (decision) {
+ case ACD_ALLOW:
+ if (acd->acd_flags & ACD_EXALLOW) {
+ result = (acd->acd_flags & ACD_ALWAYS);
+ goto out;
+ }
+ break;
+ case ACD_DENY:
+ if (acd->acd_flags & ACD_EXDENY) {
+ result = (acd->acd_flags & ACD_ALWAYS);
+ goto out;
+ }
+ break;
+ case ACD_AUTH:
+ if (acd->acd_flags & ACD_EXAUTH) {
+ result = (acd->acd_flags & ACD_ALWAYS);
+ goto out;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /*
+ * If we hit a directive that requires immediate action, exit
+ * the loop.
+ */
+ if (result & ACD_ALWAYS) break;
+ }
+
+ out:
+ /* If the decision is to request authentication, set the desired realm */
+ if (decision == ACD_AUTH) {
+ clauth->cla_realm = authrlm;
+ }
+
+ /* Combine decision with flags already in result */
+ result |= decision;
+
+ /* Return matching directive number if desired */
+ if (padn) *padn = mdn;
+
+ return result;
+}