acleval.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. /** BEGIN COPYRIGHT BLOCK
  2. * This Program is free software; you can redistribute it and/or modify it under
  3. * the terms of the GNU General Public License as published by the Free Software
  4. * Foundation; version 2 of the License.
  5. *
  6. * This Program is distributed in the hope that it will be useful, but WITHOUT
  7. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  8. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  9. *
  10. * You should have received a copy of the GNU General Public License along with
  11. * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
  12. * Place, Suite 330, Boston, MA 02111-1307 USA.
  13. *
  14. * In addition, as a special exception, Red Hat, Inc. gives You the additional
  15. * right to link the code of this Program with code not covered under the GNU
  16. * General Public License ("Non-GPL Code") and to distribute linked combinations
  17. * including the two, subject to the limitations in this paragraph. Non-GPL Code
  18. * permitted under this exception must only link to the code of this Program
  19. * through those well defined interfaces identified in the file named EXCEPTION
  20. * found in the source code files (the "Approved Interfaces"). The files of
  21. * Non-GPL Code may instantiate templates or use macros or inline functions from
  22. * the Approved Interfaces without causing the resulting work to be covered by
  23. * the GNU General Public License. Only Red Hat, Inc. may make changes or
  24. * additions to the list of Approved Interfaces. You must obey the GNU General
  25. * Public License in all respects for all of the Program code and other code used
  26. * in conjunction with the Program except the Non-GPL Code covered by this
  27. * exception. If you modify this file, you may extend this exception to your
  28. * version of the file, but you are not obligated to do so. If you do not wish to
  29. * provide this exception without modification, you must delete this exception
  30. * statement from your version and license this file solely under the GPL without
  31. * exception.
  32. *
  33. *
  34. * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
  35. * Copyright (C) 2005 Red Hat, Inc.
  36. * All rights reserved.
  37. * END COPYRIGHT BLOCK **/
  38. #ifdef HAVE_CONFIG_H
  39. # include <config.h>
  40. #endif
  41. /*
  42. * Description (acleval.c)
  43. *
  44. * This module provides functions for evaluating Access Control List
  45. * (ACL) structures in memory.
  46. *
  47. */
  48. #include "base/systems.h"
  49. #include "netsite.h"
  50. #include "libaccess/symbols.h"
  51. #include "libaccess/aclerror.h"
  52. #include "libaccess/acleval.h"
  53. #include <assert.h>
  54. /*
  55. * Description (RLMEQUIV)
  56. *
  57. * Macro for realm comparison. Both realm pointers must be non-null.
  58. * The realms are equivalent if the pointers are equal, or if the
  59. * authentication methods and database names are the same. The
  60. * prompt string is not considered.
  61. */
  62. #define RLMEQUIV(rlm1, rlm2) (((rlm1) != 0) && ((rlm2) != 0) && \
  63. (((rlm1) == (rlm2)) || \
  64. (((rlm1)->rlm_ameth == (rlm2)->rlm_ameth) && \
  65. ((rlm1)->rlm_dbname != 0) && \
  66. ((rlm2)->rlm_dbname != 0) && \
  67. !strcmp((rlm1)->rlm_dbname, \
  68. (rlm2)->rlm_dbname))))
  69. int aclDNSLookup(DNSFilter_t * dnf, const char * dnsspec, int fqdn, const char **match)
  70. {
  71. const char * subdns; /* suffix of client DNS name */
  72. void * table; /* hash table pointer */
  73. Symbol_t * sym; /* DNS spec symbol pointer */
  74. int rv; /* result value */
  75. fqdn = (fqdn) ? 1 : 0;
  76. if (match) *match = 0;
  77. /* Handle null or empty filter */
  78. if ((dnf == 0) || (dnf->dnf_hash == 0)) {
  79. return ACL_NOMATCH;
  80. }
  81. /* Got the client's DNS name? */
  82. if (!dnsspec || !*dnsspec) {
  83. /* No, use special one */
  84. dnsspec = "unknown";
  85. }
  86. /* Get hash table pointer */
  87. table = dnf->dnf_hash;
  88. /*
  89. * Look up each possible suffix for the client domain name,
  90. * starting with the entire string, and working toward the
  91. * last component.
  92. */
  93. subdns = dnsspec;
  94. while (subdns != 0) {
  95. /* Look up the domain name suffix in the hash table */
  96. rv = symTableFindSym(table, subdns, fqdn, (void **)&sym);
  97. if (rv == 0) break;
  98. /* Step to the next level */
  99. if (subdns[0] == '.') subdns += 1;
  100. subdns = strchr(subdns, '.');
  101. /* If it was fully qualified, now it's not */
  102. fqdn = 0;
  103. }
  104. /* One more possibility if nothing found yet... */
  105. if (rv) {
  106. rv = symTableFindSym(table, "*", 0, (void **)&sym);
  107. }
  108. if (rv == 0) {
  109. if (match) *match = sym->sym_name;
  110. rv = ACL_DNMATCH;
  111. }
  112. else rv = ACL_NOMATCH;
  113. return rv;
  114. }
  115. int aclIPLookup(IPFilter_t * ipf, IPAddr_t ipaddr, void **match)
  116. {
  117. IPLeaf_t * leaf; /* radix tree leaf pointer */
  118. IPAddr_t bitmask; /* bit mask for current node */
  119. IPNode_t * ipn; /* current internal node */
  120. IPNode_t * lastipn; /* last internal node seen in search */
  121. IPNode_t * mipn; /* ipn_masked subtree root pointer */
  122. if (match) *match = 0;
  123. /* Handle null or empty IP filter */
  124. if ((ipf == 0) || (ipf->ipf_tree == 0)) goto fail;
  125. lastipn = NULL;
  126. ipn = ipf->ipf_tree;
  127. /*
  128. * The tree traversal first works down the tree, under the assumption
  129. * that all of the bits in the given IP address may be significant.
  130. * The internal nodes of the tree will cause particular bits of the
  131. * IP address to be tested, and the ipn_clear or ipn_set link to
  132. * a descendant followed accordingly. The internal nodes are arranged
  133. * in such a way that high-order bits are tested before low-order bits.
  134. * Usually some bits are skipped, as they are not needed to distinguish
  135. * the entries in the tree.
  136. *
  137. * At the bottom of the tree, a leaf node may be found, or the last
  138. * descendant link may be NULL. If a leaf node is found, it is
  139. * tested for a match against the given IP address. If it doesn't
  140. * match, or the link was NULL, backtracking begins, as described
  141. * below.
  142. *
  143. * Backtracking follows the ipn_parent links back up the tree from
  144. * the last internal node, looking for internal nodes with ipn_masked
  145. * descendants. The subtrees attached to these links are traversed
  146. * downward, as before, with the same processing at the bottom as
  147. * the first downward traversal. Following the ipn_masked links is
  148. * essentially examining the possibility that the IP address bit
  149. * associated with the internal node may be masked out by the
  150. * ipl_netmask in a leaf at the bottom of such a subtree. Since
  151. * the ipn_masked links are examined from the bottom of the tree
  152. * to the top, this looks at the low-order bits first.
  153. */
  154. while (ipn != NULL) {
  155. /*
  156. * Work down the tree testing bits in the IP address indicated
  157. * by the internal nodes. Exit the loop when there are no more
  158. * internal nodes.
  159. */
  160. while ((ipn != NULL) && (ipn->ipn_type == IPN_NODE)) {
  161. /* Save pointer to internal node */
  162. lastipn = ipn;
  163. /* Get a mask for the bit this node tests */
  164. bitmask = (IPAddr_t) 1<<ipn->ipn_bit;
  165. /* Select link to follow for this IP address */
  166. ipn = (bitmask & ipaddr) ? ipn->ipn_set : ipn->ipn_clear;
  167. }
  168. /* Did we end up with a non-NULL node pointer? */
  169. if (ipn != NULL) {
  170. /* It must be a leaf node */
  171. assert(ipn->ipn_type == IPN_LEAF);
  172. leaf = (IPLeaf_t *)ipn;
  173. /* Is it a matching leaf? */
  174. if (leaf->ipl_ipaddr == (ipaddr & leaf->ipl_netmask)) goto win;
  175. }
  176. /*
  177. * Backtrack, starting at lastipn. Search each subtree
  178. * emanating from an ipn_masked link. Step up the tree
  179. * until the ipn_masked link of the node referenced by
  180. * "ipf->ipf_tree" has been considered.
  181. */
  182. for (ipn = lastipn; ipn != NULL; ipn = ipn->ipn_parent) {
  183. /*
  184. * Look for a node with a non-NULL masked link, but don't
  185. * go back to the node we just came from.
  186. */
  187. if ((ipn->ipn_masked != NULL) && (ipn->ipn_masked != lastipn)) {
  188. /* Get the root of this subtree */
  189. mipn = ipn->ipn_masked;
  190. /* If this is an internal node, start downward traversal */
  191. if (mipn->ipn_type == IPN_NODE) {
  192. ipn = mipn;
  193. break;
  194. }
  195. /* Otherwise it's a leaf */
  196. assert(mipn->ipn_type == IPN_LEAF);
  197. leaf = (IPLeaf_t *)mipn;
  198. /* Is it a matching leaf? */
  199. if (leaf->ipl_ipaddr == (ipaddr & leaf->ipl_netmask)) goto win;
  200. }
  201. /* Don't consider nodes above the given root */
  202. if (ipn == ipf->ipf_tree) goto fail;
  203. lastipn = ipn;
  204. }
  205. }
  206. fail:
  207. /* No matching entry found */
  208. return ACL_NOMATCH;
  209. win:
  210. /* Found a match in leaf */
  211. if (match) *match = (void *)leaf;
  212. return ACL_IPMATCH;
  213. }
  214. int aclUserLookup(UidUser_t * uup, UserObj_t * uoptr)
  215. {
  216. int gl1cnt; /* elements left in uup->uu_group list */
  217. int gl2cnt; /* elements left in uoptr->uo_groups list */
  218. USI_t * gl1ptr; /* pointer to next group in uup->uu_group */
  219. USI_t * gl2ptr; /* pointer to next group in uoptr->uo_groups */
  220. /* Try for a direct match on the user id */
  221. if (usiPresent(&uup->uu_user, uoptr->uo_uid)) {
  222. return ACL_USMATCH;
  223. }
  224. /*
  225. * Now we want to see if there are any matches between the
  226. * uup->uu_group group id list and the list of groups in the
  227. * user object.
  228. */
  229. gl1cnt = UILCOUNT(&uup->uu_group);
  230. gl1ptr = UILLIST(&uup->uu_group);
  231. gl2cnt = UILCOUNT(&uoptr->uo_groups);
  232. gl2ptr = UILLIST(&uoptr->uo_groups);
  233. while ((gl1cnt > 0) && (gl2cnt > 0)) {
  234. if (*gl1ptr == *gl2ptr) {
  235. return ACL_GRMATCH;
  236. }
  237. if (*gl1ptr < *gl2ptr) {
  238. ++gl1ptr;
  239. --gl1cnt;
  240. }
  241. else {
  242. ++gl2ptr;
  243. --gl2cnt;
  244. }
  245. }
  246. return ACL_NOMATCH;
  247. }
  248. /*
  249. * Description (aclEvaluate)
  250. *
  251. * This function evaluates a given ACL against specified client
  252. * information and a particular access right that is needed to
  253. * service the client. It can optionally return the ACL directive
  254. * number which allows or denies the client's access.
  255. *
  256. * Arguments:
  257. *
  258. * acl - pointer to ACL to evaluate
  259. * arid - desired access right id value
  260. * clauth - pointer to client authentication information
  261. * padn - pointer to returned ACL directive number
  262. * (may be null)
  263. *
  264. * Returns:
  265. *
  266. * A return value of zero indicates that the given ACL does not
  267. * control the desired access right, or contains no directives which
  268. * match the specified client. A positive return value contains a
  269. * value of ACD_ALLOW, ACD_DENY, or ACD_AUTH, and may also have the
  270. * ACD_ALWAYS bit flag set. The value indicates whether the client
  271. * should be allowed or denied access, or whether authentication is
  272. * needed. The ACD_ALWAYS flag indicates if the action should occur
  273. * immediately, terminating any further ACL evaluation. An error
  274. * is indicated by a negative error code (ACLERRxxxx - see aclerror.h).
  275. */
  276. int aclEvaluate(ACL_t * acl, USI_t arid, ClAuth_t * clauth, int * padn)
  277. {
  278. ACDirective_t * acd; /* current ACL directive pointer */
  279. RightSpec_t * rsp; /* pointer to rights controlled by ACL */
  280. ACClients_t * csp; /* pointer to clients specification */
  281. HostSpec_t * hsp; /* pointer to host specification */
  282. UserSpec_t * usp; /* pointer to user specification */
  283. Realm_t * rlm = 0; /* current authentication realm pointer */
  284. Realm_t * authrlm = 0; /* realm to be used for authentication */
  285. int ndir; /* ACL directive number */
  286. int rv; /* result value */
  287. int decision = 0; /* current access control decision */
  288. int result = 0; /* function return value */
  289. int mdn = 0; /* matching directive number */
  290. if (padn) *padn = 0;
  291. /* Does this ACL control the desired access right? */
  292. rsp = acl->acl_rights;
  293. if ((rsp == 0) || !usiPresent(&rsp->rs_list, arid)) {
  294. /* No, nothing to do */
  295. return 0;
  296. }
  297. ndir = 0;
  298. /* Loop on each ACL directive */
  299. for (acd = acl->acl_dirf; acd != 0; acd = acd->acd_next) {
  300. /* Bump directive number */
  301. ++ndir;
  302. /* Dispatch on directive action code */
  303. switch (acd->acd_action) {
  304. case ACD_ALLOW:
  305. case ACD_DENY:
  306. /* Loop to process list of client specifications */
  307. for (csp = acd->acd_cl; csp != 0; csp = csp->cl_next) {
  308. /* Is there a host list? */
  309. hsp = csp->cl_host;
  310. if (hsp != 0) {
  311. /* An empty host list will not match */
  312. rv = 0;
  313. /* Yes, is there an IP address filter? */
  314. if (hsp->hs_host.inh_ipf.ipf_tree != 0) {
  315. /*
  316. * Yes, see if the the client's IP address
  317. * matches anything in the IP filter.
  318. */
  319. rv = aclIPLookup(&hsp->hs_host.inh_ipf,
  320. clauth->cla_ipaddr, 0);
  321. }
  322. /* If no IP match, is there a DNS filter? */
  323. if (!rv && (hsp->hs_host.inh_dnf.dnf_hash != 0)) {
  324. /* Yes, try for a DNS match */
  325. rv = aclDNSLookup(&hsp->hs_host.inh_dnf,
  326. clauth->cla_dns, 1, 0);
  327. }
  328. /*
  329. * Does the client match the host list? If not, skip
  330. * to the next client specification.
  331. */
  332. if (!rv) continue;
  333. }
  334. /* Is there a user list? */
  335. usp = csp->cl_user;
  336. if (usp != 0) {
  337. /* Yes, has the client user been authenticated yet? */
  338. if ((clauth->cla_realm != 0) && (clauth->cla_uoptr != 0)) {
  339. /*
  340. * Yes, has the client user been authenticated in the
  341. * realm associated with this user list?
  342. */
  343. if (RLMEQUIV(rlm, clauth->cla_realm)) {
  344. /*
  345. * Yes, does the user spec allow all
  346. * authenticated users?
  347. */
  348. rv = (usp->us_flags & ACL_USALL) ? ACL_GRMATCH : 0;
  349. if (!rv) {
  350. /*
  351. * No, need to check client user against list.
  352. */
  353. rv = aclUserLookup(&usp->us_user,
  354. clauth->cla_uoptr);
  355. }
  356. /* Got a match yet? */
  357. if (rv) {
  358. /*
  359. * Yes, update the the access control decision,
  360. * clearing any pending authentication request
  361. * flag.
  362. */
  363. authrlm = 0;
  364. decision = acd->acd_action;
  365. /* Copy the "always" flag to the result */
  366. result = (acd->acd_flags & ACD_ALWAYS);
  367. mdn = ndir;
  368. }
  369. }
  370. else {
  371. /*
  372. * The client has been authenticated already,
  373. * but not in the realm used by this directive.
  374. * Since directives in a given ACL are not
  375. * independent policy statements, it seems that
  376. * the proper thing to do here is to reject
  377. * this ACL in its entirity. This case is not
  378. * an authentication failure per se, but rather
  379. * an inability to evaluate this particular
  380. * ACL directive which requires authentication.
  381. */
  382. return 0;
  383. }
  384. }
  385. else {
  386. /*
  387. * The client user has not been authenticated in this
  388. * realm yet, but could potentially be one of the
  389. * users on this user list. This directive is
  390. * therefore "potentially matching". The question
  391. * is: would it change the current decision to allow
  392. * or deny the client if the client user actually did
  393. * match the user list?
  394. */
  395. if ((authrlm == 0) && (decision != acd->acd_action)) {
  396. /*
  397. * Yes, set the "request authentication" flag,
  398. * along with ACD_ALWAYS if it is set in the
  399. * directive.
  400. */
  401. authrlm = rlm;
  402. decision = ACD_AUTH;
  403. result = (acd->acd_flags & ACD_ALWAYS);
  404. mdn = ndir;
  405. }
  406. }
  407. }
  408. else {
  409. /*
  410. * There is no user list. Therefore any user,
  411. * authenticated or not, is considered a match.
  412. * Update the decision, and clear the
  413. * "authentication requested" flag.
  414. */
  415. authrlm = 0;
  416. decision = acd->acd_action;
  417. result = (acd->acd_flags & ACD_ALWAYS);
  418. mdn = ndir;
  419. }
  420. /*
  421. * If we hit a client specification that requires
  422. * immediate action, exit the loop.
  423. */
  424. if (result & ACD_ALWAYS) break;
  425. }
  426. break;
  427. case ACD_AUTH:
  428. /* Got a pointer to a realm specification? */
  429. if (acd->acd_auth.au_realm != 0) {
  430. /* Yes, update the current realm pointer */
  431. rlm = &acd->acd_auth.au_realm->rs_realm;
  432. /* Has the client already successfully authenticated? */
  433. if ((clauth->cla_realm == 0) || (clauth->cla_uoptr == 0)) {
  434. /*
  435. * No, if this is an "always" directive, override any
  436. * previously selected realm and request authentication.
  437. */
  438. if ((acd->acd_flags & ACD_ALWAYS) != 0) {
  439. /* Set decision to request authentication */
  440. authrlm = rlm;
  441. decision = ACD_AUTH;
  442. result = ACD_ALWAYS;
  443. mdn = ndir;
  444. }
  445. }
  446. }
  447. break;
  448. case ACD_EXEC:
  449. /* Conditionally terminate ACL evaluation */
  450. switch (decision) {
  451. case ACD_ALLOW:
  452. if (acd->acd_flags & ACD_EXALLOW) {
  453. result = (acd->acd_flags & ACD_ALWAYS);
  454. goto out;
  455. }
  456. break;
  457. case ACD_DENY:
  458. if (acd->acd_flags & ACD_EXDENY) {
  459. result = (acd->acd_flags & ACD_ALWAYS);
  460. goto out;
  461. }
  462. break;
  463. case ACD_AUTH:
  464. if (acd->acd_flags & ACD_EXAUTH) {
  465. result = (acd->acd_flags & ACD_ALWAYS);
  466. goto out;
  467. }
  468. break;
  469. default:
  470. break;
  471. }
  472. break;
  473. default:
  474. break;
  475. }
  476. /*
  477. * If we hit a directive that requires immediate action, exit
  478. * the loop.
  479. */
  480. if (result & ACD_ALWAYS) break;
  481. }
  482. out:
  483. /* If the decision is to request authentication, set the desired realm */
  484. if (decision == ACD_AUTH) {
  485. clauth->cla_realm = authrlm;
  486. }
  487. /* Combine decision with flags already in result */
  488. result |= decision;
  489. /* Return matching directive number if desired */
  490. if (padn) *padn = mdn;
  491. return result;
  492. }