1 /* $NetBSD: aci.c,v 1.4 2025/09/05 21:16:24 christos Exp $ */ 2 3 /* aci.c - routines to parse and check acl's */ 4 /* $OpenLDAP$ */ 5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>. 6 * 7 * Copyright 1998-2024 The OpenLDAP Foundation. 8 * All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted only as authorized by the OpenLDAP 12 * Public License. 13 * 14 * A copy of this license is available in the file LICENSE in the 15 * top-level directory of the distribution or, alternatively, at 16 * <http://www.OpenLDAP.org/license.html>. 17 */ 18 /* Portions Copyright (c) 1995 Regents of the University of Michigan. 19 * All rights reserved. 20 * 21 * Redistribution and use in source and binary forms are permitted 22 * provided that this notice is preserved and that due credit is given 23 * to the University of Michigan at Ann Arbor. The name of the University 24 * may not be used to endorse or promote products derived from this 25 * software without specific prior written permission. This software 26 * is provided ``as is'' without express or implied warranty. 27 */ 28 29 #include <sys/cdefs.h> 30 __RCSID("$NetBSD: aci.c,v 1.4 2025/09/05 21:16:24 christos Exp $"); 31 32 #include "portable.h" 33 34 #ifdef SLAPD_ACI_ENABLED 35 36 #include <stdio.h> 37 38 #include <ac/ctype.h> 39 #include <ac/regex.h> 40 #include <ac/socket.h> 41 #include <ac/string.h> 42 #include <ac/unistd.h> 43 44 #include "slap.h" 45 #include "lber_pvt.h" 46 #include "lutil.h" 47 #include "slap-config.h" 48 49 /* use most appropriate size */ 50 #define ACI_BUF_SIZE 1024 51 52 /* move to "stable" when no longer experimental */ 53 #define SLAPD_ACI_SYNTAX "1.3.6.1.4.1.4203.666.2.1" 54 55 /* change this to "OpenLDAPset" */ 56 #define SLAPD_ACI_SET_ATTR "template" 57 58 typedef enum slap_aci_scope_t { 59 SLAP_ACI_SCOPE_ENTRY = 0x1, 60 SLAP_ACI_SCOPE_CHILDREN = 0x2, 61 SLAP_ACI_SCOPE_SUBTREE = ( SLAP_ACI_SCOPE_ENTRY | SLAP_ACI_SCOPE_CHILDREN ) 62 } slap_aci_scope_t; 63 64 enum { 65 ACI_BV_ENTRY, 66 ACI_BV_CHILDREN, 67 ACI_BV_ONELEVEL, 68 ACI_BV_SUBTREE, 69 70 ACI_BV_BR_ENTRY, 71 ACI_BV_BR_CHILDREN, 72 ACI_BV_BR_ALL, 73 74 ACI_BV_ACCESS_ID, 75 ACI_BV_PUBLIC, 76 ACI_BV_USERS, 77 ACI_BV_SELF, 78 ACI_BV_DNATTR, 79 ACI_BV_GROUP, 80 ACI_BV_ROLE, 81 ACI_BV_SET, 82 ACI_BV_SET_REF, 83 84 ACI_BV_GRANT, 85 ACI_BV_DENY, 86 87 ACI_BV_GROUP_CLASS, 88 ACI_BV_GROUP_ATTR, 89 ACI_BV_ROLE_CLASS, 90 ACI_BV_ROLE_ATTR, 91 92 ACI_BV_SET_ATTR, 93 94 ACI_BV_LAST 95 }; 96 97 static const struct berval aci_bv[] = { 98 /* scope */ 99 BER_BVC("entry"), 100 BER_BVC("children"), 101 BER_BVC("onelevel"), 102 BER_BVC("subtree"), 103 104 /* */ 105 BER_BVC("[entry]"), 106 BER_BVC("[children]"), 107 BER_BVC("[all]"), 108 109 /* type */ 110 BER_BVC("access-id"), 111 BER_BVC("public"), 112 BER_BVC("users"), 113 BER_BVC("self"), 114 BER_BVC("dnattr"), 115 BER_BVC("group"), 116 BER_BVC("role"), 117 BER_BVC("set"), 118 BER_BVC("set-ref"), 119 120 /* actions */ 121 BER_BVC("grant"), 122 BER_BVC("deny"), 123 124 /* schema */ 125 BER_BVC(SLAPD_GROUP_CLASS), 126 BER_BVC(SLAPD_GROUP_ATTR), 127 BER_BVC(SLAPD_ROLE_CLASS), 128 BER_BVC(SLAPD_ROLE_ATTR), 129 130 BER_BVC(SLAPD_ACI_SET_ATTR), 131 132 BER_BVNULL 133 }; 134 135 static AttributeDescription *slap_ad_aci; 136 137 static int 138 OpenLDAPaciValidate( 139 Syntax *syntax, 140 struct berval *val ); 141 142 static int 143 OpenLDAPaciPretty( 144 Syntax *syntax, 145 struct berval *val, 146 struct berval *out, 147 void *ctx ); 148 149 static int 150 OpenLDAPaciNormalize( 151 slap_mask_t use, 152 Syntax *syntax, 153 MatchingRule *mr, 154 struct berval *val, 155 struct berval *out, 156 void *ctx ); 157 158 #define OpenLDAPaciMatch octetStringMatch 159 160 static int 161 aci_list_map_rights( 162 struct berval *list ) 163 { 164 struct berval bv; 165 slap_access_t mask; 166 int i; 167 168 ACL_INIT( mask ); 169 for ( i = 0; acl_get_part( list, i, ',', &bv ) >= 0; i++ ) { 170 if ( bv.bv_len <= 0 ) { 171 continue; 172 } 173 174 switch ( *bv.bv_val ) { 175 case 'x': 176 /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt does not 177 * define any equivalent to the AUTH right, so I've just used 178 * 'x' for now. 179 */ 180 ACL_PRIV_SET(mask, ACL_PRIV_AUTH); 181 break; 182 case 'd': 183 /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt defines 184 * the right 'd' to mean "delete"; we hijack it to mean 185 * "disclose" for consistency wuith the rest of slapd. 186 */ 187 ACL_PRIV_SET(mask, ACL_PRIV_DISCLOSE); 188 break; 189 case 'c': 190 ACL_PRIV_SET(mask, ACL_PRIV_COMPARE); 191 break; 192 case 's': 193 /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt defines 194 * the right 's' to mean "set", but in the examples states 195 * that the right 's' means "search". The latter definition 196 * is used here. 197 */ 198 ACL_PRIV_SET(mask, ACL_PRIV_SEARCH); 199 break; 200 case 'r': 201 ACL_PRIV_SET(mask, ACL_PRIV_READ); 202 break; 203 case 'w': 204 ACL_PRIV_SET(mask, ACL_PRIV_WRITE); 205 break; 206 default: 207 break; 208 } 209 210 } 211 212 return mask; 213 } 214 215 static int 216 aci_list_has_attr( 217 struct berval *list, 218 const struct berval *attr, 219 struct berval *val ) 220 { 221 struct berval bv, left, right; 222 int i; 223 224 for ( i = 0; acl_get_part( list, i, ',', &bv ) >= 0; i++ ) { 225 if ( acl_get_part(&bv, 0, '=', &left ) < 0 226 || acl_get_part( &bv, 1, '=', &right ) < 0 ) 227 { 228 if ( ber_bvstrcasecmp( attr, &bv ) == 0 ) { 229 return(1); 230 } 231 232 } else if ( val == NULL ) { 233 if ( ber_bvstrcasecmp( attr, &left ) == 0 ) { 234 return(1); 235 } 236 237 } else { 238 if ( ber_bvstrcasecmp( attr, &left ) == 0 ) { 239 /* FIXME: this is also totally undocumented! */ 240 /* this is experimental code that implements a 241 * simple (prefix) match of the attribute value. 242 * the ACI draft does not provide for aci's that 243 * apply to specific values, but it would be 244 * nice to have. If the <attr> part of an aci's 245 * rights list is of the form <attr>=<value>, 246 * that means the aci applies only to attrs with 247 * the given value. Furthermore, if the attr is 248 * of the form <attr>=<value>*, then <value> is 249 * treated as a prefix, and the aci applies to 250 * any value with that prefix. 251 * 252 * Ideally, this would allow r.e. matches. 253 */ 254 if ( acl_get_part( &right, 0, '*', &left ) < 0 255 || right.bv_len <= left.bv_len ) 256 { 257 if ( ber_bvstrcasecmp( val, &right ) == 0 ) { 258 return 1; 259 } 260 261 } else if ( val->bv_len >= left.bv_len ) { 262 if ( strncasecmp( val->bv_val, left.bv_val, left.bv_len ) == 0 ) { 263 return(1); 264 } 265 } 266 } 267 } 268 } 269 270 return 0; 271 } 272 273 static slap_access_t 274 aci_list_get_attr_rights( 275 struct berval *list, 276 const struct berval *attr, 277 struct berval *val ) 278 { 279 struct berval bv; 280 slap_access_t mask; 281 int i; 282 283 /* loop through each rights/attr pair, skip first part (action) */ 284 ACL_INIT(mask); 285 for ( i = 1; acl_get_part( list, i + 1, ';', &bv ) >= 0; i += 2 ) { 286 if ( aci_list_has_attr( &bv, attr, val ) == 0 ) { 287 Debug( LDAP_DEBUG_ACL, 288 " <= aci_list_get_attr_rights " 289 "test %s for %s -> failed\n", 290 bv.bv_val, attr->bv_val ); 291 continue; 292 } 293 294 Debug( LDAP_DEBUG_ACL, 295 " <= aci_list_get_attr_rights " 296 "test %s for %s -> ok\n", 297 bv.bv_val, attr->bv_val ); 298 299 if ( acl_get_part( list, i, ';', &bv ) < 0 ) { 300 Debug( LDAP_DEBUG_ACL, 301 " <= aci_list_get_attr_rights " 302 "test no rights\n" ); 303 continue; 304 } 305 306 mask |= aci_list_map_rights( &bv ); 307 Debug( LDAP_DEBUG_ACL, 308 " <= aci_list_get_attr_rights " 309 "rights %s to mask 0x%x\n", 310 bv.bv_val, mask ); 311 } 312 313 return mask; 314 } 315 316 static int 317 aci_list_get_rights( 318 struct berval *list, 319 struct berval *attr, 320 struct berval *val, 321 slap_access_t *grant, 322 slap_access_t *deny ) 323 { 324 struct berval perm, actn, baseattr; 325 slap_access_t *mask; 326 int i, found; 327 328 if ( attr == NULL || BER_BVISEMPTY( attr ) ) { 329 attr = (struct berval *)&aci_bv[ ACI_BV_ENTRY ]; 330 331 } else if ( acl_get_part( attr, 0, ';', &baseattr ) > 0 ) { 332 attr = &baseattr; 333 } 334 found = 0; 335 ACL_INIT(*grant); 336 ACL_INIT(*deny); 337 /* loop through each permissions clause */ 338 for ( i = 0; acl_get_part( list, i, '$', &perm ) >= 0; i++ ) { 339 if ( acl_get_part( &perm, 0, ';', &actn ) < 0 ) { 340 continue; 341 } 342 343 if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_GRANT ], &actn ) == 0 ) { 344 mask = grant; 345 346 } else if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_DENY ], &actn ) == 0 ) { 347 mask = deny; 348 349 } else { 350 continue; 351 } 352 353 *mask |= aci_list_get_attr_rights( &perm, attr, val ); 354 *mask |= aci_list_get_attr_rights( &perm, &aci_bv[ ACI_BV_BR_ALL ], NULL ); 355 356 if ( *mask != ACL_PRIV_NONE ) { 357 found = 1; 358 } 359 } 360 361 return found; 362 } 363 364 static int 365 aci_group_member ( 366 struct berval *subj, 367 const struct berval *defgrpoc, 368 const struct berval *defgrpat, 369 Operation *op, 370 Entry *e, 371 int nmatch, 372 regmatch_t *matches 373 ) 374 { 375 struct berval subjdn; 376 struct berval grpoc; 377 struct berval grpat; 378 ObjectClass *grp_oc = NULL; 379 AttributeDescription *grp_ad = NULL; 380 const char *text; 381 int rc; 382 383 /* format of string is "{group|role}/objectClassValue/groupAttrName" */ 384 if ( acl_get_part( subj, 0, '/', &subjdn ) < 0 ) { 385 return 0; 386 } 387 388 if ( acl_get_part( subj, 1, '/', &grpoc ) < 0 ) { 389 grpoc = *defgrpoc; 390 } 391 392 if ( acl_get_part( subj, 2, '/', &grpat ) < 0 ) { 393 grpat = *defgrpat; 394 } 395 396 rc = slap_bv2ad( &grpat, &grp_ad, &text ); 397 if ( rc != LDAP_SUCCESS ) { 398 rc = 0; 399 goto done; 400 } 401 rc = 0; 402 403 grp_oc = oc_bvfind( &grpoc ); 404 405 if ( grp_oc != NULL && grp_ad != NULL ) { 406 char buf[ ACI_BUF_SIZE ]; 407 struct berval bv, ndn; 408 AclRegexMatches amatches = { 0 }; 409 410 amatches.dn_count = nmatch; 411 AC_MEMCPY( amatches.dn_data, matches, sizeof( amatches.dn_data ) ); 412 413 bv.bv_len = sizeof( buf ) - 1; 414 bv.bv_val = (char *)&buf; 415 if ( acl_string_expand( &bv, &subjdn, 416 &e->e_nname, NULL, &amatches ) ) 417 { 418 rc = LDAP_OTHER; 419 goto done; 420 } 421 422 if ( dnNormalize( 0, NULL, NULL, &bv, &ndn, op->o_tmpmemctx ) == LDAP_SUCCESS ) 423 { 424 rc = ( backend_group( op, e, &ndn, &op->o_ndn, 425 grp_oc, grp_ad ) == 0 ); 426 slap_sl_free( ndn.bv_val, op->o_tmpmemctx ); 427 } 428 } 429 430 done: 431 return rc; 432 } 433 434 static int 435 aci_mask( 436 Operation *op, 437 Entry *e, 438 AttributeDescription *desc, 439 struct berval *val, 440 struct berval *aci, 441 int nmatch, 442 regmatch_t *matches, 443 slap_access_t *grant, 444 slap_access_t *deny, 445 slap_aci_scope_t asserted_scope ) 446 { 447 struct berval bv, 448 scope, 449 perms, 450 type, 451 opts, 452 sdn; 453 int rc; 454 455 ACL_INIT( *grant ); 456 ACL_INIT( *deny ); 457 458 assert( !BER_BVISNULL( &desc->ad_cname ) ); 459 460 /* parse an aci of the form: 461 oid # scope # action;rights;attr;rights;attr 462 $ action;rights;attr;rights;attr # type # subject 463 464 [NOTE: the following comment is very outdated, 465 as the draft version it refers to (Ando, 2004-11-20)]. 466 467 See draft-ietf-ldapext-aci-model-04.txt section 9.1 for 468 a full description of the format for this attribute. 469 Differences: "this" in the draft is "self" here, and 470 "self" and "public" is in the position of type. 471 472 <scope> = {entry|children|subtree} 473 <type> = {public|users|access-id|subtree|onelevel|children| 474 self|dnattr|group|role|set|set-ref} 475 476 This routine now supports scope={ENTRY,CHILDREN} 477 with the semantics: 478 - ENTRY applies to "entry" and "subtree"; 479 - CHILDREN applies to "children" and "subtree" 480 */ 481 482 /* check that the aci has all 5 components */ 483 if ( acl_get_part( aci, 4, '#', NULL ) < 0 ) { 484 return 0; 485 } 486 487 /* check that the aci family is supported */ 488 /* FIXME: the OID is ignored? */ 489 if ( acl_get_part( aci, 0, '#', &bv ) < 0 ) { 490 return 0; 491 } 492 493 /* check that the scope matches */ 494 if ( acl_get_part( aci, 1, '#', &scope ) < 0 ) { 495 return 0; 496 } 497 498 /* note: scope can be either ENTRY or CHILDREN; 499 * they respectively match "entry" and "children" in bv 500 * both match "subtree" */ 501 switch ( asserted_scope ) { 502 case SLAP_ACI_SCOPE_ENTRY: 503 if ( ber_bvcmp( &scope, &aci_bv[ ACI_BV_ENTRY ] ) != 0 504 && ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_SUBTREE ] ) != 0 ) 505 { 506 return 0; 507 } 508 break; 509 510 case SLAP_ACI_SCOPE_CHILDREN: 511 if ( ber_bvcmp( &scope, &aci_bv[ ACI_BV_CHILDREN ] ) != 0 512 && ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_SUBTREE ] ) != 0 ) 513 { 514 return 0; 515 } 516 break; 517 518 case SLAP_ACI_SCOPE_SUBTREE: 519 /* TODO: add assertion? */ 520 return 0; 521 } 522 523 /* get the list of permissions clauses, bail if empty */ 524 if ( acl_get_part( aci, 2, '#', &perms ) <= 0 ) { 525 assert( 0 ); 526 return 0; 527 } 528 529 /* check if any permissions allow desired access */ 530 if ( aci_list_get_rights( &perms, &desc->ad_cname, val, grant, deny ) == 0 ) { 531 return 0; 532 } 533 534 /* see if we have a DN match */ 535 if ( acl_get_part( aci, 3, '#', &type ) < 0 ) { 536 assert( 0 ); 537 return 0; 538 } 539 540 /* see if we have a public (i.e. anonymous) access */ 541 if ( ber_bvcmp( &aci_bv[ ACI_BV_PUBLIC ], &type ) == 0 ) { 542 return 1; 543 } 544 545 /* otherwise require an identity */ 546 if ( BER_BVISNULL( &op->o_ndn ) || BER_BVISEMPTY( &op->o_ndn ) ) { 547 return 0; 548 } 549 550 /* see if we have a users access */ 551 if ( ber_bvcmp( &aci_bv[ ACI_BV_USERS ], &type ) == 0 ) { 552 return 1; 553 } 554 555 /* NOTE: this may fail if a DN contains a valid '#' (unescaped); 556 * just grab all the berval up to its end (ITS#3303). 557 * NOTE: the problem could be solved by providing the DN with 558 * the embedded '#' encoded as hexpairs: "cn=Foo#Bar" would 559 * become "cn=Foo\23Bar" and be safely used by aci_mask(). */ 560 #if 0 561 if ( acl_get_part( aci, 4, '#', &sdn ) < 0 ) { 562 return 0; 563 } 564 #endif 565 sdn.bv_val = type.bv_val + type.bv_len + STRLENOF( "#" ); 566 sdn.bv_len = aci->bv_len - ( sdn.bv_val - aci->bv_val ); 567 568 /* get the type options, if any */ 569 if ( acl_get_part( &type, 1, '/', &opts ) > 0 ) { 570 opts.bv_len = type.bv_len - ( opts.bv_val - type.bv_val ); 571 type.bv_len = opts.bv_val - type.bv_val - 1; 572 573 } else { 574 BER_BVZERO( &opts ); 575 } 576 577 if ( ber_bvcmp( &aci_bv[ ACI_BV_ACCESS_ID ], &type ) == 0 ) { 578 return dn_match( &op->o_ndn, &sdn ); 579 580 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SUBTREE ], &type ) == 0 ) { 581 return dnIsSuffix( &op->o_ndn, &sdn ); 582 583 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_ONELEVEL ], &type ) == 0 ) { 584 struct berval pdn; 585 586 dnParent( &sdn, &pdn ); 587 588 return dn_match( &op->o_ndn, &pdn ); 589 590 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_CHILDREN ], &type ) == 0 ) { 591 return ( !dn_match( &op->o_ndn, &sdn ) && dnIsSuffix( &op->o_ndn, &sdn ) ); 592 593 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SELF ], &type ) == 0 ) { 594 return dn_match( &op->o_ndn, &e->e_nname ); 595 596 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_DNATTR ], &type ) == 0 ) { 597 Attribute *at; 598 AttributeDescription *ad = NULL; 599 const char *text; 600 601 rc = slap_bv2ad( &sdn, &ad, &text ); 602 assert( rc == LDAP_SUCCESS ); 603 604 rc = 0; 605 for ( at = attrs_find( e->e_attrs, ad ); 606 at != NULL; 607 at = attrs_find( at->a_next, ad ) ) 608 { 609 if ( attr_valfind( at, 610 SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | 611 SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, 612 &op->o_ndn, NULL, op->o_tmpmemctx ) == 0 ) 613 { 614 rc = 1; 615 break; 616 } 617 } 618 619 return rc; 620 621 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_GROUP ], &type ) == 0 ) { 622 struct berval oc, 623 at; 624 625 if ( BER_BVISNULL( &opts ) ) { 626 oc = aci_bv[ ACI_BV_GROUP_CLASS ]; 627 at = aci_bv[ ACI_BV_GROUP_ATTR ]; 628 629 } else { 630 if ( acl_get_part( &opts, 0, '/', &oc ) < 0 ) { 631 assert( 0 ); 632 } 633 634 if ( acl_get_part( &opts, 1, '/', &at ) < 0 ) { 635 at = aci_bv[ ACI_BV_GROUP_ATTR ]; 636 } 637 } 638 639 if ( aci_group_member( &sdn, &oc, &at, op, e, nmatch, matches ) ) 640 { 641 return 1; 642 } 643 644 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_ROLE ], &type ) == 0 ) { 645 struct berval oc, 646 at; 647 648 if ( BER_BVISNULL( &opts ) ) { 649 oc = aci_bv[ ACI_BV_ROLE_CLASS ]; 650 at = aci_bv[ ACI_BV_ROLE_ATTR ]; 651 652 } else { 653 if ( acl_get_part( &opts, 0, '/', &oc ) < 0 ) { 654 assert( 0 ); 655 } 656 657 if ( acl_get_part( &opts, 1, '/', &at ) < 0 ) { 658 at = aci_bv[ ACI_BV_ROLE_ATTR ]; 659 } 660 } 661 662 if ( aci_group_member( &sdn, &oc, &at, op, e, nmatch, matches ) ) 663 { 664 return 1; 665 } 666 667 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SET ], &type ) == 0 ) { 668 if ( acl_match_set( &sdn, op, e, NULL ) ) { 669 return 1; 670 } 671 672 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SET_REF ], &type ) == 0 ) { 673 if ( acl_match_set( &sdn, op, e, (struct berval *)&aci_bv[ ACI_BV_SET_ATTR ] ) ) { 674 return 1; 675 } 676 677 } else { 678 /* it passed normalization! */ 679 assert( 0 ); 680 } 681 682 return 0; 683 } 684 685 static int 686 aci_init( void ) 687 { 688 /* OpenLDAP eXperimental Syntax */ 689 static slap_syntax_defs_rec aci_syntax_def = { 690 "( 1.3.6.1.4.1.4203.666.2.1 DESC 'OpenLDAP Experimental ACI' )", 691 SLAP_SYNTAX_HIDE, 692 NULL, 693 OpenLDAPaciValidate, 694 OpenLDAPaciPretty 695 }; 696 static slap_mrule_defs_rec aci_mr_def = { 697 "( 1.3.6.1.4.1.4203.666.4.2 NAME 'OpenLDAPaciMatch' " 698 "SYNTAX 1.3.6.1.4.1.4203.666.2.1 )", 699 SLAP_MR_HIDE | SLAP_MR_EQUALITY, NULL, 700 NULL, OpenLDAPaciNormalize, OpenLDAPaciMatch, 701 NULL, NULL, 702 NULL 703 }; 704 static struct { 705 char *name; 706 char *desc; 707 slap_mask_t flags; 708 AttributeDescription **ad; 709 } aci_at = { 710 "OpenLDAPaci", "( 1.3.6.1.4.1.4203.666.1.5 " 711 "NAME 'OpenLDAPaci' " 712 "DESC 'OpenLDAP access control information (experimental)' " 713 "EQUALITY OpenLDAPaciMatch " 714 "SYNTAX 1.3.6.1.4.1.4203.666.2.1 " 715 "USAGE directoryOperation )", 716 SLAP_AT_HIDE, 717 &slap_ad_aci 718 }; 719 720 int rc; 721 722 /* ACI syntax */ 723 rc = register_syntax( &aci_syntax_def ); 724 if ( rc != 0 ) { 725 return rc; 726 } 727 728 /* ACI equality rule */ 729 rc = register_matching_rule( &aci_mr_def ); 730 if ( rc != 0 ) { 731 return rc; 732 } 733 734 /* ACI attribute */ 735 rc = register_at( aci_at.desc, aci_at.ad, 0 ); 736 if ( rc != LDAP_SUCCESS ) { 737 Debug( LDAP_DEBUG_ANY, 738 "aci_init: at_register failed\n" ); 739 return rc; 740 } 741 742 /* install flags */ 743 (*aci_at.ad)->ad_type->sat_flags |= aci_at.flags; 744 745 return rc; 746 } 747 748 static int 749 dynacl_aci_parse( 750 ConfigArgs *c, 751 const char *opts, 752 slap_style_t sty, 753 const char *right, 754 void **privp ) 755 { 756 AttributeDescription *ad = NULL; 757 const char *text = NULL; 758 759 if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) { 760 snprintf( c->cr_msg, sizeof( c->cr_msg ), 761 "inappropriate style \"%s\" in \"aci\" by clause", 762 style_strings[sty] ); 763 Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); 764 return -1; 765 } 766 767 if ( right != NULL && *right != '\0' ) { 768 if ( slap_str2ad( right, &ad, &text ) != LDAP_SUCCESS ) { 769 snprintf( c->cr_msg, sizeof( c->cr_msg ), 770 "aci \"%s\": %s", 771 right, text ); 772 Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); 773 return -1; 774 } 775 776 } else { 777 ad = slap_ad_aci; 778 } 779 780 if ( !is_at_syntax( ad->ad_type, SLAPD_ACI_SYNTAX) ) { 781 snprintf( c->cr_msg, sizeof( c->cr_msg ), 782 "aci \"%s\": inappropriate syntax: %s", 783 right, ad->ad_type->sat_syntax_oid ); 784 Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); 785 return -1; 786 } 787 788 *privp = (void *)ad; 789 790 return 0; 791 } 792 793 static int 794 dynacl_aci_unparse( void *priv, struct berval *bv ) 795 { 796 AttributeDescription *ad = ( AttributeDescription * )priv; 797 char *ptr; 798 799 assert( ad != NULL ); 800 801 bv->bv_val = ch_malloc( STRLENOF(" aci=") + ad->ad_cname.bv_len + 1 ); 802 ptr = lutil_strcopy( bv->bv_val, " aci=" ); 803 ptr = lutil_strcopy( ptr, ad->ad_cname.bv_val ); 804 bv->bv_len = ptr - bv->bv_val; 805 806 return 0; 807 } 808 809 static int 810 dynacl_aci_mask( 811 void *priv, 812 Operation *op, 813 Entry *e, 814 AttributeDescription *desc, 815 struct berval *val, 816 int nmatch, 817 regmatch_t *matches, 818 slap_access_t *grantp, 819 slap_access_t *denyp ) 820 { 821 AttributeDescription *ad = ( AttributeDescription * )priv; 822 Attribute *at; 823 slap_access_t tgrant, tdeny, grant, deny; 824 #ifdef LDAP_DEBUG 825 char accessmaskbuf[ACCESSMASK_MAXLEN]; 826 char accessmaskbuf1[ACCESSMASK_MAXLEN]; 827 #endif /* LDAP_DEBUG */ 828 829 if ( BER_BVISEMPTY( &e->e_nname ) ) { 830 /* no ACIs in the root DSE */ 831 return -1; 832 } 833 834 /* start out with nothing granted, nothing denied */ 835 ACL_INIT(tgrant); 836 ACL_INIT(tdeny); 837 838 /* get the aci attribute */ 839 at = attr_find( e->e_attrs, ad ); 840 if ( at != NULL ) { 841 int i; 842 843 /* the aci is an multi-valued attribute. The 844 * rights are determined by OR'ing the individual 845 * rights given by the acis. 846 */ 847 for ( i = 0; !BER_BVISNULL( &at->a_nvals[i] ); i++ ) { 848 if ( aci_mask( op, e, desc, val, &at->a_nvals[i], 849 nmatch, matches, &grant, &deny, 850 SLAP_ACI_SCOPE_ENTRY ) != 0 ) 851 { 852 tgrant |= grant; 853 tdeny |= deny; 854 } 855 } 856 857 Debug( LDAP_DEBUG_ACL, " <= aci_mask grant %s deny %s\n", 858 accessmask2str( tgrant, accessmaskbuf, 1 ), 859 accessmask2str( tdeny, accessmaskbuf1, 1 ) ); 860 } 861 862 /* If the entry level aci didn't contain anything valid for the 863 * current operation, climb up the tree and evaluate the 864 * acis with scope set to subtree 865 */ 866 if ( tgrant == ACL_PRIV_NONE && tdeny == ACL_PRIV_NONE ) { 867 struct berval parent_ndn; 868 869 dnParent( &e->e_nname, &parent_ndn ); 870 while ( !BER_BVISEMPTY( &parent_ndn ) ){ 871 int i; 872 BerVarray bvals = NULL; 873 int ret, stop; 874 875 /* to solve the chicken'n'egg problem of accessing 876 * the OpenLDAPaci attribute, the direct access 877 * to the entry's attribute is unchecked; however, 878 * further accesses to OpenLDAPaci values in the 879 * ancestors occur through backend_attribute(), i.e. 880 * with the identity of the operation, requiring 881 * further access checking. For uniformity, this 882 * makes further requests occur as the rootdn, if 883 * any, i.e. searching for the OpenLDAPaci attribute 884 * is considered an internal search. If this is not 885 * acceptable, then the same check needs be performed 886 * when accessing the entry's attribute. */ 887 struct berval save_o_dn, save_o_ndn; 888 889 if ( !BER_BVISNULL( &op->o_bd->be_rootndn ) ) { 890 save_o_dn = op->o_dn; 891 save_o_ndn = op->o_ndn; 892 893 op->o_dn = op->o_bd->be_rootdn; 894 op->o_ndn = op->o_bd->be_rootndn; 895 } 896 897 Debug( LDAP_DEBUG_ACL, " checking ACI of \"%s\"\n", parent_ndn.bv_val ); 898 ret = backend_attribute( op, NULL, &parent_ndn, ad, &bvals, ACL_AUTH ); 899 900 if ( !BER_BVISNULL( &op->o_bd->be_rootndn ) ) { 901 op->o_dn = save_o_dn; 902 op->o_ndn = save_o_ndn; 903 } 904 905 switch ( ret ) { 906 case LDAP_SUCCESS : 907 stop = 0; 908 if ( !bvals ) { 909 break; 910 } 911 912 for ( i = 0; !BER_BVISNULL( &bvals[i] ); i++ ) { 913 if ( aci_mask( op, e, desc, val, 914 &bvals[i], 915 nmatch, matches, 916 &grant, &deny, 917 SLAP_ACI_SCOPE_CHILDREN ) != 0 ) 918 { 919 tgrant |= grant; 920 tdeny |= deny; 921 /* evaluation stops as soon as either a "deny" or a 922 * "grant" directive matches. 923 */ 924 if ( tgrant != ACL_PRIV_NONE || tdeny != ACL_PRIV_NONE ) { 925 stop = 1; 926 } 927 } 928 Debug( LDAP_DEBUG_ACL, "<= aci_mask grant %s deny %s\n", 929 accessmask2str( tgrant, accessmaskbuf, 1 ), 930 accessmask2str( tdeny, accessmaskbuf1, 1 ) ); 931 } 932 break; 933 934 case LDAP_NO_SUCH_ATTRIBUTE: 935 /* just go on if the aci-Attribute is not present in 936 * the current entry 937 */ 938 Debug( LDAP_DEBUG_ACL, "no such attribute\n" ); 939 stop = 0; 940 break; 941 942 case LDAP_NO_SUCH_OBJECT: 943 /* We have reached the base object */ 944 Debug( LDAP_DEBUG_ACL, "no such object\n" ); 945 stop = 1; 946 break; 947 948 default: 949 stop = 1; 950 break; 951 } 952 953 if ( stop ) { 954 break; 955 } 956 dnParent( &parent_ndn, &parent_ndn ); 957 } 958 } 959 960 *grantp = tgrant; 961 *denyp = tdeny; 962 963 return 0; 964 } 965 966 /* need to register this at some point */ 967 static slap_dynacl_t dynacl_aci = { 968 "aci", 969 dynacl_aci_parse, 970 dynacl_aci_unparse, 971 dynacl_aci_mask, 972 NULL, 973 NULL, 974 NULL 975 }; 976 977 int 978 dynacl_aci_init( void ) 979 { 980 int rc; 981 982 rc = aci_init(); 983 984 if ( rc == 0 ) { 985 rc = slap_dynacl_register( &dynacl_aci ); 986 } 987 988 return rc; 989 } 990 991 992 /* ACI syntax validation */ 993 994 /* 995 * Matches given berval to array of bervals 996 * Returns: 997 * >=0 if one if the array elements equals to this berval 998 * -1 if string was not found in array 999 */ 1000 static int 1001 bv_getcaseidx( 1002 struct berval *bv, 1003 const struct berval *arr[] ) 1004 { 1005 int i; 1006 1007 if ( BER_BVISEMPTY( bv ) ) { 1008 return -1; 1009 } 1010 1011 for ( i = 0; arr[ i ] != NULL ; i++ ) { 1012 if ( ber_bvstrcasecmp( bv, arr[ i ] ) == 0 ) { 1013 return i; 1014 } 1015 } 1016 1017 return -1; 1018 } 1019 1020 1021 /* Returns what have left in input berval after current sub */ 1022 static void 1023 bv_get_tail( 1024 struct berval *val, 1025 struct berval *sub, 1026 struct berval *tail ) 1027 { 1028 int head_len; 1029 1030 tail->bv_val = sub->bv_val + sub->bv_len; 1031 head_len = (unsigned long) tail->bv_val - (unsigned long) val->bv_val; 1032 tail->bv_len = val->bv_len - head_len; 1033 } 1034 1035 1036 /* 1037 * aci is accepted in following form: 1038 * oid#scope#rights#type#subject 1039 * Where: 1040 * oid := numeric OID (currently ignored) 1041 * scope := entry|children|subtree 1042 * rights := right[[$right]...] 1043 * right := (grant|deny);action 1044 * action := perms;attrs[[;perms;attrs]...] 1045 * perms := perm[[,perm]...] 1046 * perm := c|s|r|w|x 1047 * attrs := attribute[[,attribute]..]|"[all]" 1048 * attribute := attributeType|attributeType=attributeValue|attributeType=attributeValuePrefix* 1049 * type := public|users|self|dnattr|group|role|set|set-ref| 1050 * access_id|subtree|onelevel|children 1051 */ 1052 static int 1053 OpenLDAPaciValidatePerms( 1054 struct berval *perms ) 1055 { 1056 ber_len_t i; 1057 1058 for ( i = 0; i < perms->bv_len; ) { 1059 switch ( perms->bv_val[ i ] ) { 1060 case 'x': 1061 case 'd': 1062 case 'c': 1063 case 's': 1064 case 'r': 1065 case 'w': 1066 break; 1067 1068 default: 1069 Debug( LDAP_DEBUG_ACL, "aciValidatePerms: perms needs to be one of x,d,c,s,r,w in '%s'\n", perms->bv_val ); 1070 return LDAP_INVALID_SYNTAX; 1071 } 1072 1073 if ( ++i == perms->bv_len ) { 1074 return LDAP_SUCCESS; 1075 } 1076 1077 while ( i < perms->bv_len && perms->bv_val[ i ] == ' ' ) 1078 i++; 1079 1080 assert( i != perms->bv_len ); 1081 1082 if ( perms->bv_val[ i ] != ',' ) { 1083 Debug( LDAP_DEBUG_ACL, "aciValidatePerms: missing comma in '%s'\n", perms->bv_val ); 1084 return LDAP_INVALID_SYNTAX; 1085 } 1086 1087 do { 1088 i++; 1089 } while ( perms->bv_val[ i ] == ' ' ); 1090 } 1091 1092 return LDAP_SUCCESS; 1093 } 1094 1095 static const struct berval *ACIgrantdeny[] = { 1096 &aci_bv[ ACI_BV_GRANT ], 1097 &aci_bv[ ACI_BV_DENY ], 1098 NULL 1099 }; 1100 1101 static int 1102 OpenLDAPaciValidateRight( 1103 struct berval *action ) 1104 { 1105 struct berval bv = BER_BVNULL; 1106 int i; 1107 1108 /* grant|deny */ 1109 if ( acl_get_part( action, 0, ';', &bv ) < 0 || 1110 bv_getcaseidx( &bv, ACIgrantdeny ) == -1 ) 1111 { 1112 Debug( LDAP_DEBUG_ACL, "aciValidateRight: '%s' must be either 'grant' or 'deny'\n", bv.bv_val ); 1113 return LDAP_INVALID_SYNTAX; 1114 } 1115 1116 for ( i = 0; acl_get_part( action, i + 1, ';', &bv ) >= 0; i++ ) { 1117 if ( i & 1 ) { 1118 /* perms */ 1119 if ( OpenLDAPaciValidatePerms( &bv ) != LDAP_SUCCESS ) 1120 { 1121 return LDAP_INVALID_SYNTAX; 1122 } 1123 1124 } else { 1125 /* attr */ 1126 AttributeDescription *ad; 1127 const char *text; 1128 struct berval attr, left, right; 1129 int j; 1130 1131 /* could be "[all]" or an attribute description */ 1132 if ( ber_bvstrcasecmp( &bv, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) { 1133 continue; 1134 } 1135 1136 1137 for ( j = 0; acl_get_part( &bv, j, ',', &attr ) >= 0; j++ ) 1138 { 1139 ad = NULL; 1140 text = NULL; 1141 if ( acl_get_part( &attr, 0, '=', &left ) < 0 1142 || acl_get_part( &attr, 1, '=', &right ) < 0 ) 1143 { 1144 if ( slap_bv2ad( &attr, &ad, &text ) != LDAP_SUCCESS ) 1145 { 1146 Debug( LDAP_DEBUG_ACL, "aciValidateRight: unknown attribute: '%s'\n", attr.bv_val ); 1147 return LDAP_INVALID_SYNTAX; 1148 } 1149 } else { 1150 if ( slap_bv2ad( &left, &ad, &text ) != LDAP_SUCCESS ) 1151 { 1152 Debug( LDAP_DEBUG_ACL, "aciValidateRight: unknown attribute: '%s'\n", left.bv_val ); 1153 return LDAP_INVALID_SYNTAX; 1154 } 1155 } 1156 } 1157 } 1158 } 1159 1160 /* "perms;attr" go in pairs */ 1161 if ( i > 0 && ( i & 1 ) == 0 ) { 1162 return LDAP_SUCCESS; 1163 1164 } else { 1165 Debug( LDAP_DEBUG_ACL, "aciValidateRight: perms:attr need to be pairs in '%s'\n", action->bv_val ); 1166 return LDAP_INVALID_SYNTAX; 1167 } 1168 1169 return LDAP_SUCCESS; 1170 } 1171 1172 static int 1173 OpenLDAPaciNormalizeRight( 1174 struct berval *action, 1175 struct berval *naction, 1176 void *ctx ) 1177 { 1178 struct berval grantdeny, 1179 perms = BER_BVNULL, 1180 bv = BER_BVNULL; 1181 int idx, 1182 i; 1183 1184 /* grant|deny */ 1185 if ( acl_get_part( action, 0, ';', &grantdeny ) < 0 ) { 1186 Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: missing ';' in '%s'\n", action->bv_val ); 1187 return LDAP_INVALID_SYNTAX; 1188 } 1189 idx = bv_getcaseidx( &grantdeny, ACIgrantdeny ); 1190 if ( idx == -1 ) { 1191 Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: '%s' must be grant or deny\n", grantdeny.bv_val ); 1192 return LDAP_INVALID_SYNTAX; 1193 } 1194 1195 ber_dupbv_x( naction, (struct berval *)ACIgrantdeny[ idx ], ctx ); 1196 1197 for ( i = 1; acl_get_part( action, i, ';', &bv ) >= 0; i++ ) { 1198 struct berval nattrs = BER_BVNULL; 1199 int freenattrs = 1; 1200 if ( i & 1 ) { 1201 /* perms */ 1202 if ( OpenLDAPaciValidatePerms( &bv ) != LDAP_SUCCESS ) 1203 { 1204 return LDAP_INVALID_SYNTAX; 1205 } 1206 perms = bv; 1207 1208 } else { 1209 /* attr */ 1210 char *ptr; 1211 1212 /* could be "[all]" or an attribute description */ 1213 if ( ber_bvstrcasecmp( &bv, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) { 1214 nattrs = aci_bv[ ACI_BV_BR_ALL ]; 1215 freenattrs = 0; 1216 1217 } else { 1218 AttributeDescription *ad = NULL; 1219 AttributeDescription adstatic= { 0 }; 1220 const char *text = NULL; 1221 struct berval attr, left, right; 1222 int j; 1223 int len; 1224 1225 for ( j = 0; acl_get_part( &bv, j, ',', &attr ) >= 0; j++ ) 1226 { 1227 ad = NULL; 1228 text = NULL; 1229 /* openldap 2.1 aci compatibility [entry] -> entry */ 1230 if ( ber_bvstrcasecmp( &attr, &aci_bv[ ACI_BV_BR_ENTRY ] ) == 0 ) { 1231 ad = &adstatic; 1232 adstatic.ad_cname = aci_bv[ ACI_BV_ENTRY ]; 1233 1234 /* openldap 2.1 aci compatibility [children] -> children */ 1235 } else if ( ber_bvstrcasecmp( &attr, &aci_bv[ ACI_BV_BR_CHILDREN ] ) == 0 ) { 1236 ad = &adstatic; 1237 adstatic.ad_cname = aci_bv[ ACI_BV_CHILDREN ]; 1238 1239 /* openldap 2.1 aci compatibility [all] -> only [all] */ 1240 } else if ( ber_bvstrcasecmp( &attr, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) { 1241 ber_memfree_x( nattrs.bv_val, ctx ); 1242 nattrs = aci_bv[ ACI_BV_BR_ALL ]; 1243 freenattrs = 0; 1244 break; 1245 1246 } else if ( acl_get_part( &attr, 0, '=', &left ) < 0 1247 || acl_get_part( &attr, 1, '=', &right ) < 0 ) 1248 { 1249 if ( slap_bv2ad( &attr, &ad, &text ) != LDAP_SUCCESS ) 1250 { 1251 ber_memfree_x( nattrs.bv_val, ctx ); 1252 Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: unknown attribute: '%s'\n", attr.bv_val ); 1253 return LDAP_INVALID_SYNTAX; 1254 } 1255 1256 } else { 1257 if ( slap_bv2ad( &left, &ad, &text ) != LDAP_SUCCESS ) 1258 { 1259 ber_memfree_x( nattrs.bv_val, ctx ); 1260 Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: unknown attribute: '%s'\n", left.bv_val ); 1261 return LDAP_INVALID_SYNTAX; 1262 } 1263 } 1264 1265 1266 len = nattrs.bv_len + ( !BER_BVISEMPTY( &nattrs ) ? STRLENOF( "," ) : 0 ) 1267 + ad->ad_cname.bv_len; 1268 nattrs.bv_val = slap_sl_realloc( nattrs.bv_val, len + 1, ctx ); 1269 ptr = &nattrs.bv_val[ nattrs.bv_len ]; 1270 if ( !BER_BVISEMPTY( &nattrs ) ) { 1271 *ptr++ = ','; 1272 } 1273 ptr = lutil_strncopy( ptr, ad->ad_cname.bv_val, ad->ad_cname.bv_len ); 1274 ptr[ 0 ] = '\0'; 1275 nattrs.bv_len = len; 1276 } 1277 1278 } 1279 1280 naction->bv_val = slap_sl_realloc( naction->bv_val, 1281 naction->bv_len + STRLENOF( ";" ) 1282 + perms.bv_len + STRLENOF( ";" ) 1283 + nattrs.bv_len + 1, 1284 ctx ); 1285 1286 ptr = &naction->bv_val[ naction->bv_len ]; 1287 ptr[ 0 ] = ';'; 1288 ptr++; 1289 ptr = lutil_strncopy( ptr, perms.bv_val, perms.bv_len ); 1290 ptr[ 0 ] = ';'; 1291 ptr++; 1292 ptr = lutil_strncopy( ptr, nattrs.bv_val, nattrs.bv_len ); 1293 ptr[ 0 ] = '\0'; 1294 naction->bv_len += STRLENOF( ";" ) + perms.bv_len 1295 + STRLENOF( ";" ) + nattrs.bv_len; 1296 if ( freenattrs ) { 1297 ber_memfree_x( nattrs.bv_val, ctx ); 1298 } 1299 } 1300 } 1301 1302 /* perms;attr go in pairs */ 1303 if ( i > 1 && ( i & 1 ) ) { 1304 return LDAP_SUCCESS; 1305 1306 } else { 1307 Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: perms:attr need to be pairs in '%s'\n", action->bv_val ); 1308 return LDAP_INVALID_SYNTAX; 1309 } 1310 } 1311 1312 static int 1313 OpenLDAPaciValidateRights( 1314 struct berval *actions ) 1315 1316 { 1317 struct berval bv = BER_BVNULL; 1318 int i; 1319 1320 for ( i = 0; acl_get_part( actions, i, '$', &bv ) >= 0; i++ ) { 1321 if ( OpenLDAPaciValidateRight( &bv ) != LDAP_SUCCESS ) { 1322 return LDAP_INVALID_SYNTAX; 1323 } 1324 } 1325 1326 return LDAP_SUCCESS; 1327 } 1328 1329 static int 1330 OpenLDAPaciNormalizeRights( 1331 struct berval *actions, 1332 struct berval *nactions, 1333 void *ctx ) 1334 1335 { 1336 struct berval bv = BER_BVNULL; 1337 int i; 1338 1339 BER_BVZERO( nactions ); 1340 for ( i = 0; acl_get_part( actions, i, '$', &bv ) >= 0; i++ ) { 1341 int rc; 1342 struct berval nbv; 1343 1344 rc = OpenLDAPaciNormalizeRight( &bv, &nbv, ctx ); 1345 if ( rc != LDAP_SUCCESS ) { 1346 ber_memfree_x( nactions->bv_val, ctx ); 1347 BER_BVZERO( nactions ); 1348 return LDAP_INVALID_SYNTAX; 1349 } 1350 1351 if ( i == 0 ) { 1352 *nactions = nbv; 1353 1354 } else { 1355 nactions->bv_val = slap_sl_realloc( nactions->bv_val, 1356 nactions->bv_len + STRLENOF( "$" ) 1357 + nbv.bv_len + 1, 1358 ctx ); 1359 nactions->bv_val[ nactions->bv_len ] = '$'; 1360 AC_MEMCPY( &nactions->bv_val[ nactions->bv_len + 1 ], 1361 nbv.bv_val, nbv.bv_len + 1 ); 1362 ber_memfree_x( nbv.bv_val, ctx ); 1363 nactions->bv_len += STRLENOF( "$" ) + nbv.bv_len; 1364 } 1365 BER_BVZERO( &nbv ); 1366 } 1367 1368 return LDAP_SUCCESS; 1369 } 1370 1371 static const struct berval *OpenLDAPaciscopes[] = { 1372 &aci_bv[ ACI_BV_ENTRY ], 1373 &aci_bv[ ACI_BV_CHILDREN ], 1374 &aci_bv[ ACI_BV_SUBTREE ], 1375 1376 NULL 1377 }; 1378 1379 static const struct berval *OpenLDAPacitypes[] = { 1380 /* DN-valued */ 1381 &aci_bv[ ACI_BV_GROUP ], 1382 &aci_bv[ ACI_BV_ROLE ], 1383 1384 /* set to one past the last DN-valued type with options (/) */ 1385 #define LAST_OPTIONAL 2 1386 1387 &aci_bv[ ACI_BV_ACCESS_ID ], 1388 &aci_bv[ ACI_BV_SUBTREE ], 1389 &aci_bv[ ACI_BV_ONELEVEL ], 1390 &aci_bv[ ACI_BV_CHILDREN ], 1391 1392 /* set to one past the last DN-valued type */ 1393 #define LAST_DNVALUED 6 1394 1395 /* non DN-valued */ 1396 &aci_bv[ ACI_BV_DNATTR ], 1397 &aci_bv[ ACI_BV_PUBLIC ], 1398 &aci_bv[ ACI_BV_USERS ], 1399 &aci_bv[ ACI_BV_SELF ], 1400 &aci_bv[ ACI_BV_SET ], 1401 &aci_bv[ ACI_BV_SET_REF ], 1402 1403 NULL 1404 }; 1405 1406 static int 1407 OpenLDAPaciValidate( 1408 Syntax *syntax, 1409 struct berval *val ) 1410 { 1411 struct berval oid = BER_BVNULL, 1412 scope = BER_BVNULL, 1413 rights = BER_BVNULL, 1414 type = BER_BVNULL, 1415 subject = BER_BVNULL; 1416 int idx; 1417 int rc; 1418 1419 if ( BER_BVISEMPTY( val ) ) { 1420 Debug( LDAP_DEBUG_ACL, "aciValidatet: value is empty\n" ); 1421 return LDAP_INVALID_SYNTAX; 1422 } 1423 1424 /* oid */ 1425 if ( acl_get_part( val, 0, '#', &oid ) < 0 || 1426 numericoidValidate( NULL, &oid ) != LDAP_SUCCESS ) 1427 { 1428 /* NOTE: the numericoidValidate() is rather pedantic; 1429 * I'd replace it with X-ORDERED VALUES so that 1430 * it's guaranteed values are maintained and used 1431 * in the desired order */ 1432 Debug( LDAP_DEBUG_ACL, "aciValidate: invalid oid '%s'\n", oid.bv_val ); 1433 return LDAP_INVALID_SYNTAX; 1434 } 1435 1436 /* scope */ 1437 if ( acl_get_part( val, 1, '#', &scope ) < 0 || 1438 bv_getcaseidx( &scope, OpenLDAPaciscopes ) == -1 ) 1439 { 1440 Debug( LDAP_DEBUG_ACL, "aciValidate: invalid scope '%s'\n", scope.bv_val ); 1441 return LDAP_INVALID_SYNTAX; 1442 } 1443 1444 /* rights */ 1445 if ( acl_get_part( val, 2, '#', &rights ) < 0 || 1446 OpenLDAPaciValidateRights( &rights ) != LDAP_SUCCESS ) 1447 { 1448 return LDAP_INVALID_SYNTAX; 1449 } 1450 1451 /* type */ 1452 if ( acl_get_part( val, 3, '#', &type ) < 0 ) { 1453 Debug( LDAP_DEBUG_ACL, "aciValidate: missing type in '%s'\n", val->bv_val ); 1454 return LDAP_INVALID_SYNTAX; 1455 } 1456 idx = bv_getcaseidx( &type, OpenLDAPacitypes ); 1457 if ( idx == -1 ) { 1458 struct berval isgr; 1459 1460 if ( acl_get_part( &type, 0, '/', &isgr ) < 0 ) { 1461 Debug( LDAP_DEBUG_ACL, "aciValidate: invalid type '%s'\n", type.bv_val ); 1462 return LDAP_INVALID_SYNTAX; 1463 } 1464 1465 idx = bv_getcaseidx( &isgr, OpenLDAPacitypes ); 1466 if ( idx == -1 || idx >= LAST_OPTIONAL ) { 1467 Debug( LDAP_DEBUG_ACL, "aciValidate: invalid type '%s'\n", isgr.bv_val ); 1468 return LDAP_INVALID_SYNTAX; 1469 } 1470 } 1471 1472 /* subject */ 1473 bv_get_tail( val, &type, &subject ); 1474 if ( subject.bv_val[ 0 ] != '#' ) { 1475 Debug( LDAP_DEBUG_ACL, "aciValidate: missing subject in '%s'\n", val->bv_val ); 1476 return LDAP_INVALID_SYNTAX; 1477 } 1478 1479 if ( idx >= LAST_DNVALUED ) { 1480 if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_DNATTR ] ) { 1481 AttributeDescription *ad = NULL; 1482 const char *text = NULL; 1483 1484 rc = slap_bv2ad( &subject, &ad, &text ); 1485 if ( rc != LDAP_SUCCESS ) { 1486 Debug( LDAP_DEBUG_ACL, "aciValidate: unknown dn attribute '%s'\n", subject.bv_val ); 1487 return LDAP_INVALID_SYNTAX; 1488 } 1489 1490 if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) { 1491 /* FIXME: allow nameAndOptionalUID? */ 1492 Debug( LDAP_DEBUG_ACL, "aciValidate: wrong syntax for dn attribute '%s'\n", subject.bv_val ); 1493 return LDAP_INVALID_SYNTAX; 1494 } 1495 } 1496 1497 /* not a DN */ 1498 return LDAP_SUCCESS; 1499 1500 } else if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_GROUP ] 1501 || OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_ROLE ] ) 1502 { 1503 /* do {group|role}/oc/at check */ 1504 struct berval ocbv = BER_BVNULL, 1505 atbv = BER_BVNULL; 1506 1507 ocbv.bv_val = ber_bvchr( &type, '/' ); 1508 if ( ocbv.bv_val != NULL ) { 1509 ocbv.bv_val++; 1510 ocbv.bv_len = type.bv_len 1511 - ( ocbv.bv_val - type.bv_val ); 1512 1513 atbv.bv_val = ber_bvchr( &ocbv, '/' ); 1514 if ( atbv.bv_val != NULL ) { 1515 AttributeDescription *ad = NULL; 1516 const char *text = NULL; 1517 int rc; 1518 1519 atbv.bv_val++; 1520 atbv.bv_len = type.bv_len 1521 - ( atbv.bv_val - type.bv_val ); 1522 ocbv.bv_len = atbv.bv_val - ocbv.bv_val - 1; 1523 1524 rc = slap_bv2ad( &atbv, &ad, &text ); 1525 if ( rc != LDAP_SUCCESS ) { 1526 Debug( LDAP_DEBUG_ACL, "aciValidate: unknown group attribute '%s'\n", atbv.bv_val ); 1527 return LDAP_INVALID_SYNTAX; 1528 } 1529 } 1530 1531 if ( oc_bvfind( &ocbv ) == NULL ) { 1532 Debug( LDAP_DEBUG_ACL, "aciValidate: unknown group '%s'\n", ocbv.bv_val ); 1533 return LDAP_INVALID_SYNTAX; 1534 } 1535 } 1536 } 1537 1538 if ( BER_BVISEMPTY( &subject ) ) { 1539 /* empty DN invalid */ 1540 Debug( LDAP_DEBUG_ACL, "aciValidate: missing dn in '%s'\n", val->bv_val ); 1541 return LDAP_INVALID_SYNTAX; 1542 } 1543 1544 subject.bv_val++; 1545 subject.bv_len--; 1546 1547 /* FIXME: pass DN syntax? */ 1548 rc = dnValidate( NULL, &subject ); 1549 if ( rc != LDAP_SUCCESS ) { 1550 Debug( LDAP_DEBUG_ACL, "aciValidate: invalid dn '%s'\n", subject.bv_val ); 1551 } 1552 return rc; 1553 } 1554 1555 static int 1556 OpenLDAPaciPrettyNormal( 1557 struct berval *val, 1558 struct berval *out, 1559 void *ctx, 1560 int normalize ) 1561 { 1562 struct berval oid = BER_BVNULL, 1563 scope = BER_BVNULL, 1564 rights = BER_BVNULL, 1565 nrights = BER_BVNULL, 1566 type = BER_BVNULL, 1567 ntype = BER_BVNULL, 1568 subject = BER_BVNULL, 1569 nsubject = BER_BVNULL; 1570 int idx, 1571 rc = LDAP_SUCCESS, 1572 freesubject = 0, 1573 freetype = 0; 1574 char *ptr; 1575 1576 BER_BVZERO( out ); 1577 1578 if ( BER_BVISEMPTY( val ) ) { 1579 Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: value is empty\n" ); 1580 return LDAP_INVALID_SYNTAX; 1581 } 1582 1583 /* oid: if valid, it's already normalized */ 1584 if ( acl_get_part( val, 0, '#', &oid ) < 0 || 1585 numericoidValidate( NULL, &oid ) != LDAP_SUCCESS ) 1586 { 1587 Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid oid '%s'\n", oid.bv_val ); 1588 return LDAP_INVALID_SYNTAX; 1589 } 1590 1591 /* scope: normalize by replacing with OpenLDAPaciscopes */ 1592 if ( acl_get_part( val, 1, '#', &scope ) < 0 ) { 1593 Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing scope in '%s'\n", val->bv_val ); 1594 return LDAP_INVALID_SYNTAX; 1595 } 1596 idx = bv_getcaseidx( &scope, OpenLDAPaciscopes ); 1597 if ( idx == -1 ) { 1598 Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid scope '%s'\n", scope.bv_val ); 1599 return LDAP_INVALID_SYNTAX; 1600 } 1601 scope = *OpenLDAPaciscopes[ idx ]; 1602 1603 /* rights */ 1604 if ( acl_get_part( val, 2, '#', &rights ) < 0 ) { 1605 Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing rights in '%s'\n", val->bv_val ); 1606 return LDAP_INVALID_SYNTAX; 1607 } 1608 if ( OpenLDAPaciNormalizeRights( &rights, &nrights, ctx ) 1609 != LDAP_SUCCESS ) 1610 { 1611 return LDAP_INVALID_SYNTAX; 1612 } 1613 1614 /* type */ 1615 if ( acl_get_part( val, 3, '#', &type ) < 0 ) { 1616 Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing type in '%s'\n", val->bv_val ); 1617 rc = LDAP_INVALID_SYNTAX; 1618 goto cleanup; 1619 } 1620 idx = bv_getcaseidx( &type, OpenLDAPacitypes ); 1621 if ( idx == -1 ) { 1622 struct berval isgr; 1623 1624 if ( acl_get_part( &type, 0, '/', &isgr ) < 0 ) { 1625 Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid type '%s'\n", type.bv_val ); 1626 rc = LDAP_INVALID_SYNTAX; 1627 goto cleanup; 1628 } 1629 1630 idx = bv_getcaseidx( &isgr, OpenLDAPacitypes ); 1631 if ( idx == -1 || idx >= LAST_OPTIONAL ) { 1632 Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid type '%s'\n", isgr.bv_val ); 1633 rc = LDAP_INVALID_SYNTAX; 1634 goto cleanup; 1635 } 1636 } 1637 ntype = *OpenLDAPacitypes[ idx ]; 1638 1639 /* subject */ 1640 bv_get_tail( val, &type, &subject ); 1641 1642 if ( BER_BVISEMPTY( &subject ) || subject.bv_val[ 0 ] != '#' ) { 1643 Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing subject in '%s'\n", val->bv_val ); 1644 rc = LDAP_INVALID_SYNTAX; 1645 goto cleanup; 1646 } 1647 1648 subject.bv_val++; 1649 subject.bv_len--; 1650 1651 if ( idx < LAST_DNVALUED ) { 1652 /* FIXME: pass DN syntax? */ 1653 if ( normalize ) { 1654 rc = dnNormalize( 0, NULL, NULL, 1655 &subject, &nsubject, ctx ); 1656 } else { 1657 rc = dnPretty( NULL, &subject, &nsubject, ctx ); 1658 } 1659 1660 if ( rc == LDAP_SUCCESS ) { 1661 freesubject = 1; 1662 1663 } else { 1664 Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid subject dn '%s'\n", subject.bv_val ); 1665 goto cleanup; 1666 } 1667 1668 if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_GROUP ] 1669 || OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_ROLE ] ) 1670 { 1671 /* do {group|role}/oc/at check */ 1672 struct berval ocbv = BER_BVNULL, 1673 atbv = BER_BVNULL; 1674 1675 ocbv.bv_val = ber_bvchr( &type, '/' ); 1676 if ( ocbv.bv_val != NULL ) { 1677 ObjectClass *oc = NULL; 1678 AttributeDescription *ad = NULL; 1679 const char *text = NULL; 1680 int rc; 1681 struct berval bv; 1682 1683 bv.bv_len = ntype.bv_len; 1684 1685 ocbv.bv_val++; 1686 ocbv.bv_len = type.bv_len - ( ocbv.bv_val - type.bv_val ); 1687 1688 atbv.bv_val = ber_bvchr( &ocbv, '/' ); 1689 if ( atbv.bv_val != NULL ) { 1690 atbv.bv_val++; 1691 atbv.bv_len = type.bv_len 1692 - ( atbv.bv_val - type.bv_val ); 1693 ocbv.bv_len = atbv.bv_val - ocbv.bv_val - 1; 1694 1695 rc = slap_bv2ad( &atbv, &ad, &text ); 1696 if ( rc != LDAP_SUCCESS ) { 1697 Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: unknown group attribute '%s'\n", atbv.bv_val ); 1698 rc = LDAP_INVALID_SYNTAX; 1699 goto cleanup; 1700 } 1701 1702 bv.bv_len += STRLENOF( "/" ) + ad->ad_cname.bv_len; 1703 } 1704 1705 oc = oc_bvfind( &ocbv ); 1706 if ( oc == NULL ) { 1707 Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid group '%s'\n", ocbv.bv_val ); 1708 rc = LDAP_INVALID_SYNTAX; 1709 goto cleanup; 1710 } 1711 1712 bv.bv_len += STRLENOF( "/" ) + oc->soc_cname.bv_len; 1713 bv.bv_val = slap_sl_malloc( bv.bv_len + 1, ctx ); 1714 1715 ptr = bv.bv_val; 1716 ptr = lutil_strncopy( ptr, ntype.bv_val, ntype.bv_len ); 1717 ptr[ 0 ] = '/'; 1718 ptr++; 1719 ptr = lutil_strncopy( ptr, 1720 oc->soc_cname.bv_val, 1721 oc->soc_cname.bv_len ); 1722 if ( ad != NULL ) { 1723 ptr[ 0 ] = '/'; 1724 ptr++; 1725 ptr = lutil_strncopy( ptr, 1726 ad->ad_cname.bv_val, 1727 ad->ad_cname.bv_len ); 1728 } 1729 ptr[ 0 ] = '\0'; 1730 1731 ntype = bv; 1732 freetype = 1; 1733 } 1734 } 1735 1736 } else if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_DNATTR ] ) { 1737 AttributeDescription *ad = NULL; 1738 const char *text = NULL; 1739 int rc; 1740 1741 rc = slap_bv2ad( &subject, &ad, &text ); 1742 if ( rc != LDAP_SUCCESS ) { 1743 Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: unknown dn attribute '%s'\n", subject.bv_val ); 1744 rc = LDAP_INVALID_SYNTAX; 1745 goto cleanup; 1746 } 1747 1748 if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) { 1749 /* FIXME: allow nameAndOptionalUID? */ 1750 Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: wrong syntax for dn attribute '%s'\n", subject.bv_val ); 1751 rc = LDAP_INVALID_SYNTAX; 1752 goto cleanup; 1753 } 1754 1755 nsubject = ad->ad_cname; 1756 1757 } else if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_SET ] 1758 || OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_SET_REF ] ) 1759 { 1760 /* NOTE: dunno how to normalize it... */ 1761 nsubject = subject; 1762 } 1763 1764 1765 out->bv_len = 1766 oid.bv_len + STRLENOF( "#" ) 1767 + scope.bv_len + STRLENOF( "#" ) 1768 + nrights.bv_len + STRLENOF( "#" ) 1769 + ntype.bv_len + STRLENOF( "#" ) 1770 + nsubject.bv_len; 1771 1772 out->bv_val = slap_sl_malloc( out->bv_len + 1, ctx ); 1773 ptr = lutil_strncopy( out->bv_val, oid.bv_val, oid.bv_len ); 1774 ptr[ 0 ] = '#'; 1775 ptr++; 1776 ptr = lutil_strncopy( ptr, scope.bv_val, scope.bv_len ); 1777 ptr[ 0 ] = '#'; 1778 ptr++; 1779 ptr = lutil_strncopy( ptr, nrights.bv_val, nrights.bv_len ); 1780 ptr[ 0 ] = '#'; 1781 ptr++; 1782 ptr = lutil_strncopy( ptr, ntype.bv_val, ntype.bv_len ); 1783 ptr[ 0 ] = '#'; 1784 ptr++; 1785 if ( !BER_BVISNULL( &nsubject ) ) { 1786 ptr = lutil_strncopy( ptr, nsubject.bv_val, nsubject.bv_len ); 1787 } 1788 ptr[ 0 ] = '\0'; 1789 1790 cleanup:; 1791 if ( freesubject ) { 1792 ber_memfree_x( nsubject.bv_val, ctx ); 1793 } 1794 1795 if ( freetype ) { 1796 ber_memfree_x( ntype.bv_val, ctx ); 1797 } 1798 1799 if ( !BER_BVISNULL( &nrights ) ) { 1800 ber_memfree_x( nrights.bv_val, ctx ); 1801 } 1802 1803 return rc; 1804 } 1805 1806 static int 1807 OpenLDAPaciPretty( 1808 Syntax *syntax, 1809 struct berval *val, 1810 struct berval *out, 1811 void *ctx ) 1812 { 1813 return OpenLDAPaciPrettyNormal( val, out, ctx, 0 ); 1814 } 1815 1816 static int 1817 OpenLDAPaciNormalize( 1818 slap_mask_t use, 1819 Syntax *syntax, 1820 MatchingRule *mr, 1821 struct berval *val, 1822 struct berval *out, 1823 void *ctx ) 1824 { 1825 return OpenLDAPaciPrettyNormal( val, out, ctx, 1 ); 1826 } 1827 1828 #if SLAPD_ACI_ENABLED == SLAPD_MOD_DYNAMIC 1829 /* 1830 * FIXME: need config and Makefile.am code to ease building 1831 * as dynamic module 1832 */ 1833 int 1834 init_module( int argc, char *argv[] ) 1835 { 1836 return dynacl_aci_init(); 1837 } 1838 #endif /* SLAPD_ACI_ENABLED == SLAPD_MOD_DYNAMIC */ 1839 1840 #endif /* SLAPD_ACI_ENABLED */ 1841 1842