acleval.cpp 15 KB

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