acleval.cpp 15 KB

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