acleval.cpp 17 KB

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